Swift AVPlayer:自定义播放速率下视频循环的正确实现方式
解决AVPlayer修改播放速率后循环卡顿的问题
看起来你遇到的这个问题挺典型的——默认速率下循环正常,但改了播放速率后几次循环就卡第一帧,而且结束通知不再触发。我帮你分析下原代码里可能的问题,再给你调整后的解决方案:
原代码的潜在问题
你的循环逻辑依赖AVPlayerItemDidPlayToEndTime通知,然后同步执行seek(to: .zero)、play()、player.rate = self.playerRate,这里有几个隐患:
- Seek是异步操作:你直接在seek后立刻调用play和设置rate,可能seek还没完成,播放状态就被打乱了,尤其是在非1.0速率下,这种时序问题更容易触发异常。
- 通知绑定对象的局限性:你注册通知时指定了
player?.currentItem作为object,如果后续currentItem有变更(哪怕你没主动改,某些场景下AVPlayer也可能内部调整),通知就收不到了。 - Rate设置时机不对:在play之后再设置rate,可能和AVPlayer内部的播放状态管理冲突,导致后续的结束通知无法正常触发。
优化后的循环实现
我调整了逻辑,主要解决异步seek的时序问题,同时优化通知的监听方式:
1. 调整通知注册方式
先把通知注册改成不绑定具体的currentItem,而是监听所有AVPlayerItem的结束通知,然后在回调里判断是不是当前player的item:
// 注册通知时不要指定object,监听所有AVPlayerItem的结束事件 NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: nil, queue: .main) { [weak self] notification in guard let self = self, let player = self.player, let endedItem = notification.object as? AVPlayerItem, endedItem == player.currentItem else { return } self.restartPlayerWithRate(player) }
2. 用异步Seek的CompletionHandler控制时序
把seek、play、rate设置放到seek的完成回调里,确保seek完成后再执行后续操作:
private func restartPlayerWithRate(_ player: AVPlayer) { // 异步seek到起始位置,确保seek完成后再操作 player.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] completed in guard let self = self, completed else { return } // 先设置rate,再调用play,避免状态冲突 player.rate = self.playerRate player.play() } }
3. 额外的状态保护(可选)
如果你的player可能频繁切换item或者速率,建议在设置rate前检查player的状态,确保它处于readyToPlay:
private func restartPlayerWithRate(_ player: AVPlayer) { guard player.currentItem?.status == .readyToPlay else { // 如果item还没准备好,可以延迟重试或者做其他处理 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.restartPlayerWithRate(player) } return } player.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] completed in guard let self = self, completed else { return } player.rate = self.playerRate player.play() } }
为什么这样能解决问题?
- 异步seek的回调确保了我们在真正回到起始帧后再开始播放,避免了非1.0速率下的时序混乱;
- 先设置rate再调用play,让AVPlayer一开始就以指定速率播放,避免了中途修改rate导致的状态异常;
- 不绑定具体currentItem的通知监听,确保只要是当前player的item播放结束,我们都能收到通知,不会因为item的微妙变化丢失回调。
你可以试试这个调整后的逻辑,应该能解决循环几次后卡顿的问题。
内容的提问来源于stack exchange,提问作者Niall Kiddle
相关产品推荐
相关产品推荐

