566 lines
20 KiB
Swift
566 lines
20 KiB
Swift
//
|
||
// 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)")
|
||
// }
|
||
//
|
||
//
|
||
//}
|