swift - 无法播放加载了 AVAssetResourceLoaderDelegate 的视频

swift - cannot play a video loaded with AVAssetResourceLoaderDelegate

我正在尝试使用加载到 AVAssetResourceLoaderDelegate 中的 AVPlayer 播放视频,但我总是得到一个空白屏幕并且视频从未播放过。

这是代码:

let asset = AVURLAsset(url: URL(string: "fakescheme://video.mp4")!)
asset.resourceLoader.setDelegate(ResourceLoaderDelegate(), queue: DispatchQueue.main)
let item = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: item)

let playerLayer = AVPlayerLayer(player: self.player)
playerLayer!.frame = self.view.bounds;
self.view.layer.addSublayer(self.playerLayer!)
player!.play()

...

class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
    public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource resourceLoadingRequest: AVAssetResourceLoadingRequest) -> Bool {

        var newRequest = URLRequest(url: URL(string: "https://example.com/video.mp4")!)
        newRequest.allHTTPHeaderFields = resourceLoadingRequest.request.allHTTPHeaderFields

        let sessionTask = URLSession.shared.dataTask(with: newRequest) { data, response, error in
            resourceLoadingRequest.contentInformationRequest?.contentType = "video/mp4"
            resourceLoadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
            resourceLoadingRequest.contentInformationRequest?.contentLength = Int64(data.count)

            resourceLoadingRequest.response = response

            resourceLoadingRequest.dataRequest?.respond(with: data)
            resourceLoadingRequest.finishLoading()
        }

        sessionTask.resume()
        return true
    }
}

这是基本思路:

fakescheme:// 导致调用 resourceLoader() 委托,在其中发出新的 HTTP 请求以下载实际视频。

然后它调用 resourceLoadingRequest.dataRequest?.respond(with: data) 应该让 AVPlayer 播放下载的视频。 数据变量已正确填充,但我总是黑屏。

视频文件也很好,如果我直接将它输入 AVURLAsset() 就可以播放。

我试过一百万种东西和组合,但无法使用委托播放。任何帮助将不胜感激!

编辑:我将添加更多信息。

我尝试使用 AES 加密视频执行此操作,它使用 3 个文件 - .m3u8、加密密钥和 .ts 视频。 我设法让它使用委托下载 .m3u8 和密钥,但是当我尝试使用视频文件进行下载时,我再次出现黑屏。

这让我想到可能需要分批下载,但我不确定如何正确地进行下载。 我也找不到任何关于它的文档,例如 - 你是否应该将 HTTP headers 设置为好像它来自 Web 服务器,你是否应该设置 contentInformationRequest 等。委托被调用 2 次:

// first time: 
resourceLoadingRequest.dataRequest!.requestedLength == 2,
resourceLoadingRequest.dataRequest!.requestsAllDataToEndOfResource == false

// second time: 
resourceLoadingRequest.dataRequest!.requestedLength == MAX_INT,
resourceLoadingRequest.dataRequest!.requestsAllDataToEndOfResource == true

我不知道该怎么做 - 我两次都提供了整个视频,但都没有成功。

我终于让它工作了。所以上面的代码有2个问题:

  1. contentType 应为 AVFileType.mp4.rawValue,即“public.mpeg-4”。传递“video/mp4”或其他值会破坏它

  2. resourceLoadingRequest.dataRequest!.requestedLength确实需要尊重,所以视频文件需要按要求分块发送。

这是工作委托代码:

// this IF is an ugly way to catch the first request to the delegate
// in this request you should populate the contentInformationRequest struct with the size of the video, etc
if (resourceLoadingRequest.dataRequest!.requestedLength == 2) {
    let bytes : [UInt8] = [0x0, 0x0] // these are the first 2 bytes of the video, as requested
    let data = Data(bytes: bytes, count: bytes.count)
    
    resourceLoadingRequest.contentInformationRequest?.contentType = AVFileType.mp4.rawValue // this is public.mpeg-4, video/mp4 does not work
    resourceLoadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
    resourceLoadingRequest.contentInformationRequest?.contentLength = Int64(videoSize)
    
    resourceLoadingRequest.dataRequest!.respond(with: data)
    resourceLoadingRequest.finishLoading()
    
    return true
}

// here we are at the second request. the OS may request the entire file, or a portion of it 
// here we don't need to set any headers or contentInformationRequest, just reply with the requested data
// take a look at resourceLoadingRequest.dataRequest!.requestedLength, requestedOffset, currentOffset, requestsAllDataToEndOfResource
resourceLoadingRequest.dataRequest?.respond(with: data)
resourceLoadingRequest.finishLoading()

return true

这是整个文件的代码供参考:

import Foundation
import AVKit

class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
    public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource resourceLoadingRequest: AVAssetResourceLoadingRequest) -> Bool {
        if ((resourceLoadingRequest.request.url?.absoluteString.contains(".mp4"))!) {
            // replace the fakeScheme and get the original video url
            var originalVideoURLComps = URLComponents(url: resourceLoadingRequest.request.url!, resolvingAgainstBaseURL: false)!
            originalVideoURLComps.scheme = "file"
            let originalVideoURL = originalVideoURLComps.url
            
            var videoSize = 0
            do {
                let value = try originalVideoURL!.resourceValues(forKeys: [.fileSizeKey])
                videoSize = value.fileSize!
            } catch {
                print("error getting video size")
            }
            
            if (resourceLoadingRequest.contentInformationRequest != nil) {
                // this is the first request where we should tell the OS what file is to be downloaded
                let bytes : [UInt8] = [0x0, 0x0]    // TODO: repeat .requestedLength times?
                let data = Data(bytes: bytes, count: bytes.count)
                
                resourceLoadingRequest.contentInformationRequest?.contentType = AVFileType.mp4.rawValue // this is public.mpeg-4, video/mp4 does not work
                resourceLoadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
                resourceLoadingRequest.contentInformationRequest?.contentLength = Int64(videoSize)
                
                resourceLoadingRequest.dataRequest!.respond(with: data)
                resourceLoadingRequest.finishLoading()
                
                return true
            }
            
            // this is the second request where the actual file is to be downloaded
            
            let requestedLength = resourceLoadingRequest.dataRequest!.requestedLength
            let requestedOffset = resourceLoadingRequest.dataRequest!.requestedOffset
            let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: requestedLength)
            
            let inputStream = InputStream(url: originalVideoURL!)   // TODO: keep the stream open until a new file is requested?
            inputStream!.open()
            
            if (requestedOffset > 0) {
                // move the stream pointer to the requested position
                let buffer2 = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(requestedOffset))
                inputStream!.read(buffer2, maxLength: Int(requestedOffset)) // TODO: the requestedOffset may be int64, but this gets truncated to int!
                buffer2.deallocate()
            }
            inputStream!.read(buffer, maxLength: requestedLength)
            
            // decrypt the video
            if (requestedOffset == 0) { // TODO: this == 0 may not always work?
                // if you use custom encryption, you can decrypt the video here, buffer[] holds the bytes
            }
        
            let data = Data(bytes: buffer, count: requestedLength)
            
            resourceLoadingRequest.dataRequest?.respond(with: data)
            resourceLoadingRequest.finishLoading()
            
            buffer.deallocate()
            inputStream!.close()
            
            return true
        }
        
        return false
    }
}