NCPlayerToolBar.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  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. enum sliderEventType {
  47. case began
  48. case ended
  49. case moved
  50. }
  51. var playbackSliderEvent: sliderEventType = .ended
  52. var isFullscreen: Bool = false
  53. private let hud = JGProgressHUD()
  54. private var ncplayer: NCPlayer?
  55. private var metadata: tableMetadata?
  56. private let audioSession = AVAudioSession.sharedInstance()
  57. private var pointSize: CGFloat = 0
  58. private weak var viewerMediaPage: NCViewerMediaPage?
  59. // MARK: - View Life Cycle
  60. override func awakeFromNib() {
  61. super.awakeFromNib()
  62. self.backgroundColor = UIColor.black.withAlphaComponent(0.1)
  63. fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.up.left.and.arrow.down.right", color: .white), for: .normal)
  64. subtitleButton.setImage(NCUtility.shared.loadImage(named: "captions.bubble", color: .white), for: .normal)
  65. subtitleButton.isEnabled = false
  66. audioButton.setImage(NCUtility.shared.loadImage(named: "speaker.zzz", color: .white), for: .normal)
  67. audioButton.isEnabled = false
  68. if UIDevice.current.userInterfaceIdiom == .pad {
  69. pointSize = 60
  70. } else {
  71. pointSize = 50
  72. }
  73. playerButtonView.spacing = pointSize
  74. playerButtonView.isHidden = true
  75. backButton.setImage(NCUtility.shared.loadImage(named: "gobackward.10", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  76. playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  77. forwardButton.setImage(NCUtility.shared.loadImage(named: "goforward.10", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  78. playbackSlider.setThumbImage(UIImage(systemName: "circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15)), for: .normal)
  79. playbackSlider.value = 0
  80. playbackSlider.tintColor = .white
  81. playbackSlider.addTarget(self, action: #selector(playbackValChanged(slider:event:)), for: .valueChanged)
  82. utilityView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(gestureRecognizer:))))
  83. playbackSliderView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(gestureRecognizer:))))
  84. playerButtonView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(gestureRecognizer:))))
  85. labelCurrentTime.textColor = .white
  86. labelLeftTime.textColor = .white
  87. // Normally hide
  88. self.alpha = 0
  89. self.isHidden = true
  90. }
  91. required init?(coder aDecoder: NSCoder) {
  92. super.init(coder: aDecoder)
  93. }
  94. deinit {
  95. print("deinit NCPlayerToolBar")
  96. }
  97. // MARK: -
  98. func setBarPlayer(position: Float, ncplayer: NCPlayer? = nil, metadata: tableMetadata? = nil, viewerMediaPage: NCViewerMediaPage? = nil) {
  99. if let ncplayer = ncplayer {
  100. self.ncplayer = ncplayer
  101. }
  102. if let metadata = metadata {
  103. self.metadata = metadata
  104. }
  105. if let viewerMediaPage = viewerMediaPage {
  106. self.viewerMediaPage = viewerMediaPage
  107. }
  108. playerButtonView.isHidden = true
  109. playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  110. playbackSlider.value = position
  111. labelCurrentTime.text = "--:--"
  112. labelLeftTime.text = "--:--"
  113. if viewerMediaScreenMode == .normal {
  114. show()
  115. } else {
  116. hide()
  117. }
  118. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = position
  119. }
  120. public func update() {
  121. guard let ncplayer = self.ncplayer,
  122. let length = ncplayer.player.media?.length.intValue
  123. else { return }
  124. let position = ncplayer.player.position
  125. let positionInSecond = position * Float(length / 1000)
  126. // SLIDER & TIME
  127. if playbackSliderEvent == .ended {
  128. playbackSlider.value = position
  129. }
  130. labelCurrentTime.text = ncplayer.player.time.stringValue
  131. labelLeftTime.text = ncplayer.player.remainingTime?.stringValue
  132. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPMediaItemPropertyPlaybackDuration] = length / 1000
  133. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = positionInSecond
  134. }
  135. public func updateTopToolBar(videoSubTitlesIndexes: [Any], audioTrackIndexes: [Any]) {
  136. if let metadata = metadata, metadata.isVideo {
  137. self.subtitleButton.isEnabled = true
  138. self.audioButton.isEnabled = true
  139. }
  140. }
  141. // MARK: -
  142. public func show() {
  143. UIView.animate(withDuration: 0.5, animations: {
  144. self.alpha = 1
  145. }, completion: { (_: Bool) in
  146. self.isHidden = false
  147. })
  148. }
  149. func hide() {
  150. UIView.animate(withDuration: 0.5, animations: {
  151. self.alpha = 0
  152. }, completion: { (_: Bool) in
  153. self.isHidden = true
  154. })
  155. }
  156. func playButtonPause() {
  157. playButton.setImage(NCUtility.shared.loadImage(named: "pause.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  158. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 1
  159. }
  160. func playButtonPlay() {
  161. playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize)), for: .normal)
  162. MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 0
  163. }
  164. // MARK: - Event / Gesture
  165. @objc func playbackValChanged(slider: UISlider, event: UIEvent) {
  166. guard let touchEvent = event.allTouches?.first,
  167. let ncplayer = ncplayer
  168. else { return }
  169. let newPosition = playbackSlider.value
  170. switch touchEvent.phase {
  171. case .began:
  172. viewerMediaPage?.timerAutoHide?.invalidate()
  173. playbackSliderEvent = .began
  174. case .moved:
  175. ncplayer.playerPosition(newPosition)
  176. playbackSliderEvent = .moved
  177. case .ended:
  178. ncplayer.playerPosition(newPosition)
  179. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  180. self.playbackSliderEvent = .ended
  181. self.viewerMediaPage?.startTimerAutoHide()
  182. }
  183. default:
  184. break
  185. }
  186. }
  187. // MARK: - Action
  188. @objc func tap(gestureRecognizer: UITapGestureRecognizer) { }
  189. @IBAction func tapFullscreen(_ sender: Any) {
  190. isFullscreen = !isFullscreen
  191. if isFullscreen {
  192. fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.down.right.and.arrow.up.left", color: .white), for: .normal)
  193. } else {
  194. fullscreenButton.setImage(NCUtility.shared.loadImage(named: "arrow.up.left.and.arrow.down.right", color: .white), for: .normal)
  195. }
  196. viewerMediaPage?.changeScreenMode(mode: viewerMediaScreenMode)
  197. }
  198. @IBAction func tapSubTitle(_ sender: Any) {
  199. guard let player = ncplayer?.player else { return }
  200. let spuTracks = player.videoSubTitlesNames
  201. let spuTrackIndexes = player.videoSubTitlesIndexes
  202. toggleMenuSubTitle(spuTracks: spuTracks, spuTrackIndexes: spuTrackIndexes)
  203. }
  204. @IBAction func tapAudio(_ sender: Any) {
  205. guard let player = ncplayer?.player else { return }
  206. let audioTracks = player.audioTrackNames
  207. let audioTrackIndexes = player.audioTrackIndexes
  208. toggleMenuAudio(audioTracks: audioTracks, audioTrackIndexes: audioTrackIndexes)
  209. }
  210. @IBAction func tapPlayerPause(_ sender: Any) {
  211. guard let ncplayer = ncplayer else { return }
  212. if ncplayer.isPlay() {
  213. ncplayer.playerPause()
  214. } else {
  215. ncplayer.playerPlay()
  216. }
  217. self.viewerMediaPage?.startTimerAutoHide()
  218. }
  219. @IBAction func tapForward(_ sender: Any) {
  220. guard let ncplayer = ncplayer else { return }
  221. ncplayer.jumpForward(10)
  222. self.viewerMediaPage?.startTimerAutoHide()
  223. }
  224. @IBAction func tapBack(_ sender: Any) {
  225. guard let ncplayer = ncplayer else { return }
  226. ncplayer.jumpBackward(10)
  227. self.viewerMediaPage?.startTimerAutoHide()
  228. }
  229. }
  230. extension NCPlayerToolBar {
  231. func toggleMenuSubTitle(spuTracks: [Any], spuTrackIndexes: [Any]) {
  232. var actions = [NCMenuAction]()
  233. var subTitleIndex: Int?
  234. if let data = NCManageDatabase.shared.getVideo(metadata: metadata), let idx = data.currentVideoSubTitleIndex {
  235. subTitleIndex = idx
  236. } else if let idx = ncplayer?.player.currentVideoSubTitleIndex {
  237. subTitleIndex = Int(idx)
  238. }
  239. if !spuTracks.isEmpty {
  240. for index in 0...spuTracks.count - 1 {
  241. guard let title = spuTracks[index] as? String, let idx = spuTrackIndexes[index] as? Int32, let metadata = self.metadata else { return }
  242. actions.append(
  243. NCMenuAction(
  244. title: title,
  245. icon: UIImage(),
  246. onTitle: title,
  247. onIcon: UIImage(),
  248. selected: (subTitleIndex ?? -9999) == idx,
  249. on: (subTitleIndex ?? -9999) == idx,
  250. action: { _ in
  251. self.ncplayer?.player.currentVideoSubTitleIndex = idx
  252. NCManageDatabase.shared.addVideo(metadata: metadata, currentVideoSubTitleIndex: Int(idx))
  253. }
  254. )
  255. )
  256. }
  257. }
  258. actions.append(
  259. NCMenuAction(
  260. title: NSLocalizedString("_add_subtitle_", comment: ""),
  261. icon: UIImage(),
  262. onTitle: NSLocalizedString("_add_subtitle_", comment: ""),
  263. onIcon: UIImage(),
  264. selected: false,
  265. on: false,
  266. action: { _ in
  267. let storyboard = UIStoryboard(name: "NCSelect", bundle: nil)
  268. let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
  269. let viewController = navigationController.topViewController as! NCSelect
  270. viewController.delegate = self
  271. viewController.typeOfCommandView = .nothing
  272. viewController.includeDirectoryE2EEncryption = false
  273. viewController.enableSelectFile = true
  274. viewController.type = "subtitle"
  275. self.viewerMediaPage?.present(navigationController, animated: true, completion: nil)
  276. }
  277. )
  278. )
  279. viewerMediaPage?.presentMenu(with: actions, menuColor: UIColor(hexString: "#1C1C1EFF"), textColor: .white)
  280. }
  281. func toggleMenuAudio(audioTracks: [Any], audioTrackIndexes: [Any]) {
  282. var actions = [NCMenuAction]()
  283. var audioIndex: Int?
  284. if let data = NCManageDatabase.shared.getVideo(metadata: metadata), let idx = data.currentAudioTrackIndex {
  285. audioIndex = idx
  286. } else if let idx = ncplayer?.player.currentAudioTrackIndex {
  287. audioIndex = Int(idx)
  288. }
  289. if !audioTracks.isEmpty {
  290. for index in 0...audioTracks.count - 1 {
  291. guard let title = audioTracks[index] as? String, let idx = audioTrackIndexes[index] as? Int32, let metadata = self.metadata else { return }
  292. actions.append(
  293. NCMenuAction(
  294. title: title,
  295. icon: UIImage(),
  296. onTitle: title,
  297. onIcon: UIImage(),
  298. selected: (audioIndex ?? -9999) == idx,
  299. on: (audioIndex ?? -9999) == idx,
  300. action: { _ in
  301. self.ncplayer?.player.currentAudioTrackIndex = idx
  302. NCManageDatabase.shared.addVideo(metadata: metadata, currentAudioTrackIndex: Int(idx))
  303. }
  304. )
  305. )
  306. }
  307. }
  308. actions.append(
  309. NCMenuAction(
  310. title: NSLocalizedString("_add_audio_", comment: ""),
  311. icon: UIImage(),
  312. onTitle: NSLocalizedString("_add_audio_", comment: ""),
  313. onIcon: UIImage(),
  314. selected: false,
  315. on: false,
  316. action: { _ in
  317. let storyboard = UIStoryboard(name: "NCSelect", bundle: nil)
  318. let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
  319. let viewController = navigationController.topViewController as! NCSelect
  320. viewController.delegate = self
  321. viewController.typeOfCommandView = .nothing
  322. viewController.includeDirectoryE2EEncryption = false
  323. viewController.enableSelectFile = true
  324. viewController.type = "audio"
  325. self.viewerMediaPage?.present(navigationController, animated: true, completion: nil)
  326. }
  327. )
  328. )
  329. viewerMediaPage?.presentMenu(with: actions, menuColor: UIColor(hexString: "#1C1C1EFF"), textColor: .white)
  330. }
  331. }
  332. extension NCPlayerToolBar: NCSelectDelegate {
  333. func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool) {
  334. if let metadata = metadata, let viewerMediaPage = viewerMediaPage {
  335. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  336. let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
  337. if CCUtility.fileProviderStorageExists(metadata) {
  338. addPlaybackSlave(type: type, metadata: metadata)
  339. } else {
  340. var downloadRequest: DownloadRequest?
  341. hud.indicatorView = JGProgressHUDRingIndicatorView()
  342. hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
  343. if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
  344. indicatorView.ringWidth = 1.5
  345. }
  346. hud.tapOnHUDViewBlock = { _ in
  347. if let request = downloadRequest {
  348. request.cancel()
  349. }
  350. }
  351. hud.show(in: viewerMediaPage.view)
  352. NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, requestHandler: { request in
  353. downloadRequest = request
  354. }, taskHandler: { _ in
  355. }, progressHandler: { progress in
  356. self.hud.progress = Float(progress.fractionCompleted)
  357. }) { account, _, _, _, _, _, error in
  358. self.hud.dismiss()
  359. if error == .success {
  360. self.addPlaybackSlave(type: type, metadata: metadata)
  361. } else if error.errorCode != 200 {
  362. NCContentPresenter.shared.showError(error: error)
  363. }
  364. }
  365. }
  366. }
  367. }
  368. func addPlaybackSlave(type: String, metadata: tableMetadata) {
  369. let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
  370. if type == "subtitle" {
  371. self.ncplayer?.player.addPlaybackSlave(URL(fileURLWithPath: fileNameLocalPath), type: .subtitle, enforce: true)
  372. } else if type == "audio" {
  373. self.ncplayer?.player.addPlaybackSlave(URL(fileURLWithPath: fileNameLocalPath), type: .audio, enforce: true)
  374. }
  375. }
  376. }