Veloria/Veloria/Libs/Player/VPPlayer.swift
2025-05-22 15:06:33 +08:00

327 lines
9.6 KiB
Swift

//
// VPPlayer.swift
// Veloria
//
// Created by Veloria on 2025/5/22.
//
import UIKit
import ZFPlayer
@objc protocol VPPlayerDelegate {
///
// @objc optional func vp_onDurationUpdate(_ player: SPPlayer, duration: Int)
//
// ///
// @objc optional func vp_onCurrentPositionUpdate(_ player: SPPlayer, position: Int)
///
@objc optional func vp_player(_ player: VPPlayer, playStateDidChanged state: VPPlayer.PlayState)
///
@objc optional func vp_player(_ player: VPPlayer, loadStateDidChange state: VPPlayer.LoadState)
///
@objc optional func vp_playTimeChanged(_ player: VPPlayer, currentTime: Int, duration: Int)
///
@objc optional func vp_firstRenderedStart(_ player: VPPlayer)
///
@objc optional func vp_playerReadyToPlay(_ player: VPPlayer)
///
@objc optional func vp_playCompletion(_ player: VPPlayer)
///
@objc optional func vp_playLoadingEnd(_ player: VPPlayer)
}
class VPPlayer: NSObject {
@objc enum PlayState: Int {
case unknown
case playing
case paused
case failed
case stopped
}
@objc enum LoadState: Int {
case unknown
case prepare
case playable
case playthroughOK
case stalled
}
weak var delegate: VPPlayerDelegate?
private(set) lazy var isPlaying = false
private(set) lazy var playState: PlayState = .unknown
private(set) lazy var loadState: LoadState = .unknown
/**
*/
private var isAddIdleTimerDisabledObserver = false
///
private var interruptionType: AVAudioSession.InterruptionType?
///
var duration: Int {
return Int(self.player.totalTime)
}
///
var currentPosition: Int {
return Int(self.player.currentTime)
}
///0.5 - 2
var rate: Float {
set {
player.rate = newValue
}
get {
return player.rate
}
}
var playerView: UIView? {
didSet {
playerView?.addSubview(player.view)
player.view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
private lazy var player: ZFAVPlayerManager = {
let player = ZFAVPlayerManager()
player.shouldAutoPlay = false
return player
}()
var isLoop = true
deinit {
NotificationCenter.default.removeObserver(self)
self.stop()
}
override init() {
super.init()
//()
NotificationCenter.default.addObserver(self, selector: #selector(interruptionNotification(sender:)), name: AVAudioSession.interruptionNotification, object: nil)
//
NotificationCenter.default.addObserver(self, selector: #selector(routeChangeNotification(sender:)), name: AVAudioSession.routeChangeNotification, object: nil)
player.scalingMode = .aspectFill
vp_addAction()
}
/**
*/
private func addIdleTimerDisabledObserver() {
if !isAddIdleTimerDisabledObserver {
isAddIdleTimerDisabledObserver = true
UIApplication.shared.addObserver(self, forKeyPath: "idleTimerDisabled", options: NSKeyValueObservingOptions.new, context: nil)
}
}
/**
*/
private func removeIdleTimerDisabledObserver() {
if isAddIdleTimerDisabledObserver {
isAddIdleTimerDisabledObserver = false
UIApplication.shared.removeObserver(self, forKeyPath: "idleTimerDisabled")
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if !UIApplication.shared.isIdleTimerDisabled {
UIApplication.shared.isIdleTimerDisabled = true
}
}
func setPlayUrl(url: String) {
let proxyURL = KTVHTTPCache.proxyURL(withOriginalURL: URL(string: url))
if proxyURL != self.player.assetURL {
self.player.assetURL = proxyURL
}
// self.prepare()
}
///
func prepare() {
self.player.prepareToPlay()
}
func stop() {
self.isPlaying = false
player.stop()
self.removeIdleTimerDisabledObserver()
UIApplication.shared.isIdleTimerDisabled = false
}
func start() {
self.isPlaying = true
if self.interruptionType != .began {
player.play()
}
UIApplication.shared.isIdleTimerDisabled = true
self.addIdleTimerDisabledObserver()
}
///
func pause() {
self.isPlaying = false
player.pause()
self.removeIdleTimerDisabledObserver()
UIApplication.shared.isIdleTimerDisabled = false
}
///
func replay() {
self.isPlaying = true
self.player.replay()
UIApplication.shared.isIdleTimerDisabled = true
self.addIdleTimerDisabledObserver()
}
func seekToTime(toTime: Int) {
var time = toTime
if time < 0 {
time = 0
}
if time > self.duration {
time = self.duration
}
self.player.seek(toTime: TimeInterval(time), completionHandler: nil)
}
}
extension VPPlayer {
private func vp_addAction() {
//
player.playerPlayTimeChanged = { [weak self] (asset, currentTime, duration) in
guard let self = self else { return }
self.delegate?.vp_playTimeChanged?(self, currentTime: Int(currentTime), duration: Int(duration))
}
//
player.playerPlayStateChanged = { [weak self] (asset, playState) in
guard let self = self else { return }
if playState == .playStatePlaying && !isPlaying {
self.pause()
} else if playState == .playStatePaused, isPlaying, self.interruptionType != .began {
self.start()
}
switch playState {
case .playStateUnknown:
self.playState = .unknown
case .playStatePlaying:
self.playState = .playing
case .playStatePaused:
self.playState = .paused
case .playStatePlayStopped:
self.playState = .stopped
case .playStatePlayFailed:
self.playState = .failed
default:
self.playState = .unknown
}
self.delegate?.vp_player?(self, playStateDidChanged: self.playState)
vpLog(message: "播放状态====\(playState)")
}
//
player.playerLoadStateChanged = { [weak self] (asset, loadState) in
guard let self = self else { return }
if loadState == .playable && !isPlaying {
self.pause()
} else if loadState == .playable, isPlaying, self.player.playState != .playStatePlaying, self.interruptionType != .began {
self.start()
}
switch loadState {
case .prepare:
self.loadState = .prepare
case .playable:
self.loadState = .playable
case .playthroughOK:
self.loadState = .playthroughOK
case .stalled:
self.loadState = .stalled
default:
self.loadState = .unknown
break
}
self.delegate?.vp_player?(self, loadStateDidChange: self.loadState)
}
//
player.playerPlayFailed = { [weak self] (asset, error) in
vpLog(message: "错误信息====\(error)")
}
//
player.playerDidToEnd = { [weak self] (asset) in
guard let self = self else { return }
if isLoop {
self.replay()
} else {
self.isPlaying = false
self.prepare()
self.delegate?.vp_playCompletion?(self)
}
}
player.playerReadyToPlay = { [weak self] (asset, assetURL) in
guard let self = self else { return }
do {
try AVAudioSession.sharedInstance().setCategory(.playback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
}
self.delegate?.vp_playerReadyToPlay?(self)
}
}
}
//MARK: -------------- --------------
extension VPPlayer {
@objc private func interruptionNotification(sender: Notification) {
guard let userInfo = sender.userInfo else { return }
guard let interruptionType = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt else { return }
self.interruptionType = AVAudioSession.InterruptionType(rawValue: interruptionType)
if self.interruptionType == .began {
self.player.pause()
} else {
if self.isPlaying {
self.player.play()
}
}
}
@objc private func routeChangeNotification(sender: Notification) {
// self.pause()
}
}