// // MP_PlayerManager.swift // MusicPlayer // // Created by Mr.Zhou on 2024/5/10. // import UIKit import AVFoundation import MediaPlayer import AVKit import Kingfisher ///播放器播放状态 enum MP_PlayerStateType:Int { ///未启动 case Null = 0 ///运行中 case Playing = 1 ///暂停中 case Pause = 2 } ///播放器播放方式 enum MP_PlayerPlayType:Int { ///列表顺序播放 case normal = 0 ///列表随机播放 case random = 1 ///单曲播放(当前音乐无限循环) case single = 2 var title:String{ switch self { case .normal: return "顺序播放" case .random: return "随机播放" case .single: return "单曲循环" } } } ///计时器状态 enum MP_TimerStateType:Int { ///运行 case Resume = 0 ///暂停 case Suspend = 1 } ///播放器启动时执行事件(播放的音乐) typealias MP_PlayTimerStartAction = () -> Void ///播放器运行时执行事件(当前时间值,最大时间值) typealias MP_PlayTimerRunAction = (_ currentTime:TimeInterval, _ duration:TimeInterval) -> Void ///播放器结束时执行事件 typealias MP_PlayTimerEndAction = () -> Void ///播放器暂停时执行事件 typealias MP_PlayTimerPauseAction = () -> Void ///播放器继续时执行事件 typealias MP_PlayTimerResumeAction = () -> Void ///播放器终止时执行事件 typealias MP_PlayTimerStopAction = () -> Void ///播放器调整进度时执行事件 typealias MP_PlayTimerEditEndAction = () -> Void ///播放器缓存值执行事件 typealias MP_PlayCacheValueAction = (_ currentValue:TimeInterval, _ duration:TimeInterval) -> Void ///播放器 class MP_PlayerManager:NSObject{ ///控制器单例 static let shared = MP_PlayerManager() ///播放器 lazy var player:AVPlayer = { let player = AVPlayer() player.rate = 1 return player }() ///播放器视图 lazy var videoLayer:AVPlayerLayer = { let layer:AVPlayerLayer = .init(player: player) layer.videoGravity = .resizeAspect layer.backgroundColor = UIColor.black.cgColor return layer }() //远程控制中心 var center:MPRemoteCommandCenter?{ didSet{ print("更新了远程控制中心") } } ///延迟计时器 private var timer:DispatchSourceTimer? ///延迟计时值 private var times:TimeInterval = 0 ///计时器队列 private var queue:DispatchQueue? ///load模块 var loadPlayer:MPPositive_PlayerLoadViewModel!{ willSet{ DispatchQueue.main.async { [weak self] in guard let self = self else {return} guard loadPlayer != nil else { if newValue != nil { //当load模块接受到新值的时候,发出通知,提醒底部模块状态切换 NotificationCenter.notificationKey.post(notificationName: .pup_bottom_show) } return } if newValue != nil { //当load模块接受到新值的时候,发出通知,提醒底部模块状态切换 NotificationCenter.notificationKey.post(notificationName: .pup_bottom_show) }else { //用户清空了load模块,隐藏播放器 NotificationCenter.notificationKey.post(notificationName: .player_delete_list) playState = .Null player.pause() center?.playCommand.removeTarget(self) center?.pauseCommand.removeTarget(self) center?.nextTrackCommand.removeTarget(self) center?.previousTrackCommand.removeTarget(self) center?.changePlaybackPositionCommand.removeTarget(self) center = nil do { try AVAudioSession.sharedInstance().setActive(false) } catch { print("Error deactivating audio session: \(error)") } } } } } //当前音乐的字典 private var currentInfo:[String:Any]? //是否展示最后一首状态 private var isLast:Bool = false //设置是否页面展示最后一首状态 func setLastStatus(bool:Bool) { isLast = bool } //当前播放器状态 private var playState:MP_PlayerStateType = .Null{ didSet{ //当播放器状态发生变化时,对播放器按钮状态进行切换 NotificationCenter.notificationKey.post(notificationName: .switch_player_status, object: playState) } } //是否因为广告原因延迟了播放(当广告加载时,延迟播放器状态切换) // var isAdLate:Bool? = false{ // didSet{ // DispatchQueue.main.asyncAfter(deadline: .now()) { // [weak self] in // guard let self = self else {return} // guard let ad = isAdLate, loadPlayer?.currentVideo != nil else {return} // //当监听到插页广告的展示或消失时 // if ad == true { // //展示插页广告中 // switch playState { // case .Null://当前音乐加载中 // //直接暂停音乐 // player.pause() // case .Playing://当前音乐播放中 // //直接暂停音乐 // player.pause() // case .Pause://当前音乐暂停中 // //继续暂停 // player.pause() // } // }else { // //插页广告销毁了 // switch playState { // case .Null://当前音乐加载中 // //判断音乐本身是否有足够数据播放 // if loadPlayer?.currentVideo?.resourcePlayerItem?.isPlaybackLikelyToKeepUp == true { // //具备足够的数据,让音乐恢复播放 // playState = .Playing // player.play() // }else { // //不具备,不操作 // playState = .Null // player.pause() // } // case .Playing://当前音乐播放中 // playState = .Playing // //恢复播放 // player.play() // case .Pause://当前音乐暂停中 // playState = .Pause // //继续暂停 // player.pause() // } // } // } // } // } ///获取播放器播放状态 func getPlayState() -> MP_PlayerStateType { return playState } ///当前播放器播放方法 private var playType:MP_PlayerPlayType = .normal{ didSet{ DispatchQueue.main.async { //当播放器播放方式变化后,发出通知 NotificationCenter.notificationKey.post(notificationName: .player_type_switch) } } } ///获取播放器播放方法 func getPlayType() -> MP_PlayerPlayType { return playType } /// 设置播放器播放方式 /// - Parameter type: 新的类型 func setPlayType(_ type:MP_PlayerPlayType) { playType = type print("播放器播放方法已经改为\(type.title)") } ///计时器状态 private var timerType:MP_TimerStateType = .Suspend ///播放器启动时执行事件记录 private var startActionBlock:MP_PlayTimerStartAction? ///播放器运行时执行事件记录 var runActionBlock:MP_PlayTimerRunAction? ///底部播放器进度槽 var bottomProgressBlock:MP_PlayTimerRunAction? ///播放器缓存值闭包 var cacheValueBlock:MP_PlayCacheValueAction? ///播放实例状态的监听器 private var statusObservation:NSKeyValueObservation? ///播放实例缓存值的监听器 private var loadedTimeRangesObservation:NSKeyValueObservation? ///播放实例是否支持播放的监听器 private var playbackLikelyToKeepUpObservation:NSKeyValueObservation? ///播放实例报错的监听器 private var errorObservation:NSKeyValueObservation? ///播放实例播放资格监听器 private var playEntitlementObservation:NSKeyValueObservation? private override init() { super.init() //初始化计时器 timer?.cancel() queue = DispatchQueue(label: "com.playerTimer.timer",attributes: .concurrent) timer = DispatchSource.makeTimerSource(queue: queue) //设置计时器每0.1秒触发一次,误差在0.01秒 timer?.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(10)) // 设置计时器触发时执行的回调 timer?.setEventHandler(handler: { [weak self] in //将计时值增加0.1秒 self?.times += 0.1 }) // 添加观察者,监听播放结束事件 NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_ :)), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem) //监听用户切换了音乐 NotificationCenter.notificationKey.add(observer: self, selector: #selector(userSwitchCurrentVideoAction(_ :)), notificationName: .positive_player_reload) //监听网络状态恢复可用 NotificationCenter.notificationKey.add(observer: self, selector: #selector(netWorkReachableAction(_ :)), notificationName: .net_switch_reachable) //监听蓝牙设备的状态 NotificationCenter.default.addObserver(self, selector: #selector(handleAudioSessionRouteChangeAction(_ :)), name: AVAudioSession.routeChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleAppStateChange(_ :)), name: UIApplication.didEnterBackgroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleAppStateChange(_ :)), name: UIApplication.willEnterForegroundNotification, object: nil) //设置一个秒为刻度的时间值 let interval:CMTime = .init(seconds: 1, preferredTimescale: .init(1)) //为播放器添加运行时主线程每秒触发事件 player.addPeriodicTimeObserver(forInterval: interval, queue: .main, using: { [weak self] (time) in guard let self = self else { return } cacheLoadTimes() //转化为当前播放进度秒值 let currentDuration = CMTimeGetSeconds(time) //更新控制中心的进度条 updateNowPlayingInfo() //获取当前播放音乐资源的最大时间值 let maxDuration = getMusicDuration() if maxDuration.isNaN == false { //判断当值进度是否超越最大时间值 if currentDuration <= maxDuration { //没有,执行运行时时间 if runActionBlock != nil { runActionBlock!(currentDuration, maxDuration) } if bottomProgressBlock != nil { bottomProgressBlock!(currentDuration, maxDuration) } } } }) } deinit { NotificationCenter.default.removeObserver(self) center?.playCommand.removeTarget(self) center?.pauseCommand.removeTarget(self) center?.nextTrackCommand.removeTarget(self) center?.previousTrackCommand.removeTarget(self) timer?.cancel() timer = nil } /// 开始播放音乐 /// - Parameters: /// - startAction: 开始播放时需要执行的事件 /// - runAction: 播放途中需要执行的事件 /// - endAction: 结束播放时需要执行的事件 func play(startAction:MP_PlayTimerStartAction? = nil) { guard loadPlayer != nil, loadPlayer?.currentVideo != nil else { //当两项数据皆为空时,播放器无法播放 print("Player No Data") return } //检索播放器状态 switch playState { case .Null://未启动 break case .Playing://启动中 player.pause() case .Pause://暂停中 break } //记录事件 if startAction != nil { startActionBlock = startAction } //检索是否具备播放资格 guard loadPlayer?.currentVideo?.isPlayEntitlement != false else { //明确不可播放, 执行下一首 if MP_NetWorkManager.shared.netWorkStatu == .reachable { nextEvent() } return } if let currentVideo = loadPlayer?.currentVideo { //覆盖播放器原有的playerItem player.replaceCurrentItem(with: currentVideo.resourcePlayerItem) if center == nil { setCommandCenter() } //启动计时器 startTimer() //对当前播放PlayerItem设置监听状态 if currentVideo.isKVO == false { //准备状态 statusObservation?.invalidate() statusObservation = currentVideo.resourcePlayerItem?.observe(\.status, options: [.old,.new], changeHandler: { [weak self] item, change in guard let self = self else {return} if item.status == .readyToPlay { //判断当前播放器是否在播放当前音乐中 if playState != .Playing { //当statuVlaue值等于playerItem准备播放的值,说明已经准备好播放 print("当前音乐-\(currentVideo.title ?? "") 已经准备好播放") } }else { print("当前音乐-\(currentVideo.title ?? "") 未做好准备播放,失败原因是\(currentVideo.resourcePlayerItem?.error?.localizedDescription ?? "")") MP_AnalyticsManager.shared.player_b_failure_errorAction(currentVideo.song.videoId ?? "", videoname: currentVideo.title ?? "", artistname: currentVideo.song.shortBylineText ?? "", error: currentVideo.resourcePlayerItem?.error?.localizedDescription ?? "Failed to buffer data") if currentVideo.isKVO == true { suspendTimer() currentVideo.isKVO = false statusObservation?.invalidate() loadedTimeRangesObservation?.invalidate() playbackLikelyToKeepUpObservation?.invalidate() errorObservation?.invalidate() //重新配置数据 loadPlayer?.remakeImproveData { [weak self] in self?.play() } } } }) //当前缓冲值 loadedTimeRangesObservation?.invalidate() loadedTimeRangesObservation = currentVideo.resourcePlayerItem?.observe(\.loadedTimeRanges, options: [.old,.new], changeHandler: { [weak self] item, change in guard let self = self else {return} cacheLoadTimes() }) //是否具备足够播放的能力 playbackLikelyToKeepUpObservation?.invalidate() playbackLikelyToKeepUpObservation = currentVideo.resourcePlayerItem?.observe(\.isPlaybackLikelyToKeepUp, options: [.old,.new], changeHandler: { [weak self] item, change in guard let self = self else {return} if let playbackLikelyToKeepUp = change.newValue, playbackLikelyToKeepUp == true { if playState != .Playing && playState != .Pause { //还未播放当前音乐,启动播放 playState = .Playing // //检索是否展示了插页广告 // if isAdLate != true { // //当前是否上次播放展示状态,是的话,手动暂停 // if isLast { // pause() // isLast = false // }else { // player.play() // } // }else { // // } //当前是否上次播放展示状态,是的话,手动暂停 if isLast { pause() isLast = false }else { player.play() } //暂停计时器,并获取延时值 suspendTimer() if let currentVideo = loadPlayer?.currentVideo { MP_AnalyticsManager.shared.player_b_success_actionAction(currentVideo.song.videoId ?? "", videoname: currentVideo.title ?? "", artistname: currentVideo.song.shortBylineText ?? "") } //执行开始播放闭包 if startActionBlock != nil { startActionBlock!() } } }else { //没有足够的数据支持播放 player.pause() playState = .Null } }) //报错提醒 errorObservation?.invalidate() errorObservation = currentVideo.resourcePlayerItem?.observe(\.error, options: [.old,.new], changeHandler: { [weak self] item, change in guard let self = self else {return} if let error = change.newValue, let nsError = error { print("当前音乐-\(currentVideo.title ?? "") 未做好准备播放,失败原因是\(nsError.localizedDescription)") MP_AnalyticsManager.shared.player_b_failure_errorAction(currentVideo.song.videoId ?? "", videoname: currentVideo.title ?? "", artistname: currentVideo.song.shortBylineText ?? "", error: nsError.localizedDescription) } }) //播放资格 playEntitlementObservation?.invalidate() playEntitlementObservation = currentVideo.observe(\.isPlayEntitlement, options: [.old,.new], changeHandler: { [weak self] item, change in guard let self = self, MP_NetWorkManager.shared.netWorkStatu == .reachable else {return} if change.newValue == 0 { //当前音乐不具备播放资格,直接下一首 nextEvent() } }) currentVideo.isKVO = true //将进度回归为0 player.seek(to: .zero) updateNowPlayingInfo() } } } ///启动计时器 func startTimer() { guard timerType == .Suspend else { return } times = 0 //运行计时器 timer?.resume() timerType = .Resume } ///暂停计时器 func suspendTimer() { guard timerType == .Resume else { return } //暂停计时器 timer?.suspend() timerType = .Suspend guard times != 0 else { return } let times = Int(self.times) let msTimes = times*1000 MP_AnalyticsManager.shared.player_b_delay_actionAction(loadPlayer?.currentVideo?.song.videoId ?? "", videoname: loadPlayer?.currentVideo?.title ?? "", artistname: loadPlayer?.currentVideo?.song.shortBylineText ?? "", delay: "\(msTimes)ms") } ///监听到音视频路由发生变化 @objc private func handleAudioSessionRouteChangeAction(_ notification: Notification) { guard let info = notification.userInfo, let reasonValue = info[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue:reasonValue) else { return } switch reason { case .newDeviceAvailable:// 新设备连接 print("设备已连接") //继续运行 case .oldDeviceUnavailable:// 设备断开 print("设备已断开") //暂停当前播放 pause() default: break } } ///当播放器退往后台的时候 @objc private func handleAppStateChange(_ notification: Notification) { if notification.name == UIApplication.didEnterBackgroundNotification { //进入后台时解绑AVPlayer videoLayer.player = nil }else if notification.name == UIApplication.willEnterForegroundNotification { videoLayer.player = player } } ///网络状态恢复正常 @objc private func netWorkReachableAction(_ sender:Notification) { //监听到网络状态恢复,检索当前播放器是否正在播放 if loadPlayer?.currentVideo != nil { if playState == .Playing, let currentItem = loadPlayer?.currentVideo?.resourcePlayerItem { //有音乐播放,获取当前播放进度 let currentTime = currentItem.currentTime() //手动调整播放时间点,以此重启播放器缓存 player.seek(to: currentTime) player.play() playState = .Playing } } } //确定播放器播放后限时状态 func playerStatuTimerAction() { MPPositive_Debouncer.shared.playCall { [weak self] in guard let self = self else {return} //10秒后检索播放器是否在播放 if playState != .Playing { if isLast == true { MP_HUD.text("Failed to obtain resource, please try again later".localizableString(), delay: 2.0, completion: nil) } } } } //获取缓冲值 private func cacheLoadTimes() { //获取当前播放Item的缓冲值组 if let timeRanges = loadPlayer?.currentVideo?.resourcePlayerItem?.loadedTimeRanges.map({$0.timeRangeValue}), let first = timeRanges.first { //获取开始时间的秒数 let startSeconds = first.start.seconds //获取缓冲区的持续时间 let durationSeconds = first.duration.seconds //计算当前缓冲总时间 let bufferedSeconds = startSeconds + durationSeconds //获取当前播放音乐资源的最大时间值 let maxDuration = getMusicDuration() //传递缓存值 if cacheValueBlock != nil { cacheValueBlock!(bufferedSeconds, maxDuration) } } } //MARK: - 获取当前音乐总长度 ///获取音乐资源总时长 private func getMusicDuration() -> TimeInterval { return CMTimeGetSeconds(player.currentItem?.duration ?? .zero) } //MARK: - 音乐播放结束 //当前音乐播放结束时 @objc private func playerDidFinishPlaying(_ sender:Notification) { //检索播放器对象 guard playState == .Playing else { return } //将当前播放音乐存入最近听过 if let song = loadPlayer?.currentVideo?.song { createRecentData(3, song: song) } switch playType { case .single: //重播 player.seek(to: CMTime.zero) player.play() default: //当前音乐播放器正在播放中,下一首 nextEvent() } } ///设置最近听过 private func createRecentData(_ level:Int16, song:MPPositive_SongItemModel) { //检索是否已经存在这个数据 MPPositive_RecentlyModel.fetch(predicate: .init(format: "videoId == %@", (song.videoId ?? ""))) { results in if results.isEmpty { //数据库中没有记录,新建一个 let item = try? MPPositive_RecentlyModel.create() item?.coverImage = song.coverUrls?.last item?.reviewImage = song.reviewUrls?.last item?.title = song.title item?.subtitle = (song.longBylineText ?? "")+(song.shortBylineText ?? "") item?.videoId = song.videoId item?.lyricsID = song.lyricsID item?.relatedID = song.relatedID item?.addTime = Date() item?.level = level item?.artistID = song.artistID item?.albumID = song.albumID item?.playListID = song.playListID }else { //不为空,调整数据 if let item = results.first { if (item.level ?? 0) < level { item.level = level } item.addTime = Date() } } MPPositive_RecentlyModel.save() } } //MARK: - 暂停播放 ///内部暂停播放 private func pause() { //检索播放状态,是否进行中 guard playState == .Playing else { //未处于播放中 print("Player is not in playing") return } //暂停播放器 player.pause() //切换播放器状态 playState = .Pause } /// 暂停播放 /// - Parameter pauseAction: 暂停时要执行的事件 func pause(_ pauseAction:MP_PlayTimerPauseAction? = nil) { //检索播放状态,是否进行中 guard playState == .Playing else { //未处于播放中 print("Player is not in playing") return } if pauseAction != nil { pauseAction!() } //暂停播放器 player.pause() //切换播放器状态 playState = .Pause } ///因为广告展示暂停 func adToPause() { //强行转入加载状态 playState = .Null player.pause() } ///因为广告注销继续 func adToResume() { } //MARK: - 继续播放 /// 继续播放 /// - Parameter resumeAction: 继续时要执行的事件 func resume(_ resumeAction:MP_PlayTimerResumeAction? = nil) { //检索播放状态,是否暂停中 guard playState == .Pause else { //未处于暂停中 print("Player is not paused") return } if resumeAction != nil { resumeAction!() } //继续播放器 player.play() //切换播放器状态 playState = .Playing } ///内部继续播放 private func resume() { //检索播放状态,是否暂停中 guard playState == .Pause else { //未处于暂停中 print("Player is not paused") return } //继续播放器 player.play() //切换播放器状态 playState = .Playing } //MARK: - 停止播放 //停止播放 func stop() { //检索播放状态,是否已启动 guard playState != .Null else { //未启动 print("Player is not started") return } player.pause() playState = .Null } //MARK: - 切歌(上一首/下一首) ///上一首歌事件 func previousEvent() { //将播放器状态调整未播放 playState = .Null var nextIndex:Int = 0 //判断当前音乐播放方式 switch playType { case .random://随机,播放随机列表内容 //检索当前列表是否为只有一首 guard (loadPlayer?.randomVideos?.count ?? 0) != 1 else { player.seek(to: .zero) playState = .Playing player.play() return } for (index, item) in (loadPlayer?.randomVideos ?? []).enumerated() { if item.videoId == loadPlayer?.currentVideo?.song.videoId { //找到播放音乐的索引 nextIndex = index - 1 } } //假如next为负数,则直接播放列表最后一首 if nextIndex < 0 { //播放列表最后一首 let last = loadPlayer?.randomVideos?.last loadPlayer?.improveData(last?.videoId ?? "", isRandom: true) }else { //查询列表对应单曲 if let song = loadPlayer?.randomVideos?[nextIndex] { loadPlayer?.improveData(song.videoId ?? "", isRandom: true) } } default://常规播放或者单曲播放 guard (loadPlayer?.songVideos?.count ?? 0) != 1 else { player.seek(to: .zero) playState = .Playing player.play() return } for (index, item) in (loadPlayer?.songVideos ?? []).enumerated() { if item.videoId == loadPlayer?.currentVideo?.song.videoId { //找到播放音乐的索引 nextIndex = index - 1 } } //假如next为负数,则直接播放列表最后一首 if nextIndex < 0 { //播放列表最后一首 let last = loadPlayer?.songVideos?.last loadPlayer?.improveData(last?.videoId ?? "") }else { //查询列表对应单曲 if let song = loadPlayer?.songVideos?[nextIndex] { loadPlayer?.improveData(song.videoId ?? "") } } } } ///下一首歌事件 func nextEvent() { //将播放器状态调整未播放 playState = .Null var nextIndex:Int = 0 switch playType { case .random: guard let randomVideos = loadPlayer?.randomVideos, randomVideos.count != 1 else { player.seek(to: .zero) playState = .Playing player.play() return } for (index, item) in randomVideos.enumerated() { if item.videoId == loadPlayer?.currentVideo?.song.videoId { //找到播放音乐的索引 nextIndex = index + 1 } } //超出播放列表数 if nextIndex > (randomVideos.count-1) { //播放列表第一首 let first = randomVideos.first loadPlayer?.improveData(first?.videoId ?? "", isRandom: true) }else { //存在下一首,获取下一首ID,并播放 if let song = loadPlayer?.randomVideos?[nextIndex] { loadPlayer?.improveData(song.videoId ?? "", isRandom: true) } } default: guard let songVideos = loadPlayer?.songVideos, songVideos.count != 1 else { player.seek(to: .zero) playState = .Playing player.play() return } for (index, item) in songVideos.enumerated() { if item.videoId == loadPlayer?.currentVideo?.song.videoId { //找到播放音乐的索引 nextIndex = index + 1 } } //超出播放列表数 if nextIndex > (songVideos.count-1) { //播放列表第一首 let first = songVideos.first loadPlayer?.improveData(first?.videoId ?? "") }else { //存在下一首,获取下一首ID,并播放 if let song = loadPlayer?.songVideos?[nextIndex] { loadPlayer?.improveData(song.videoId ?? "") } } } } ///记录用户切歌 private func switchEvent() { //获得当前播放音乐以及播放时长 guard let currentVideo = loadPlayer?.currentVideo else {return} if let currentDruation = player.currentItem?.duration { let times = CMTimeGetSeconds(currentDruation) if times >= 60 { //作为二级最近听过 createRecentData(2, song: currentVideo.song) }else { //作为低级最近听过 createRecentData(1, song: currentVideo.song) } }else { //作为用户略过的歌曲,不操作 } } ///监听到用户切换当前音乐 @objc private func userSwitchCurrentVideoAction(_ sender:Notification) { //将播放器状态调整未播放 playState = .Null //暂停播放 player.pause() //优先获取传递的值 if let video = sender.object as? MPPositive_SongViewModel { if video.isKVO == true { //切歌时移除KVO监听 statusObservation?.invalidate() playbackLikelyToKeepUpObservation?.invalidate() errorObservation?.invalidate() loadedTimeRangesObservation?.invalidate() video.isKVO = false } } if cacheValueBlock != nil { cacheValueBlock!(0, 1) } if loadPlayer?.currentVideo != nil { //开始播放 play(startAction: startActionBlock) } } ///播放器进度调整状态 func setEditPorgressStatu() { guard playState != .Null else { return } //播放器进入暂停状态 pause() } /// 调整播放器进度值,必须和 setEditPorgressStatu()搭配使用 /// - Parameters: /// - progress: 要调整进度值(保证在0-1范围内,超出该方法不会响应) func setEditProgressEnd(_ progress:Float, endAction:MP_PlayTimerEditEndAction? = nil) { guard playState != .Null else { return } guard progress >= 0, progress <= 1 else { return } //根据当前进度值设置时间节点 let timePoint:Double = Double(progress)*getMusicDuration() //设置对应的时间值 let time:CMTime = .init(seconds: timePoint, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) //调整播放器时间 player.seek(to: time) //恢复播放 resume() if endAction != nil { endAction!() } } //MARK: - 远程中心 func setCommandCenter() { // 实例化远程控制中心 center = MPRemoteCommandCenter.shared() //设置控制中心各项操作 //播放 center!.playCommand.addTarget(handler: { [weak self] (event) in guard let self = self else { return .noActionableNowPlayingItem} if loadPlayer?.currentVideo != nil && playState == .Pause { resume() return .success }else { return .noActionableNowPlayingItem } }) //暂停 center!.pauseCommand.addTarget(handler: { [weak self] (event) in guard let self = self else { return .noActionableNowPlayingItem} if loadPlayer?.currentVideo != nil && playState == .Playing { pause() return .success }else { return .noActionableNowPlayingItem } }) //上一首 center!.previousTrackCommand.addTarget { [weak self] (event) in guard let self = self else { return .noActionableNowPlayingItem} if loadPlayer?.currentVideo != nil { previousEvent() return .success }else { return .noActionableNowPlayingItem } } //下一首歌 center!.nextTrackCommand.addTarget { [weak self] (event) in guard let self = self else { return .noActionableNowPlayingItem} if loadPlayer?.currentVideo != nil { nextEvent() return .success }else { return .noActionableNowPlayingItem } } //拖拽进度 center?.changePlaybackPositionCommand.addTarget(handler: { [weak self] event in guard let self = self else { return .noActionableNowPlayingItem} guard let positionEvent = event as? MPChangePlaybackPositionCommandEvent else { return .commandFailed } if loadPlayer?.currentVideo != nil { self.player.seek(to: CMTime(seconds: positionEvent.positionTime, preferredTimescale: 1)) return .success }else { return .noActionableNowPlayingItem } }) } //设置远程中心内容更新 func updateNowPlayingInfo() { guard loadPlayer?.currentVideo != nil else {return} //设置info字典信息 currentInfo = [:] //展示标题 currentInfo![MPMediaItemPropertyTitle] = loadPlayer?.currentVideo?.title ?? "" //设置艺术家 currentInfo![MPMediaItemPropertyArtist] = loadPlayer?.currentVideo?.song?.shortBylineText ?? "" //设置专辑 currentInfo![MPMediaItemPropertyAlbumTitle] = loadPlayer?.currentVideo?.song?.longBylineText //当前时长 currentInfo![MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(player.currentItem?.currentTime() ?? .zero) //设置歌曲时长 currentInfo![MPMediaItemPropertyPlaybackDuration] = CMTimeGetSeconds(player.currentItem?.duration ?? .zero) currentInfo![MPNowPlayingInfoPropertyPlaybackRate] = 1.0 let reviewURL = URL(string: loadPlayer?.currentVideo?.song?.reviewUrls?.last ?? "")! KingfisherManager.shared.retrieveImage(with: reviewURL) { [weak self]result in switch result { case .success(let imageResult): let image = imageResult.image self?.currentInfo?[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { size in return image }) // 确保更新MPNowPlayingInfoCenter的操作在主线程中 DispatchQueue.main.async { //更新远程中心 MPNowPlayingInfoCenter.default().nowPlayingInfo = self?.currentInfo } case .failure(_): self?.currentInfo?[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: placeholderImage.size, requestHandler: { size in return placeholderImage }) // 确保更新MPNowPlayingInfoCenter的操作在主线程中 DispatchQueue.main.async { //更新远程中心 MPNowPlayingInfoCenter.default().nowPlayingInfo = self?.currentInfo } } } } func getVideoDimensions(from playerItem: AVPlayerItem, completion: @escaping (CGFloat, CGFloat) -> Void) { guard let asset = playerItem.asset as? AVURLAsset else { completion(0, 0) return } asset.loadValuesAsynchronously(forKeys: ["tracks"]) { var error: NSError? = nil let status = asset.statusOfValue(forKey: "tracks", error: &error) if status == .loaded { let videoTracks = asset.tracks(withMediaType: .video) guard let videoTrack = videoTracks.first else { completion(0, 0) return } let size = videoTrack.naturalSize.applying(videoTrack.preferredTransform) completion(abs(size.width), abs(size.height)) } else { completion(0, 0) } } } ///判断是否为封面视频资源 func isCoverVideo(playerItem: AVPlayerItem, completion: @escaping (Bool) -> Void) { getVideoDimensions(from: playerItem) { width, height in if width == 0 || height == 0 { //高和宽有一个为0,必定是封面资源 completion(true) } else { completion(width <= 1.5 * height) } } } }