是什么导致AVAssetCache报告完全下载的资产无法离线播放?

时间:2018-07-26 21:53:21

标签: avfoundation hls avasset fairplay avassetdownloadtask

我正在开发一个iOS应用,该应用可通过HLS播放经过FairPlay加密的音频,并支持下载和流式传输。在飞行模式下,我无法播放下载的内容。如果我在下载完成后从本地URL创建一个AVURLAsset,则asset.assetCache.isPlayableOffline返回NO,并且当我尝试以飞行模式玩游戏时,如果可以肯定,它仍然会尝试请求其中一个。 m3u8播放列表文件。

我的主播放列表如下:

#EXTM3U
# Created with Bento4 mp4-hls.py version 1.1.0r623

#EXT-X-VERSION:5
#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,URI="skd://url/to/key?KID=foobar",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"


# Media Playlists
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=133781,BANDWIDTH=134685,CODECS="mp4a.40.2" media-1/stream.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=67526,BANDWIDTH=67854,CODECS="mp4a.40.2" media-2/stream.m3u8

流播放列表如下:

#EXTM3U
#EXT-X-VERSION:5
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:30
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://url/to/key?KID=foobar",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"
#EXTINF:30.000181,
#EXT-X-BYTERANGE:470290@0
media.aac
# more segments...
#EXT-X-ENDLIST

下载资产:

AVURLAsset *asset = [AVURLAsset assetWithURL:myM3u8Url];
[asset.resourceLoader setDelegate:[FairPlayKeyManager instance] queue:[FairPlayKeyManager queue]];
asset.resourceLoader.preloadsEligibleContentKeys = YES;
AVAssetDownloadTask *task = [self.session assetDownloadTaskWithURLAsset:asset assetTitle:@"Track" assetArtworkData:imgData options:nil];
[task resume];

在委托人的URLSession:assetDownloadTask:didFinishDownloadingToURL:中:

self.downloadedPath = location.relativePath;

在委托人的URLSession:task:didCompleteWithError:中:

if (!error)
{
  NSString *strUrl = [NSHomeDirectory() stringByAppendingPathComponent:self.downloadedPath];
  NSURL *url = [NSURL fileURLWithPath:strUrl];
  AVURLAsset *localAsset = [AVURLAsset assetWithURL:url];
  if (!localAsset.assetCache.playableOffline)
    NSLog(@"Oh no!"); //not playable offline
}

除了资产缓存报告无法离线播放之外,下载不会产生任何错误。但是,如果您切换到飞行模式并尝试播放下载的资产,它将正确地向资源加载器委托请求一个密钥(并且我使用的是持久密钥,因此可以在离线状态下正常工作),然后尝试请求media-1/stream.m3u8

这里没有我要处理的陷阱吗?播放列表文件是否应该有所不同?我缺少的任务或资产上有一些财产吗?

2 个答案:

答案 0 :(得分:1)

我认为在检查asset.assetCache.isPlayableOffline之前,您需要检查的东西很少。

  1. 您的KSM是否配置为支持公平竞赛离线播放?
    • 访问Apple's FairPlay Streaming Website
    • 下载Fairplay示例SDK(FairPlay Streaming Server SDK(4.2.0))
    • 打开 HLSCatalogWithFPS-AVAssetResourceLoader HLSCatalogWithFPS-AVContentKeySession
    • 使您的KSM适应示例项目,以检查FPS离线播放是否效果良好
  2. 检查您的密钥请求过程
    • 由于您未提供任何与密钥请求过程相关的代码,所以我不知道您是否正确请求并接收了ckc数据
    • 完成下载并不意味着您已获得ckc或持久性密钥。调试以检查是否从KSM获得正确的ckc数据。 (如果您的KSM未将内容配置为可离线播放,则在请求带有永久密钥选项的ckc时,您可能会出错)
