iOS实现视频边播放边缓存的解决方案

 更新时间:2025年05月16日 08:30:59   作者:90后晨仔  
这篇文章主要介绍了文章介绍了如何使用AVPlayer、AVAssetResourceLoaderDelegate、URLSession和本地缓存技术实现iOS视频边播放边缓存功能,需要的朋友可以参考下

一、技术实现思路

1. 核心组件

  • AVPlayer:iOS 原生视频播放器,支持网络视频流播放。
  • AVAssetResourceLoaderDelegate:自定义资源加载器,拦截播放器的请求,动态提供缓存数据。
  • URLSession:用于网络请求,下载视频数据。
  • OutputStream/InputStream:读取和写入本地缓存文件。

2. 实现流程

  • 初始化播放器:使用 AVPlayerAVURLAsset 加载视频 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()
        }
    }
}

六、总结

通过结合 AVPlayerURLSessionAVAssetResourceLoaderDelegate 和本地缓存技术,可以高效实现视频的边播放边缓存功能。该方案不仅提升了用户体验,还能有效减少网络流量消耗。对于复杂场景(如 HLS 流媒体、高并发下载),可进一步结合开源库(如 KTVHTTPCacheTBPlayer)简化开发流程。

以上就是iOS实现视频边播放边缓存的解决方案的详细内容,更多关于iOS视频边播放边缓存的资料请关注脚本之家其它相关文章!

相关文章

  • iOS开发之拦截URL转换成本地路由模块URLRewrite详解

    iOS开发之拦截URL转换成本地路由模块URLRewrite详解

    这篇文章主要给大家介绍了关于iOS开发之拦截URL转换成本地路由模块URLRewrite的相关资料,这是最近在工作中遇到的一个需求,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面跟着小编来一起看看吧。
    2017-08-08
  • iOS实现带动画的环形进度条

    iOS实现带动画的环形进度条

    这篇文章主要为大家详细介绍了iOS实现带动画的环形进度条,同时带数字同步效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-01-01
  • iOS开发之UIScrollView控件详解

    iOS开发之UIScrollView控件详解

    UIScrollView是一个非常重要的控件,其可以展示比设备屏幕更大区域的内容,我们可以通过手指滑动来查看内容视图的每一部分内容,也可以通过手指捏合来对内容视图进行缩放操作,我们每天开发中都不断显式或隐式地与UIScrollView打交道,下面给大家详细介绍UIScrollView控件。
    2016-09-09
  • iOS中的实时远程配置全纪录

    iOS中的实时远程配置全纪录

    这篇文章主要给大家介绍了关于iOS中实时远程配置的相关资料,文中通过示例代码介绍的非常详细,对各位iOS开发者们具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-01-01
  • 详解IOS图片压缩处理

    详解IOS图片压缩处理

    在日常IOS开发中,感觉图片尺寸太大,想压缩成小一点像素的。那么该如何做呢?本文通过“压缩”两个概念及实例来告诉大家如何进行图片压缩处理才是最好的。
    2016-07-07
  • iOS Segment带滑动条切换效果

    iOS Segment带滑动条切换效果

    这篇文章主要为大家详细介绍了iOS Segment带滑动条切换,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • 在iOS开发的Quartz2D使用中实现图片剪切和截屏功能

    在iOS开发的Quartz2D使用中实现图片剪切和截屏功能

    这篇文章主要介绍了在iOS开发的Quartz2D使用中实现图片剪切和截屏功能的方法,代码基于传统的Objective-C,需要的朋友可以参考下
    2015-12-12
  • iOS中WKWebView的一些特殊使用总结

    iOS中WKWebView的一些特殊使用总结

    这篇文章主要给大家介绍了关于iOS中WKWebView的一些特殊使用,文中通过示例代码介绍的非常详细,对大家学习或者使用iOS具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-12-12
  • 详解iOS开发中UITableview cell 顶部空白的多种设置方法

    详解iOS开发中UITableview cell 顶部空白的多种设置方法

    这篇文章主要介绍了详解iOS开发中UITableview cell 顶部空白的多种设置方法的相关资料,需要的朋友可以参考下
    2016-04-04
  • ios微信浏览器返回不刷新问题完美解决方法

    ios微信浏览器返回不刷新问题完美解决方法

    这篇文章主要介绍了ios微信浏览器返回不刷新问题完美解决方法,需要的朋友可以参考下
    2017-09-09

最新评论