// // NCPlayerToolBar.swift // Nextcloud // // Created by Marino Faggiana on 01/07/21. // Copyright © 2021 Marino Faggiana. All rights reserved. // // Author Marino Faggiana // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // import Foundation import NextcloudKit import CoreMedia import UIKit import AVKit import MediaPlayer import MobileVLCKit import FloatingPanel import JGProgressHUD import Alamofire class NCPlayerToolBar: UIView { @IBOutlet weak var utilityView: UIView! @IBOutlet weak var fullscreenButton: UIButton! @IBOutlet weak var subtitleButton: UIButton! @IBOutlet weak var audioButton: UIButton! @IBOutlet weak var playerButtonView: UIStackView! @IBOutlet weak var backButton: UIButton! @IBOutlet weak var playButton: UIButton! @IBOutlet weak var forwardButton: UIButton! @IBOutlet weak var playbackSliderView: UIView! @IBOutlet weak var playbackSlider: UISlider! @IBOutlet weak var labelLeftTime: UILabel! @IBOutlet weak var labelCurrentTime: UILabel! @IBOutlet weak var repeatButton: UIButton! enum sliderEventType { case began case ended case moved } var playbackSliderEvent: sliderEventType = .ended var isFullscreen: Bool = false var playRepeat: Bool = false private let hud = JGProgressHUD() private var ncplayer: NCPlayer? private var metadata: tableMetadata? private let audioSession = AVAudioSession.sharedInstance() private var pointSize: CGFloat = 0 private weak var viewerMediaPage: NCViewerMediaPage? // MARK: - View Life Cycle override func awakeFromNib() { super.awakeFromNib() self.backgroundColor = UIColor.black.withAlphaComponent(0.1) fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.up.left.and.arrow.down.right", color: .white), for: .normal) subtitleButton.setImage(NCUtility.shared.loadImage(named: "captions.bubble", color: .white), for: .normal) subtitleButton.isEnabled = false audioButton.setImage(NCUtility.shared.loadImage(named: "speaker.zzz", color: .white), for: .normal) audioButton.isEnabled = false if UIDevice.current.userInterfaceIdiom == .pad { pointSize = 60 } else { pointSize = 50 } playerButtonView.spacing = pointSize playerButtonView.isHidden = true backButton.setImage(NCUtility.shared.loadImage(named: "gobackward.10", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal) playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal) forwardButton.setImage(NCUtility.shared.loadImage(named: "goforward.10", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal) playbackSlider.setThumbImage(UIImage(systemName: "circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15)), for: .normal) playbackSlider.value = 0 playbackSlider.tintColor = .white playbackSlider.addTarget(self, action: #selector(playbackValChanged(slider:event:)), for: .valueChanged) repeatButton.setImage(NCUtility.shared.loadImage(named: "repeat", color: .gray), for: .normal) utilityView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(gestureRecognizer:)))) playbackSliderView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(gestureRecognizer:)))) playerButtonView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(gestureRecognizer:)))) labelCurrentTime.textColor = .white labelLeftTime.textColor = .white // Normally hide self.alpha = 0 self.isHidden = true } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } deinit { print("deinit NCPlayerToolBar") } // MARK: - func setBarPlayer(position: Float, ncplayer: NCPlayer? = nil, metadata: tableMetadata? = nil, viewerMediaPage: NCViewerMediaPage? = nil) { if let ncplayer = ncplayer { self.ncplayer = ncplayer } if let metadata = metadata { self.metadata = metadata } if let viewerMediaPage = viewerMediaPage { self.viewerMediaPage = viewerMediaPage } playerButtonView.isHidden = true playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal) playbackSlider.value = position labelCurrentTime.text = "--:--" labelLeftTime.text = "--:--" if viewerMediaScreenMode == .normal { show() } else { hide() } MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = position } public func update() { guard let ncplayer = self.ncplayer, let length = ncplayer.player.media?.length.intValue else { return } let position = ncplayer.player.position let positionInSecond = position * Float(length / 1000) // SLIDER & TIME if playbackSliderEvent == .ended { playbackSlider.value = position } labelCurrentTime.text = ncplayer.player.time.stringValue labelLeftTime.text = ncplayer.player.remainingTime?.stringValue MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPMediaItemPropertyPlaybackDuration] = length / 1000 MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = positionInSecond } public func updateTopToolBar(videoSubTitlesIndexes: [Any], audioTrackIndexes: [Any]) { if let metadata = metadata, metadata.isVideo { self.subtitleButton.isEnabled = true self.audioButton.isEnabled = true } } // MARK: - public func show() { UIView.animate(withDuration: 0.5, animations: { self.alpha = 1 }, completion: { (_: Bool) in self.isHidden = false }) } func hide() { UIView.animate(withDuration: 0.5, animations: { self.alpha = 0 }, completion: { (_: Bool) in self.isHidden = true }) } func playButtonPause() { playButton.setImage(NCUtility.shared.loadImage(named: "pause.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal) MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 1 } func playButtonPlay() { playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal) MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 0 } // MARK: - Event / Gesture @objc func playbackValChanged(slider: UISlider, event: UIEvent) { guard let touchEvent = event.allTouches?.first, let ncplayer = ncplayer else { return } let newPosition = playbackSlider.value switch touchEvent.phase { case .began: viewerMediaPage?.timerAutoHide?.invalidate() playbackSliderEvent = .began case .moved: ncplayer.playerPosition(newPosition) playbackSliderEvent = .moved case .ended: ncplayer.playerPosition(newPosition) DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.playbackSliderEvent = .ended self.viewerMediaPage?.startTimerAutoHide() } default: break } } // MARK: - Action @objc func tap(gestureRecognizer: UITapGestureRecognizer) { } @IBAction func tapFullscreen(_ sender: Any) { isFullscreen = !isFullscreen if isFullscreen { fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.down.right.and.arrow.up.left", color: .white), for: .normal) } else { fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.up.left.and.arrow.down.right", color: .white), for: .normal) } viewerMediaPage?.changeScreenMode(mode: viewerMediaScreenMode) } @IBAction func tapSubTitle(_ sender: Any) { guard let player = ncplayer?.player else { return } let spuTracks = player.videoSubTitlesNames let spuTrackIndexes = player.videoSubTitlesIndexes toggleMenuSubTitle(spuTracks: spuTracks, spuTrackIndexes: spuTrackIndexes) } @IBAction func tapAudio(_ sender: Any) { guard let player = ncplayer?.player else { return } let audioTracks = player.audioTrackNames let audioTrackIndexes = player.audioTrackIndexes toggleMenuAudio(audioTracks: audioTracks, audioTrackIndexes: audioTrackIndexes) } @IBAction func tapPlayerPause(_ sender: Any) { guard let ncplayer = ncplayer else { return } if ncplayer.isPlay() { ncplayer.playerPause() } else { ncplayer.playerPlay() } self.viewerMediaPage?.startTimerAutoHide() } @IBAction func tapForward(_ sender: Any) { guard let ncplayer = ncplayer else { return } ncplayer.jumpForward(10) self.viewerMediaPage?.startTimerAutoHide() } @IBAction func tapBack(_ sender: Any) { guard let ncplayer = ncplayer else { return } ncplayer.jumpBackward(10) self.viewerMediaPage?.startTimerAutoHide() } @IBAction func tapRepeat(_ sender: Any) { if playRepeat { playRepeat = false repeatButton.setImage(NCUtility.shared.loadImage(named: "repeat", color: .gray), for: .normal) } else { playRepeat = true repeatButton.setImage(NCUtility.shared.loadImage(named: "repeat", color: .white), for: .normal) } } } extension NCPlayerToolBar { func toggleMenuSubTitle(spuTracks: [Any], spuTrackIndexes: [Any]) { var actions = [NCMenuAction]() var subTitleIndex: Int? if let data = NCManageDatabase.shared.getVideo(metadata: metadata), let idx = data.currentVideoSubTitleIndex { subTitleIndex = idx } else if let idx = ncplayer?.player.currentVideoSubTitleIndex { subTitleIndex = Int(idx) } if !spuTracks.isEmpty { for index in 0...spuTracks.count - 1 { guard let title = spuTracks[index] as? String, let idx = spuTrackIndexes[index] as? Int32, let metadata = self.metadata else { return } actions.append( NCMenuAction( title: title, icon: UIImage(), onTitle: title, onIcon: UIImage(), selected: (subTitleIndex ?? -9999) == idx, on: (subTitleIndex ?? -9999) == idx, action: { _ in self.ncplayer?.player.currentVideoSubTitleIndex = idx NCManageDatabase.shared.addVideo(metadata: metadata, currentVideoSubTitleIndex: Int(idx)) } ) ) } actions.append(.seperator(order: 0)) } actions.append( NCMenuAction( title: NSLocalizedString("_add_subtitle_", comment: ""), icon: UIImage(), onTitle: NSLocalizedString("_add_subtitle_", comment: ""), onIcon: UIImage(), selected: false, on: false, action: { _ in guard let metadata = self.metadata else { return } let storyboard = UIStoryboard(name: "NCSelect", bundle: nil) let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController let viewController = navigationController.topViewController as! NCSelect viewController.delegate = self viewController.typeOfCommandView = .nothing viewController.includeDirectoryE2EEncryption = false viewController.enableSelectFile = true viewController.type = "subtitle" viewController.serverUrl = metadata.serverUrl self.viewerMediaPage?.present(navigationController, animated: true, completion: nil) } ) ) viewerMediaPage?.presentMenu(with: actions, menuColor: UIColor(hexString: "#1C1C1EFF"), textColor: .white) } func toggleMenuAudio(audioTracks: [Any], audioTrackIndexes: [Any]) { var actions = [NCMenuAction]() var audioIndex: Int? if let data = NCManageDatabase.shared.getVideo(metadata: metadata), let idx = data.currentAudioTrackIndex { audioIndex = idx } else if let idx = ncplayer?.player.currentAudioTrackIndex { audioIndex = Int(idx) } if !audioTracks.isEmpty { for index in 0...audioTracks.count - 1 { guard let title = audioTracks[index] as? String, let idx = audioTrackIndexes[index] as? Int32, let metadata = self.metadata else { return } actions.append( NCMenuAction( title: title, icon: UIImage(), onTitle: title, onIcon: UIImage(), selected: (audioIndex ?? -9999) == idx, on: (audioIndex ?? -9999) == idx, action: { _ in self.ncplayer?.player.currentAudioTrackIndex = idx NCManageDatabase.shared.addVideo(metadata: metadata, currentAudioTrackIndex: Int(idx)) } ) ) } actions.append(.seperator(order: 0)) } actions.append( NCMenuAction( title: NSLocalizedString("_add_audio_", comment: ""), icon: UIImage(), onTitle: NSLocalizedString("_add_audio_", comment: ""), onIcon: UIImage(), selected: false, on: false, action: { _ in guard let metadata = self.metadata else { return } let storyboard = UIStoryboard(name: "NCSelect", bundle: nil) let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController let viewController = navigationController.topViewController as! NCSelect viewController.delegate = self viewController.typeOfCommandView = .nothing viewController.includeDirectoryE2EEncryption = false viewController.enableSelectFile = true viewController.type = "audio" viewController.serverUrl = metadata.serverUrl self.viewerMediaPage?.present(navigationController, animated: true, completion: nil) } ) ) viewerMediaPage?.presentMenu(with: actions, menuColor: UIColor(hexString: "#1C1C1EFF"), textColor: .white) } } extension NCPlayerToolBar: NCSelectDelegate { func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], indexPath: [IndexPath], overwrite: Bool, copy: Bool, move: Bool) { if let metadata = metadata, let viewerMediaPage = viewerMediaPage { let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)! if CCUtility.fileProviderStorageExists(metadata) { addPlaybackSlave(type: type, metadata: metadata) } else { var downloadRequest: DownloadRequest? hud.indicatorView = JGProgressHUDRingIndicatorView() hud.textLabel.text = NSLocalizedString("_downloading_", comment: "") hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "") if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView { indicatorView.ringWidth = 1.5 } hud.tapOnHUDViewBlock = { _ in if let request = downloadRequest { request.cancel() } } hud.show(in: viewerMediaPage.view) NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, requestHandler: { request in downloadRequest = request }, taskHandler: { _ in }, progressHandler: { progress in self.hud.progress = Float(progress.fractionCompleted) }) { account, _, _, _, _, _, error in self.hud.dismiss() if error == .success { self.addPlaybackSlave(type: type, metadata: metadata) } else if error.errorCode != 200 { NCContentPresenter.shared.showError(error: error) } } } } } func addPlaybackSlave(type: String, metadata: tableMetadata) { let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)! if type == "subtitle" { self.ncplayer?.player.addPlaybackSlave(URL(fileURLWithPath: fileNameLocalPath), type: .subtitle, enforce: true) } else if type == "audio" { self.ncplayer?.player.addPlaybackSlave(URL(fileURLWithPath: fileNameLocalPath), type: .audio, enforce: true) } } }