iOS 16及以上FairPlay离线播放报错问题求助
在iOS 16及以上系统中,下载并播放加密HLS URL内容时出现授权错误,但旧版本iOS可正常运行。已查阅相关iOS 16 FairPlay变更资料但未解决问题,报错信息如下:
Error Domain=AVFoundationErrorDomain Code=-11835 "Cannot Open" UserInfo={NSLocalizedFailureReason=This content is not authorized., NSLocalizedDescription=Cannot Open, NSUnderlyingError=0x2826f03f0 {Error Domain=NSOSStatusErrorDomain Code=-42668 "(null)"}}
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedFailureReason=An unknown error occurred (-19156), NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x28272d620 {Error Domain=NSOSStatusErrorDomain Code=-19156 "(null)"}}
iOS 16变更对代码逻辑的影响分析
你的代码中存在多个可能触发iOS 16 FairPlay兼容性问题的点,以下是具体分析和修复建议:
1. FairPlay协议版本过时
iOS 16对FairPlay协议版本进行了更新,代码中硬编码使用AVContentKeyRequestProtocolVersionsKey: [1](版本1),而新版本系统可能要求使用更高的协议版本(版本2),旧版本协议会被拒绝授权。
修复方案:根据系统版本动态选择协议版本:
let protocolVersions = ProcessInfo.processInfo.isOperatingSystemAtLeast(OperatingSystemVersion(majorVersion: 16, minorVersion: 0, patchVersion: 0)) ? [2] : [1] keyRequest.makeStreamingContentKeyRequestData(forApp: applicationCertificate, contentIdentifier: assetIDData, options: [AVContentKeyRequestProtocolVersionsKey: protocolVersions], completionHandler: completionHandler)
2. 错误处理逻辑不严谨
在contentKeySession(_:contentKeyRequest:didFailWithError:)中,对所有错误都尝试请求持久化密钥,这种逻辑在iOS 16中会触发无效的密钥请求,导致授权失败。
修复方案:仅在特定错误场景下请求持久化密钥:
func contentKeySession(_ session: AVContentKeySession, contentKeyRequest keyRequest: AVContentKeyRequest, didFailWithError err: Error) { print("Error: \(err)") // 仅在密钥未找到的情况下尝试请求持久化密钥 if let avError = err as? AVError, avError.code == .contentKeyNotFound { try? keyRequest.respondByRequestingPersistableContentKeyRequestAndReturnError() } }
3. 同步请求阻塞主线程
代码中使用DispatchGroup.wait()同步等待证书和密钥请求,这种方式在iOS 16中会导致主线程阻塞,触发超时或授权验证失败。
修复方案:改用异步回调处理网络请求:
func requestApplicationCertificate(_ asset: Asset?, completion: @escaping (Result<Data, Error>) -> Void) { guard let certificateURL = URL(string: "https://lic.drmtoday.com/license-server-fairplay/cert/ibakatv") else { completion(.failure(ProgramError.missingApplicationCertificate)) return } URLSession.shared.dataTask(with: certificateURL) { data, _, error in if let error = error { completion(.failure(error)) return } guard let data = data else { completion(.failure(ProgramError.missingApplicationCertificate)) return } completion(.success(data)) }.resume() }
4. Asset ID提取逻辑错误
代码中使用整个URL字符串生成assetIDData,iOS 16对Asset ID的格式验证更严格,无效的Asset ID会直接导致授权失败。
修复方案:提取正确的密钥ID作为Asset ID:
guard let contentKeyIdentifierString = keyRequest.identifier as? String, let contentKeyIdentifierURL = URL(string: contentKeyIdentifierString), // 提取query中的keyId或URL最后路径段作为有效Asset ID let assetIDString = contentKeyIdentifierURL.queryParameters?["keyId"] ?? contentKeyIdentifierURL.lastPathComponent, let assetIDData = assetIDString.data(using: .utf8) else { print("Failed to retrieve valid assetID from the keyRequest!") return }
5. 持久化密钥存在性检查未实现
代码中引用了persistableContentKeyExistsOnDisk(withContentKeyIdentifier:)函数但未实现,导致判断逻辑失效,在iOS 16中触发不必要的密钥请求。
修复方案:补全该函数实现:
func persistableContentKeyExistsOnDisk(withContentKeyIdentifier identifier: String) -> Bool { let filename = getDocumentsDirectory().appendingPathComponent(identifier + ".dat") return FileManager.default.fileExists(atPath: filename.path) }
总结
iOS 16对FairPlay DRM的协议版本、异步处理要求和格式验证都进行了严格调整,上述代码中的过时逻辑是导致授权错误的主要原因。建议按照修复方案逐一排查,重点优先更新协议版本和异步处理逻辑。
内容的提问来源于stack exchange,提问作者Aviraj Patel

