NCPlayerToolBar.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  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,
  125. let length = ncplayer.player.media?.length.intValue
  126. else { return }
  127. let position = ncplayer.player.position
  128. let positionInSecond = position * Float(length / 1000)
  129. // SLIDER & TIME
  130. if playbackSliderEvent == .ended {
  131. playbackSlider.value = position
  132. }
  133. labelCurrentTime.text = ncplayer.player.time.stringValue
  134. labelLeftTime.text = ncplayer.player.remainingTime?.stringValue
  135. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPMediaItemPropertyPlaybackDuration] = length / 1000
  136. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = positionInSecond
  137. }
  138. public func updateTopToolBar(videoSubTitlesIndexes: [Any], audioTrackIndexes: [Any]) {
  139. if let metadata = metadata, metadata.isVideo {
  140. self.subtitleButton.isEnabled = true
  141. self.audioButton.isEnabled = true
  142. }
  143. }
  144. // MARK: -
  145. public func show() {
  146. UIView.animate(withDuration: 0.5, animations: {
  147. self.alpha = 1
  148. }, completion: { (_: Bool) in
  149. self.isHidden = false
  150. })
  151. }
  152. func hide() {
  153. UIView.animate(withDuration: 0.5, animations: {
  154. self.alpha = 0
  155. }, completion: { (_: Bool) in
  156. self.isHidden = true
  157. })
  158. }
  159. func playButtonPause() {
  160. playButton.setImage(NCUtility.shared.loadImage(named: "pause.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  161. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 1
  162. }
  163. func playButtonPlay() {
  164. playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  165. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 0
  166. }
  167. // MARK: - Event / Gesture
  168. @objc func playbackValChanged(slider: UISlider, event: UIEvent) {
  169. guard let touchEvent = event.allTouches?.first,
  170. let ncplayer = ncplayer
  171. else { return }
  172. let newPosition = playbackSlider.value
  173. switch touchEvent.phase {
  174. case .began:
  175. viewerMediaPage?.timerAutoHide?.invalidate()
  176. playbackSliderEvent = .began
  177. case .moved:
  178. ncplayer.playerPosition(newPosition)
  179. playbackSliderEvent = .moved
  180. case .ended:
  181. ncplayer.playerPosition(newPosition)
  182. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  183. self.playbackSliderEvent = .ended
  184. self.viewerMediaPage?.startTimerAutoHide()
  185. }
  186. default:
  187. break
  188. }
  189. }
  190. // MARK: - Action
  191. @objc func tap(gestureRecognizer: UITapGestureRecognizer) { }
  192. @IBAction func tapFullscreen(_ sender: Any) {
  193. isFullscreen = !isFullscreen
  194. if isFullscreen {
  195. fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.down.right.and.arrow.up.left", color: .white), for: .normal)
  196. } else {
  197. fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.up.left.and.arrow.down.right", color: .white), for: .normal)
  198. }
  199. viewerMediaPage?.changeScreenMode(mode: viewerMediaScreenMode)
  200. }
  201. @IBAction func tapSubTitle(_ sender: Any) {
  202. guard let player = ncplayer?.player else { return }
  203. let spuTracks = player.videoSubTitlesNames
  204. let spuTrackIndexes = player.videoSubTitlesIndexes
  205. toggleMenuSubTitle(spuTracks: spuTracks, spuTrackIndexes: spuTrackIndexes)
  206. }
  207. @IBAction func tapAudio(_ sender: Any) {
  208. guard let player = ncplayer?.player else { return }
  209. let audioTracks = player.audioTrackNames
  210. let audioTrackIndexes = player.audioTrackIndexes
  211. toggleMenuAudio(audioTracks: audioTracks, audioTrackIndexes: audioTrackIndexes)
  212. }
  213. @IBAction func tapPlayerPause(_ sender: Any) {
  214. guard let ncplayer = ncplayer else { return }
  215. if ncplayer.isPlay() {
  216. ncplayer.playerPause()
  217. } else {
  218. ncplayer.playerPlay()
  219. }
  220. self.viewerMediaPage?.startTimerAutoHide()
  221. }
  222. @IBAction func tapForward(_ sender: Any) {
  223. guard let ncplayer = ncplayer else { return }
  224. ncplayer.jumpForward(10)
  225. self.viewerMediaPage?.startTimerAutoHide()
  226. }
  227. @IBAction func tapBack(_ sender: Any) {
  228. guard let ncplayer = ncplayer else { return }
  229. ncplayer.jumpBackward(10)
  230. self.viewerMediaPage?.startTimerAutoHide()
  231. }
  232. @IBAction func tapRepeat(_ sender: Any) {
  233. if playRepeat {
  234. playRepeat = false
  235. repeatButton.setImage(NCUtility.shared.loadImage(named: "repeat", color: .gray), for: .normal)
  236. } else {
  237. playRepeat = true
  238. repeatButton.setImage(NCUtility.shared.loadImage(named: "repeat", color: .white), for: .normal)
  239. }
  240. }
  241. }
  242. extension NCPlayerToolBar {
  243. func toggleMenuSubTitle(spuTracks: [Any], spuTrackIndexes: [Any]) {
  244. var actions = [NCMenuAction]()
  245. var subTitleIndex: Int?
  246. if let data = NCManageDatabase.shared.getVideo(metadata: metadata), let idx = data.currentVideoSubTitleIndex {
  247. subTitleIndex = idx
  248. } else if let idx = ncplayer?.player.currentVideoSubTitleIndex {
  249. subTitleIndex = Int(idx)
  250. }
  251. if !spuTracks.isEmpty {
  252. for index in 0...spuTracks.count - 1 {
  253. guard let title = spuTracks[index] as? String, let idx = spuTrackIndexes[index] as? Int32, let metadata = self.metadata else { return }
  254. actions.append(
  255. NCMenuAction(
  256. title: title,
  257. icon: UIImage(),
  258. onTitle: title,
  259. onIcon: UIImage(),
  260. selected: (subTitleIndex ?? -9999) == idx,
  261. on: (subTitleIndex ?? -9999) == idx,
  262. action: { _ in
  263. self.ncplayer?.player.currentVideoSubTitleIndex = idx
  264. NCManageDatabase.shared.addVideo(metadata: metadata, currentVideoSubTitleIndex: Int(idx))
  265. }
  266. )
  267. )
  268. }
  269. actions.append(.seperator(order: 0))
  270. }
  271. actions.append(
  272. NCMenuAction(
  273. title: NSLocalizedString("_add_subtitle_", comment: ""),
  274. icon: UIImage(),
  275. onTitle: NSLocalizedString("_add_subtitle_", comment: ""),
  276. onIcon: UIImage(),
  277. selected: false,
  278. on: false,
  279. action: { _ in
  280. guard let metadata = self.metadata else { return }
  281. let storyboard = UIStoryboard(name: "NCSelect", bundle: nil)
  282. let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
  283. let viewController = navigationController.topViewController as! NCSelect
  284. viewController.delegate = self
  285. viewController.typeOfCommandView = .nothing
  286. viewController.includeDirectoryE2EEncryption = false
  287. viewController.enableSelectFile = true
  288. viewController.type = "subtitle"
  289. viewController.serverUrl = metadata.serverUrl
  290. self.viewerMediaPage?.present(navigationController, animated: true, completion: nil)
  291. }
  292. )
  293. )
  294. viewerMediaPage?.presentMenu(with: actions, menuColor: UIColor(hexString: "#1C1C1EFF"), textColor: .white)
  295. }
  296. func toggleMenuAudio(audioTracks: [Any], audioTrackIndexes: [Any]) {
  297. var actions = [NCMenuAction]()
  298. var audioIndex: Int?
  299. if let data = NCManageDatabase.shared.getVideo(metadata: metadata), let idx = data.currentAudioTrackIndex {
  300. audioIndex = idx
  301. } else if let idx = ncplayer?.player.currentAudioTrackIndex {
  302. audioIndex = Int(idx)
  303. }
  304. if !audioTracks.isEmpty {
  305. for index in 0...audioTracks.count - 1 {
  306. guard let title = audioTracks[index] as? String, let idx = audioTrackIndexes[index] as? Int32, let metadata = self.metadata else { return }
  307. actions.append(
  308. NCMenuAction(
  309. title: title,
  310. icon: UIImage(),
  311. onTitle: title,
  312. onIcon: UIImage(),
  313. selected: (audioIndex ?? -9999) == idx,
  314. on: (audioIndex ?? -9999) == idx,
  315. action: { _ in
  316. self.ncplayer?.player.currentAudioTrackIndex = idx
  317. NCManageDatabase.shared.addVideo(metadata: metadata, currentAudioTrackIndex: Int(idx))
  318. }
  319. )
  320. )
  321. }
  322. actions.append(.seperator(order: 0))
  323. }
  324. actions.append(
  325. NCMenuAction(
  326. title: NSLocalizedString("_add_audio_", comment: ""),
  327. icon: UIImage(),
  328. onTitle: NSLocalizedString("_add_audio_", comment: ""),
  329. onIcon: UIImage(),
  330. selected: false,
  331. on: false,
  332. action: { _ in
  333. guard let metadata = self.metadata else { return }
  334. let storyboard = UIStoryboard(name: "NCSelect", bundle: nil)
  335. let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
  336. let viewController = navigationController.topViewController as! NCSelect
  337. viewController.delegate = self
  338. viewController.typeOfCommandView = .nothing
  339. viewController.includeDirectoryE2EEncryption = false
  340. viewController.enableSelectFile = true
  341. viewController.type = "audio"
  342. viewController.serverUrl = metadata.serverUrl
  343. self.viewerMediaPage?.present(navigationController, animated: true, completion: nil)
  344. }
  345. )
  346. )
  347. viewerMediaPage?.presentMenu(with: actions, menuColor: UIColor(hexString: "#1C1C1EFF"), textColor: .white)
  348. }
  349. }
  350. extension NCPlayerToolBar: NCSelectDelegate {
  351. func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool) {
  352. if let metadata = metadata, let viewerMediaPage = viewerMediaPage {
  353. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  354. let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
  355. if CCUtility.fileProviderStorageExists(metadata) {
  356. addPlaybackSlave(type: type, metadata: metadata)
  357. } else {
  358. var downloadRequest: DownloadRequest?
  359. hud.indicatorView = JGProgressHUDRingIndicatorView()
  360. hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
  361. if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
  362. indicatorView.ringWidth = 1.5
  363. }
  364. hud.tapOnHUDViewBlock = { _ in
  365. if let request = downloadRequest {
  366. request.cancel()
  367. }
  368. }
  369. hud.show(in: viewerMediaPage.view)
  370. NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, requestHandler: { request in
  371. downloadRequest = request
  372. }, taskHandler: { _ in
  373. }, progressHandler: { progress in
  374. self.hud.progress = Float(progress.fractionCompleted)
  375. }) { account, _, _, _, _, _, error in
  376. self.hud.dismiss()
  377. if error == .success {
  378. self.addPlaybackSlave(type: type, metadata: metadata)
  379. } else if error.errorCode != 200 {
  380. NCContentPresenter.shared.showError(error: error)
  381. }
  382. }
  383. }
  384. }
  385. }
  386. func addPlaybackSlave(type: String, metadata: tableMetadata) {
  387. let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
  388. if type == "subtitle" {
  389. self.ncplayer?.player.addPlaybackSlave(URL(fileURLWithPath: fileNameLocalPath), type: .subtitle, enforce: true)
  390. } else if type == "audio" {
  391. self.ncplayer?.player.addPlaybackSlave(URL(fileURLWithPath: fileNameLocalPath), type: .audio, enforce: true)
  392. }
  393. }
  394. }