播放器更改

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_PlayCacheValueAction = (Float) -> Void
typealias MP_PlayCacheValueAction = (_ currentValue:TimeInterval, _ duration:TimeInterval) -> 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!
private var player:AVPlayer = AVPlayer()
///load
var loadPlayer:MPPositive_PlayerLoadViewModel!{
didSet{
@ -76,7 +69,6 @@ class MP_PlayerManager:NSObject{
didSet{
//
NotificationCenter.notificationKey.post(notificationName: .switch_player_status, object: playState)
}
}
///
@ -104,73 +96,19 @@ class MP_PlayerManager:NSObject{
}
///
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.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 + 30
let rate = Float(byte)/duration
if self.cacheValueBlock != nil {
self.cacheValueBlock!(rate)
}
}
}
}
///
/// - Parameters:
/// - startAction:
@ -182,103 +120,120 @@ class MP_PlayerManager:NSObject{
print("Player No Data")
return
}
//
stopAndReleaseStream(&player)
//
switch playState {
case .Null://
break
case .Playing://
player.pause()
case .Pause://
break
}
//
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 = 2
}
//
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 ?? "")加载失败")
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!()
//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)
}
}
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)
}
///
private func findTurePlayer(_ stream:FSAudioStream) -> Bool {
guard let currentVideoURL = loadPlayer?.currentVideo?.resourcePlayerURL as? NSURL else {
return false
//KVO
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath else {
return
}
let streamURL = stream.url
if streamURL == currentVideoURL {
return true
}else {
return false
//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
}
}
///
private func preloadNext(_ url:URL) -> FSAudioStream{
let stream = FSAudioStream(url: url)
stream?.maxRetryCount = 1
//
stream!.preload()
print("下一首已经在预加载")
return stream!
//MARK: -
///
private func getMusicDuration() -> TimeInterval {
return CMTimeGetSeconds(player.currentItem?.duration ?? .zero)
}
//MARK: -
//
@objc private func playerDidFinishPlaying() {
@objc private func playerDidFinishPlaying(_ sender:Notification) {
//
guard playState == .Playing else {
return
}
switch playType {
case .single:
var postion = FSStreamPosition()
postion.position = 0
//
player.seek(to: postion)
player.seek(to: CMTime.zero)
player.play()
default:
//
@ -330,8 +285,7 @@ class MP_PlayerManager:NSObject{
resumeAction!()
}
//
// player.play()
player.pause()
player.play()
//
playState = .Playing
}
@ -344,8 +298,7 @@ class MP_PlayerManager:NSObject{
return
}
//
// player.play()
player.pause()
player.play()
//
playState = .Playing
}
@ -359,7 +312,7 @@ class MP_PlayerManager:NSObject{
print("Player is not started")
return
}
player.stop()
player.pause()
playState = .Null
}
//MARK: - /
@ -372,7 +325,7 @@ class MP_PlayerManager:NSObject{
switch playType {
case .random://
for (index, item) in loadPlayer.randomVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo?.song.videoId {
if item.videoId == loadPlayer.currentVideo.song.videoId {
//
nextIndex = index - 1
}
@ -381,15 +334,15 @@ class MP_PlayerManager:NSObject{
if nextIndex < 0 {
//
let last = loadPlayer.randomVideos.last
loadPlayer.improveData(last?.videoId ?? "", isRandom: true)
loadPlayer.improveData(last?.videoId ?? "")
}else {
//
let song = loadPlayer.randomVideos[nextIndex]
loadPlayer.improveData(song.videoId ?? "", isRandom: true)
loadPlayer.improveData(song.videoId ?? "")
}
default://
for (index, item) in loadPlayer.songVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo?.song.videoId {
if item.videoId == loadPlayer.currentVideo.song.videoId {
//
nextIndex = index - 1
}
@ -414,7 +367,7 @@ class MP_PlayerManager:NSObject{
switch playType {
case .random:
for (index, item) in loadPlayer.randomVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo?.song.videoId {
if item.videoId == loadPlayer.currentVideo.song.videoId {
//
nextIndex = index + 1
}
@ -423,15 +376,15 @@ class MP_PlayerManager:NSObject{
if nextIndex > (loadPlayer.randomVideos.count-1) {
//
let first = loadPlayer.randomVideos.first
loadPlayer.improveData(first?.videoId ?? "", isRandom: true)
loadPlayer.improveData(first?.videoId ?? "")
}else {
//,ID
let song = loadPlayer.randomVideos[nextIndex]
loadPlayer.improveData(song.videoId ?? "", isRandom: true)
loadPlayer.improveData(song.videoId ?? "")
}
default:
for (index, item) in loadPlayer.songVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo?.song.videoId {
if item.videoId == loadPlayer.currentVideo.song.videoId {
//
nextIndex = index + 1
}
@ -452,21 +405,18 @@ class MP_PlayerManager:NSObject{
@objc private func userSwitchCurrentVideoAction(_ sender:Notification) {
//
playState = .Null
//
if player != nil {
//
stopAndReleaseStream(&player)
//
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 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)
}
if loadPlayer.currentVideo != nil {
//
play(startAction: startActionBlock)
}
}
@ -482,85 +432,22 @@ class MP_PlayerManager:NSObject{
/// - Parameters:
/// - progress: 0-1
func setEditProgressEnd(_ progress:Float, endAction:MP_PlayTimerEditEndAction? = nil) {
guard playState != .Null, let player = player, findTurePlayer(player) else {
guard playState != .Null else {
return
}
guard progress >= 0, progress <= 1 else {
return
}
//
let timePoint:Double = Double(progress)*getMusicDuration()
//
var time:FSStreamPosition = .init()
time.position = progress
//
let currentTime = player.currentTimePlayed.playbackTimeInSeconds
if progress != currentTime {
//
player.seek(to: time)
//
resume()
if endAction != nil {
endAction!()
}
let time:CMTime = .init(seconds: timePoint, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
//
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)")
// }
//
//
//}

View File

@ -8,20 +8,16 @@
import UIKit
import AVKit
import AVFoundation
import FreeStreamer
class MPPositive_SongViewModel: NSObject {
///
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 resourceAsset:MP_AVURLAsset!
var resourcePlayerURL:URL!
///
var coverUrl:URL?
///
@ -41,10 +37,11 @@ class MPPositive_SongViewModel: NSObject {
init(_ song:MPPositive_SongItemModel) {
super.init()
self.song = song
// resourcePlayerItem = nil
configure()
}
deinit {
resourcePlayerItem = nil
resourcePlayerAsset = nil
resourcePlayerURL = nil
}
//
@ -53,14 +50,12 @@ class MPPositive_SongViewModel: NSObject {
index = song.index
if let first = song.resourceUrls?.first {
//
if isDlownd == true {
resourcePlayerURL = .init(string:first)
}else {
//使
resourcePlayerURL = .init(string: first)
}
resourcePlayerURL = .init(string:first)
resourcePlayerAsset = .init(url: resourcePlayerURL)
preloadAsset(resourcePlayerAsset)
resourcePlayerItem = .init(asset: resourcePlayerAsset)
}
//
if song.reviewUrls?.first != nil {
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
}
//
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)
}
//
MP_PlayerManager.shared.cacheValueBlock = { [weak self] progress in
MP_PlayerManager.shared.cacheValueBlock = { [weak self] (value, duration) in
guard let self = self else { return }
if progress <= 1 {
coverView.progressView.setProgress(progress, animated: false)
if value < duration {
//
let float = value/duration
coverView.progressView.setProgress(Float(float), animated: false)
}else {
//
coverView.progressView.setProgress(1, animated: false)
}
}