iOS Swift中AVPlayer流式音频缓冲过长,playImmediatelyAtRate失效求助
我之前做长音频流式播放时也踩过这个一模一样的坑!AVPlayer默认的缓冲逻辑在遇到不支持分段请求的服务器或者大文件时,确实会出现playbackBufferEmpty一直为true、必须等全量下载完才能播放的情况,分享几个亲测有效的解决方案:
1. 先确认服务器支持Byte-Range请求
这是最容易被忽略的前提!如果你的音频服务器没有开启范围请求(Byte-Range),AVPlayer就没办法分段下载音频,只能等整个文件下载完成才能开始播放。
你可以用终端命令测试服务器是否支持:
curl -I https://your-audio-url.mp3
查看响应头里是否有Accept-Ranges: bytes这一项,如果没有,需要后端配置服务器开启Range请求(比如Nginx、Apache都有对应的简单配置项)。
2. 用AVAssetResourceLoader实现自定义加载逻辑
如果服务器已经支持Range,但AVPlayer的默认缓冲还是不够灵活,可以用AVAssetResourceLoader完全掌控音频数据的下载和供给,实现真正的边下边播,甚至还能加本地缓存逻辑。
示例代码大概是这样:
class AudioResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate { private var urlSession: URLSession? private var pendingRequests = [AVAssetResourceLoadingRequest]() override init() { super.init() urlSession = URLSession(configuration: .default, delegate: nil, delegateQueue: DispatchQueue.global()) } func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool { guard let url = loadingRequest.request.url else { return false } // 处理AVPlayer发起的Range请求 var request = URLRequest(url: url) if let rangeHeader = loadingRequest.request.value(forHTTPHeaderField: "Range") { request.setValue(rangeHeader, forHTTPHeaderField: "Range") } let dataTask = urlSession?.dataTask(with: request) { [weak self] data, response, error in guard let self = self else { return } defer { self.pendingRequests.removeAll { $0 == loadingRequest } } guard let httpResponse = response as? HTTPURLResponse, let data = data else { loadingRequest.finishLoading(with: error) return } // 告诉AVPlayer音频的基本信息 loadingRequest.contentInformationRequest?.contentType = httpResponse.mimeType loadingRequest.contentInformationRequest?.contentLength = httpResponse.expectedContentLength loadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true // 把下载的数据传给AVPlayer loadingRequest.dataRequest?.respond(with: data) loadingRequest.finishLoading() } dataTask?.resume() pendingRequests.append(loadingRequest) return true } } // 使用方式 let loaderDelegate = AudioResourceLoaderDelegate() let audioAsset = AVURLAsset(url: yourAudioURL) audioAsset.resourceLoader.setDelegate(loaderDelegate, queue: DispatchQueue.global()) let playerItem = AVPlayerItem(asset: audioAsset) let player = AVPlayer(playerItem: playerItem) player.playImmediatelyAtRate(1.0)
3. 调整AVPlayerItem的缓冲参数
通过设置preferredForwardBufferDuration告诉AVPlayer提前缓冲的时长,缩短启动播放的等待时间。设置一个较小的值(比如10秒)可以让播放更快启动:
let playerItem = AVPlayerItem(asset: audioAsset) // 设置为10秒,系统会根据网络情况调整,但会优先满足这个目标 playerItem.preferredForwardBufferDuration = 10.0 let player = AVPlayer(playerItem: playerItem)
注意:这个值不是强制的,如果网络极差,AVPlayer还是会等缓冲足够内容才会播放,但大多数场景下能有效改善启动速度。
4. 正确监听播放状态
不要只依赖playbackBufferEmpty,结合playbackLikelyToKeepUp状态来处理播放逻辑会更可靠:
var playbackObserver: NSKeyValueObservation? playbackObserver = player.observe(\.currentItem?.playbackLikelyToKeepUp) { [weak self] player, _ in guard let self = self else { return } if player.currentItem?.playbackLikelyToKeepUp == true { player.play() } }
当playbackLikelyToKeepUp变为true时,说明AVPlayer有足够的缓冲可以持续播放,这时候再调用play会更稳妥。
内容的提问来源于stack exchange,提问作者L.L

