NCPlayerToolBar.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. //
  2. // NCPlayerToolBar.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 01/07/21.
  6. // Copyright © 2021 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import Foundation
  24. import NextcloudKit
  25. import CoreMedia
  26. import UIKit
  27. import AVKit
  28. import MediaPlayer
  29. import MobileVLCKit
  30. import FloatingPanel
  31. import JGProgressHUD
  32. import Alamofire
  33. class NCPlayerToolBar: UIView {
  34. @IBOutlet weak var utilityView: UIView!
  35. @IBOutlet weak var fullscreenButton: UIButton!
  36. @IBOutlet weak var subtitleButton: UIButton!
  37. @IBOutlet weak var audioButton: UIButton!
  38. @IBOutlet weak var playerButtonView: UIStackView!
  39. @IBOutlet weak var backButton: UIButton!
  40. @IBOutlet weak var playButton: UIButton!
  41. @IBOutlet weak var forwardButton: UIButton!
  42. @IBOutlet weak var playbackSliderView: UIView!
  43. @IBOutlet weak var playbackSlider: UISlider!
  44. @IBOutlet weak var labelLeftTime: UILabel!
  45. @IBOutlet weak var labelCurrentTime: UILabel!
  46. @IBOutlet weak var repeatButton: UIButton!
  47. enum sliderEventType {
  48. case began
  49. case ended
  50. case moved
  51. }
  52. var playbackSliderEvent: sliderEventType = .ended
  53. var isFullscreen: Bool = false
  54. var playRepeat: Bool = false
  55. private let hud = JGProgressHUD()
  56. private var ncplayer: NCPlayer?
  57. private var metadata: tableMetadata?
  58. private let audioSession = AVAudioSession.sharedInstance()
  59. private var pointSize: CGFloat = 0
  60. private weak var viewerMediaPage: NCViewerMediaPage?
  61. // MARK: - View Life Cycle
  62. override func awakeFromNib() {
  63. super.awakeFromNib()
  64. self.backgroundColor = UIColor.black.withAlphaComponent(0.1)
  65. fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.up.left.and.arrow.down.right", color: .white), for: .normal)
  66. subtitleButton.setImage(NCUtility.shared.loadImage(named: "captions.bubble", color: .white), for: .normal)
  67. subtitleButton.isEnabled = false
  68. audioButton.setImage(NCUtility.shared.loadImage(named: "speaker.zzz", color: .white), for: .normal)
  69. audioButton.isEnabled = false
  70. if UIDevice.current.userInterfaceIdiom == .pad {
  71. pointSize = 60
  72. } else {
  73. pointSize = 50
  74. }
  75. playerButtonView.spacing = pointSize
  76. playerButtonView.isHidden = true
  77. backButton.setImage(NCUtility.shared.loadImage(named: "gobackward.10", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  78. playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  79. forwardButton.setImage(NCUtility.shared.loadImage(named: "goforward.10", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  80. playbackSlider.setThumbImage(UIImage(systemName: "circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15)), for: .normal)
  81. playbackSlider.value = 0
  82. playbackSlider.tintColor = .white
  83. playbackSlider.addTarget(self, action: #selector(playbackValChanged(slider:event:)), for: .valueChanged)
  84. repeatButton.setImage(NCUtility.shared.loadImage(named: "repeat", color: .gray), for: .normal)
  85. utilityView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(gestureRecognizer:))))
  86. playbackSliderView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(gestureRecognizer:))))
  87. playerButtonView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(gestureRecognizer:))))
  88. labelCurrentTime.textColor = .white
  89. labelLeftTime.textColor = .white
  90. // Normally hide
  91. self.alpha = 0
  92. self.isHidden = true
  93. }
  94. required init?(coder aDecoder: NSCoder) {
  95. super.init(coder: aDecoder)
  96. }
  97. deinit {
  98. print("deinit NCPlayerToolBar")
  99. }
  100. // MARK: -
  101. func setBarPlayer(position: Float, ncplayer: NCPlayer? = nil, metadata: tableMetadata? = nil, viewerMediaPage: NCViewerMediaPage? = nil) {
  102. if let ncplayer = ncplayer {
  103. self.ncplayer = ncplayer
  104. }
  105. if let metadata = metadata {
  106. self.metadata = metadata
  107. }
  108. if let viewerMediaPage = viewerMediaPage {
  109. self.viewerMediaPage = viewerMediaPage
  110. }
  111. playerButtonView.isHidden = true
  112. playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  113. playbackSlider.value = position
  114. labelCurrentTime.text = "--:--"
  115. labelLeftTime.text = "--:--"
  116. if viewerMediaScreenMode == .normal {
  117. show()
  118. } else {
  119. hide()
  120. }
  121. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = position
  122. }
  123. public func update() {
  124. guard let ncplayer = self.ncplayer, let length = ncplayer.player.media?.length.intValue else { return }
  125. let position = ncplayer.player.position
  126. let positionInSecond = position * Float(length / 1000)
  127. // SLIDER & TIME
  128. if playbackSliderEvent == .ended {
  129. playbackSlider.value = position
  130. }
  131. labelCurrentTime.text = ncplayer.player.time.stringValue
  132. labelLeftTime.text = ncplayer.player.remainingTime?.stringValue
  133. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPMediaItemPropertyPlaybackDuration] = length / 1000
  134. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = positionInSecond
  135. }
  136. public func updateTopToolBar(videoSubTitlesIndexes: [Any], audioTrackIndexes: [Any]) {
  137. if let metadata = metadata, metadata.isVideo {
  138. self.subtitleButton.isEnabled = true
  139. self.audioButton.isEnabled = true
  140. }
  141. }
  142. // MARK: -
  143. public func show() {
  144. UIView.animate(withDuration: 0.5, animations: {
  145. self.alpha = 1
  146. }, completion: { (_: Bool) in
  147. self.isHidden = false
  148. })
  149. }
  150. func hide() {
  151. UIView.animate(withDuration: 0.5, animations: {
  152. self.alpha = 0
  153. }, completion: { (_: Bool) in
  154. self.isHidden = true
  155. })
  156. }
  157. func playButtonPause() {
  158. playButton.setImage(NCUtility.shared.loadImage(named: "pause.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  159. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 1
  160. }
  161. func playButtonPlay() {
  162. playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  163. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 0
  164. }
  165. // MARK: - Event / Gesture
  166. @objc func playbackValChanged(slider: UISlider, event: UIEvent) {
  167. guard let touchEvent = event.allTouches?.first,
  168. let ncplayer = ncplayer
  169. else { return }
  170. let newPosition = playbackSlider.value
  171. switch touchEvent.phase {
  172. case .began:
  173. viewerMediaPage?.timerAutoHide?.invalidate()
  174. playbackSliderEvent = .began
  175. case .moved:
  176. ncplayer.playerPosition(newPosition)
  177. playbackSliderEvent = .moved
  178. case .ended:
  179. ncplayer.playerPosition(newPosition)
  180. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  181. self.playbackSliderEvent = .ended
  182. self.viewerMediaPage?.startTimerAutoHide()
  183. }
  184. default:
  185. break
  186. }
  187. }
  188. // MARK: - Action
  189. @objc func tap(gestureRecognizer: UITapGestureRecognizer) { }
  190. @IBAction func tapFullscreen(_ sender: Any) {
  191. isFullscreen = !isFullscreen
  192. if isFullscreen {
  193. fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.down.right.and.arrow.up.left", color: .white), for: .normal)
  194. } else {
  195. fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.up.left.and.arrow.down.right", color: .white), for: .normal)
  196. }
  197. viewerMediaPage?.changeScreenMode(mode: viewerMediaScreenMode)
  198. }
  199. @IBAction func tapSubTitle(_ sender: Any) {
  200. guard let player = ncplayer?.player else { return }
  201. let spuTracks = player.videoSubTitlesNames
  202. let spuTrackIndexes = player.videoSubTitlesIndexes
  203. toggleMenuSubTitle(spuTracks: spuTracks, spuTrackIndexes: spuTrackIndexes)
  204. }
  205. @IBAction func tapAudio(_ sender: Any) {
  206. guard let player = ncplayer?.player else { return }
  207. let audioTracks = player.audioTrackNames
  208. let audioTrackIndexes = player.audioTrackIndexes
  209. toggleMenuAudio(audioTracks: audioTracks, audioTrackIndexes: audioTrackIndexes)
  210. }
  211. @IBAction func tapPlayerPause(_ sender: Any) {
  212. guard let ncplayer = ncplayer else { return }
  213. if ncplayer.isPlay() {
  214. ncplayer.playerPause()
  215. } else {
  216. ncplayer.playerPlay()
  217. }
  218. self.viewerMediaPage?.startTimerAutoHide()
  219. }
  220. @IBAction func tapForward(_ sender: Any) {
  221. guard let ncplayer = ncplayer else { return }
  222. ncplayer.jumpForward(10)
  223. self.viewerMediaPage?.startTimerAutoHide()
  224. }
  225. @IBAction func tapBack(_ sender: Any) {
  226. guard let ncplayer = ncplayer else { return }
  227. ncplayer.jumpBackward(10)
  228. self.viewerMediaPage?.startTimerAutoHide()
  229. }
  230. @IBAction func tapRepeat(_ sender: Any) {
  231. if playRepeat {
  232. playRepeat = false
  233. repeatButton.setImage(NCUtility.shared.loadImage(named: "repeat", color: .gray), for: .normal)
  234. } else {
  235. playRepeat = true
  236. repeatButton.setImage(NCUtility.shared.loadImage(named: "repeat", color: .white), for: .normal)
  237. }
  238. }
  239. }
  240. extension NCPlayerToolBar {
  241. func toggleMenuSubTitle(spuTracks: [Any], spuTrackIndexes: [Any]) {
  242. var actions = [NCMenuAction]()
  243. var subTitleIndex: Int?
  244. if let data = NCManageDatabase.shared.getVideo(metadata: metadata), let idx = data.currentVideoSubTitleIndex {
  245. subTitleIndex = idx
  246. } else if let idx = ncplayer?.player.currentVideoSubTitleIndex {
  247. subTitleIndex = Int(idx)
  248. }
  249. if !spuTracks.isEmpty {
  250. for index in 0...spuTracks.count - 1 {
  251. guard let title = spuTracks[index] as? String, let idx = spuTrackIndexes[index] as? Int32, let metadata = self.metadata else { return }
  252. actions.append(
  253. NCMenuAction(
  254. title: title,
  255. icon: UIImage(),
  256. onTitle: title,
  257. onIcon: UIImage(),
  258. selected: (subTitleIndex ?? -9999) == idx,
  259. on: (subTitleIndex ?? -9999) == idx,
  260. action: { _ in
  261. self.ncplayer?.player.currentVideoSubTitleIndex = idx
  262. NCManageDatabase.shared.addVideo(metadata: metadata, currentVideoSubTitleIndex: Int(idx))
  263. }
  264. )
  265. )
  266. }
  267. actions.append(.seperator(order: 0))
  268. }
  269. actions.append(
  270. NCMenuAction(
  271. title: NSLocalizedString("_add_subtitle_", comment: ""),
  272. icon: UIImage(),
  273. onTitle: NSLocalizedString("_add_subtitle_", comment: ""),
  274. onIcon: UIImage(),
  275. selected: false,
  276. on: false,
  277. action: { _ in
  278. guard let metadata = self.metadata else { return }
  279. let storyboard = UIStoryboard(name: "NCSelect", bundle: nil)
  280. let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
  281. let viewController = navigationController.topViewController as! NCSelect
  282. viewController.delegate = self
  283. viewController.typeOfCommandView = .nothing
  284. viewController.includeDirectoryE2EEncryption = false
  285. viewController.enableSelectFile = true
  286. viewController.type = "subtitle"
  287. viewController.serverUrl = metadata.serverUrl
  288. self.viewerMediaPage?.present(navigationController, animated: true, completion: nil)
  289. }
  290. )
  291. )
  292. viewerMediaPage?.presentMenu(with: actions, menuColor: UIColor(hexString: "#1C1C1EFF"), textColor: .white)
  293. }
  294. func toggleMenuAudio(audioTracks: [Any], audioTrackIndexes: [Any]) {
  295. var actions = [NCMenuAction]()
  296. var audioIndex: Int?
  297. if let data = NCManageDatabase.shared.getVideo(metadata: metadata), let idx = data.currentAudioTrackIndex {
  298. audioIndex = idx
  299. } else if let idx = ncplayer?.player.currentAudioTrackIndex {
  300. audioIndex = Int(idx)
  301. }
  302. if !audioTracks.isEmpty {
  303. for index in 0...audioTracks.count - 1 {
  304. guard let title = audioTracks[index] as? String, let idx = audioTrackIndexes[index] as? Int32, let metadata = self.metadata else { return }
  305. actions.append(
  306. NCMenuAction(
  307. title: title,
  308. icon: UIImage(),
  309. onTitle: title,
  310. onIcon: UIImage(),
  311. selected: (audioIndex ?? -9999) == idx,
  312. on: (audioIndex ?? -9999) == idx,
  313. action: { _ in
  314. self.ncplayer?.player.currentAudioTrackIndex = idx
  315. NCManageDatabase.shared.addVideo(metadata: metadata, currentAudioTrackIndex: Int(idx))
  316. }
  317. )
  318. )
  319. }
  320. actions.append(.seperator(order: 0))
  321. }
  322. actions.append(
  323. NCMenuAction(
  324. title: NSLocalizedString("_add_audio_", comment: ""),
  325. icon: UIImage(),
  326. onTitle: NSLocalizedString("_add_audio_", comment: ""),
  327. onIcon: UIImage(),
  328. selected: false,
  329. on: false,
  330. action: { _ in
  331. guard let metadata = self.metadata else { return }
  332. let storyboard = UIStoryboard(name: "NCSelect", bundle: nil)
  333. let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
  334. let viewController = navigationController.topViewController as! NCSelect
  335. viewController.delegate = self
  336. viewController.typeOfCommandView = .nothing
  337. viewController.includeDirectoryE2EEncryption = false
  338. viewController.enableSelectFile = true
  339. viewController.type = "audio"
  340. viewController.serverUrl = metadata.serverUrl
  341. self.viewerMediaPage?.present(navigationController, animated: true, completion: nil)
  342. }
  343. )
  344. )
  345. viewerMediaPage?.presentMenu(with: actions, menuColor: UIColor(hexString: "#1C1C1EFF"), textColor: .white)
  346. }
  347. }
  348. extension NCPlayerToolBar: NCSelectDelegate {
  349. func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], indexPath: [IndexPath], overwrite: Bool, copy: Bool, move: Bool) {
  350. if let metadata = metadata, let viewerMediaPage = viewerMediaPage {
  351. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  352. let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
  353. if CCUtility.fileProviderStorageExists(metadata) {
  354. addPlaybackSlave(type: type, metadata: metadata)
  355. } else {
  356. var downloadRequest: DownloadRequest?
  357. hud.indicatorView = JGProgressHUDRingIndicatorView()
  358. hud.textLabel.text = NSLocalizedString("_downloading_", comment: "")
  359. hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
  360. if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
  361. indicatorView.ringWidth = 1.5
  362. }
  363. hud.tapOnHUDViewBlock = { _ in
  364. if let request = downloadRequest {
  365. request.cancel()
  366. }
  367. }
  368. hud.show(in: viewerMediaPage.view)
  369. NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, requestHandler: { request in
  370. downloadRequest = request
  371. }, taskHandler: { _ in
  372. }, progressHandler: { progress in
  373. self.hud.progress = Float(progress.fractionCompleted)
  374. }) { account, _, _, _, _, _, error in
  375. self.hud.dismiss()
  376. if error == .success {
  377. self.addPlaybackSlave(type: type, metadata: metadata)
  378. } else if error.errorCode != 200 {
  379. NCContentPresenter.shared.showError(error: error)
  380. }
  381. }
  382. }
  383. }
  384. }
  385. func addPlaybackSlave(type: String, metadata: tableMetadata) {
  386. let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
  387. if type == "subtitle" {
  388. self.ncplayer?.player.addPlaybackSlave(URL(fileURLWithPath: fileNameLocalPath), type: .subtitle, enforce: true)
  389. } else if type == "audio" {
  390. self.ncplayer?.player.addPlaybackSlave(URL(fileURLWithPath: fileNameLocalPath), type: .audio, enforce: true)
  391. }
  392. }
  393. }