开发者社区 问答 正文

为什么AVPlayer周期性观察器显示设备上开始时间之前的当前时间?

带着AVPlayer,在寻求特定的时间(例如0.5s)并调用play()之后,定期观察器对玩家的回调很少currentTime() 以前寻找位置。下面是代码:

试验波档案

import AVFoundation

class Player {

    init() {
        let session = AVAudioSession.sharedInstance()
        do {
            try session.setCategory(.playAndRecord, mode: .videoRecording)
            try session.setActive(true)
            session.requestRecordPermission { allowed in
                if !allowed {
                    fatalError("you must allow mic access")
                }
            }
        } catch {
            fatalError(error.localizedDescription)
        }

        addPeriodicTimeObserver(to: player)

        if let url = Bundle.main.url(forResource: "test_loop", withExtension: "wav") {
            let item = AVPlayerItem(asset: AVAsset(url: url))
            player.replaceCurrentItem(with: item)
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
            self?.seek() {
                DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                    self?.play()
                }
            }
        }
    }

    func addPeriodicTimeObserver(to player: AVPlayer) {
        let time = CMTime(seconds: 0.04, preferredTimescale: timeScale)

        _ = player.addPeriodicTimeObserver(forInterval: time, queue: .main) { [unowned self] _ in
            if
                self.playCalled == true &&
                self.player.currentItem?.status == .readyToPlay &&
                self.player.status == .readyToPlay &&
                self.player.timeControlStatus == .playing {
                print("currentTime \(self.player.currentTime().seconds)")
            }
        }
    }

    func seek(completion: @escaping () -> Void) {
        let time = CMTime(seconds: 0.5, preferredTimescale: timeScale)
        player.seek(to: time, toleranceBefore: .zero, toleranceAfter: .zero) { [unowned self] in
            if $0 {
                print("seek current time: \(self.player.currentTime().seconds)")
                completion()
            }
        }
    }

    func play() {
        if let currentItem = player.currentItem, currentItem.status == .readyToPlay {
            player.play()
            playCalled = true
        }
    }

    let player = AVPlayer()
    let timeScale = CMTimeScale(NSEC_PER_SEC)

    var playCalled: Bool = false

}

它产生:

seek current time: 0.5
currentTime 0.475462793
currentTime 0.475983585
currentTime 0.48074996
currentTime 0.521489168
currentTime 0.561431835
currentTime 0.601529793
currentTime 0.641514043
[...]

重要注记只有在设备上运行时才会发生这种情况。模拟器运行如出一辙。

正如您所看到的,我试图保护自己,因为我无法在任何当前时间打印任何可能相关的时间,但我仍然得到了令人困惑的结果。关于这个问题可能是什么以及如何解决它,有什么想法吗?我很想看到现在的时间盯着我寻找的那一刻:)

展开
收起
游客5akardh5cojhg 2019-12-11 18:44:08 551 分享 版权
阿里云 AI 助理回答

从您提供的代码片段来看,您正在尝试使用AVFoundation框架在iOS中实现音频播放,并且希望在特定时间点(如0.5秒)开始播放音频,同时通过一个定期观察器来跟踪播放的当前时间。但是,代码中存在一些不完整和可能引起混淆的部分,我将尽力根据您的描述提供帮助和建议。

首先,确保AVPlayer实例(在这里没有直接展示其声明)已经被正确定义为类的一个属性,例如:

var player = AVPlayer()

接下来,关于您的问题,这里有几个关键点需要注意和调整:

  1. 初始化与权限请求:您正确地设置了AVAudioSession并请求了录音权限,但请注意,如果您的应用仅播放音频而不需要录音,设置.playAndRecord类别和请求录音权限是不必要的。可以改为:

    try session.setCategory(.ambient, mode: .default)
    
  2. 添加周期性时间观察器:您提到的时间回调不频繁,这可能是因为观察间隔设置得过短或者处理逻辑存在问题。确保addPeriodicTimeObserver方法被正确调用,并检查回调逻辑是否符合预期。此外,确保timeScale有正确的值,通常情况下,对于秒级别的更新,使用CMTimeScale(NSEC_PER_SEC)是一个好选择。

  3. seek() 和 play() 的调用:您提到了seek()方法,但在提供的代码中并未显示其实现。确保您在调用play()之前已经正确设置了播放头位置。示例中的异步调用看起来是为了模拟延迟后操作,这是合理的,但请确保seek()方法内部确实改变了播放位置,并且在回调中确认操作成功。

  4. playCalled 标记:您代码中提到的playCalled变量用于标记play()是否被调用,以决定是否在时间观察器的回调里执行某些操作。请确保这个变量被正确定义并在play()调用后设置为true

基于以上分析,以下是修改后的代码段示例,注意我添加了一些假设性的修正和注释来帮助理解:

import AVFoundation

class Player {
    var player = AVPlayer()
    var playCalled = false // 添加playCalled标志
    let timeScale = CMTimeScale(NSEC_PER_SEC) // 定义时间尺度

    init() {
        let session = AVAudioSession.sharedInstance()
        do {
            try session.setCategory(.ambient, mode: .default) // 更改类别为适合播放的
            try session.setActive(true)
        } catch {
            print("Error setting up audio session: \(error.localizedDescription)")
        }

        if let url = Bundle.main.url(forResource: "test_loop", withExtension: "wav") {
            let item = AVPlayerItem(asset: AVAsset(url: url))
            player.replaceCurrentItem(with: item)
            addPeriodicTimeObserver(to: player)
        }
        
        schedulePlayAfterDelay()
    }

    func addPeriodicTimeObserver(to player: AVPlayer) {
        let time = CMTime(seconds: 0.5, preferredTimescale: timeScale) // 假设每半秒检查一次
        _ = player.addPeriodicTimeObserver(forInterval: time, queue: .main) { [weak self] currentTime in
            guard let self = self else { return }
            if self.playCalled, currentTime >= currentTime { // 确保play被调用且到达指定时间
                print("Reached the target time after play was called.")
            }
        }
    }

    func seek(to time: TimeInterval, completion: @escaping () -> Void) {
        let targetTime = CMTimeMake(value: Int64(time * Double(timeScale)), timescale: timeScale)
        player.seek(to: targetTime, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] success in
            guard let self = self, success else { return }
            self.playCalled = true // 寻找成功后设置标志
            completion()
        }
    }

    func play() {
        player.play()
    }

    private func schedulePlayAfterDelay() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            self.seek(to: 0.5, completion: {
                DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                    self.play()
                }
            })
        }
    }
}

这段代码做了以下调整: - 将音频会话类别更改为更适合播放的类型。 - 在初始化时直接检查和加载音频资源。 - 调整了周期性时间观察器的间隔,并在回调中检查playCalled状态以及是否达到目标播放时间。 - 明确实现了seek(to:completion:)方法,并在成功后设置playCalled。 - 使用私有方法schedulePlayAfterDelay()来安排延迟后的播放和寻址操作。

请根据您的具体需求进一步调整和完善代码。

有帮助
无帮助
AI 助理回答生成答案可能存在不准确,仅供参考
0 条回答
写回答
取消 提交回答
问答地址: