295 lines
10 KiB
Swift
295 lines
10 KiB
Swift
//
|
||
// 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!()
|
||
}
|
||
}
|
||
}
|