iOS 实现视频边播放边缓存的解决方案
昨天 22:30
字数 5695
阅读 14
已编辑
一、技术实现思路
1. 核心组件
AVPlayer:iOS 原生视频播放器,支持网络视频流播放。AVAssetResourceLoaderDelegate:自定义资源加载器,拦截播放器的请求,动态提供缓存数据。URLSession:用于网络请求,下载视频数据。OutputStream/InputStream:读取和写入本地缓存文件。
2. 实现流程
- 初始化播放器:使用
AVPlayer和AVURLAsset加载视频 URL。 - 自定义资源加载器:通过
AVAssetResourceLoaderDelegate拦截播放器的请求,动态提供缓存数据。 - 网络下载与缓存:使用
URLSession下载视频数据,并通过OutputStream写入本地文件。 - 分片缓存与断点续传:根据播放器的请求范围(
Range),分块下载和缓存视频数据。 - 播放器与缓存协同:播放器实时读取缓存文件,同时网络下载继续进行。
二、核心代码实现
1. 初始化播放器与缓存
import AVFoundation
class VideoPlayerManager {
private var player: AVPlayer?
private var cacheURL: URL!
private var outputStream: OutputStream?
private var inputStream: InputStream?
func startPlayback(url: URL) {
// 创建缓存文件路径
cacheURL = FileManager.default.temporaryDirectory.appendingPathComponent("cachedVideo.mp4")
// 初始化输出流(用于写入缓存)
outputStream = OutputStream(toFileAtPath: cacheURL.path, append: true)
outputStream?.open()
// 初始化输入流(用于读取缓存)
inputStream = InputStream(url: cacheURL)!
inputStream?.open()
// 创建 AVPlayer 并绑定播放源
let asset = AVURLAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playerItem)
// 自定义资源加载器
let resourceLoaderDelegate = ResourceLoaderDelegate(outputStream: outputStream, inputStream: inputStream)
asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: .main)
// 开始播放
player?.play()
// 启动下载任务
startDownloadTask(url: url)
}
private func startDownloadTask(url: URL) {
var request = URLRequest(url: url)
request.httpMethod = "GET"
// 设置 Range 请求头(断点续传)
if let fileData = try? Data(contentsOf: cacheURL), fileData.count > 0 {
let range = "bytes=\(fileData.count)-"
request.setValue(range, forHTTPHeaderField: "Range")
}
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
guard let self = self else { return }
if let data = data {
// 将下载的数据写入缓存文件
self.writeDataToFile(data: data)
}
}
task.resume()
}
private func writeDataToFile(data: Data) {
if let outputStream = outputStream {
let buffer = [UInt8](data)
outputStream.write(buffer, maxLength: buffer.count)
outputStream.flush()
}
}
}
2. 自定义资源加载器
class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate {
private let outputStream: OutputStream?
private let inputStream: InputStream?
private var cachedData: Data = Data()
init(outputStream: OutputStream?, inputStream: InputStream?) {
self.outputStream = outputStream
self.inputStream = inputStream
}
func resourceLoader(_ resourceLoader: AVAssetResourceLoader,
shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
// 实时读取缓存数据并返回给播放器
DispatchQueue.global().async {
var buffer = [UInt8](repeating: 0, count: 1024)
while self.inputStream?.hasBytesAvailable == true {
let bytesRead = self.inputStream?.read(&buffer, maxLength: buffer.count) ?? 0
if bytesRead > 0 {
let data = Data(bytes: buffer, count: bytesRead)
loadingRequest.dataRequest.respond(with: data)
}
}
}
return true
}
func resourceLoader(_ resourceLoader: AVAssetResourceLoader,
didCancel loadingRequest: AVAssetResourceLoadingRequest) {
// 处理取消请求
loadingRequest.dataRequest.finishLoading()
}
}
三、关键点解析
1. 缓存管理
- 本地缓存:使用
OutputStream将下载的视频数据写入本地文件(如沙盒目录),避免重复下载。 - 分片缓存:根据播放器的请求范围(
Range),分块下载和缓存视频数据,确保播放流畅。
2. 断点续传
- Range 请求头:通过设置
Range: bytes=起始字节-,实现断点续传,避免网络中断后重复下载。 - 缓存文件检查:在下载前检查本地缓存文件大小,动态调整
Range请求头。
3. 播放器与缓存协同
-
实时读取缓存:通过
InputStream从本地缓存文件中读取已下载的数据,实时传递给AVPlayer。 -
动态更新缓存:在播放过程中,网络下载任务持续运行,确保缓存文件逐步完整。
-
-
四、优化建议
1. 错误处理与重试
- 网络错误重试:在网络中断时自动重试下载任务,避免播放中断。
- 缓存文件清理:定期清理过期缓存文件,避免占用过多磁盘空间。
2. 性能优化
- 异步线程处理:使用
DispatchQueue异步处理数据读写,避免阻塞主线程。 - 内存管理:避免一次性加载大文件到内存,优先使用本地缓存。
五、使用 KTVHTTPCache 的简化方案
1. 接入缓存
import KTVHTTPCache
class VideoCacheManager {
func initCache() {
do {
try KTVHTTPCache.proxyStart()
let maxLength: Int64 = 300 * 1024 * 1024 // 300MB
KTVHTTPCache.cacheSetMaxCacheLength(maxLength)
} catch {
print("Proxy Start Failure: $error)")
}
}
func playVideo(url: URL) {
let proxyURLString = KTVHTTPCache.proxyURLString(withOriginalURLString: url.absoluteString)
let proxyURL = URL(string: proxyURLString)!
let player = AVPlayer(url: proxyURL)
player.play()
}
}
2. 实现预加载
func preloadVideos(urls: [URL]) {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
for url in urls {
queue.addOperation {
let proxyURLString = KTVHTTPCache.proxyURLString(withOriginalURLString: url.absoluteString)
let proxyURL = URL(string: proxyURLString)!
let request = URLRequest(url: proxyURL)
let task = URLSession.shared.dataTask(with: request) { _, _, _ in }
task.resume()
}
}
}
六、总结
通过结合 AVPlayer、URLSession、AVAssetResourceLoaderDelegate 和本地缓存技术,可以高效实现视频的边播放边缓存功能。该方案不仅提升了用户体验,还能有效减少网络流量消耗。对于复杂场景(如 HLS 流媒体、高并发下载),可进一步结合开源库(如 KTVHTTPCache 或 TBPlayer)简化开发流程。
参考文献:
本文转自 https://juejin.cn/post/7504007819075600411,如有侵权,请联系删除。
0人点赞>
0 条评论
排序方式
时间
投票
快来抢占一楼吧
请登录后发表评论
相关推荐
文章归档
最新文章
最受欢迎
昨天 23:49
昨天 23:45
昨天 23:39
昨天 22:30
6 评论
3 评论
1 评论
0 评论