如何实现AVAssetImageGenerator生成FairPlay加密HLS流的缩略图?
解决FairPlay加密HLS流使用AVAssetImageGenerator生成缩略图的问题
先定位问题根源:获取错误信息
你之前没拿到错误信息,这是排查的核心。别用同步生成方法,改用generateCGImagesAsynchronously(forTimes:completionHandler:),在回调里直接查看error参数,它会明确告诉你是密钥请求失败、片段解密失败还是其他加载问题。
确保密钥请求的正确处理
虽然I-Frame播放列表的#EXT-X-KEY和主播放列表一致,但要确认resourceLoader(_:shouldWaitForLoadingOfRequestedResource)的处理逻辑没有遗漏:
- 拦截到I-Frame的密钥请求(
.skd结尾的URL)时,返回的CKC必须是对应资源ID的有效密钥数据 - 正确设置响应的
contentType为AVContentKeyTypeFairPlayStreaming,同时确保数据长度和实际CKC数据匹配 - 示例处理代码:
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool { guard let url = loadingRequest.request.url, url.pathExtension == "skd" else { return false } // 替换为你获取对应资源ID的CKC逻辑 guard let ckcData = fetchCKC(for: loadingRequest.request) else { loadingRequest.finishLoading(with: NSError(domain: "YourDomain", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法获取CKC"])) return false } let response = URLResponse(url: url, mimeType: "application/octet-stream", expectedContentLength: ckcData.count, textEncodingName: nil) loadingRequest.contentInformationRequest?.contentType = AVContentKeyTypeFairPlayStreaming as String loadingRequest.contentInformationRequest?.contentLength = Int64(ckcData.count) loadingRequest.dataRequest?.respond(with: ckcData) loadingRequest.finishLoading() return true }
正确配置AVAssetImageGenerator的前置流程
别直接复用播放用的AVURLAsset就开始生成缩略图,要确保asset加载完成后再操作:
- 确保AVURLAsset已关联AVContentKeySession,并且resourceLoader代理已设置
- 调用
loadValuesAsynchronously(forKeys:)等待asset的playable状态加载完成,再初始化AVAssetImageGenerator - 示例流程代码:
let hlsURL = URL(string: "你的FairPlay加密HLS地址")! let asset = AVURLAsset(url: hlsURL) asset.resourceLoader.setDelegate(self, queue: DispatchQueue(label: "com.yourdomain.resourceLoader")) // 关联密钥会话 contentKeySession.addContentKeyRecipient(asset) // 等待asset加载完成 asset.loadValuesAsynchronously(forKeys: ["playable"]) { var error: NSError? let status = asset.statusOfValue(forKey: "playable", error: &error) guard status == .loaded else { print("Asset加载失败:\(error?.localizedDescription ?? "未知错误")") return } // 初始化生成器并生成缩略图 let generator = AVAssetImageGenerator(asset: asset) generator.appliesPreferredTrackTransform = true // 保证缩略图方向正确 let targetTime = CMTimeMakeWithSeconds(10, preferredTimescale: 600) // 取第10秒的帧 generator.generateCGImagesAsynchronously(forTimes: [NSValue(time: targetTime)]) { _, image, _, result, error in if let error = error { print("生成缩略图失败:\(error.localizedDescription)") return } if result == .succeeded, let thumbnailImage = image { // 处理生成的缩略图 } } }
额外检查点
- 确认I-Frame播放列表的所有片段请求都能被resourceLoader拦截到,有些I-Frame片段的URL路径可能和主视频不同,需要确保拦截逻辑覆盖到
- 检查CKC是否针对I-Frame的密钥请求正确生成,即使
#EXT-X-KEY标签一致,部分服务端可能对I-Frame请求返回不同的资源ID,需要确保你的CKC获取逻辑能正确匹配
为什么AVPlayerViewController可以正常工作
AVPlayerViewController内部会复用AVPlayer的密钥会话,并且系统对其缩略图生成流程做了封装,自动处理了I-Frame加载和密钥请求的所有环节,不需要手动干预。
内容的提问来源于stack exchange,提问作者Maik
相关产品推荐
相关产品推荐

