我已经向苹果支持写过信,他们的回复对我来说并不新鲜。在我与他们交谈之前,他们提供给我的信息都是我在wwdc视频和文档中得到的。(https://developer.apple.com/streaming/fps/)
此外,我将描述如何使用AES-128加密以离线模式播放HLS。 GitHub上的示例 描述了以下过程。请注意AVDownloadTask在模拟器上无法正常工作,因此您应该有一台设备进行此实现。
首先,您需要一个流URL。
步骤1:
在创建AVURLAsset之前,我们应该取得流URL,并将其方案更改为无效(例如:https->fakehttps,我通过URLComponents完成),并将AVAssetResourceLoaderDelegate分配给新创建的URL资产。所有这些更改都会强制AVAssetDownloadTask调用:
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
}
(因为AVFoundation看到一个无效的URL并且不知道该怎么处理,所以它在调用。)
步骤2:当代理被调用时,我们应该检查URL是否与之前的相同。我们需要将方案更改回有效的方案,并使用它创建一个简单的URLSession。我们将获得第一个应该类似于以下内容的.m3u8文件:
https://avid.avid.net/avid/information_about_stream1
https://avid.avid.net/avid/information_about_stream2
https://avid.avid.net/avid/information_about_stream3
步骤 3: 从这个数据中解析出所有需要的信息,并将所有 https 方案更改为无效的 fakehttps。
现在,您应该设置 AVAssetResourceLoadingRequest,从 shouldWaitForLoadingOfRequestedResource 委托中进行:
loadingRequest.contentInformationRequest?.contentType = response.mimeType
loadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
loadingRequest.contentInformationRequest?.contentLength = response.expectedContentLength
loadingRequest.dataRequest?.respond(with: modifiedData)
loadingRequest.finishLoading()
downloadTask?.resume()
其中: response -> 来自URLSession的响应, modifiedData -> 带有更改URL的数据
恢复您的下载任务,并在shouldWaitForLoadingOfRequestedResource委托中返回true
步骤4: 如果一切正常,AVAssetDownloadDelegate将会触发:
- (void)URLSession:(NSURLSession *)session assetDownloadTask:(AVAssetDownloadTask *)assetDownloadTask didResolveMediaSelection:(AVMediaSelection *)resolvedMediaSelection NS_AVAILABLE_IOS(9_0) {
}
第五步:
当AVFoundation选择最佳媒体流URL时,我们已经将所有的https更改为fakehttps。此时,shouldWaitForLoadingOfRequestedResource将再次触发,并从第一个.m3u8文件中选择一个URL。
第六步:
当委托再次被调用时,我们应该检查URL是否是我们需要的那个。然后,再次将伪装方案更改为有效方案,并使用该URL创建一个简单的URLSession。我们将获得第二个.m3u8文件:
https://avid.avid.net/avid/information_about_stream1
https://avid.avid.net/avid/information_about_stream2
https://avid.avid.net/avid/information_about_stream3
第七步:解析第二个 .m3u8 文件并从中获取所有所需的信息,同时注意
我们有一个用于加密密钥的URL。
步骤8:在将一些信息发送回AVAssetDownloadDelegate之前,我们需要从服务器下载密钥并将其本地保存在设备上。之后,您应该将第二个.m3u8文件中的URI=https://avid.avid.net/avid/key
更改为无效的URI=fakehttps://avid.avid.net/avid/key
,或者可能是您保存本地密钥的本地文件路径。现在,您应该从shouldWaitForLoadingOfRequestedResource委托中设置AVAssetResourceLoadingRequest,例如:
loadingRequest.contentInformationRequest?.contentType = response.mimeType
loadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
loadingRequest.contentInformationRequest?.contentLength = response.expectedContentLength
loadingRequest.dataRequest?.respond(with: modifiedData)
loadingRequest.finishLoading()
downloadTask?.resume()
其中:response -> 来自URLSession的响应,modifiedData -> 含有更改后URL的数据
恢复您的下载任务,并在shouldWaitForLoadingOfRequestedResource代理中返回true(与第3步相同)。
步骤9:当下载任务尝试使用修改的 URI=
创建请求时,又是一个无效的请求,此时 shouldWaitForLoadingOfRequestedResource 会再次触发。在这种情况下,您需要检测并创建新的数据与您的持久键(本地保存的键)。要注意, contentType
应为 AVStreamingKeyDeliveryPersistentContentKeyType
,否则AVFoundation将无法理解其中包含的密钥。
loadingRequest.contentInformationRequest?.contentType = AVStreamingKeyDeliveryPersistentContentKeyType
loadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
loadingRequest.contentInformationRequest?.contentLength = keyData.count
loadingRequest.dataRequest?.respond(with: keyData)
loadingRequest.finishLoading()
downloadTask?.resume()
步骤10:
AVFoundation会自动下载块。
下载完成后,将调用此代理:
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
}
当你想从设备播放流时,应该将location
保存在某个地方,并从此location
URL创建AVURLAsset。
所有这些信息都由AVFoundation本地保存,因此下次尝试在离线AVURLAsset中播放本地内容时,委托将被调用,因为URI = fakehttps://avid.avid.net/avid/key
,那是一个无效的链接,在这里你将再次执行第9步,视频将以离线模式播放。
如果有更好的实现方法,欢迎告诉我。
Github上的示例