// // 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() ///播放器 private lazy var player:AVPlayer = { let player = AVPlayer() player.rate = 1 return player }() //远程控制中心 private var center:MPRemoteCommandCenter? ///延迟计时器 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 playState:MP_PlayerStateType = .Null{ didSet{ //当播放器状态发生变化时,对播放器按钮状态进行切换 NotificationCenter.notificationKey.post(notificationName: .switch_player_status, object: playState) } } //是否因为广告原因延迟了播放(当广告加载时,延迟播放器状态切换) var isAdLate:Bool?{ 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 cacheValueBlock:MP_PlayCacheValueAction! ///播放实例状态的监听器 private var statusObservation:NSKeyValueObservation? ///播放实例缓存值的监听器 private var loadedTimeRangesObservation:NSKeyValueObservation? ///播放实例是否支持播放的监听器 private var playbackLikelyToKeepUpObservation:NSKeyValueObservation? ///播放实例报错的监听器 private var errorObservation: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) //设置一个秒为刻度的时间值 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) } } } }) } 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 } //覆盖播放器原有的playerItem player.replaceCurrentItem(with: loadPlayer?.currentVideo?.resourcePlayerItem) if center == nil { setCommandCenter() } //启动计时器 startTimer() //对当前播放PlayerItem设置监听状态 if loadPlayer.currentVideo?.isKVO == false { //准备状态 statusObservation?.invalidate() statusObservation = loadPlayer?.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("当前音乐-\(loadPlayer?.currentVideo?.title ?? "") 已经准备好播放") } }else { print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 未做好准备播放,失败原因是\(loadPlayer.currentVideo?.resourcePlayerItem.error?.localizedDescription ?? "")") MP_AnalyticsManager.shared.player_b_failure_errorAction(loadPlayer?.currentVideo?.song.videoId ?? "", videoname: loadPlayer?.currentVideo?.title ?? "", artistname: loadPlayer?.currentVideo?.song.shortBylineText ?? "", error: loadPlayer.currentVideo?.resourcePlayerItem.error?.localizedDescription ?? "Failed to buffer data") if loadPlayer?.currentVideo?.isKVO == true { suspendTimer() loadPlayer?.currentVideo?.isKVO = false //重新配置数据 loadPlayer.remakeImproveData { [weak self] in self?.play() } } } }) //当前缓冲值 loadedTimeRangesObservation?.invalidate() loadedTimeRangesObservation = loadPlayer?.currentVideo?.resourcePlayerItem?.observe(\.loadedTimeRanges, options: [.old,.new], changeHandler: { [weak self] item, change in guard let self = self else {return} cacheLoadTimes() }) //是否具备足够播放的能力 playbackLikelyToKeepUpObservation?.invalidate() playbackLikelyToKeepUpObservation = loadPlayer?.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 { player.play() } //暂停计时器,并获取延时值 suspendTimer() MP_AnalyticsManager.shared.player_b_success_actionAction(loadPlayer?.currentVideo?.song.videoId ?? "", videoname: loadPlayer?.currentVideo?.title ?? "", artistname: loadPlayer?.currentVideo?.song.shortBylineText ?? "") //执行开始播放闭包 if startActionBlock != nil { startActionBlock!() } } }else { //没有足够的数据支持播放 player.pause() playState = .Null } }) //报错提醒 errorObservation?.invalidate() errorObservation = loadPlayer?.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("当前音乐-\(loadPlayer?.currentVideo?.title ?? "") 未做好准备播放,失败原因是\(nsError.localizedDescription)") MP_AnalyticsManager.shared.player_b_failure_errorAction(loadPlayer?.currentVideo?.song.videoId ?? "", videoname: loadPlayer?.currentVideo?.title ?? "", artistname: loadPlayer?.currentVideo?.song.shortBylineText ?? "", error: nsError.localizedDescription) } }) loadPlayer.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 netWorkReachableAction(_ sender:Notification) { //监听到网络状态恢复,检索当前播放器是否正在播放 if loadPlayer?.currentVideo != nil { //有音乐播放,获取当前播放进度 let currentTime = loadPlayer?.currentVideo?.resourcePlayerItem?.currentTime() ?? .zero //手动调整播放时间点,以此重启播放器缓存 player.seek(to: currentTime) player.play() playState = .Playing } } //获取缓冲值 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 } switch playType { case .single: //重播 player.seek(to: CMTime.zero) player.play() default: //当前音乐播放器正在播放中,下一首 nextEvent() } } //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 (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 } } //超出播放列表数 if nextIndex > ((loadPlayer?.randomVideos?.count ?? 0)-1) { //播放列表第一首 let first = loadPlayer?.randomVideos?.first loadPlayer.improveData(first?.videoId ?? "", isRandom: true) }else { //存在下一首,获取下一首ID,并播放 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 } } //超出播放列表数 if nextIndex > (loadPlayer?.songVideos?.count ?? 0)-1 { //播放列表第一首 let first = loadPlayer?.songVideos?.first loadPlayer.improveData(first?.videoId ?? "") }else { //存在下一首,获取下一首ID,并播放 if let song = loadPlayer?.songVideos?[nextIndex] { loadPlayer.improveData(song.videoId ?? "") } } } } ///监听到用户切换当前音乐 @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: - 远程中心 private 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 && MP_NetWorkManager.shared.netWorkStatu == .reachable { previousEvent() return .success }else { return .noActionableNowPlayingItem } } //下一首歌 center!.nextTrackCommand.addTarget { [weak self] (event) in guard let self = self else { return .noActionableNowPlayingItem} if loadPlayer?.currentVideo != nil && MP_NetWorkManager.shared.netWorkStatu == .reachable { nextEvent() return .success }else { return .noActionableNowPlayingItem } } //拖拽进度 center?.changePlaybackPositionCommand.addTarget(handler: { [weak self] event in guard let self = self else { return .noActionableNowPlayingItem} guard MP_NetWorkManager.shared.netWorkStatu == .reachable 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 } } } } }