[英]Issue with updating playback time in MPNowPlayingInfoCenter from AVPlayer player periodic time observer block
我在更新播放信息时遇到问题。 请看一下附件中的gif。 在记录播放结束时设置为不正确的值。
我通过来自计时器块的键MPNowPlayingInfoPropertyElapsedPlaybackTime
更新该值。 我检查该值是否有效,将其转换为秒并设置该值。 MPMediaItemPropertyPlaybackDuration
值从初始值设定MPMediaItemPropertyPlaybackDuration
中设置。
我能找到的最接近的解决方案是在playerDidFinishedPlaying
函数中再次设置播放时间。 在这种情况下,进度滑块会走到最后,但我仍然可以看到它向后跳了一会儿。
这是播放器的一个实现:
import AVFoundation
import MediaPlayer
class Player {
var onPlay: (() -> Void)?
var onPause: (() -> Void)?
var onStop: (() -> Void)?
var onProgressUpdate: ((Float) -> Void)?
var onTimeUpdate: ((TimeInterval) -> Void)?
var onStartLoading: (() -> Void)?
var onFinishLoading: (() -> Void)?
private var player: AVPlayer?
private var timeObserverToken: Any?
private var durationSeconds: Float64 = 0
private static let preferredTimescale = CMTimeScale(NSEC_PER_SEC)
private static let seekTolerance = CMTimeMakeWithSeconds(1, preferredTimescale: preferredTimescale)
private var nowPlayingInfo = [String : Any]() {
didSet {
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
}
init(url: URL, name: String) {
self.nowPlayingInfo[MPMediaItemPropertyTitle] = name
let asset = AVURLAsset(url: url)
onStartLoading?()
asset.loadValuesAsynchronously(forKeys: ["playable"]) { [weak self] in
guard let self = self else { return }
let durationSeconds = CMTimeGetSeconds(asset.duration)
self.durationSeconds = durationSeconds
self.nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = Int(durationSeconds)
let playerItem = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: playerItem)
player.actionAtItemEnd = .pause
self.player = player
self.configureStopObserver()
self.setupRemoteTransportControls()
self.onTimeUpdate?(0)
self.onFinishLoading?()
}
}
func seek(progress: Float) {
guard let player = player else { return }
let targetTimeValue = durationSeconds * Float64(progress)
let targetTime = CMTimeMakeWithSeconds(targetTimeValue, preferredTimescale: Self.preferredTimescale)
let tolerance = CMTimeMakeWithSeconds(1, preferredTimescale: Self.preferredTimescale)
player.seek(to: targetTime, toleranceBefore: tolerance, toleranceAfter: tolerance)
}
func playPause() {
guard let player = player else { return }
if player.isPlaying {
player.pause()
onPause?()
} else {
let currentSeconds = CMTimeGetSeconds(player.currentTime())
if durationSeconds - currentSeconds < 1 {
let targetTime = CMTimeMakeWithSeconds(0, preferredTimescale: Self.preferredTimescale)
player.seek(to: targetTime)
}
player.play()
onPlay?()
}
}
private func configureStopObserver() {
guard let player = player else { return }
NotificationCenter.default.addObserver(self,
selector: #selector(playerDidFinishedPlaying),
name: .AVPlayerItemDidPlayToEndTime,
object: player.currentItem)
}
@objc private func playerDidFinishedPlaying() {
guard let player = player else { return }
let currentSeconds = CMTimeGetSeconds(player.currentTime())
self.onTimeUpdate?(TimeInterval(currentSeconds))
// self.nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = Int(currentSeconds)
// self.nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
onStop?()
}
func handleAppearing() {
subscribeToTimeObserver()
configureAndActivateAudioSession()
}
func handleDisappearing() {
unsubscribeFromTimeObserver()
deactivateAudioSession()
}
private func configureAndActivateAudioSession() {
let audioSession = AVAudioSession.sharedInstance()
try? audioSession.setCategory(.playback, mode: .default, options: [])
try? audioSession.setActive(true, options: [])
}
private func deactivateAudioSession() {
guard let player = player else { return }
player.pause()
try? AVAudioSession.sharedInstance().setActive(false, options: [])
}
private func subscribeToTimeObserver() {
guard let player = player else { return }
let preferredTimescale = CMTimeScale(NSEC_PER_SEC)
let interval = CMTimeMakeWithSeconds(0.1, preferredTimescale: preferredTimescale)
timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: nil, using: { [weak self] time in
guard let self = self else { return }
let timeIsValid = time.flags.rawValue & CMTimeFlags.valid.rawValue == 1
let timeHasBeenRounded = time.flags.rawValue & CMTimeFlags.hasBeenRounded.rawValue == 1
if !timeIsValid && !timeHasBeenRounded {
return
}
let currentSeconds = CMTimeGetSeconds(time)
self.onTimeUpdate?(TimeInterval(currentSeconds))
self.nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = Int(currentSeconds)
self.nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
let progress = Float(currentSeconds / self.durationSeconds)
self.onProgressUpdate?(progress)
})
}
private func unsubscribeFromTimeObserver() {
if let token = timeObserverToken, let player = player {
player.removeTimeObserver(token)
}
}
private func setupRemoteTransportControls() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in
guard
let self = self,
let player = self.player
else { return .commandFailed }
if let event = event as? MPChangePlaybackPositionCommandEvent {
let targetTime = CMTimeMakeWithSeconds(event.positionTime, preferredTimescale: Self.preferredTimescale)
player.seek(to: targetTime, toleranceBefore: Self.seekTolerance, toleranceAfter: Self.seekTolerance)
return .success
}
return .commandFailed
}
commandCenter.playCommand.addTarget { [weak self] event in
guard
let self = self,
let player = self.player
else { return .commandFailed }
if !player.isPlaying {
player.play()
self.onPlay?()
return .success
}
return .commandFailed
}
commandCenter.pauseCommand.addTarget { [weak self] event in
guard
let self = self,
let player = self.player
else { return .commandFailed }
if player.isPlaying {
player.pause()
self.onPause?()
return .success
}
return .commandFailed
}
}
}
private extension AVPlayer {
var isPlaying: Bool {
return rate != 0 && error == nil
}
}
您不需要一遍又一遍地设置经过的时间。
只需在开始播放新资产或搜索后更新 elapsedTime。
如果您正确设置了播放速率(默认值应为 1.0),则 nowPlayingInfoCenter 会自行更新时间。
我有完全一样的问题。 为了缓解它,我会在玩家的timeControlStatus
属性变为paused
时更新正在播放的信息(播放速率/持续时间/位置)。 像这样的东西:
observations.append(player.observe(\.timeControlStatus, options: [.old, .new]) { [weak self] (player, change) in
switch player.timeControlStatus {
case .paused:
self?.updateNowPlayingInfo()
case .playing:
self?.updateNowPlayingInfo()
case .waitingToPlayAtSpecifiedRate:
break
default:
DDLogWarn("Player: unknown timeControlStatus value")
}
})
这样我就不会像你所说的那样令人不快地跳回去。
我有同样的问题...
对我来说,当播放器的 currentTime 变量设置为不同的时间时,我会在后台更新播放时间。
由于它是我的应用程序在没有用户输入的情况下更新值,因此它从未起作用。 我非常确信我永远不会让它工作。
最后 - 对我有用的东西。 首先是几个规则。
例如 - 这是我发给你们的一些代码,供您思考。
class InfoCenter {
private static var info = [String : Any]()
class func initNowPlayingInfo(_ param: Any) {
// setup your info dictionary here
DispatchQueue.main.async(execute: {
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
})
}
class func syncPlaybackRate(setInfo set: Bool) {
DispatchQueue.main.async(execute: {
if UIApplication.shared.applicationState != .inactive {
if let player = player {
info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentPlaybackRate
if set {
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}
}
}
})
}
class func syncPlaybackTime(setInfo set: Bool) {
if UIApplication.shared.applicationState != .inactive {
if let player = player {
DispatchQueue.main.async(execute: {
if let song = song {
info[MPNowPlayingInfoPropertyPlaybackProgress] = player.currentPlaybackTime / song.getEndTime()
}
info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentPlaybackTime
if set {
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}
})
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.