You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AVPlayer从4倍速切换至1/2倍速时currentTime停止更新求助

解决iOS 10.2下AVPlayer播放m3u8切换低倍速时currentTime停更的问题

哥们,我之前维护iOS10兼容的视频播放项目时,碰到过几乎一模一样的问题!AVPlayer在iOS10上处理HLS(m3u8)流的倍速切换时,确实存在个小bug:从4倍速这类高倍速切回1/2倍速时,播放器内部的时间同步回调会“卡壳”,导致currentTime看起来停住了,但实际播放还在继续,切回高倍速时又会突然跳到真实进度。

问题根源

iOS10的AVPlayer在处理HLS流的倍速切换时,高倍速模式下会跳过部分分片加载来提升播放效率,当切换到低倍速时,播放器的时间更新机制没有正确触发,导致对外暴露的currentTime没有同步到实际播放进度。

可行的解决办法

1. 强制触发时间更新+监听时间跳转通知

在切换到低倍速时,手动触发一次时间更新,同时监听AVPlayerItemTimeJumped通知来捕捉播放器内部的时间跳转,确保UI能同步真实进度:

func updatePlaybackRate(to rate: Float) {
    player.rate = rate
    
    // 仅针对iOS10及以下做兼容处理
    guard #available(iOS 11.0, false) else { return }
    
    if rate <= 2.0 {
        // 切换低倍速后,延迟0.1秒手动更新一次时间(给播放器缓冲时间)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
            guard let self = self, let currentItem = self.player.currentItem else { return }
            self.syncCurrentTimeUI(with: currentItem.currentTime())
            // 注册时间跳转通知,捕捉播放器内部的进度变化
            NotificationCenter.default.addObserver(
                self,
                selector: #selector(self.handleTimeJumped(_:)),
                name: .AVPlayerItemTimeJumped,
                object: currentItem
            )
        }
    } else {
        // 切回高倍速时移除通知,避免重复监听
        NotificationCenter.default.removeObserver(
            self,
            name: .AVPlayerItemTimeJumped,
            object: player.currentItem
        )
    }
}

@objc private func handleTimeJumped(_ notification: Notification) {
    guard let item = notification.object as? AVPlayerItem else { return }
    syncCurrentTimeUI(with: item.currentTime())
}

// 用于更新UI的辅助方法
private func syncCurrentTimeUI(with time: CMTime) {
    // 这里替换成你自己的UI更新逻辑,比如格式化时间显示
    let seconds = CMTimeGetSeconds(time)
    currentTimeLabel.text = String(format: "%.1f", seconds)
}

2. 重置AVPlayerItem(极端情况备用)

如果上面的方法还是没解决问题,可以尝试在切换低倍速时,保存当前真实进度,然后重新加载AVPlayerItem并seek到对应位置:

var savedCurrentTime: CMTime = .zero

func resetPlayerItemForLowRate(_ rate: Float) {
    guard let currentItem = player.currentItem, rate <= 2.0 else { return }
    
    // 获取当前实际播放时间(即使UI显示不对,内部可能仍有正确值)
    savedCurrentTime = currentItem.currentTime()
    // 保存原播放地址
    guard let asset = currentItem.asset as? AVURLAsset, let url = asset.url else { return }
    
    // 创建新的PlayerItem
    let newItem = AVPlayerItem(url: url)
    // 监听加载完成,seek到原进度并设置倍速
    newItem.addObserver(self, forKeyPath: "status", options: [.new], context: nil)
    
    player.replaceCurrentItem(with: newItem)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard let item = object as? AVPlayerItem, keyPath == "status", item.status == .readyToPlay else { return }
    
    // seek到之前保存的进度
    player.seek(to: savedCurrentTime) { [weak self] _ in
        guard let self = self else { return }
        self.player.rate = 1.0 // 或目标低倍速
        self.syncCurrentTimeUI(with: savedCurrentTime)
    }
    // 移除观察者
    item.removeObserver(self, forKeyPath: "status")
}

这个方法会有短暂的加载过程,但能彻底解决时间不同步的问题,适合极端场景。

3. 唤醒播放状态

有时候切换低倍速后,播放器的时间更新线程会“休眠”,可以尝试在设置倍速后,重新调用一次player.play()(即使已经在播放):

func setPlaybackRate(_ rate: Float) {
    player.rate = rate
    if #available(iOS 11.0, false), rate <= 2.0 {
        // 强制唤醒播放状态
        DispatchQueue.main.async {
            self.player.play()
        }
    }
}

额外说明

这个bug在iOS11及以上的系统中已经被苹果修复了,如果你的项目可以逐步放弃iOS10的兼容,升级系统版本是最彻底的解决方案。

内容的提问来源于stack exchange,提问作者Utashev Ruslan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:25:55