我在更新播放信息时遇到问题。请看附件的gif。在记录的末尾,回放设置为不正确的值。
我通过定时器块中的键MPNowPlayingInfoPropertyElapsedPlaybackTime
更新值。我检查该值是否有效,将其转换为秒并设置该值。 MPMediaItemPropertyPlaybackDuration
的值是通过初始化程序设置的。
我能找到的最接近的解决方案是在playerDidFinishedPlaying
func中再次设置播放时间。在这种情况下,进度滑块将移至末尾,但我仍然可以看到它跳了一会儿。
这里是播放器的实现:
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。如果正确设置了playbackRate(默认值应为1.0),则nowPlayingInfoCenter会更新时间本身。