播放器更改

This commit is contained in:
Mr.zhou 2024-05-29 17:48:19 +08:00
parent 4621f95204
commit 3c20413986
3 changed files with 195 additions and 261 deletions

View File

@ -44,20 +44,13 @@ typealias MP_PlayTimerStopAction = () -> Void
/// ///
typealias MP_PlayTimerEditEndAction = () -> Void typealias MP_PlayTimerEditEndAction = () -> Void
/// ///
typealias MP_PlayCacheValueAction = (Float) -> Void typealias MP_PlayCacheValueAction = (_ currentValue:TimeInterval, _ duration:TimeInterval) -> Void
/// ///
class MP_PlayerManager:NSObject{ class MP_PlayerManager:NSObject{
/// ///
static let shared = MP_PlayerManager() static let shared = MP_PlayerManager()
/// ///
// private var player:AVPlayer = AVPlayer() private var player:AVPlayer = AVPlayer()
///
private var player:FSAudioStream!
// ///
private var next:FSAudioStream!
///
private var timer:DispatchSourceTimer!
///load ///load
var loadPlayer:MPPositive_PlayerLoadViewModel!{ var loadPlayer:MPPositive_PlayerLoadViewModel!{
didSet{ didSet{
@ -76,7 +69,6 @@ class MP_PlayerManager:NSObject{
didSet{ didSet{
// //
NotificationCenter.notificationKey.post(notificationName: .switch_player_status, object: playState) NotificationCenter.notificationKey.post(notificationName: .switch_player_status, object: playState)
} }
} }
/// ///
@ -104,73 +96,19 @@ class MP_PlayerManager:NSObject{
} }
/// ///
private var startActionBlock:MP_PlayTimerStartAction! private var startActionBlock:MP_PlayTimerStartAction!
/// ///
var runActionBlock:MP_PlayTimerRunAction! var runActionBlock:MP_PlayTimerRunAction!
/// ///
var cacheValueBlock:MP_PlayCacheValueAction! var cacheValueBlock:MP_PlayCacheValueAction!
private override init() { private override init() {
super.init() super.init()
// player.automaticallyWaitsToMinimizeStalling = false //
//// player.delegate = self NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_ :)), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
// //
// 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(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 { deinit {
NotificationCenter.default.removeObserver(self) 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 + 30
let rate = Float(byte)/duration
if self.cacheValueBlock != nil {
self.cacheValueBlock!(rate)
}
}
}
}
/// ///
/// - Parameters: /// - Parameters:
/// - startAction: /// - startAction:
@ -182,103 +120,120 @@ class MP_PlayerManager:NSObject{
print("Player No Data") print("Player No Data")
return return
} }
// //
stopAndReleaseStream(&player) switch playState {
case .Null://
break
case .Playing://
player.pause()
case .Pause://
break
}
// //
if startAction != nil { if startAction != nil {
startActionBlock = startAction startActionBlock = startAction
} }
if next != nil, (next.url == (loadPlayer.currentVideo.resourcePlayerURL! as NSURL)) { //playerItem
player = next player.replaceCurrentItem(with: loadPlayer.currentVideo.resourcePlayerItem)
}else { //0
// player.seek(to: .zero)
player = .init(url: loadPlayer.currentVideo.resourcePlayerURL!) //
player.maxRetryCount = 2 let interval:CMTime = .init(seconds: 1, preferredTimescale: .init(1))
} //线
// player.addPeriodicTimeObserver(forInterval: interval, queue: .main, using: { [weak self] (time) in
let index = loadPlayer.listViewVideos.firstIndex(of: loadPlayer.currentVideo) ?? 0 guard let self = self else { return }
if (loadPlayer.listViewVideos.count-1) > index { //
stopAndReleaseStream(&next) let currentDuration = CMTimeGetSeconds(time)
//URL //
let nextURL = loadPlayer.listViewVideos[index + 1].resourcePlayerURL let maxDuration = getMusicDuration()
next = preloadNext(nextURL!) if maxDuration.isNaN == false {
} //
// if currentDuration <= maxDuration {
player.play() //
// if runActionBlock != nil {
player.onStateChange = { runActionBlock!(currentDuration, maxDuration)
[weak self] status in
guard let self = self, findTurePlayer(player) else {return}
switch status {
case .fsAudioStreamFailed://
print("\(loadPlayer.currentVideo?.title ?? "")加载失败")
playState = .Null
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
} }
} })
//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
private func findTurePlayer(_ stream:FSAudioStream) -> Bool { override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let currentVideoURL = loadPlayer?.currentVideo?.resourcePlayerURL as? NSURL else { guard let keyPath = keyPath else {
return false return
} }
let streamURL = stream.url //keyPath
if streamURL == currentVideoURL { switch keyPath {
return true case "status"://playerItem
}else { if let statuValue = change?[.newKey] as? Int, statuValue == AVPlayerItem.Status.readyToPlay.rawValue {
return false //
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 preloadNext(_ url:URL) -> FSAudioStream{ private func getMusicDuration() -> TimeInterval {
let stream = FSAudioStream(url: url) return CMTimeGetSeconds(player.currentItem?.duration ?? .zero)
stream?.maxRetryCount = 1
//
stream!.preload()
print("下一首已经在预加载")
return stream!
} }
//MARK: - //MARK: -
// //
@objc private func playerDidFinishPlaying() { @objc private func playerDidFinishPlaying(_ sender:Notification) {
// //
guard playState == .Playing else { guard playState == .Playing else {
return return
} }
switch playType { switch playType {
case .single: case .single:
var postion = FSStreamPosition()
postion.position = 0
// //
player.seek(to: postion) player.seek(to: CMTime.zero)
player.play() player.play()
default: default:
// //
@ -330,8 +285,7 @@ class MP_PlayerManager:NSObject{
resumeAction!() resumeAction!()
} }
// //
// player.play() player.play()
player.pause()
// //
playState = .Playing playState = .Playing
} }
@ -344,8 +298,7 @@ class MP_PlayerManager:NSObject{
return return
} }
// //
// player.play() player.play()
player.pause()
// //
playState = .Playing playState = .Playing
} }
@ -359,7 +312,7 @@ class MP_PlayerManager:NSObject{
print("Player is not started") print("Player is not started")
return return
} }
player.stop() player.pause()
playState = .Null playState = .Null
} }
//MARK: - / //MARK: - /
@ -372,7 +325,7 @@ class MP_PlayerManager:NSObject{
switch playType { switch playType {
case .random:// case .random://
for (index, item) in loadPlayer.randomVideos.enumerated() { for (index, item) in loadPlayer.randomVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo?.song.videoId { if item.videoId == loadPlayer.currentVideo.song.videoId {
// //
nextIndex = index - 1 nextIndex = index - 1
} }
@ -381,15 +334,15 @@ class MP_PlayerManager:NSObject{
if nextIndex < 0 { if nextIndex < 0 {
// //
let last = loadPlayer.randomVideos.last let last = loadPlayer.randomVideos.last
loadPlayer.improveData(last?.videoId ?? "", isRandom: true) loadPlayer.improveData(last?.videoId ?? "")
}else { }else {
// //
let song = loadPlayer.randomVideos[nextIndex] let song = loadPlayer.randomVideos[nextIndex]
loadPlayer.improveData(song.videoId ?? "", isRandom: true) loadPlayer.improveData(song.videoId ?? "")
} }
default:// default://
for (index, item) in loadPlayer.songVideos.enumerated() { for (index, item) in loadPlayer.songVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo?.song.videoId { if item.videoId == loadPlayer.currentVideo.song.videoId {
// //
nextIndex = index - 1 nextIndex = index - 1
} }
@ -414,7 +367,7 @@ class MP_PlayerManager:NSObject{
switch playType { switch playType {
case .random: case .random:
for (index, item) in loadPlayer.randomVideos.enumerated() { for (index, item) in loadPlayer.randomVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo?.song.videoId { if item.videoId == loadPlayer.currentVideo.song.videoId {
// //
nextIndex = index + 1 nextIndex = index + 1
} }
@ -423,15 +376,15 @@ class MP_PlayerManager:NSObject{
if nextIndex > (loadPlayer.randomVideos.count-1) { if nextIndex > (loadPlayer.randomVideos.count-1) {
// //
let first = loadPlayer.randomVideos.first let first = loadPlayer.randomVideos.first
loadPlayer.improveData(first?.videoId ?? "", isRandom: true) loadPlayer.improveData(first?.videoId ?? "")
}else { }else {
//,ID //,ID
let song = loadPlayer.randomVideos[nextIndex] let song = loadPlayer.randomVideos[nextIndex]
loadPlayer.improveData(song.videoId ?? "", isRandom: true) loadPlayer.improveData(song.videoId ?? "")
} }
default: default:
for (index, item) in loadPlayer.songVideos.enumerated() { for (index, item) in loadPlayer.songVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo?.song.videoId { if item.videoId == loadPlayer.currentVideo.song.videoId {
// //
nextIndex = index + 1 nextIndex = index + 1
} }
@ -452,21 +405,18 @@ class MP_PlayerManager:NSObject{
@objc private func userSwitchCurrentVideoAction(_ sender:Notification) { @objc private func userSwitchCurrentVideoAction(_ sender:Notification) {
// //
playState = .Null playState = .Null
// //
if player != nil { player.pause()
// //
stopAndReleaseStream(&player) 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 cacheValueBlock != nil { if loadPlayer.currentVideo != nil {
cacheValueBlock!(0) //
} play(startAction: startActionBlock)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
[weak self] in
guard let self = self else {return}
if loadPlayer.currentVideo != nil {
//
play(startAction: startActionBlock)
}
} }
} }
@ -482,85 +432,22 @@ class MP_PlayerManager:NSObject{
/// - Parameters: /// - Parameters:
/// - progress: 0-1 /// - progress: 0-1
func setEditProgressEnd(_ progress:Float, endAction:MP_PlayTimerEditEndAction? = nil) { func setEditProgressEnd(_ progress:Float, endAction:MP_PlayTimerEditEndAction? = nil) {
guard playState != .Null, let player = player, findTurePlayer(player) else { guard playState != .Null else {
return return
} }
guard progress >= 0, progress <= 1 else { guard progress >= 0, progress <= 1 else {
return return
} }
//
let timePoint:Double = Double(progress)*getMusicDuration()
// //
var time:FSStreamPosition = .init() let time:CMTime = .init(seconds: timePoint, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
time.position = progress //
// player.seek(to: time)
let currentTime = player.currentTimePlayed.playbackTimeInSeconds //
if progress != currentTime { resume()
// if endAction != nil {
player.seek(to: time) endAction!()
//
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)")
// }
//
//
//}

View File

@ -8,20 +8,16 @@
import UIKit import UIKit
import AVKit import AVKit
import AVFoundation import AVFoundation
import FreeStreamer
class MPPositive_SongViewModel: NSObject { class MPPositive_SongViewModel: NSObject {
/// ///
var index:Int! var index:Int!
///
// var resourcePlayerItem:AVPlayerItem!
// var resourcePlayerItem:MP_AVPlayerItem!
// var resourcePlayerItem:CachingPlayerItem!
/// ///
// var resourcePlayerItem:FSAudioStream? var resourcePlayerItem:AVPlayerItem!
///
var resourcePlayerAsset:AVURLAsset!
/// ///
var resourcePlayerURL:URL? var resourcePlayerURL:URL!
///
// var resourceAsset:MP_AVURLAsset!
/// ///
var coverUrl:URL? var coverUrl:URL?
/// ///
@ -41,10 +37,11 @@ class MPPositive_SongViewModel: NSObject {
init(_ song:MPPositive_SongItemModel) { init(_ song:MPPositive_SongItemModel) {
super.init() super.init()
self.song = song self.song = song
// resourcePlayerItem = nil
configure() configure()
} }
deinit { deinit {
resourcePlayerItem = nil
resourcePlayerAsset = nil
resourcePlayerURL = nil resourcePlayerURL = nil
} }
// //
@ -53,14 +50,12 @@ class MPPositive_SongViewModel: NSObject {
index = song.index index = song.index
if let first = song.resourceUrls?.first { if let first = song.resourceUrls?.first {
// resourcePlayerURL = .init(string:first)
if isDlownd == true { resourcePlayerAsset = .init(url: resourcePlayerURL)
resourcePlayerURL = .init(string:first) preloadAsset(resourcePlayerAsset)
}else { resourcePlayerItem = .init(asset: resourcePlayerAsset)
//使
resourcePlayerURL = .init(string: first)
}
} }
// //
if song.reviewUrls?.first != nil { if song.reviewUrls?.first != nil {
coverUrl = .init(string: song.reviewUrls!.last!) coverUrl = .init(string: song.reviewUrls!.last!)
@ -98,4 +93,53 @@ class MPPositive_SongViewModel: NSObject {
// //
isDlownd = MPPositive_DownloadItemModel.fetch(.init(format: "videoId == %@", song.videoId)).count != 0 isDlownd = MPPositive_DownloadItemModel.fetch(.init(format: "videoId == %@", song.videoId)).count != 0
} }
//
func preloadAsset(_ asset:AVURLAsset) {
//
if #available(iOS 16, *) {
//ios16
Task{
do{
let playable = try await asset.load(.isPlayable)
if playable == true {
print("\(self.title ?? "")预加载成功")
}else {
//
switch asset.status(of: .isPlayable) {
case .failed(let erro):
print("\(title ?? "")预加载失败,失败原因:\(erro.localizedDescription)")
default:
break
}
}
}catch{
print("预加载失败:\(error.localizedDescription)")
}
}
}else {
//ios16
let keys = ["playable"]
asset.loadValuesAsynchronously(forKeys: keys) {
[weak self] in
guard let self = self else {return}
for key in keys {
var error: NSError? = nil
let status = asset.statusOfValue(forKey: key, error: &error)
switch status {
case .loaded:
// key
DispatchQueue.main.async {
print("\(self.title ?? "")预加载成功")
}
case .failed:
print("\(title ?? "")预加载失败,失败原因:\(error?.localizedDescription ?? "")")
case .cancelled:
print("\(title ?? "")预加载被取消了")
default:
break
}
}
}
}
}
} }

View File

@ -133,11 +133,14 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
coverView.sliderView.value = Float(value) coverView.sliderView.value = Float(value)
} }
// //
MP_PlayerManager.shared.cacheValueBlock = { [weak self] progress in MP_PlayerManager.shared.cacheValueBlock = { [weak self] (value, duration) in
guard let self = self else { return } guard let self = self else { return }
if progress <= 1 { if value < duration {
coverView.progressView.setProgress(progress, animated: false) //
let float = value/duration
coverView.progressView.setProgress(Float(float), animated: false)
}else { }else {
//
coverView.progressView.setProgress(1, animated: false) coverView.progressView.setProgress(1, animated: false)
} }
} }