美文网首页swiftswift知识点(iOS)音视频
简洁易懂的ResourceLoaderDelegate实现AVP

简洁易懂的ResourceLoaderDelegate实现AVP

作者: 荔枝lizhi_iOS程序猿 | 来源:发表于2021-08-03 10:58 被阅读0次

    最近在研究ios Swift 的 AVPlayer 边下边播,在网上找了很多相关内容,实现起来都比较复杂,对于自己实现参照太难,经过我的不懈努力,和耗费了很多精力,终于找到了比较简单易懂的方法,现在分享给大家,希望可以给遇到相同的问题的小伙伴以启发 from lizhi荔枝

    1.说明

    本篇文章主要讲述 ios AVPlayer 通过 ResourceLoaderDelegate实现下载数据,下一遍文章再讲述数据存储等。

    通过AVAssetResourceLoaderDelegate 实现变下边播,只需要下载一遍的流量就可以。

    2.方案思路

    AVAssetResourceLoaderDelegate
    首先了解一下AVAssetResourceLoaderDelegate所在的层:

    ResouceLoader层次图

    其中核心类:

    • AVAssetResourceLoader:这个类负责多媒体(音视频)二进制数据的加载(下载),然后回调给上层Asset,让视频播放。但是这个类作为AVURLAsset是只读属性,但是它允许下面这个代理去如何加载数据资源。

    • AVAssetResourceLoaderDelegate:它是一个协议,那么任何实现了该协议的对象都可以充当AVAssetResourceLoader的代理来指示视频数据的加载,既然数据资源可以有开发人员自行加载然后再回填给播放器,那么缓存就可以有自己控制了,OK,这就是我们这个方案的思路。

    • AVPlayer 和 ResourceLoader 的数据流交互如下


      11_副本.png

    3. AVPlayer 通过ResourceLoader 下载需要实现的方法

    a.通过自定义scheme来创建avplayer,并给AVURLAsset指定代理 (LZAVPlayer)
        private func preparePlayer() {
            guard let url = URL(string: mediaUrl) else {
                return
            }
            //下载
            var component = URLComponents(url: url, resolvingAgainstBaseURL: true)!
            component.scheme = "LZStream"
            let streamUrl: URL = component.url!
            let urlAssrt = AVURLAsset(url: streamUrl)
            videoResourceLoader = LZResourceLoader(originalURL: url)
    
            guard let resourceLoader = videoResourceLoader else {
                return
            }
            urlAssrt.resourceLoader.setDelegate(videoResourceLoader, queue: resourceLoader.resourceLoaderQueue)
           let playerItem = AVPlayerItem(asset: urlAssrt)
            if player == nil {
                player = AVPlayer(playerItem: playerItem)
            } else {
                player?.replaceCurrentItem(with: playerItem)
            }
        }
    
    b.代理实现AVAssetResourceLoader的代理方法(SUResourceLoader对象)
    // MARK: - AVAssetResourceLoaderDelegate
    extension LZResourceLoader: AVAssetResourceLoaderDelegate {
        public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
            // 启动下载任务,同时保留loadingRequest
            if  videoDownload == nil{
                videoDownload = LZResourceDownloader(originalUrl: originalURL)
            }
            print("--[shouldWait]-- loadRequest = \(loadingRequest)")
            videoDownload?.addDownload(loadingRequest: loadingRequest)
            return true
        }
        public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {
            print("-- resourceLoader|didCancel")
            videoDownload?.removeDownload(loadingRequest: loadingRequest)
        }
    }
    
    c.通过urlsession下载数据
       func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
            if let loadingRequest = self.loadingRequests.first {
                self.fillInContentInformationRequest(loadingRequest, from: response)
            }
            completionHandler(.allow)
        }
        
        func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
            if let loadingRequest = self.loadingRequests.first {
                loadingRequest.dataRequest?.respond(with: data)
            }
        }
        
        func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
            print("--[urlSession] didCompleteWithError ")
            if error != nil {
                if let loadingRequest = self.loadingRequests.first {
                    loadingRequest.finishLoading(with: error)
                }
                
            }else {
                if let loadingRequest = self.loadingRequests.first {
                    loadingRequest.finishLoading()
                }
            }
            self.loadingRequests.removeFirst()
            self.loadNextToLoadedResource()
        }
    
    d.填充响应数据loadingRequest

    的contentInformationRequest

        //填充请求
        func fillInContentInformationRequest(_ loadingRestst:AVAssetResourceLoadingRequest,from response:URLResponse) {
            var contentLength = Int64(0)
            var isByteRangeAccessSupported = true
            var contentType: String = ""
            
            guard let httpResponse = response as? HTTPURLResponse else {
                return
            }
            // 将headers的key都转换为小写
            var headers = [String: Any]()
            for key in httpResponse.allHeaderFields.keys {
                let lowercased = (key as! String).lowercased()
                headers[lowercased] = httpResponse.allHeaderFields[key]
            }
            isByteRangeAccessSupported = (headers["accept-ranges"] as? String) == "bytes"
            if let rangeText = headers["content-range"] as? String, let lengthText = rangeText.split(separator: "/").last {
                contentLength = Int64(lengthText)!
            }
            if let mimeType = response.mimeType,
               let aContentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType as CFString, nil)?.takeRetainedValue()
            {
                contentType = aContentType as String
            }
            
            loadingRestst.contentInformationRequest?.contentType = contentType
            loadingRestst.contentInformationRequest?.contentLength = contentLength
            loadingRestst.contentInformationRequest?.isByteRangeAccessSupported = isByteRangeAccessSupported
        }
    
    e.填充data数据到loadingRequest (respondWithData)
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
            if let loadingRequest = self.loadingRequests.first {
                loadingRequest.dataRequest?.respond(with: data)
            }
        }
    
    f.如果完全响应了所需要的数据,则完成loadingRequest (finishLoading)
     func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
            print("--[urlSession] didCompleteWithError ")
            if error != nil {
                if let loadingRequest = self.loadingRequests.first {
                    loadingRequest.finishLoading(with: error)
                }
                
            }else {
                if let loadingRequest = self.loadingRequests.first {
                    loadingRequest.finishLoading()
                }
            }
            self.loadingRequests.removeFirst()
            self.loadNextToLoadedResource()
        }
    

    待续....

    代码地址 LZAVPlayer

    知识需研究,码字很不易,请点赞支持,from 荔枝lizhi, ;转载请注明出处

    相关文章

      网友评论

        本文标题:简洁易懂的ResourceLoaderDelegate实现AVP

        本文链接:https://www.haomeiwen.com/subject/rqjdvltx.html