// // MP_PlayerManager.swift // MusicPlayer // // Created by Mr.Zhou on 2024/5/10. // import UIKit import AVFoundation import MediaPlayer import AVKit import FreeStreamer ///播放器播放状态 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 } ///播放器启动时执行事件(播放的音乐) 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 = (Float) -> Void ///播放器 class MP_PlayerManager:NSObject{ ///控制器单例 static let shared = MP_PlayerManager() ///播放器 // private var player:AVPlayer = AVPlayer() ///当前播放流 private var player:FSAudioStream! // ///预加载下一首流 private var next:FSAudioStream! ///计时器 private var timer:DispatchSourceTimer! ///load模块 var loadPlayer:MPPositive_PlayerLoadViewModel!{ didSet{ if loadPlayer != nil { //当load模块接受到新值的时候,发出通知,提醒底部模块状态切换 NotificationCenter.notificationKey.post(notificationName: .pup_bottom_show) }else { //用户清空了load模块,隐藏播放器 NotificationCenter.notificationKey.post(notificationName: .player_delete_list) playState = .Null } } } //当前播放器状态 private var playState:MP_PlayerStateType = .Null{ didSet{ //当播放器状态发生变化时,对播放器按钮状态进行切换 NotificationCenter.notificationKey.post(notificationName: .switch_player_status, object: playState) } } ///获取播放器播放状态 func getPlayState() -> MP_PlayerStateType { return playState } ///当前播放器播放方法 private var playType:MP_PlayerPlayType = .normal{ didSet{ //当播放器播放方式变化后,发出通知 NotificationCenter.notificationKey.post(notificationName: .player_type_switch) } } ///获取播放器播放方法 func getPlayType() -> MP_PlayerPlayType { return playType } /// 设置播放器播放方式 /// - Parameter type: 新的类型 func setPlayType(_ type:MP_PlayerPlayType) { playType = type if playType == .random { } } ///播放器启动时执行事件记录 private var startActionBlock:MP_PlayTimerStartAction! ///播放器运行时执行事件记录 var runActionBlock:MP_PlayTimerRunAction! ///播放器缓存值闭包 var cacheValueBlock:MP_PlayCacheValueAction! private override init() { super.init() // player.automaticallyWaitsToMinimizeStalling = false //// player.delegate = self // // 添加观察者,监听播放结束事件 // 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) //创建倒计时器队列 let queue = DispatchQueue(label: "com.MPPlayerTimer.queue") //创建倒计时器 timer = DispatchSource.makeTimerSource(queue: queue) //设置计时器的起始时间以及触发事件频率为一秒一次 timer!.schedule(deadline: .now(), repeating: .seconds(1)) //计时器设置触发事件 timer.setEventHandler { [weak self] in guard let self = self else {return} timerAction() } //计时器启动 timer.resume() } deinit { NotificationCenter.default.removeObserver(self) player = nil timer.cancel() timer = nil } ///计时器每秒执行事件 private func timerAction() { //判断当前播放器是否存在 guard let player = player, playState == .Playing else {return} DispatchQueue.main.async {[weak self] in guard let self = self, findTurePlayer(player) else {return} //存在,每秒获取播放进度(当前时间,总时间) let duration = player.duration.playbackTimeInSeconds let currentTime = player.currentTimePlayed.playbackTimeInSeconds //调用进度闭包 if self.runActionBlock != nil { self.runActionBlock!(TimeInterval(currentTime), TimeInterval(duration)) } //检索媒体是否存入缓存 if player.cached == true || loadPlayer.currentVideo.isDlownd == true { if self.cacheValueBlock != nil { self.cacheValueBlock!(1) } }else { //伪装每秒缓存进度 let byte = currentTime + 100 let rate = Float(byte)/duration if self.cacheValueBlock != nil { self.cacheValueBlock!(rate) } } } } /// 开始播放音乐 /// - Parameters: /// - startAction: 开始播放时需要执行的事件 /// - runAction: 播放途中需要执行的事件 /// - endAction: 结束播放时需要执行的事件 func play(startAction:MP_PlayTimerStartAction? = nil) { guard loadPlayer != nil, loadPlayer.currentVideo != nil else { //当两项数据皆为空时,播放器无法播放 print("Player No Data") return } //清除旧流媒体 stopAndReleaseStream(&player) //记录事件 if startAction != nil { startActionBlock = startAction } if next != nil, (next.url == (loadPlayer.currentVideo.resourcePlayerURL! as NSURL)) { player = next }else { //配置当前播放音乐 player = .init(url: loadPlayer.currentVideo.resourcePlayerURL!) player.maxRetryCount = 3 } //预加载下一首(假如有的话) let index = loadPlayer.listViewVideos.firstIndex(of: loadPlayer.currentVideo) ?? 0 if (loadPlayer.listViewVideos.count-1) > index { stopAndReleaseStream(&next) //纯在下一首,获取下一位的URL let nextURL = loadPlayer.listViewVideos[index + 1].resourcePlayerURL next = preloadNext(nextURL!) } //开始播放 player.play() //获取播放器状态 player.onStateChange = { [weak self] status in guard let self = self, findTurePlayer(player) else {return} switch status { case .fsAudioStreamFailed://加载失败 print("\(loadPlayer.currentVideo?.title ?? "")加载失败") case .fsAudioStreamRetryingFailed://重试都失败了 print("\(loadPlayer.currentVideo?.title ?? "")重试失败") print("失败URL:\(String(describing: loadPlayer.currentVideo?.resourcePlayerURL))") //重新获取资源 loadPlayer.remakeImproveData { [weak self] in guard let self = self else {return} //配置当前播放音乐 player?.url = loadPlayer.currentVideo.resourcePlayerURL! as NSURL } case .fsAudioStreamPlaying://加载成功 //开始播放/正在播放 print("\(loadPlayer.currentVideo?.title ?? "")开始播放") if playState != .Playing { playState = .Playing if startAction != nil { startAction!() } } case .fsAudioStreamPlaybackCompleted://播放完成 playerDidFinishPlaying() case .fsAudioStreamEndOfFile://加载完成 print("\(loadPlayer.currentVideo?.title ?? "")加载完毕") default: break } } } ///对流的检查,判断当前调用流是否是播放流 private func findTurePlayer(_ stream:FSAudioStream) -> Bool { guard let currentVideoURL = loadPlayer?.currentVideo?.resourcePlayerURL as? NSURL else { return false } let streamURL = stream.url if streamURL == currentVideoURL { return true }else { return false } } ///预加载下一首流 private func preloadNext(_ url:URL) -> FSAudioStream{ let stream = FSAudioStream(url: url) stream?.maxRetryCount = 1 // 开始预加载数据 stream!.preload() print("下一首已经在预加载") return stream! } //MARK: - 音乐播放结束 //当前音乐播放结束时 @objc private func playerDidFinishPlaying() { //检索播放器对象 guard playState == .Playing else { return } switch playType { case .single: var postion = FSStreamPosition() postion.position = 0 //重播 player.seek(to: postion) 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 } //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() player.pause() //切换播放器状态 playState = .Playing } ///内部继续播放 private func resume() { //检索播放状态,是否暂停中 guard playState == .Pause else { //未处于暂停中 print("Player is not paused") return } //继续播放器 // player.play() player.pause() //切换播放器状态 playState = .Playing } //MARK: - 停止播放 //停止播放 func stop() { //检索播放状态,是否已启动 guard playState != .Null else { //未启动 print("Player is not started") return } player.stop() playState = .Null } //MARK: - 切歌(上一首/下一首) ///上一首歌事件 func previousEvent() { //将播放器状态调整未播放 playState = .Null var nextIndex:Int = 0 //判断当前音乐播放方式 switch playType { case .random://随机,播放随机列表内容 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 { //查询列表对应单曲 let song = loadPlayer.randomVideos[nextIndex] loadPlayer.improveData(song.videoId ?? "", isRandom: true) } default://常规播放或者单曲播放 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 { //查询列表对应单曲 let song = loadPlayer.songVideos[nextIndex] loadPlayer.improveData(song.videoId ?? "") } } } ///下一首歌事件 func nextEvent() { //将播放器状态调整未播放 playState = .Null var nextIndex:Int = 0 switch playType { case .random: for (index, item) in loadPlayer.randomVideos.enumerated() { if item.videoId == loadPlayer.currentVideo?.song.videoId { //找到播放音乐的索引 nextIndex = index + 1 } } //超出播放列表数 if nextIndex > (loadPlayer.randomVideos.count-1) { //播放列表第一首 let first = loadPlayer.randomVideos.first loadPlayer.improveData(first?.videoId ?? "", isRandom: true) }else { //存在下一首,获取下一首ID,并播放 let song = loadPlayer.randomVideos[nextIndex] loadPlayer.improveData(song.videoId ?? "", isRandom: true) } default: for (index, item) in loadPlayer.songVideos.enumerated() { if item.videoId == loadPlayer.currentVideo?.song.videoId { //找到播放音乐的索引 nextIndex = index + 1 } } //超出播放列表数 if nextIndex > (loadPlayer.songVideos.count-1) { //播放列表第一首 let first = loadPlayer.songVideos.first loadPlayer.improveData(first?.videoId ?? "") }else { //存在下一首,获取下一首ID,并播放 let song = loadPlayer.songVideos[nextIndex] loadPlayer.improveData(song.videoId ?? "") } } } ///监听到用户切换当前音乐 @objc private func userSwitchCurrentVideoAction(_ sender:Notification) { //将播放器状态调整未播放 playState = .Null //清理所有的流 if player != nil { //清除所有流 stopAndReleaseStream(&player) } if cacheValueBlock != nil { cacheValueBlock!(0) } DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in guard let self = self else {return} 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, let player = player, findTurePlayer(player) else { return } guard progress >= 0, progress <= 1 else { return } //设置对应的时间值 var time:FSStreamPosition = .init() time.position = progress //获取当前值的大小 let currentTime = player.currentTimePlayed.playbackTimeInSeconds if progress != currentTime { //调整播放器时间 player.seek(to: time) //恢复播放 resume() if endAction != nil { endAction!() } } } ///清除流的方法 private func stopAndReleaseStream(_ stream: inout FSAudioStream?) { stream?.stop() // 停止流 stream?.onStateChange = nil // 清除所有可能的回调 stream?.onFailure = nil stream = nil } } ////MARK: - 媒体项目协议处理 //extension MP_PlayerManager: MP_AVPlayerItemDelegate { // ///当媒体项目初次缓存后 // func playerItemReadyToPlay(_ playerItem: MP_AVPlayerItem) { // DispatchQueue.main.async { // [weak self] in // guard let self = self else {return} // if playState != .Playing { // //还未播放当前音乐,启动播放 // player.play() // playState = .Playing // //执行开始播放闭包 // if startActionBlock != nil { // startActionBlock!() // } // // } // } // } // ///当媒体项目收到新缓存后 // func playerItem(_ playerItem: MP_AVPlayerItem, progress:Float) { // DispatchQueue.main.async { // [weak self] in // guard let self = self else {return} // //传递缓存值 // if cacheValueBlock != nil { // cacheValueBlock!(progress) // } // } // } // ///当媒体项目完全加载后 // func playerItem(_ playerItem: MP_AVPlayerItem, didFinishLoadingData data: Data) { // print("\(loadPlayer.currentVideo.title ?? "") 已经完全缓存完毕") // DispatchQueue.main.async { // [weak self] in // guard let self = self else {return} // //传递缓存值 // if cacheValueBlock != nil { // cacheValueBlock!(1) // } // } // } // ///当媒体项目加载数据中断(比如断网了),导致停止播放时 // func playerItemPlaybackStalled(_ playerItem: MP_AVPlayerItem) { // print("中断了") // } // ///当媒体项目加载错误时调用。 // func playerItem(_ playerItem: MP_AVPlayerItem, loadingError error: any Error) { // print("\(loadPlayer.currentVideo.title ?? "") 发生错误,---\(error)") // } // // //}