1010 lines
41 KiB
Swift
1010 lines
41 KiB
Swift
//
|
||
// 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()
|
||
///播放器
|
||
lazy var player:AVPlayer = {
|
||
let player = AVPlayer()
|
||
player.rate = 1
|
||
return player
|
||
}()
|
||
///播放器视图
|
||
lazy var videoLayer:AVPlayerLayer = {
|
||
let layer:AVPlayerLayer = .init(player: player)
|
||
layer.videoGravity = .resizeAspect
|
||
layer.backgroundColor = UIColor.black.cgColor
|
||
return layer
|
||
}()
|
||
//远程控制中心
|
||
var center:MPRemoteCommandCenter?{
|
||
didSet{
|
||
print("更新了远程控制中心")
|
||
}
|
||
}
|
||
///延迟计时器
|
||
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 isLast:Bool = false
|
||
//设置是否页面展示最后一首状态
|
||
func setLastStatus(bool:Bool) {
|
||
isLast = bool
|
||
}
|
||
//当前播放器状态
|
||
private var playState:MP_PlayerStateType = .Null{
|
||
didSet{
|
||
//当播放器状态发生变化时,对播放器按钮状态进行切换
|
||
NotificationCenter.notificationKey.post(notificationName: .switch_player_status, object: playState)
|
||
}
|
||
}
|
||
//是否因为广告原因延迟了播放(当广告加载时,延迟播放器状态切换)
|
||
// var isAdLate:Bool? = false{
|
||
// 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 bottomProgressBlock:MP_PlayTimerRunAction?
|
||
///播放器缓存值闭包
|
||
var cacheValueBlock:MP_PlayCacheValueAction?
|
||
///播放实例状态的监听器
|
||
private var statusObservation:NSKeyValueObservation?
|
||
///播放实例缓存值的监听器
|
||
private var loadedTimeRangesObservation:NSKeyValueObservation?
|
||
///播放实例是否支持播放的监听器
|
||
private var playbackLikelyToKeepUpObservation:NSKeyValueObservation?
|
||
///播放实例报错的监听器
|
||
private var errorObservation:NSKeyValueObservation?
|
||
///播放实例播放资格监听器
|
||
private var playEntitlementObservation: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)
|
||
NotificationCenter.default.addObserver(self, selector: #selector(handleAppStateChange(_ :)), name: UIApplication.didEnterBackgroundNotification, object: nil)
|
||
NotificationCenter.default.addObserver(self, selector: #selector(handleAppStateChange(_ :)), name: UIApplication.willEnterForegroundNotification, 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)
|
||
}
|
||
if bottomProgressBlock != nil {
|
||
bottomProgressBlock!(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
|
||
}
|
||
//检索是否具备播放资格
|
||
guard loadPlayer?.currentVideo?.isPlayEntitlement != false else {
|
||
//明确不可播放, 执行下一首
|
||
if MP_NetWorkManager.shared.netWorkStatu == .reachable {
|
||
nextEvent()
|
||
}
|
||
return
|
||
}
|
||
if let currentVideo = loadPlayer?.currentVideo {
|
||
//覆盖播放器原有的playerItem
|
||
player.replaceCurrentItem(with: currentVideo.resourcePlayerItem)
|
||
if center == nil {
|
||
setCommandCenter()
|
||
}
|
||
//启动计时器
|
||
startTimer()
|
||
//对当前播放PlayerItem设置监听状态
|
||
if currentVideo.isKVO == false {
|
||
//准备状态
|
||
statusObservation?.invalidate()
|
||
statusObservation = 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("当前音乐-\(currentVideo.title ?? "") 已经准备好播放")
|
||
}
|
||
}else {
|
||
print("当前音乐-\(currentVideo.title ?? "") 未做好准备播放,失败原因是\(currentVideo.resourcePlayerItem?.error?.localizedDescription ?? "")")
|
||
MP_AnalyticsManager.shared.player_b_failure_errorAction(currentVideo.song.videoId ?? "", videoname: currentVideo.title ?? "", artistname: currentVideo.song.shortBylineText ?? "", error: currentVideo.resourcePlayerItem?.error?.localizedDescription ?? "Failed to buffer data")
|
||
if currentVideo.isKVO == true {
|
||
suspendTimer()
|
||
currentVideo.isKVO = false
|
||
statusObservation?.invalidate()
|
||
loadedTimeRangesObservation?.invalidate()
|
||
playbackLikelyToKeepUpObservation?.invalidate()
|
||
errorObservation?.invalidate()
|
||
//重新配置数据
|
||
loadPlayer?.remakeImproveData {
|
||
[weak self] in
|
||
self?.play()
|
||
}
|
||
}
|
||
}
|
||
})
|
||
//当前缓冲值
|
||
loadedTimeRangesObservation?.invalidate()
|
||
loadedTimeRangesObservation = currentVideo.resourcePlayerItem?.observe(\.loadedTimeRanges, options: [.old,.new], changeHandler: { [weak self] item, change in
|
||
guard let self = self else {return}
|
||
cacheLoadTimes()
|
||
})
|
||
//是否具备足够播放的能力
|
||
playbackLikelyToKeepUpObservation?.invalidate()
|
||
playbackLikelyToKeepUpObservation = 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 {
|
||
// //当前是否上次播放展示状态,是的话,手动暂停
|
||
// if isLast {
|
||
// pause()
|
||
// isLast = false
|
||
// }else {
|
||
// player.play()
|
||
// }
|
||
// }else {
|
||
//
|
||
// }
|
||
//当前是否上次播放展示状态,是的话,手动暂停
|
||
if isLast {
|
||
pause()
|
||
isLast = false
|
||
}else {
|
||
player.play()
|
||
}
|
||
//暂停计时器,并获取延时值
|
||
suspendTimer()
|
||
if let currentVideo = loadPlayer?.currentVideo {
|
||
MP_AnalyticsManager.shared.player_b_success_actionAction(currentVideo.song.videoId ?? "", videoname: currentVideo.title ?? "", artistname: currentVideo.song.shortBylineText ?? "")
|
||
|
||
}
|
||
//执行开始播放闭包
|
||
if startActionBlock != nil {
|
||
startActionBlock!()
|
||
}
|
||
|
||
}
|
||
}else {
|
||
//没有足够的数据支持播放
|
||
player.pause()
|
||
playState = .Null
|
||
}
|
||
})
|
||
//报错提醒
|
||
errorObservation?.invalidate()
|
||
errorObservation = 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("当前音乐-\(currentVideo.title ?? "") 未做好准备播放,失败原因是\(nsError.localizedDescription)")
|
||
MP_AnalyticsManager.shared.player_b_failure_errorAction(currentVideo.song.videoId ?? "", videoname: currentVideo.title ?? "", artistname: currentVideo.song.shortBylineText ?? "", error: nsError.localizedDescription)
|
||
}
|
||
})
|
||
//播放资格
|
||
playEntitlementObservation?.invalidate()
|
||
playEntitlementObservation = currentVideo.observe(\.isPlayEntitlement, options: [.old,.new], changeHandler: { [weak self] item, change in
|
||
guard let self = self, MP_NetWorkManager.shared.netWorkStatu == .reachable else {return}
|
||
if change.newValue == 0 {
|
||
//当前音乐不具备播放资格,直接下一首
|
||
nextEvent()
|
||
}
|
||
})
|
||
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 handleAppStateChange(_ notification: Notification) {
|
||
if notification.name == UIApplication.didEnterBackgroundNotification {
|
||
//进入后台时解绑AVPlayer
|
||
videoLayer.player = nil
|
||
}else if notification.name == UIApplication.willEnterForegroundNotification {
|
||
videoLayer.player = player
|
||
}
|
||
}
|
||
|
||
///网络状态恢复正常
|
||
@objc private func netWorkReachableAction(_ sender:Notification) {
|
||
//监听到网络状态恢复,检索当前播放器是否正在播放
|
||
if loadPlayer?.currentVideo != nil {
|
||
if playState == .Playing, let currentItem = loadPlayer?.currentVideo?.resourcePlayerItem {
|
||
//有音乐播放,获取当前播放进度
|
||
let currentTime = currentItem.currentTime()
|
||
//手动调整播放时间点,以此重启播放器缓存
|
||
player.seek(to: currentTime)
|
||
player.play()
|
||
playState = .Playing
|
||
}
|
||
}
|
||
}
|
||
//确定播放器播放后限时状态
|
||
func playerStatuTimerAction() {
|
||
MPPositive_Debouncer.shared.playCall {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
//10秒后检索播放器是否在播放
|
||
if playState != .Playing {
|
||
if isLast == true {
|
||
MP_HUD.text("Failed to obtain resource, please try again later".localizableString(), delay: 2.0, completion: nil)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
//获取缓冲值
|
||
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 let item = sender.object as? MP_AVPlayerItem else {return}
|
||
//检索播放器对象
|
||
guard playState == .Playing else {
|
||
return
|
||
}
|
||
//将当前播放音乐存入最近听过
|
||
if let song = loadPlayer?.currentVideo?.song {
|
||
createRecentData(3, song: song)
|
||
}
|
||
switch playType {
|
||
case .single:
|
||
//重播
|
||
player.seek(to: CMTime.zero)
|
||
player.play()
|
||
default:
|
||
//当前音乐播放器正在播放中,下一首
|
||
nextEvent()
|
||
}
|
||
}
|
||
///设置最近听过
|
||
private func createRecentData(_ level:Int16, song:MPPositive_SongItemModel) {
|
||
//检索是否已经存在这个数据
|
||
MPPositive_RecentlyModel.fetch(predicate: .init(format: "videoId == %@", (song.videoId ?? ""))) { results in
|
||
if results.isEmpty {
|
||
//数据库中没有记录,新建一个
|
||
let item = try? MPPositive_RecentlyModel.create()
|
||
item?.coverImage = song.coverUrls?.last
|
||
item?.reviewImage = song.reviewUrls?.last
|
||
item?.title = song.title
|
||
item?.subtitle = (song.longBylineText ?? "")+(song.shortBylineText ?? "")
|
||
item?.videoId = song.videoId
|
||
item?.lyricsID = song.lyricsID
|
||
item?.relatedID = song.relatedID
|
||
item?.addTime = Date()
|
||
item?.level = level
|
||
item?.artistID = song.artistID
|
||
item?.albumID = song.albumID
|
||
item?.playListID = song.playListID
|
||
}else {
|
||
//不为空,调整数据
|
||
if let item = results.first {
|
||
if (item.level) < level {
|
||
item.level = level
|
||
}
|
||
item.addTime = Date()
|
||
}
|
||
}
|
||
MPPositive_RecentlyModel.save()
|
||
}
|
||
}
|
||
|
||
//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 let randomVideos = loadPlayer?.randomVideos, randomVideos.count != 1 else {
|
||
player.seek(to: .zero)
|
||
playState = .Playing
|
||
player.play()
|
||
return
|
||
}
|
||
for (index, item) in randomVideos.enumerated() {
|
||
if item.videoId == loadPlayer?.currentVideo?.song.videoId {
|
||
//找到播放音乐的索引
|
||
nextIndex = index + 1
|
||
}
|
||
}
|
||
//超出播放列表数
|
||
if nextIndex > (randomVideos.count-1) {
|
||
//播放列表第一首
|
||
let first = randomVideos.first
|
||
loadPlayer?.improveData(first?.videoId ?? "", isRandom: true)
|
||
}else {
|
||
//存在下一首,获取下一首ID,并播放
|
||
if let song = loadPlayer?.randomVideos?[nextIndex] {
|
||
loadPlayer?.improveData(song.videoId ?? "", isRandom: true)
|
||
}
|
||
}
|
||
default:
|
||
guard let songVideos = loadPlayer?.songVideos, songVideos.count != 1 else {
|
||
player.seek(to: .zero)
|
||
playState = .Playing
|
||
player.play()
|
||
return
|
||
}
|
||
for (index, item) in songVideos.enumerated() {
|
||
if item.videoId == loadPlayer?.currentVideo?.song.videoId {
|
||
//找到播放音乐的索引
|
||
nextIndex = index + 1
|
||
}
|
||
}
|
||
//超出播放列表数
|
||
if nextIndex > (songVideos.count-1) {
|
||
//播放列表第一首
|
||
let first = songVideos.first
|
||
loadPlayer?.improveData(first?.videoId ?? "")
|
||
}else {
|
||
//存在下一首,获取下一首ID,并播放
|
||
if let song = loadPlayer?.songVideos?[nextIndex] {
|
||
loadPlayer?.improveData(song.videoId ?? "")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
///记录用户切歌
|
||
private func switchEvent() {
|
||
//获得当前播放音乐以及播放时长
|
||
guard let currentVideo = loadPlayer?.currentVideo else {return}
|
||
if let currentDruation = player.currentItem?.duration {
|
||
let times = CMTimeGetSeconds(currentDruation)
|
||
if times >= 60 {
|
||
//作为二级最近听过
|
||
createRecentData(2, song: currentVideo.song)
|
||
}else {
|
||
//作为低级最近听过
|
||
createRecentData(1, song: currentVideo.song)
|
||
}
|
||
}else {
|
||
//作为用户略过的歌曲,不操作
|
||
}
|
||
}
|
||
|
||
///监听到用户切换当前音乐
|
||
@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: - 远程中心
|
||
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 {
|
||
previousEvent()
|
||
return .success
|
||
}else {
|
||
return .noActionableNowPlayingItem
|
||
}
|
||
}
|
||
//下一首歌
|
||
center!.nextTrackCommand.addTarget { [weak self] (event) in
|
||
guard let self = self else { return .noActionableNowPlayingItem}
|
||
if loadPlayer?.currentVideo != nil {
|
||
nextEvent()
|
||
return .success
|
||
}else {
|
||
return .noActionableNowPlayingItem
|
||
}
|
||
}
|
||
//拖拽进度
|
||
center?.changePlaybackPositionCommand.addTarget(handler: { [weak self] event in
|
||
guard let self = self 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
|
||
}
|
||
}
|
||
}
|
||
}
|
||
func getVideoDimensions(from playerItem: AVPlayerItem, completion: @escaping (CGFloat, CGFloat) -> Void) {
|
||
guard let asset = playerItem.asset as? AVURLAsset else {
|
||
completion(0, 0)
|
||
return
|
||
}
|
||
|
||
asset.loadValuesAsynchronously(forKeys: ["tracks"]) {
|
||
var error: NSError? = nil
|
||
let status = asset.statusOfValue(forKey: "tracks", error: &error)
|
||
|
||
if status == .loaded {
|
||
let videoTracks = asset.tracks(withMediaType: .video)
|
||
guard let videoTrack = videoTracks.first else {
|
||
completion(0, 0)
|
||
return
|
||
}
|
||
|
||
let size = videoTrack.naturalSize.applying(videoTrack.preferredTransform)
|
||
completion(abs(size.width), abs(size.height))
|
||
} else {
|
||
completion(0, 0)
|
||
}
|
||
}
|
||
}
|
||
///判断是否为封面视频资源
|
||
func isCoverVideo(playerItem: AVPlayerItem, completion: @escaping (Bool) -> Void) {
|
||
getVideoDimensions(from: playerItem) { width, height in
|
||
if width == 0 || height == 0 {
|
||
//高和宽有一个为0,必定是封面资源
|
||
completion(true)
|
||
} else {
|
||
completion(width <= 1.5 * height)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|