AVPlayer停止在线模式下播放AES加密的离线HLS视频

时间:2017-09-07 13:37:42

标签: ios aes avplayer hls avplayeritem

我编写了一个代码来下载HLS视频并在离线模式下播放。 此代码适用于编码视频。现在我有一个AES加密的视频,我们有自定义加密密钥。下载AES加密的HLS视频后,我使用下面给出的代码来提供解密视频的密钥。

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {

NSString *scheme = loadingRequest.request.URL.scheme;

if ([scheme isEqualToString:@"ckey"]) {

    NSString *request = loadingRequest.request.URL.host;
    NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:request];

    if (data) {
        [loadingRequest.dataRequest respondWithData:data];
        [loadingRequest finishLoading];
    } else {
        // Data loading fail
    }
}
return NO; }       

我正在拦截对密钥的请求并传递存储在UserDefaults中的密钥以进行解密。

当我的设备的wifi或数据连接关闭时,带有自定义键的AES加密HLS视频播放效果良好。

如果我在启用设备的WiFi或数据连接时开始播放此视频,或者是 我在播放视频时启用了设备的wifi或数据连接;视频会立即停止播放而不会出现任何错误,也不会再次播放。

我已经检查了playerItem的accessLog和errorLog,但是没有找到任何有用的东西。

在下载HLS内容后提供自定义URL密钥我通过替换

来更新.m3u8文件的内容
  

URI =" ..."

字符串

  

URI =" CKEY://..."

这是为AES加密视频提供密钥的正确方法吗?

这可能是造成这种行为的原因以及如何解决这个问题?

提前致谢。

1 个答案:

答案 0 :(得分:0)

最后我设法解决了这个问题。下载的HLS视频的粗略包结构如下所示:

HLS.movpkg 
 |_ 0-12345
    |_ 123.m3u8
    |_ StreamInfoBoot.xml
    |_ StreamInfoRoot.xml
    |_ <>.frag
 |_ boot.xml
  1. boot.xml包含HLS的网络URL(基于https:基于)
  2. StreamBootInfo.xml包含HLS URL(基于https:)和本地下载的.frag文件之间的映射。
  3. 在离线模式下,HLS视频播放效果非常好。但是当启用网络连接时,它指的是https:URL而不是本地.frag文件。

    我用自定义方案(fakehttps :)替换了这些文件中的https:scheme,以限制AVPlayer上线获取资源。

    这件事解决了我的问题,但我不知道它背后的确切原因以及AVPlayer如何播放HLS。

    我提到了this并且有了一些想法,所以试了一下。

    我正在进一步更新此答案,以解释如何在离线模式下播放加密视频。

      
        
    1. 获取视频解密所需的密钥。

    2.   
    3. 将该密钥保存在某处。

    4.         

      您可以将该密钥保存为NSData中的DataUserDefault对象。我使用视频文件名作为密钥,以便在UserDefaults中保存密钥数据。

           
          
      1. 使用FileManager API迭代.movpkg内的所有文件。

      2.   
      3. 获取每个.m3u8文件的内容,并将URI="some key url"替换为URI =“ckey:// keyusedToSaveKeyDataInUserDefaults”

      4.         

        您可以参考下面给出的代码来完成此过程。

      if let url = asset.asset?.url, let data = data {
    
                let keyFileName = "\(asset.contentCode!).key"
                UserDefaults.standard.set(data, forKey: keyFileName)
    
                do {
    
                    // ***** Create key file *****
                    let keyFilePath = "ckey://\(keyFileName)"
    
                    let subDirectories = try fileManager.contentsOfDirectory(at: url,
                                                                                     includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants)
    
                    for url in subDirectories {
    
                        var isDirectory: ObjCBool = false
    
                        if fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) {
    
                            if isDirectory.boolValue {
    
                                let path = url.path as NSString
    
                                let folderName = path.lastPathComponent
                                let playlistFilePath = path.appendingPathComponent("\(folderName).m3u8")
    
                                if fileManager.fileExists(atPath: playlistFilePath) {
    
                                    var fileContent = try String.init(contentsOf: URL.init(fileURLWithPath: playlistFilePath))
    
                                    let stringArray = self.matches(for: "URI=\"(.+?)\"", in: fileContent)
    
                                    for pattern in stringArray {
                                        fileContent = fileContent.replacingOccurrences(of: pattern, with: "URI=\"\(keyFilePath)\"")
                                    }
    
                                    try fileContent.write(toFile: playlistFilePath, atomically: true, encoding: .utf8)
                                }
    
                                let streamInfoXML = path.appendingPathComponent("StreamInfoBoot.xml")
    
                                if fileManager.fileExists(atPath: streamInfoXML) {
    
                                    var fileContent = try String.init(contentsOf: URL.init(fileURLWithPath: streamInfoXML))
                                    fileContent = fileContent.replacingOccurrences(of: "https:", with: "fakehttps:")
                                    try fileContent.write(toFile: streamInfoXML, atomically: true, encoding: .utf8)
                                }
                            } else {
    
                                if url.lastPathComponent == "boot.xml" {
    
                                    let bootXML = url.path
    
                                    if fileManager.fileExists(atPath: bootXML) {
    
                                        var fileContent = try String.init(contentsOf: URL.init(fileURLWithPath: bootXML))
                                        fileContent = fileContent.replacingOccurrences(of: "https:", with: "fakehttps:")
                                        try fileContent.write(toFile: bootXML, atomically: true, encoding: .utf8)
                                    }
                                }
                            }
                        }
                    }
    
                    userInfo[Asset.Keys.state] = Asset.State.downloaded.rawValue
    
                    // Update download status to db
                    let user = RoboUser.sharedObject()
                    let sqlDBManager = RoboSQLiteDatabaseManager.init(databaseManagerForCourseCode: user?.lastSelectedCourse)
                    sqlDBManager?.updateContentDownloadStatus(downloaded, forContentCode: asset.contentCode!)
    
                    self.notifyServerAboutContentDownload(asset: asset)
    
                    NotificationCenter.default.post(name: AssetDownloadStateChangedNotification, object: nil, userInfo: userInfo)
                } catch  {
                }
            }
    
    func matches(for regex: String, in text: String) -> [String] {
    
        do {
            let regex = try NSRegularExpression(pattern: regex)
            let nsString = text as NSString
            let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
            return results.map { nsString.substring(with: $0.range)}
        } catch let error {
            print("invalid regex: \(error.localizedDescription)")
            return []
        }
    }
    

    这将更新您的下载包结构,以便在离线模式下播放加密视频。

    现在最后要做的是在AVAssetResourceLoader类的给定方法下面实现如下

    - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
    
        NSString *scheme = loadingRequest.request.URL.scheme;
    
        if ([scheme isEqualToString:@"ckey"]) {
    
            NSString *request = loadingRequest.request.URL.host;
            NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:request];
    
            if (data) {
                loadingRequest.contentInformationRequest.contentType = AVStreamingKeyDeliveryPersistentContentKeyType;
                loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
                loadingRequest.contentInformationRequest.contentLength = data.length;
                [loadingRequest.dataRequest respondWithData:data];
                [loadingRequest finishLoading];
            } else {
                // Data loading fail
            }
        }
    
        return YES;
    }
    

    此方法将在播放时为视频解密提供视频密钥。

相关问题