349 lines
11 KiB
Swift
349 lines
11 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 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()
|
||
}
|
||
}
|