PlayDetailViewController.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. //
  2. // PlayDetailViewController.swift
  3. // ColorfulWallpaper
  4. //
  5. // Created by nkl on 2024/9/13.
  6. //
  7. //import ADManager
  8. import Combine
  9. import Foundation
  10. import KLTips
  11. import MediaPlayer
  12. import SJVideoPlayer
  13. import TSVideoKit
  14. import UIKit
  15. class PlayDetailViewController: LWBGViewController {
  16. var playControl: TSVideoPlayController {
  17. if !(TSVideoOperator.shared.playerController.player.playbackController is SJIJKMediaPlaybackController) {
  18. TSVideoOperator.shared.playerController.player.playbackController = SJIJKMediaPlaybackController()
  19. TSVideoOperator.shared.playerController.player.isPausedInBackground = false
  20. TSVideoOperator.shared.playerController.player.rotationManager?.isDisabledAutorotation = true
  21. TSVideoOperator.shared.playerController.player.delayInSecondsForHiddenPlaceholderImageView = 0
  22. }
  23. return TSVideoOperator.shared.playerController
  24. }
  25. lazy var customLoading: SJLoadingView = SJLoadingView()
  26. lazy var placeHolderView: PlayDetailPlaceHolderView = PlayDetailPlaceHolderView()
  27. lazy var retryButton: UIButton = {
  28. let btn = UIButton()
  29. btn.setTitle("Reload", for: .normal)
  30. btn.setTitleColor(.white, for: .normal)
  31. btn.titleLabel?.font = .systemFont(ofSize: 14)
  32. btn.customCornerRadius = 20
  33. btn.isHidden = true
  34. return btn
  35. }()
  36. lazy var topView: PlayDetailTopView = PlayDetailTopView()
  37. lazy var controlView: PlayDetailControlView = PlayDetailControlView()
  38. var downloader: NewChunkDownloader?
  39. private var timer: Timer?
  40. private var cancellable: [AnyCancellable] = []
  41. private var downloaderCancellable: [AnyCancellable] = []
  42. override func viewDidLoad() {
  43. super.viewDidLoad()
  44. bgImageView.image = .viewMainBg
  45. playControl.view.backgroundColor = .clear
  46. addNotifaction()
  47. addActionTargets()
  48. }
  49. func updateNavibar(video: TSVideo?) {
  50. if video?.videoStatus == .cached {
  51. topView.moreButton.isHidden = false
  52. } else {
  53. topView.moreButton.isHidden = true
  54. }
  55. }
  56. override func viewDidLayoutSubviews() {
  57. super.viewDidLayoutSubviews()
  58. // controlView.setRectCorner(corner: [.topLeft, .topRight], radii: CGSize.init(width: 24, height: 24))
  59. retryButton.setGradient(colors: [.hexColor("#72F4E6"), .hexColor("#374BEC")], index: 0)
  60. }
  61. override func addNotifaction() {
  62. super.addNotifaction()
  63. addPlayNotifaction()
  64. addTaskNotifaction()
  65. addSleepNotifacion()
  66. }
  67. func addSleepNotifacion() {
  68. PlayerManager.shared.$sleepModel.receive(on: DispatchQueue.main).sink { [weak self] model in
  69. guard let self = self else {
  70. return
  71. }
  72. self.topView.timeButton.setTitle(model.countTime.hhmmss, for: .normal)
  73. self.topView.timeButton.isSelected = model.isOpen
  74. }.store(in: &cancellable)
  75. NotificationCenter.default.addObserver(self, selector: #selector(updateLikeInfo), name: kDataChangedNotifactionName, object: nil)
  76. NotificationCenter.default.addObserver(self, selector: #selector(retryAudio(notify:)), name: kSingleVideoFailNotifactionName, object: nil)
  77. }
  78. @objc func retryAudio(notify: Notification) {
  79. if let videoId = notify.object as? String,
  80. videoId == PlayerManager.shared.currentVideo?.videoId {
  81. TSNewDownloadManager.shared.retryDownload(videoId: videoId, isAudio: true) { [weak self] downloader in
  82. guard let self = self else { return }
  83. self.downloader = downloader
  84. self.updateDownloader(id: videoId)
  85. }
  86. }
  87. }
  88. func addPlayNotifaction() {
  89. playControl.viewModel.$loadState.receive(on: DispatchQueue.main).sink { [weak self] state in
  90. guard let self = self else { return }
  91. self.loadingStateChangeHandler(state: state)
  92. }.store(in: &cancellable)
  93. playControl.viewModel.$currentTime.receive(on: DispatchQueue.main).sink { [weak self] timeValue in
  94. guard let self = self else { return }
  95. self.controlView.updateTime(currentTime: timeValue, duration: self.playControl.player.duration)
  96. }.store(in: &cancellable)
  97. playControl.viewModel.$playStatus.receive(on: DispatchQueue.main).sink { [weak self] status in
  98. guard let self = self else { return }
  99. self.playStatusChangeHandler(status)
  100. }.store(in: &cancellable)
  101. playControl.viewModel.$loopMode.receive(on: DispatchQueue.main).sink { [weak self] loop in
  102. guard let self = self else { return }
  103. self.controlView.playModelButton.setImage(UIImage(named: loop.icon), for: .normal)
  104. }.store(in: &cancellable)
  105. playControl.viewModel.$currentVideo.receive(on: DispatchQueue.main).dropFirst().scan((nil, nil)) { previous, current in
  106. (previous.1, current)
  107. }.sink { [weak self] oldValue, newValue in
  108. guard let self = self else { return }
  109. /// 即将播放的和正在播放的视频是同一个
  110. if newValue != nil &&
  111. oldValue?.videoId == newValue?.videoId {
  112. PlayerManager.shared.showPlayerViewController()
  113. return
  114. }
  115. if let mVideo = newValue {
  116. self.controlView.updateVideoInfo(video: mVideo)
  117. PlayerManager.shared.miniBar.updateVideoInfo(video: mVideo, state: .pause)
  118. self.updateDownloader(id: mVideo.videoId ?? "")
  119. } else {
  120. self.controlView.resetVideoInfo()
  121. self.controlView.resetProgress()
  122. PlayerManager.shared.miniBar.resetVideoInfo()
  123. }
  124. self.updatePlaceHoderView(video: newValue, status: .stopped)
  125. self.updateNavibar(video: newValue)
  126. if let videoId = newValue?.videoId {
  127. UserDefaults.standard.set(videoId, forKey: "LAST_PlAYED_VIDEO_ID")
  128. }
  129. NotificationCenter.default.post(name: kDataChangedNotifactionName, object: nil)
  130. }.store(in: &cancellable)
  131. }
  132. func updatePlaceHoderView(video: TSVideo?, status: VLCMediaPlayerState) {
  133. guard let mVideo = video else {
  134. return
  135. }
  136. playControl.view.isHidden = false
  137. if mVideo.isOnline {
  138. placeHolderView.isHidden = true
  139. playControl.player.presentView.placeholderImageView.kf.setImage(with: mVideo.iconUrl)
  140. } else {
  141. playControl.player.presentView.placeholderImageView.image = nil
  142. if mVideo.isAudio {
  143. placeHolderView.isHidden = false
  144. playControl.view.isHidden = true
  145. } else {
  146. print("placeHolderView.frame === \(placeHolderView.frame)")
  147. if mVideo.videoStatus == .cached {
  148. placeHolderView.isHidden = true
  149. } else {
  150. switch status {
  151. case .stopped, .opening:
  152. placeHolderView.isHidden = false
  153. default:
  154. placeHolderView.isHidden = true
  155. }
  156. }
  157. }
  158. }
  159. }
  160. func loadingStateChangeHandler(state: TSLoadState) {
  161. /// 展示状态
  162. if state == .loading {
  163. retryButton.isHidden = true
  164. showLoading()
  165. } else if state == .fail {
  166. hideLoading()
  167. retryButton.isHidden = false
  168. } else if state == .success {
  169. retryButton.isHidden = true
  170. hideLoading()
  171. } else {
  172. retryButton.isHidden = true
  173. hideLoading()
  174. }
  175. /// 按钮是否可用
  176. if state == .loading {
  177. controlView.playButton.playingState = .pause
  178. controlView.playlistButton.isEnabled = false
  179. controlView.progressView.isUserInteractionEnabled = false
  180. } else {
  181. controlView.playlistButton.isEnabled = true
  182. controlView.progressView.isUserInteractionEnabled = true
  183. }
  184. }
  185. fileprivate func playStatusChangeHandler(_ status: VLCMediaPlayerState) {
  186. if status == .playing {
  187. controlView.playButton.playingState = .playing
  188. updatePlaceHoderView(video: playControl.viewModel.currentVideo, status: status)
  189. if let video = playControl.viewModel.currentVideo {
  190. PlayerManager.shared.miniBar.updateVideoInfo(video: video, state: .playing)
  191. }
  192. } else if status == .paused || status == .ended || status == .stopped {
  193. controlView.playButton.playingState = .pause
  194. if let video = playControl.viewModel.currentVideo {
  195. PlayerManager.shared.miniBar.updateVideoInfo(video: video, state: .pause)
  196. }
  197. } else if status == .buffering {
  198. /// 缓冲了展示loading
  199. if !playControl.player.isPlaying && !controlView.progressView.isDragging {
  200. showLoading()
  201. } else {
  202. hideLoading()
  203. }
  204. }
  205. }
  206. func addTaskNotifaction() {
  207. // 单个完成回调
  208. NotificationCenter.default.addObserver(self, selector: #selector(updateDownoloadStatus(notify:)), name: kGroupDownloadFinishNotifactionName, object: nil)
  209. }
  210. func updateDownloader(id: String) {
  211. if let downloader = TSNewDownloadManager.shared.fetchDownloader(identifier: id) {
  212. downloaderCancellable.removeAll()
  213. self.downloader = downloader
  214. downloader.$progress.receive(on: DispatchQueue.main).sink { [weak self] progress in
  215. self?.controlView.downloadButton.progressView.set(progress: Double(progress))
  216. }.store(in: &downloaderCancellable)
  217. downloader.$downloadState.receive(on: DispatchQueue.main).sink { state in
  218. self.controlView.setDownloadButtonStates(status: state)
  219. }.store(in: &downloaderCancellable)
  220. } else {
  221. downloaderCancellable.removeAll()
  222. downloader = nil
  223. if let video = TSVideoOperator.shared.dataManager.fetchVideo(videoId: id) {
  224. controlView.setDownloadButtonStates(status: video.videoStatus.asChunkDownloadStatus)
  225. } else {
  226. controlView.setDownloadButtonStates(status: .idle)
  227. }
  228. }
  229. }
  230. @objc func updateDownoloadStatus(notify: Notification) {
  231. if let videoId = notify.object as? String,
  232. let video = TSVideoOperator.shared.dataManager.fetchVideo(videoId: videoId),
  233. let currentVideo = TSVideoOperator.shared.playerViewModel.currentVideo {
  234. if videoId == currentVideo.videoId {
  235. controlView.setDownloadButtonStates(status: video.videoStatus.asChunkDownloadStatus)
  236. }
  237. }
  238. }
  239. @objc func showMoreOperation() {
  240. let vc = CWOperateViewController(types: [.like, .addPlaylist, .share, .deleteVideo])
  241. vc.modalPresentationStyle = .overFullScreen
  242. vc.operteItem = playControl.viewModel.currentVideo
  243. PlayerManager.shared.rootVc?.present(vc, animated: true)
  244. }
  245. func countDownTime() {
  246. timer?.invalidate()
  247. timer = nil
  248. PlayerManager.shared.sleepModel.isOpen = true
  249. timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
  250. PlayerManager.shared.sleepModel.countTime -= 1
  251. if PlayerManager.shared.sleepModel.countTime < 1 {
  252. /// 倒计时结束,暂停播放器
  253. self.playControl.pause()
  254. self.stopTimer()
  255. }
  256. }
  257. }
  258. func stopTimer() {
  259. PlayerManager.shared.sleepModel.isOpen = false
  260. PlayerManager.shared.sleepModel.countTime = 0
  261. timer?.invalidate()
  262. timer = nil
  263. }
  264. func addActionTargets() {
  265. topView.backButton.addTarget(self, action: #selector(swapDown), for: .touchUpInside)
  266. topView.timeButton.addTarget(self, action: #selector(showSleepTimeController), for: .touchUpInside)
  267. topView.moreButton.addTarget(self, action: #selector(showMoreOperation), for: .touchUpInside)
  268. controlView.playButton.addTarget(self, action: #selector(playButtonAction), for: .touchUpInside)
  269. controlView.listButton.addTarget(self, action: #selector(playlistButtonAction), for: .touchUpInside)
  270. controlView.nextButton.addTarget(self, action: #selector(nextButtonAction), for: .touchUpInside)
  271. controlView.lastButton.addTarget(self, action: #selector(lastButtonAction), for: .touchUpInside)
  272. controlView.playModelButton.addTarget(self, action: #selector(playModeButtonAction), for: .touchUpInside)
  273. controlView.downloadButton.addTarget(self, action: #selector(downloadButtonAction), for: .touchUpInside)
  274. controlView.playlistButton.addTarget(self, action: #selector(addToPlaylistAction), for: .touchUpInside)
  275. controlView.favouriteButton.addTarget(self, action: #selector(favouriteButtonAction), for: .touchUpInside)
  276. retryButton.addTarget(self, action: #selector(reloadVideo), for: .touchUpInside)
  277. controlView.progressView.tappedExeBlock = { [weak self] _, progress in
  278. self?.controlView.progressView.value = progress
  279. let durationTime = (self?.playControl.player.duration ?? 0)
  280. let currentTime = durationTime * progress
  281. print("duration---\(durationTime),current ---- \(currentTime)")
  282. self?.controlView.updateTime(currentTime: currentTime, duration: durationTime)
  283. self?.playControl.pause()
  284. self?.playControl.seekToProgress(progress: progress) { [weak self] _ in
  285. self?.playControl.play()
  286. }
  287. }
  288. // 创建下滑手势识别器
  289. let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(swapDown))
  290. swipeDownGesture.direction = .down
  291. // 添加手势识别器到 myView
  292. view.addGestureRecognizer(swipeDownGesture)
  293. }
  294. @objc func reloadVideo() {
  295. playControl.viewModel.reloadCurrentVideo()
  296. }
  297. override func addChildren() {
  298. super.addChildren()
  299. controlView.progressView.delegate = self
  300. placeHolderView.isHidden = true
  301. addChild(playControl)
  302. view.addSubview(topView)
  303. view.addSubview(playControl.view)
  304. view.addSubview(controlView)
  305. view.addSubview(placeHolderView)
  306. view.addSubview(customLoading)
  307. view.addSubview(retryButton)
  308. view.bringSubviewToFront(topView)
  309. }
  310. override func makeConstarints() {
  311. super.makeConstarints()
  312. topView.snp.makeConstraints { make in
  313. make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
  314. make.leading.trailing.equalToSuperview()
  315. make.height.equalTo(44)
  316. }
  317. let vRadio = UIScreen.kScreenHeight / 896.0
  318. playControl.view.snp.makeConstraints { make in
  319. make.top.equalTo(topView.snp.bottom).offset(100 * vRadio)
  320. make.leading.trailing.equalToSuperview()
  321. make.height.equalTo(playControl.view.snp.width).multipliedBy(9.0 / 16.0)
  322. }
  323. retryButton.snp.makeConstraints { make in
  324. make.center.equalTo(playControl.view)
  325. make.height.equalTo(40)
  326. make.width.equalTo(100)
  327. }
  328. customLoading.snp.makeConstraints { make in
  329. make.center.equalTo(playControl.view)
  330. }
  331. placeHolderView.iconView.contentMode = .scaleAspectFit
  332. placeHolderView.snp.makeConstraints { make in
  333. make.top.equalTo(topView.snp.bottom).offset(50 * vRadio)
  334. make.leading.trailing.equalToSuperview()
  335. make.height.equalTo(placeHolderView.snp.width).multipliedBy(9.0 / 16.0)
  336. }
  337. controlView.snp.makeConstraints { make in
  338. make.leading.trailing.bottom.equalToSuperview()
  339. make.top.equalTo(playControl.view.snp.bottom).offset(40)
  340. make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
  341. }
  342. }
  343. }
  344. //
  345. /// targets
  346. extension PlayDetailViewController {
  347. @objc func swapDown() {
  348. PlayerManager.shared.hiddePlayerViewController()
  349. }
  350. @objc func playButtonAction() {
  351. /// 播放器没源,但是有当前播放歌曲
  352. if PlayerManager.shared.player?.playControl.player.urlAsset == nil, let current = PlayerManager.shared.currentVideo {
  353. PlayerManager.shared.playVideo(video: current, list: [], scene: .local, onceAdKey: "")
  354. } else {
  355. PlayerManager.shared.playOrPause()
  356. }
  357. }
  358. @objc func playlistButtonAction() {
  359. let vc = PlayDetailListViewContoller()
  360. vc.viewModel.videos = playControl.viewModel.currentVideos
  361. vc.viewModel.recommendDatas = playControl.viewModel.recommendDatas
  362. vc.viewModel.playScene = playControl.viewModel.scene
  363. vc.modalPresentationStyle = .overFullScreen
  364. present(vc, animated: true)
  365. }
  366. @objc func nextButtonAction() {
  367. if FitManager.isAr {
  368. playControl.playLast()
  369. } else {
  370. playControl.playNext()
  371. }
  372. }
  373. @objc func lastButtonAction() {
  374. if FitManager.isAr {
  375. playControl.playNext()
  376. } else {
  377. playControl.playLast()
  378. }
  379. }
  380. @objc func playModeButtonAction() {
  381. playControl.viewModel.loopMode = playControl.viewModel.loopMode.nextMode()
  382. THUD.toast(playControl.viewModel.loopMode.rawValue, at: .center)
  383. }
  384. @objc func favouriteButtonAction() {
  385. if let video = TSVideoOperator.shared.playerViewModel.currentVideo {
  386. if video.isFavorite {
  387. TSVideoOperator.shared.dataManager.dislikeCurrentVideo { _ in
  388. updateLikeInfo()
  389. }
  390. } else {
  391. TSVideoOperator.shared.dataManager.likeCurrentVideo { _ in
  392. updateLikeInfo()
  393. }
  394. }
  395. }
  396. }
  397. @objc func updateLikeInfo() {
  398. if let video = TSVideoOperator.shared.playerViewModel.currentVideo {
  399. controlView.favouriteButton.isSelected = video.isFavorite
  400. }
  401. }
  402. @objc func downloadButtonAction(sender: DownloadButton) {
  403. if let video = TSVideoOperator.shared.playerViewModel.currentVideo,
  404. let videoId = video.videoId {
  405. switch sender.downloadState {
  406. case .idle(isAnimate: false), .idle(isAnimate: true):
  407. // if PurchaseManager.default.isVip {
  408. TSNewDownloadManager.shared.downloadVideo(videoId: videoId, isAudio: false) { [weak self] downloader in
  409. self?.downloader = downloader
  410. self?.updateDownloader(id: videoId)
  411. }
  412. if let rootVc = PlayerManager.shared.rootVc {
  413. rootVc.presentingViewController?.dismiss(animated: false)
  414. // if !PurchaseManager.default.isVip {
  415. // ADManager.shared.showAd(scene: ADScene.downloadInsert, from: rootVc)
  416. // }
  417. }
  418. // } else {
  419. // let loading = ADLoadingViewController()
  420. // loading.adScene = .downloadReward
  421. // loading.finishedHandler = { [weak self] finished in
  422. // if finished {
  423. // TSNewDownloadManager.shared.downloadVideo(videoId: videoId, isAudio: false) { [weak self] downloader in
  424. // self?.downloader = downloader
  425. // self?.updateDownloader(id: videoId)
  426. // }
  427. // }
  428. // }
  429. // PlayerManager.shared.rootVc?.present(loading, animated: false)
  430. // }
  431. break
  432. case .pause:
  433. TSNewDownloadManager.shared.resumeTask(id: videoId) { [weak self] downloader in
  434. self?.downloader = downloader
  435. self?.updateDownloader(id: videoId)
  436. }
  437. case .downloading:
  438. TSNewDownloadManager.shared.pauseTask(id: videoId) { [weak self] downloader in
  439. self?.downloader = downloader
  440. self?.updateDownloader(id: videoId)
  441. }
  442. case .retry:
  443. TSNewDownloadManager.shared.retryDownload(videoId: videoId, isAudio: false) { [weak self] downloader in
  444. self?.downloader = downloader
  445. self?.updateDownloader(id: videoId)
  446. }
  447. break
  448. default:
  449. break
  450. }
  451. }
  452. }
  453. // 调用这个方法来显示加载圈
  454. func showLoading() {
  455. customLoading.start()
  456. }
  457. // 调用这个方法来隐藏加载圈
  458. func hideLoading() {
  459. customLoading.stop()
  460. }
  461. @objc func addToPlaylistAction() {
  462. if let video = TSVideoOperator.shared.playerViewModel.currentVideo {
  463. let addVc: AddPlayListViewController = AddPlayListViewController(video: video)
  464. addVc.addSuccessBlock = {
  465. THUD.toast("Added Successfully".localized())
  466. }
  467. addVc.modalPresentationStyle = .overFullScreen
  468. PlayerManager.shared.rootVc?.present(addVc, animated: true)
  469. }
  470. }
  471. @objc func showSleepTimeController() {
  472. let times: [Int] = [15, 30, 60, 90, 120]
  473. // 创建 UIAlertController 实例
  474. let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  475. for time in times {
  476. let option = UIAlertAction(title: "\(time) minutes", style: .default) { [weak self] _ in
  477. PlayerManager.shared.sleepModel.countTime = Double(time) * 60.0
  478. self?.countDownTime()
  479. }
  480. alertController.addAction(option)
  481. }
  482. // 添加关闭按钮
  483. let offAction = UIAlertAction(title: "Turn Off".localized(), style: .default) { _ in
  484. self.stopTimer()
  485. }
  486. offAction.setValue(UIColor.red, forKey: "titleTextColor")
  487. alertController.addAction(offAction)
  488. let cancelAction = UIAlertAction(title: "Cancel".localized(), style: .cancel) { _ in }
  489. alertController.addAction(cancelAction)
  490. present(alertController, animated: true, completion: nil)
  491. }
  492. }