簡體   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