繁体   English   中英

从 AVPlayer 播放器定期时间观察器块更新 MPNowPlayingInfoCenter 中的播放时间的问题

[英]Issue with updating playback time in MPNowPlayingInfoCenter from AVPlayer player periodic time observer block

我在更新播放信息时遇到问题。 请看一下附件中的gif。 在记录播放结束时设置为不正确的值。

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 变量设置为不同的时间时,我会在后台更新播放时间。

由于它是我的应用程序在没有用户输入的情况下更新值,因此它从未起作用。 我非常确信我永远不会让它工作。

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

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

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM