123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- //
- // PlayDetailViewController.swift
- // ColorfulWallpaper
- //
- // Created by nkl on 2024/9/13.
- //
- //import ADManager
- import Combine
- import Foundation
- import KLTips
- import MediaPlayer
- import SJVideoPlayer
- import TSVideoKit
- import UIKit
- class PlayDetailViewController: LWBGViewController {
- var playControl: TSVideoPlayController {
- if !(TSVideoOperator.shared.playerController.player.playbackController is SJIJKMediaPlaybackController) {
- TSVideoOperator.shared.playerController.player.playbackController = SJIJKMediaPlaybackController()
- TSVideoOperator.shared.playerController.player.isPausedInBackground = false
- TSVideoOperator.shared.playerController.player.rotationManager?.isDisabledAutorotation = true
- TSVideoOperator.shared.playerController.player.delayInSecondsForHiddenPlaceholderImageView = 0
- }
- return TSVideoOperator.shared.playerController
- }
- lazy var customLoading: SJLoadingView = SJLoadingView()
- lazy var placeHolderView: PlayDetailPlaceHolderView = PlayDetailPlaceHolderView()
- lazy var retryButton: UIButton = {
- let btn = UIButton()
- btn.setTitle("Reload", for: .normal)
- btn.setTitleColor(.white, for: .normal)
- btn.titleLabel?.font = .systemFont(ofSize: 14)
- btn.customCornerRadius = 20
- btn.isHidden = true
- return btn
- }()
- lazy var topView: PlayDetailTopView = PlayDetailTopView()
- lazy var controlView: PlayDetailControlView = PlayDetailControlView()
- var downloader: NewChunkDownloader?
- private var timer: Timer?
- private var cancellable: [AnyCancellable] = []
- private var downloaderCancellable: [AnyCancellable] = []
- override func viewDidLoad() {
- super.viewDidLoad()
- bgImageView.image = .viewMainBg
- playControl.view.backgroundColor = .clear
- addNotifaction()
- addActionTargets()
- }
- func updateNavibar(video: TSVideo?) {
- if video?.videoStatus == .cached {
- topView.moreButton.isHidden = false
- } else {
- topView.moreButton.isHidden = true
- }
- }
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- // controlView.setRectCorner(corner: [.topLeft, .topRight], radii: CGSize.init(width: 24, height: 24))
- retryButton.setGradient(colors: [.hexColor("#72F4E6"), .hexColor("#374BEC")], index: 0)
- }
- override func addNotifaction() {
- super.addNotifaction()
- addPlayNotifaction()
- addTaskNotifaction()
- addSleepNotifacion()
- }
- func addSleepNotifacion() {
- PlayerManager.shared.$sleepModel.receive(on: DispatchQueue.main).sink { [weak self] model in
- guard let self = self else {
- return
- }
- self.topView.timeButton.setTitle(model.countTime.hhmmss, for: .normal)
- self.topView.timeButton.isSelected = model.isOpen
- }.store(in: &cancellable)
- NotificationCenter.default.addObserver(self, selector: #selector(updateLikeInfo), name: kDataChangedNotifactionName, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(retryAudio(notify:)), name: kSingleVideoFailNotifactionName, object: nil)
- }
- @objc func retryAudio(notify: Notification) {
- if let videoId = notify.object as? String,
- videoId == PlayerManager.shared.currentVideo?.videoId {
- TSNewDownloadManager.shared.retryDownload(videoId: videoId, isAudio: true) { [weak self] downloader in
- guard let self = self else { return }
- self.downloader = downloader
- self.updateDownloader(id: videoId)
- }
- }
- }
- func addPlayNotifaction() {
- playControl.viewModel.$loadState.receive(on: DispatchQueue.main).sink { [weak self] state in
- guard let self = self else { return }
- self.loadingStateChangeHandler(state: state)
- }.store(in: &cancellable)
- playControl.viewModel.$currentTime.receive(on: DispatchQueue.main).sink { [weak self] timeValue in
- guard let self = self else { return }
- self.controlView.updateTime(currentTime: timeValue, duration: self.playControl.player.duration)
- }.store(in: &cancellable)
- playControl.viewModel.$playStatus.receive(on: DispatchQueue.main).sink { [weak self] status in
- guard let self = self else { return }
- self.playStatusChangeHandler(status)
- }.store(in: &cancellable)
- playControl.viewModel.$loopMode.receive(on: DispatchQueue.main).sink { [weak self] loop in
- guard let self = self else { return }
- self.controlView.playModelButton.setImage(UIImage(named: loop.icon), for: .normal)
- }.store(in: &cancellable)
- playControl.viewModel.$currentVideo.receive(on: DispatchQueue.main).dropFirst().scan((nil, nil)) { previous, current in
- (previous.1, current)
- }.sink { [weak self] oldValue, newValue in
- guard let self = self else { return }
- /// 即将播放的和正在播放的视频是同一个
- if newValue != nil &&
- oldValue?.videoId == newValue?.videoId {
- PlayerManager.shared.showPlayerViewController()
- return
- }
- if let mVideo = newValue {
- self.controlView.updateVideoInfo(video: mVideo)
- PlayerManager.shared.miniBar.updateVideoInfo(video: mVideo, state: .pause)
- self.updateDownloader(id: mVideo.videoId ?? "")
- } else {
- self.controlView.resetVideoInfo()
- self.controlView.resetProgress()
- PlayerManager.shared.miniBar.resetVideoInfo()
- }
- self.updatePlaceHoderView(video: newValue, status: .stopped)
- self.updateNavibar(video: newValue)
- if let videoId = newValue?.videoId {
- UserDefaults.standard.set(videoId, forKey: "LAST_PlAYED_VIDEO_ID")
- }
- NotificationCenter.default.post(name: kDataChangedNotifactionName, object: nil)
- }.store(in: &cancellable)
- }
- func updatePlaceHoderView(video: TSVideo?, status: VLCMediaPlayerState) {
- guard let mVideo = video else {
- return
- }
- playControl.view.isHidden = false
- if mVideo.isOnline {
- placeHolderView.isHidden = true
- playControl.player.presentView.placeholderImageView.kf.setImage(with: mVideo.iconUrl)
- } else {
- playControl.player.presentView.placeholderImageView.image = nil
- if mVideo.isAudio {
- placeHolderView.isHidden = false
- playControl.view.isHidden = true
- } else {
- print("placeHolderView.frame === \(placeHolderView.frame)")
- if mVideo.videoStatus == .cached {
- placeHolderView.isHidden = true
- } else {
- switch status {
- case .stopped, .opening:
- placeHolderView.isHidden = false
- default:
- placeHolderView.isHidden = true
- }
- }
- }
- }
- }
- func loadingStateChangeHandler(state: TSLoadState) {
- /// 展示状态
- if state == .loading {
- retryButton.isHidden = true
- showLoading()
- } else if state == .fail {
- hideLoading()
- retryButton.isHidden = false
- } else if state == .success {
- retryButton.isHidden = true
- hideLoading()
- } else {
- retryButton.isHidden = true
- hideLoading()
- }
- /// 按钮是否可用
- if state == .loading {
- controlView.playButton.playingState = .pause
- controlView.playlistButton.isEnabled = false
- controlView.progressView.isUserInteractionEnabled = false
- } else {
- controlView.playlistButton.isEnabled = true
- controlView.progressView.isUserInteractionEnabled = true
- }
- }
- fileprivate func playStatusChangeHandler(_ status: VLCMediaPlayerState) {
- if status == .playing {
- controlView.playButton.playingState = .playing
- updatePlaceHoderView(video: playControl.viewModel.currentVideo, status: status)
- if let video = playControl.viewModel.currentVideo {
- PlayerManager.shared.miniBar.updateVideoInfo(video: video, state: .playing)
- }
- } else if status == .paused || status == .ended || status == .stopped {
- controlView.playButton.playingState = .pause
- if let video = playControl.viewModel.currentVideo {
- PlayerManager.shared.miniBar.updateVideoInfo(video: video, state: .pause)
- }
- } else if status == .buffering {
- /// 缓冲了展示loading
- if !playControl.player.isPlaying && !controlView.progressView.isDragging {
- showLoading()
- } else {
- hideLoading()
- }
- }
- }
- func addTaskNotifaction() {
- // 单个完成回调
- NotificationCenter.default.addObserver(self, selector: #selector(updateDownoloadStatus(notify:)), name: kGroupDownloadFinishNotifactionName, object: nil)
- }
- func updateDownloader(id: String) {
- if let downloader = TSNewDownloadManager.shared.fetchDownloader(identifier: id) {
- downloaderCancellable.removeAll()
- self.downloader = downloader
- downloader.$progress.receive(on: DispatchQueue.main).sink { [weak self] progress in
- self?.controlView.downloadButton.progressView.set(progress: Double(progress))
- }.store(in: &downloaderCancellable)
- downloader.$downloadState.receive(on: DispatchQueue.main).sink { state in
- self.controlView.setDownloadButtonStates(status: state)
- }.store(in: &downloaderCancellable)
- } else {
- downloaderCancellable.removeAll()
- downloader = nil
- if let video = TSVideoOperator.shared.dataManager.fetchVideo(videoId: id) {
- controlView.setDownloadButtonStates(status: video.videoStatus.asChunkDownloadStatus)
- } else {
- controlView.setDownloadButtonStates(status: .idle)
- }
- }
- }
- @objc func updateDownoloadStatus(notify: Notification) {
- if let videoId = notify.object as? String,
- let video = TSVideoOperator.shared.dataManager.fetchVideo(videoId: videoId),
- let currentVideo = TSVideoOperator.shared.playerViewModel.currentVideo {
- if videoId == currentVideo.videoId {
- controlView.setDownloadButtonStates(status: video.videoStatus.asChunkDownloadStatus)
- }
- }
- }
- @objc func showMoreOperation() {
- let vc = CWOperateViewController(types: [.like, .addPlaylist, .share, .deleteVideo])
- vc.modalPresentationStyle = .overFullScreen
- vc.operteItem = playControl.viewModel.currentVideo
- PlayerManager.shared.rootVc?.present(vc, animated: true)
- }
- func countDownTime() {
- timer?.invalidate()
- timer = nil
- PlayerManager.shared.sleepModel.isOpen = true
- timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
- PlayerManager.shared.sleepModel.countTime -= 1
- if PlayerManager.shared.sleepModel.countTime < 1 {
- /// 倒计时结束,暂停播放器
- self.playControl.pause()
- self.stopTimer()
- }
- }
- }
- func stopTimer() {
- PlayerManager.shared.sleepModel.isOpen = false
- PlayerManager.shared.sleepModel.countTime = 0
- timer?.invalidate()
- timer = nil
- }
- func addActionTargets() {
- topView.backButton.addTarget(self, action: #selector(swapDown), for: .touchUpInside)
- topView.timeButton.addTarget(self, action: #selector(showSleepTimeController), for: .touchUpInside)
- topView.moreButton.addTarget(self, action: #selector(showMoreOperation), for: .touchUpInside)
- controlView.playButton.addTarget(self, action: #selector(playButtonAction), for: .touchUpInside)
- controlView.listButton.addTarget(self, action: #selector(playlistButtonAction), for: .touchUpInside)
- controlView.nextButton.addTarget(self, action: #selector(nextButtonAction), for: .touchUpInside)
- controlView.lastButton.addTarget(self, action: #selector(lastButtonAction), for: .touchUpInside)
- controlView.playModelButton.addTarget(self, action: #selector(playModeButtonAction), for: .touchUpInside)
- controlView.downloadButton.addTarget(self, action: #selector(downloadButtonAction), for: .touchUpInside)
- controlView.playlistButton.addTarget(self, action: #selector(addToPlaylistAction), for: .touchUpInside)
- controlView.favouriteButton.addTarget(self, action: #selector(favouriteButtonAction), for: .touchUpInside)
- retryButton.addTarget(self, action: #selector(reloadVideo), for: .touchUpInside)
- controlView.progressView.tappedExeBlock = { [weak self] _, progress in
- self?.controlView.progressView.value = progress
- let durationTime = (self?.playControl.player.duration ?? 0)
- let currentTime = durationTime * progress
- print("duration---\(durationTime),current ---- \(currentTime)")
- self?.controlView.updateTime(currentTime: currentTime, duration: durationTime)
- self?.playControl.pause()
- self?.playControl.seekToProgress(progress: progress) { [weak self] _ in
- self?.playControl.play()
- }
- }
- // 创建下滑手势识别器
- let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(swapDown))
- swipeDownGesture.direction = .down
- // 添加手势识别器到 myView
- view.addGestureRecognizer(swipeDownGesture)
- }
- @objc func reloadVideo() {
- playControl.viewModel.reloadCurrentVideo()
- }
- override func addChildren() {
- super.addChildren()
- controlView.progressView.delegate = self
- placeHolderView.isHidden = true
- addChild(playControl)
- view.addSubview(topView)
- view.addSubview(playControl.view)
- view.addSubview(controlView)
- view.addSubview(placeHolderView)
- view.addSubview(customLoading)
- view.addSubview(retryButton)
- view.bringSubviewToFront(topView)
- }
- override func makeConstarints() {
- super.makeConstarints()
- topView.snp.makeConstraints { make in
- make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
- make.leading.trailing.equalToSuperview()
- make.height.equalTo(44)
- }
- let vRadio = UIScreen.kScreenHeight / 896.0
- playControl.view.snp.makeConstraints { make in
- make.top.equalTo(topView.snp.bottom).offset(100 * vRadio)
- make.leading.trailing.equalToSuperview()
- make.height.equalTo(playControl.view.snp.width).multipliedBy(9.0 / 16.0)
- }
- retryButton.snp.makeConstraints { make in
- make.center.equalTo(playControl.view)
- make.height.equalTo(40)
- make.width.equalTo(100)
- }
- customLoading.snp.makeConstraints { make in
- make.center.equalTo(playControl.view)
- }
- placeHolderView.iconView.contentMode = .scaleAspectFit
- placeHolderView.snp.makeConstraints { make in
- make.top.equalTo(topView.snp.bottom).offset(50 * vRadio)
- make.leading.trailing.equalToSuperview()
- make.height.equalTo(placeHolderView.snp.width).multipliedBy(9.0 / 16.0)
- }
- controlView.snp.makeConstraints { make in
- make.leading.trailing.bottom.equalToSuperview()
- make.top.equalTo(playControl.view.snp.bottom).offset(40)
- make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
- }
- }
- }
- //
- /// targets
- extension PlayDetailViewController {
- @objc func swapDown() {
- PlayerManager.shared.hiddePlayerViewController()
- }
- @objc func playButtonAction() {
- /// 播放器没源,但是有当前播放歌曲
- if PlayerManager.shared.player?.playControl.player.urlAsset == nil, let current = PlayerManager.shared.currentVideo {
- PlayerManager.shared.playVideo(video: current, list: [], scene: .local, onceAdKey: "")
- } else {
- PlayerManager.shared.playOrPause()
- }
- }
- @objc func playlistButtonAction() {
- let vc = PlayDetailListViewContoller()
- vc.viewModel.videos = playControl.viewModel.currentVideos
- vc.viewModel.recommendDatas = playControl.viewModel.recommendDatas
- vc.viewModel.playScene = playControl.viewModel.scene
- vc.modalPresentationStyle = .overFullScreen
- present(vc, animated: true)
- }
- @objc func nextButtonAction() {
- if FitManager.isAr {
- playControl.playLast()
- } else {
- playControl.playNext()
- }
- }
- @objc func lastButtonAction() {
- if FitManager.isAr {
- playControl.playNext()
- } else {
- playControl.playLast()
- }
- }
- @objc func playModeButtonAction() {
- playControl.viewModel.loopMode = playControl.viewModel.loopMode.nextMode()
- THUD.toast(playControl.viewModel.loopMode.rawValue, at: .center)
- }
- @objc func favouriteButtonAction() {
- if let video = TSVideoOperator.shared.playerViewModel.currentVideo {
- if video.isFavorite {
- TSVideoOperator.shared.dataManager.dislikeCurrentVideo { _ in
- updateLikeInfo()
- }
- } else {
- TSVideoOperator.shared.dataManager.likeCurrentVideo { _ in
- updateLikeInfo()
- }
- }
- }
- }
- @objc func updateLikeInfo() {
- if let video = TSVideoOperator.shared.playerViewModel.currentVideo {
- controlView.favouriteButton.isSelected = video.isFavorite
- }
- }
- @objc func downloadButtonAction(sender: DownloadButton) {
- if let video = TSVideoOperator.shared.playerViewModel.currentVideo,
- let videoId = video.videoId {
- switch sender.downloadState {
- case .idle(isAnimate: false), .idle(isAnimate: true):
- // if PurchaseManager.default.isVip {
- TSNewDownloadManager.shared.downloadVideo(videoId: videoId, isAudio: false) { [weak self] downloader in
- self?.downloader = downloader
- self?.updateDownloader(id: videoId)
- }
- if let rootVc = PlayerManager.shared.rootVc {
- rootVc.presentingViewController?.dismiss(animated: false)
- // if !PurchaseManager.default.isVip {
- // ADManager.shared.showAd(scene: ADScene.downloadInsert, from: rootVc)
- // }
- }
- // } else {
- // let loading = ADLoadingViewController()
- // loading.adScene = .downloadReward
- // loading.finishedHandler = { [weak self] finished in
- // if finished {
- // TSNewDownloadManager.shared.downloadVideo(videoId: videoId, isAudio: false) { [weak self] downloader in
- // self?.downloader = downloader
- // self?.updateDownloader(id: videoId)
- // }
- // }
- // }
- // PlayerManager.shared.rootVc?.present(loading, animated: false)
- // }
- break
- case .pause:
- TSNewDownloadManager.shared.resumeTask(id: videoId) { [weak self] downloader in
- self?.downloader = downloader
- self?.updateDownloader(id: videoId)
- }
- case .downloading:
- TSNewDownloadManager.shared.pauseTask(id: videoId) { [weak self] downloader in
- self?.downloader = downloader
- self?.updateDownloader(id: videoId)
- }
- case .retry:
- TSNewDownloadManager.shared.retryDownload(videoId: videoId, isAudio: false) { [weak self] downloader in
- self?.downloader = downloader
- self?.updateDownloader(id: videoId)
- }
- break
- default:
- break
- }
- }
- }
- // 调用这个方法来显示加载圈
- func showLoading() {
- customLoading.start()
- }
- // 调用这个方法来隐藏加载圈
- func hideLoading() {
- customLoading.stop()
- }
- @objc func addToPlaylistAction() {
- if let video = TSVideoOperator.shared.playerViewModel.currentVideo {
- let addVc: AddPlayListViewController = AddPlayListViewController(video: video)
- addVc.addSuccessBlock = {
- THUD.toast("Added Successfully".localized())
- }
- addVc.modalPresentationStyle = .overFullScreen
- PlayerManager.shared.rootVc?.present(addVc, animated: true)
- }
- }
- @objc func showSleepTimeController() {
- let times: [Int] = [15, 30, 60, 90, 120]
- // 创建 UIAlertController 实例
- let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
- for time in times {
- let option = UIAlertAction(title: "\(time) minutes", style: .default) { [weak self] _ in
- PlayerManager.shared.sleepModel.countTime = Double(time) * 60.0
- self?.countDownTime()
- }
- alertController.addAction(option)
- }
- // 添加关闭按钮
- let offAction = UIAlertAction(title: "Turn Off".localized(), style: .default) { _ in
- self.stopTimer()
- }
- offAction.setValue(UIColor.red, forKey: "titleTextColor")
- alertController.addAction(offAction)
- let cancelAction = UIAlertAction(title: "Cancel".localized(), style: .cancel) { _ in }
- alertController.addAction(cancelAction)
- present(alertController, animated: true, completion: nil)
- }
- }
|