func handlePersistableContentKeyRequest(keyRequest: AVPersistableContentKeyRequest) {

        /*
         The key ID is the URI from the EXT-X-KEY tag in the playlist (e.g. "skd://key65") and the
         asset ID in this case is "key65".
         */
        guard let contentKeyIdentifierString = keyRequest.identifier as? String,
            let contentKeyIdentifierURL = URL(string: contentKeyIdentifierString),
            let assetIDString = contentKeyIdentifierURL.host,
            let assetIDData = assetIDString.data(using: .utf8)
            else {
                print("Failed to retrieve the assetID from the keyRequest!")
                return
        }

        do {

            let completionHandler = { [weak self] (spcData: Data?, error: Error?) in
                guard let strongSelf = self else { return }
                if let error = error {
                    keyRequest.processContentKeyResponseError(error)

                    strongSelf.pendingPersistableContentKeyIdentifiers.remove(assetIDString)
                    return
                }

                guard let spcData = spcData else { return }

                do {
                    // Send SPC to Key Server and obtain CKC
                    let ckcData = try strongSelf.requestContentKeyFromKeySecurityModule(spcData: spcData, assetID: assetIDString)

                    let persistentKey = try keyRequest.persistableContentKey(fromKeyVendorResponse: ckcData, options: nil)

                    try strongSelf.writePersistableContentKey(contentKey: persistentKey, withContentKeyIdentifier: assetIDString)

                    /*
                     AVContentKeyResponse is used to represent the data returned from the key server when requesting a key for
                     decrypting content.
                     */
                    let keyResponse = AVContentKeyResponse(fairPlayStreamingKeyResponseData: persistentKey)

                    /*
                     Provide the content key response to make protected content available for processing.
                     */
                    keyRequest.processContentKeyResponse(keyResponse)

                    let assetName = strongSelf.contentKeyToStreamNameMap.removeValue(forKey: assetIDString)!

                    if !strongSelf.contentKeyToStreamNameMap.values.contains(assetName) {
                        NotificationCenter.default.post(name: .DidSaveAllPersistableContentKey,
                                                        object: nil,
                                                        userInfo: ["name": assetName])
                    }

                    strongSelf.pendingPersistableContentKeyIdentifiers.remove(assetIDString)
                } catch {
                    keyRequest.processContentKeyResponseError(error)

                    strongSelf.pendingPersistableContentKeyIdentifiers.remove(assetIDString)
                }
            }

            // Check to see if we can satisfy this key request using a saved persistent key file.
            if persistableContentKeyExistsOnDisk(withContentKeyIdentifier: assetIDString) {

                let urlToPersistableKey = urlForPersistableContentKey(withContentKeyIdentifier: assetIDString)

                guard let contentKey = FileManager.default.contents(atPath: urlToPersistableKey.path) else {
                    // Error Handling.

                    pendingPersistableContentKeyIdentifiers.remove(assetIDString)

                    /*
                     Key requests should never be left dangling.
                     Attempt to create a new persistable key.
                     */
                    let applicationCertificate = try requestApplicationCertificate()
                    keyRequest.makeStreamingContentKeyRequestData(forApp: applicationCertificate,
                                                                  contentIdentifier: assetIDData,
                                                                  options: [AVContentKeyRequestProtocolVersionsKey: [1]],
                                                                  completionHandler: completionHandler)

                    return
                }

                /*
                 Create an AVContentKeyResponse from the persistent key data to use for requesting a key for
                 decrypting content.
                 */
                let keyResponse = AVContentKeyResponse(fairPlayStreamingKeyResponseData: contentKey)

                // Provide the content key response to make protected content available for processing.
                keyRequest.processContentKeyResponse(keyResponse)

                return
            }

            let applicationCertificate = try requestApplicationCertificate()

            keyRequest.makeStreamingContentKeyRequestData(forApp: applicationCertificate,
                                                          contentIdentifier: assetIDData,
                                                          options: [AVContentKeyRequestProtocolVersionsKey: [1]],
                                                          completionHandler: completionHandler)
        } catch {
            print("Failure responding to an AVPersistableContentKeyRequest when attemping to determine if key is already available for use on disk.")
        }
    }

答案 1 :(得分:1)

事实证明,这是因为我正在从中下载音频的URL(例如https://mywebsite.com/path/to/master.m3u8重定向到CDN URL(https://my.cdn/other/path/to/master.m3u8)。{{1 }}簿记,以至于当我尝试离线播放生成的下载文件时,它认为它需要网络中的更多文件。我将其归档为Radar43285278。我通过手动向AVAssetDownloadTask请求来解决此问题相同的URL,然后为HEAD提供最终的重定向URL。