Veloria/Veloria/Libs/Player/VPPlayer.swift
2025-07-02 13:50:41 +08:00

349 lines
11 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.

//
// 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 systemPause = 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()
}
}
}
var coverImageView: UIImageView? {
return self.player.view.coverImageView
}
private lazy var playerController: ZFPlayerController = {
let playerController = ZFPlayerController()
playerController.currentPlayerManager = player
return playerController
}()
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.systemPause = false
self.isPlaying = true
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.systemPause {
self.start()
}
switch playState {
case .playStateUnknown:
vpLog(message: "播放状态====playStateUnknown")
self.playState = .unknown
case .playStatePlaying:
vpLog(message: "播放状态====playStatePlaying")
self.playState = .playing
case .playStatePaused:
vpLog(message: "播放状态====playStatePaused")
self.playState = .paused
case .playStatePlayStopped:
vpLog(message: "播放状态====playStatePlayStopped")
self.playState = .stopped
case .playStatePlayFailed:
vpLog(message: "播放状态====playStatePlayFailed")
self.playState = .failed
default:
self.playState = .unknown
}
self.delegate?.vp_player?(self, playStateDidChanged: self.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.systemPause {
self.start()
}
switch loadState {
case .prepare:
vpLog(message: "加载状态====prepare\(loadState)")
self.loadState = .prepare
case .playable:
vpLog(message: "加载状态====playable\(loadState)")
self.loadState = .playable
case .playthroughOK:
vpLog(message: "加载状态====playthroughOK\(loadState)")
self.loadState = .playthroughOK
case .stalled:
vpLog(message: "加载状态====stalled\(loadState)")
self.loadState = .stalled
default:
vpLog(message: "加载状态====unknown\(loadState)")
self.loadState = .unknown
break
}
self.delegate?.vp_player?(self, loadStateDidChange: self.loadState)
}
//
player.playerPlayFailed = { (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.isPlaying {
self.systemPause = true
self.player.pause()
} else if self.interruptionType == .ended {
self.systemPause = false
if self.isPlaying {
self.player.play()
}
}
vpLog(message: "======音频发生变化\(interruptionType)")
}
@objc private func routeChangeNotification(sender: Notification) {
// self.pause()
}
}