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

问题描述 投票:0回答:1

我在更新播放信息时遇到问题。请看附件的gif。在记录的末尾,回放设置为不正确的值。

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

swift avplayer mpnowplayinginfocenter cmtime
1个回答
0
投票

您无需一遍又一遍地设置经过时间。当您开始播放新资产或寻找之后,只需更新elapsedTime。如果正确设置了playbackRate(默认值应为1.0),则nowPlayingInfoCenter会更新时间本身。

© www.soinside.com 2019 - 2024. All rights reserved.