Music_Player3/MusicPlayer/MP/Common/Tool(工具封装)/MP_PlayerManager.swift

453 lines
17 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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
///
typealias MP_PlayCacheValueAction = (_ currentValue:TimeInterval, _ duration:TimeInterval) -> Void
///
class MP_PlayerManager:NSObject{
///
static let shared = MP_PlayerManager()
///
private var player:AVPlayer = AVPlayer()
///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()
//
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)
}
///
/// - 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
}
//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)
//
let maxDuration = getMusicDuration()
if maxDuration.isNaN == false {
//
if currentDuration <= maxDuration {
//
if runActionBlock != nil {
runActionBlock!(currentDuration, maxDuration)
}
}
}
})
//PlayerItem
//
loadPlayer.currentVideo?.resourcePlayerItem?.addObserver(self, forKeyPath: "status", options: [.old,.new], context: nil)
//
loadPlayer.currentVideo?.resourcePlayerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.old,.new], context: nil)
}
//KVO
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath else {
return
}
//keyPath
switch keyPath {
case "status"://playerItem
if let statuValue = change?[.newKey] as? Int, statuValue == AVPlayerItem.Status.readyToPlay.rawValue {
//
if playState != .Playing {
//statuVlaueplayerItem
print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 已经准备好播放")
//
print("开始播放音乐-\(loadPlayer.currentVideo?.title ?? "")")
player.play()
playState = .Playing
//
if startActionBlock != nil {
startActionBlock!()
}
}
}else {
print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 未做好准备播放,失败原因是\(loadPlayer.currentVideo?.resourcePlayerItem.error?.localizedDescription ?? "")")
//
loadPlayer.remakeImproveData {
[weak self] in
guard let self = self else {return}
//
play()
}
}
case "loadedTimeRanges"://
//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)
}
}
case "playbackLikelyToKeepUp"://
break
default:
break
}
}
//MARK: -
///
private func getMusicDuration() -> TimeInterval {
return CMTimeGetSeconds(player.currentItem?.duration ?? .zero)
}
//MARK: -
//
@objc private func playerDidFinishPlaying(_ sender:Notification) {
//
guard playState == .Playing else {
return
}
switch playType {
case .single:
//
player.seek(to: CMTime.zero)
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()
//
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://
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 ?? "")
}else {
//
let song = loadPlayer.randomVideos[nextIndex]
loadPlayer.improveData(song.videoId ?? "")
}
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 ?? "")
}else {
//,ID
let song = loadPlayer.randomVideos[nextIndex]
loadPlayer.improveData(song.videoId ?? "")
}
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
//
player.pause()
//
if let video = sender.object as? MPPositive_SongViewModel {
//KVO
video.resourcePlayerItem.removeObserver(self, forKeyPath: "status")
video.resourcePlayerItem.removeObserver(self, forKeyPath: "loadedTimeRanges")
// video.resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
}
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!()
}
}
}