// // MP_PlayerManager.swift // MusicPlayer // // Created by Mr.Zhou on 2024/5/10. // import UIKit import AVFoundation import MediaPlayer import AVKit ///播放器播放状态 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 ///播放器 class MP_PlayerManager{ ///控制器单例 static let shared = MP_PlayerManager() ///播放器 private var player:AVPlayer = AVPlayer() ///load模块 var loadPlayer:MPPositive_PlayerLoadViewModel! //当前播放器状态 private var playState:MP_PlayerStateType = .Null ///播放器启动时执行事件记录 private var startActionBlock:MP_PlayTimerStartAction! ///播放器运行时执行事件记录 private var runActionBlock:MP_PlayTimerRunAction! private init() { // 添加观察者,监听播放结束事件 NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_ :)), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem) NotificationCenter.notificationKey.add(observer: self, selector: #selector(userSwitchCurrentVideoAction(_ :)), notificationName: .positive_player_reload) } deinit { NotificationCenter.default.removeObserver(self) } ///获取播放器播放状态 func getPlayState() -> MP_PlayerStateType { return playState } /// 开始播放音乐 /// - Parameters: /// - startAction: 开始播放时需要执行的事件 /// - runAction: 播放途中需要执行的事件 /// - endAction: 结束播放时需要执行的事件 func play(startAction:MP_PlayTimerStartAction? = nil, runAction:MP_PlayTimerRunAction? = nil) { //检索播放器状态 switch playState { case .Null://未启动 break case .Playing://启动中 player.pause() case .Pause://暂停中 break } //记录事件 if startAction != nil { startActionBlock = startAction } if runAction != nil { runActionBlock = runAction } //判断是否有PlayerItem player.replaceCurrentItem(with: loadPlayer.currentVideo.resourcePlayerItem) //将进度回归为0 player.seek(to: .zero) //设置一个秒为刻度的时间值 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 } //转化为当前播放进度秒值 let currentDuration = CMTimeGetSeconds(time) //当current为0时执行开始事件 if currentDuration == 0 { if startActionBlock != nil { startActionBlock!() } } //获取当前播放音乐资源的最大时间值 let maxDuration = getMusicDuration() if maxDuration.isNaN == false { //判断当值进度是否超越最大时间值 if currentDuration <= maxDuration { //没有,执行运行时时间 if runActionBlock != nil { runActionBlock!(currentDuration, maxDuration) } } } }) //播放 player.play() playState = .Playing //启动除了当前播放意外的预加载内容 // let set = Set(loadPlayer.listViewVideos.filter({$0.index != loadPlayer.currentVideo.index})) // set.forEach { item in // if item.canBePreloaded() == false { // item.preloadPlayerItem() // } // } } ///获取音乐资源总时长 private func getMusicDuration() -> TimeInterval { return CMTimeGetSeconds(player.currentItem?.duration ?? .zero) } //MARK: - 音乐播放结束 //当前音乐播放结束时 @objc private func playerDidFinishPlaying(_ sender:Notification) { //检索播放器对象 guard playState == .Playing else { return } //当前音乐播放器正在播放中,下一首 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() //切换播放器状态 playState = .Playing } ///内部继续播放 private func resume() { //检索播放状态,是否暂停中 guard playState == .Pause else { //未处于暂停中 print("Player is not paused") return } //继续播放器 player.play() //切换播放器状态 playState = .Playing } //MARK: - 停止播放 //停止播放 private func stop() { //检索播放状态,是否已启动 guard playState != .Null else { //未启动 print("Player is not started") return } player.pause() playState = .Null } //MARK: - 切歌(上一首/下一首) ///上一首歌事件 func previousEvent() { //判断是否存在上一首音乐 let targetIndex = loadPlayer.listViewVideos.firstIndex(of: loadPlayer.currentVideo) if targetIndex == 0 { //当前音乐第一首,更新列表内容,获取最后一首歌,并播放 let last = loadPlayer.songVideos.last loadPlayer.improveData(last?.videoId ?? "") }else { //存在上一首,获取上一首ID,并播放 let song = loadPlayer.songVideos.first(where: {$0.index == (loadPlayer.currentVideo.index-1)}) loadPlayer.improveData(song?.videoId ?? "") } } ///下一首歌事件 func nextEvent() { //判断是否存在下一首音乐 let targetIndex = loadPlayer.listViewVideos.firstIndex(of: loadPlayer.currentVideo) if targetIndex == (loadPlayer.listViewVideos.count - 1) { //当前音乐最后一首,更新列表内容,获取第一首歌,并播放 let first = loadPlayer.songVideos.first loadPlayer.improveData(first?.videoId ?? "") }else { //存在下一首,获取下一首ID,并播放 let song = loadPlayer.songVideos.first(where: {$0.index == (loadPlayer.currentVideo.index+1)}) loadPlayer.improveData(song?.videoId ?? "") } } ///监听到用户切换当前音乐 @objc private func userSwitchCurrentVideoAction(_ sender:Notification) { if loadPlayer.currentVideo != nil { //开始播放 play(startAction: startActionBlock,runAction: runActionBlock) }else { //用户删除了音乐,播放下一首音乐 } } ///播放器进度调整状态 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!() } } }