You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现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加载完成后再操作:

  1. 确保AVURLAsset已关联AVContentKeySession,并且resourceLoader代理已设置
  2. 调用loadValuesAsynchronously(forKeys:)等待asset的playable状态加载完成,再初始化AVAssetImageGenerator
  3. 示例流程代码:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 09:08:20