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

AVPlayer回到前台后缓冲区清空问题技术求助

AVPlayer后台返回后缓冲清空重新加载的问题

我基于AVPlayer开发了一款带有进度条缓冲检测功能的自定义视频播放器,遇到如下问题:暂停视频后将应用退至后台约5秒(主屏幕左右滑动切换),重新打开应用继续播放时,缓冲区会被清空并重新开始缓冲,同时isPlaybackBufferEmpty观察者会被触发。

以下是我的播放器代码:

@IBOutlet weak var progressBar: UIProgressView!
@IBOutlet var containerPlayer: PlayerView!
    
@objc var player = AVPlayer()
        
let toPass = "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8"
        
private var bufferObserver: NSKeyValueObservation?
private var playerItemBufferEmptyObserver: NSKeyValueObservation?
private var playerItemBufferKeepUpObserver: NSKeyValueObservation?
    
override func viewDidLoad() {
  super.viewDidLoad()
            
  containerPlayer.player = self.player
  containerPlayer.playerLayer.player = self.player
            
  startPlayer()
}
    
func startPlayer() {
   if let videoURL = URL(string: self.toPass) {
      let asset = AVURLAsset(url: videoURL)
                
      let item = AVPlayerItem(asset: asset)
                
      self.player.replaceCurrentItem(with: item)
      self.player.currentItem?.preferredPeakBitRate = 0
                
      self.player.currentItem?.preferredForwardBufferDuration = 10
    
      self.player.play()
                
      playerItemBufferEmptyObserver = item.observe(\.isPlaybackBufferEmpty, options: [.old, .new]) { [weak self] (playerItem, _) in
          if playerItem.isPlaybackBufferEmpty {
               print("playerItem.isPlaybackBufferEmpty")
          }
      }
    
      playerItemBufferKeepUpObserver = item.observe(\.isPlaybackLikelyToKeepUp, options: [.new]) { [weak self] (playerItem, _) in
          if playerItem.isPlaybackLikelyToKeepUp {
             print("playerItem.isPlaybackLikelyToKeepUp")
          }
       }
    
       bufferObserver = item.observe(\.loadedTimeRanges, options:  [.new], changeHandler: { (playerItem, change) in
            if let timeRange = playerItem.loadedTimeRanges.first?.timeRangeValue {
                        let startSeconds = timeRange.start.seconds
                        let durationSeconds = timeRange.duration.seconds
                        let bufferEnd = startSeconds + durationSeconds
    
                        let positionTimeElapsed: Float64 = Float64(bufferEnd) / playerItem.asset.duration.seconds
                        self.progressBar.progress = Float(positionTimeElapsed)
                    }
                })
            }
        }
    }

问题原因

iOS系统为了优化内存占用,当应用进入后台后会主动回收AVPlayerItem的缓冲资源。当应用回到前台继续播放时,之前的缓存已被系统清理,因此会触发isPlaybackBufferEmpty并重新开始缓冲。

解决方案

1. 监听前后台状态,管理播放器生命周期

注册前后台通知,在后台时暂停播放器并记录播放位置;回到前台时恢复播放并跳转至之前的位置,触发缓冲恢复:

private var currentPlaybackTime: Double = 0

override func viewDidLoad() {
    super.viewDidLoad()
    // 原有初始化代码
    registerBackgroundNotifications()
}

private func registerBackgroundNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}

@objc private func appDidEnterBackground() {
    player.pause()
    currentPlaybackTime = player.currentItem?.currentTime().seconds ?? 0
}

@objc private func appWillEnterForeground() {
    guard let item = player.currentItem else { return }
    player.seek(to: CMTimeMakeWithSeconds(currentPlaybackTime, preferredTimescale: 1)) { [weak self] _ in
        self?.player.play()
    }
}

2. 复用AVPlayerItem,避免重复创建

将AVPlayerItem设为类属性,仅在首次启动或切换视频时初始化,避免频繁创建导致缓冲丢失:

private var playerItem: AVPlayerItem?

func startPlayer() {
    guard playerItem == nil else {
        player.replaceCurrentItem(with: playerItem)
        return
    }
    if let videoURL = URL(string: self.toPass) {
        let asset = AVURLAsset(url: videoURL)
        playerItem = AVPlayerItem(asset: asset)
        player.replaceCurrentItem(with: playerItem)
        // 原有缓冲设置、观察者注册代码
    }
}

3. 调整缓冲策略(可选)

适当延长preferredForwardBufferDuration,同时可尝试关闭自动 stall 等待(需注意内存占用):

playerItem?.preferredForwardBufferDuration = 30
playerItem?.automaticallyWaitsToMinimizeStalling = false

4. 清理观察者,避免内存泄漏

在控制器销毁时移除所有KVO观察者和通知监听:

deinit {
    bufferObserver?.invalidate()
    playerItemBufferEmptyObserver?.invalidate()
    playerItemBufferKeepUpObserver?.invalidate()
    NotificationCenter.default.removeObserver(self)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 09:05:20