iOS Swift:AVPlayer播放DRM保护MPD视频时streamingContentKeyRequestData获取失败
问题解决:AVPlayer集成FairPlay DRM调用
streamingContentKeyRequestData失败 核心问题分析
你遇到的streamingContentKeyRequestData调用失败,主要源于Content ID提取错误、DRM请求处理逻辑不严谨、平台DRM支持混淆以及代码中的基础错误,下面是针对性的修复方案:
关键修复点
1. 修正基础错误:MPD URL拼写
你代码中MPD URL写成了htps://(缺少字母t),这会导致AVURLAsset无法正常加载,先修正为https://:
guard let url = URL(string: "https://z2cltd.akamaized.net/mini01/vods/49/s1e1-andhera-ujala-ghani-chaon-copy-3395/64366c9ccc181-4606-mp4-cbcs.mpd") else { return }
2. 正确提取Content ID
你当前硬编码MPD的host作为Content ID是错误的,Content ID需要从DRM请求的初始化数据中提取,或者从MPD的ContentProtection字段获取。如果后端约定用视频唯一标识作为Content ID,可以从MPD URL中解析:
// 从MPD URL解析Content ID(根据后端约定调整) guard let mpdUrl = asset?.url, let lastPathComponent = mpdUrl.pathComponents.last, let contentId = lastPathComponent.components(separatedBy: "-mp4-cbcs.mpd").first, let contentIdData = contentId.data(using: .utf8) else { print("无法提取Content ID") loadingRequest.finishLoading(with: NSError(domain: "DRMError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法提取Content ID"])) return false }
3. 先验证DRM请求类型
在shouldWaitForLoadingOfRequestedResource中,先判断当前请求是否是DRM密钥请求,避免处理无关请求:
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool { // 仅处理DRM密钥请求 guard loadingRequest.contentInformationRequest?.contentType == AVStreamingKeyDeliveryContentKeyType else { // 非DRM请求,返回false让系统默认处理 return false } // 后续DRM处理逻辑... }
4. 移除不必要的Persistent Key选项
如果你不需要离线播放的持久化密钥,先移除AVAssetResourceLoadingRequestStreamingContentKeyRequestRequiresPersistentKey选项,简化调试流程:
// 移除Persistent Key选项,先测试基础播放流程 _spcData = try loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData, options: nil)
5. 处理Widevine DRM的平台限制
iOS的AVPlayer不支持Widevine DRM,Widevine是Google针对Android平台的DRM方案。iOS上只能播放MPD中未加密的内容,或者要求后端提供适配FairPlay DRM的视频流。
修改后的完整代码示例
class ViewController: UIViewController { var player: AVPlayer? var asset: AVURLAsset? // 从后端返回的配置中获取正确地址 let fairPlayCertificateUrl = URL(string: "https://mw.example.net/static/FairPlay.der")! let keyServerUrl = URL(string: "https://mw.example.net/ksm")! override func viewDidLoad() { super.viewDidLoad() guard let mpdUrl = URL(string: "https://z2cltd.akamaized.net/mini01/vods/49/s1e1-andhera-ujala-ghani-chaon-copy-3395/64366c9ccc181-4606-mp4-cbcs.mpd") else { print("MPD URL无效") return } asset = AVURLAsset(url: mpdUrl) asset?.resourceLoader.setDelegate(self, queue: DispatchQueue.global(qos: .default)) let playerItem = AVPlayerItem(asset: asset!) // 监听playerItem状态 playerItem.observe(\.status, changeHandler: onStatusObserverChanged) player = AVPlayer(playerItem: playerItem) let playerLayer = AVPlayerLayer(player: player) playerLayer.frame = view.bounds playerLayer.videoGravity = .resizeAspectFill view.layer.addSublayer(playerLayer) player?.play() } private func onStatusObserverChanged(playerItem: AVPlayerItem, change: NSKeyValueObservedChange<AVPlayerItem.Status>) { switch playerItem.status { case .failed: if let error = playerItem.error { print("播放失败:\(error.localizedDescription)") } case .readyToPlay: print("视频准备就绪") default: break } } } extension ViewController: AVAssetResourceLoaderDelegate { func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool { // 仅处理DRM密钥请求 guard loadingRequest.contentInformationRequest?.contentType == AVStreamingKeyDeliveryContentKeyType else { return false } // 从MPD URL提取Content ID(根据后端约定调整) guard let mpdUrl = asset?.url, let lastPathComponent = mpdUrl.pathComponents.last, let contentId = lastPathComponent.components(separatedBy: "-mp4-cbcs.mpd").first, let contentIdData = contentId.data(using: .utf8) else { print("无法提取Content ID") loadingRequest.finishLoading(with: NSError(domain: "DRMError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法提取Content ID"])) return false } // 请求FairPlay证书 let certificateTask = URLSession.shared.dataTask(with: fairPlayCertificateUrl) { [weak self] data, _, error in guard let self = self else { return } guard let certificateData = data, error == nil else { print("证书请求失败:\(error?.localizedDescription ?? "未知错误")") loadingRequest.finishLoading(with: error ?? NSError(domain: "DRMError", code: -2, userInfo: [NSLocalizedDescriptionKey: "证书请求失败"])) return } do { // 生成SPC数据 let spcData = try loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData, options: nil) // 请求CKC密钥 let spcBase64 = spcData.base64EncodedString() let bodyData = "spc=\(spcBase64)&assetId=\(contentId)".data(using: .utf8) var keyRequest = URLRequest(url: self.keyServerUrl) keyRequest.httpMethod = "POST" keyRequest.httpBody = bodyData URLSession.shared.dataTask(with: keyRequest) { data, _, error in guard let ckcData = data, error == nil else { print("CKC请求失败:\(error?.localizedDescription ?? "未知错误")") loadingRequest.finishLoading(with: error ?? NSError(domain: "DRMError", code: -3, userInfo: [NSLocalizedDescriptionKey: "密钥请求失败"])) return } // 处理CKC响应,返回给AVPlayer guard let dataRequest = loadingRequest.dataRequest else { loadingRequest.finishLoading(with: NSError(domain: "DRMError", code: -4, userInfo: [NSLocalizedDescriptionKey: "数据请求无效"])) return } dataRequest.respond(with: ckcData) loadingRequest.finishLoading() }.resume() } catch let spcError { print("生成SPC失败:\(spcError.localizedDescription)") loadingRequest.finishLoading(with: spcError) } } certificateTask.resume() return true } }
额外调试建议
- 打印
streamingContentKeyRequestData抛出的具体错误信息,排查证书格式、Content ID匹配等问题; - 抓包验证证书请求和CKC请求的响应是否符合后端要求;
- 确认后端FairPlay服务接受的参数格式(如SPC编码方式、Content ID传递规则)是否与你的请求一致。
内容的提问来源于stack exchange,提问作者zaid afzal
相关产品推荐
相关产品推荐

