【问题标题】:Issue with updating playback time in MPNowPlayingInfoCenter from AVPlayer player periodic time observer block从 AVPlayer 播放器周期性时间观察器块更新 MPNowPlayingInfoCenter 中的播放时间的问题
【发布时间】:2020-02-12 16:27:48
【问题描述】:

我在更新播放信息时遇到问题。请看一下所附的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
    }
}

【问题讨论】:

  • 我也有同样的问题。看起来像一个 iOS 错误(

标签: swift avplayer mpnowplayinginfocenter cmtime


【解决方案1】:

您无需一遍又一遍地设置经过的时间。
只需在您开始播放新资产或搜索后更新 elapsedTime。
如果您正确设置了playbackRate(默认应为1.0),nowPlayingInfoCenter 会自行更新时间。

【讨论】:

  • @AlexEdunov 这对你有帮助吗?
【解决方案2】:

我也有同样的问题。为了缓解这种情况,当玩家的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")
        }

    })

这样我就没有你所说的那种不愉快的跳回了。

【讨论】:

    【解决方案3】:

    我遇到了同样的问题...

    对我来说,当播放器的 currentTime 变量设置为不同的时间时,我会在后台更新播放时间。

    因为我的应用程序在没有用户输入的情况下更新了值,所以它从来没有工作过。我非常确信我永远不会让它工作。

    终于 - 对我有用的东西。首先是几个规则。

    1. 始终保留您自己的整个信息词典副本并进行更新。
    2. 同时设置整个信息字典(IE 不要直接在 MPNowPlayingInfoCenter.default().nowPlayingInfo 上设置单独的键。它不会像你想要的那样工作。)更新整个事情。
    3. 请勿连续或快速连续多次更新字典。因此,如果您有方法来更新经常一起调用的速率和播放时间,请确保您只更新本地字典中的两个键并将 nowPlayingInfo 设置一次。

    例如 - 这是我给你们的一些代码供大家思考。

    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
                    }
                })
            }
        }
    }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-17
      • 2021-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多