Swift中FairPlay DRM播放求助:AVAssetResourceDelegate未调用
搞定FairPlay DRM的ResourceLoaderDelegate触发与m3u8加载问题
我看了你遇到的问题:用HTTPS URL创建AVURLAsset时,AVAssetResourceLoaderDelegate完全不触发;改成自定义scheme(比如drm)后代理终于调用了,但又拿不到m3u8文件。这其实是AVFoundation的默认行为导致的——系统会直接处理HTTPS协议的请求,不会走自定义ResourceLoader;而自定义scheme的请求,AVFoundation不知道怎么解析,得我们手动转发回HTTPS获取媒体内容。
下面是修复后的完整代码,我给你标了关键修改点:
import Foundation import AVKit import os.log public struct DRMVideoData{ var drmKey: String? var proxyFairPlay: String var fileFairPlay: String var idVideo: String } class VODDRMImplementation: NSObject, AVAssetResourceLoaderDelegate { let domain = "DRMDelegate.ContentKeyQueue" let contentKeyDelegateQueue = DispatchQueue(label: "DRMDelegate.ContentKeyQueue") var drmData: DRMVideoData? // 存储原始HTTPS视频地址,后续用来转换自定义scheme请求 private var originalHttpsUrl: URL? func startPlayerWithDRM(_ videoDRM: DRMVideoData,_ player: AVPlayer?,_ playerLayer: AVPlayerLayer?, c: @escaping (AVPlayer?, AVPlayerLayer?) -> Void) { guard let originalUrl = URL(string: videoDRM.fileFairPlay) else { c(nil, nil) return } self.originalHttpsUrl = originalUrl // 替换scheme为自定义的"drm",确保ResourceLoader能被触发 var urlComponents = URLComponents(url: originalUrl, resolvingAgainstBaseURL: false)! urlComponents.scheme = "drm" guard let drmSchemeUrl = urlComponents.url else { c(nil, nil) return } self.drmData = videoDRM let asset = AVURLAsset(url: drmSchemeUrl) asset.resourceLoader.setDelegate(self, queue: self.contentKeyDelegateQueue) let playerItem = AVPlayerItem(asset: asset) let player = AVPlayer(playerItem: playerItem) let playerLayer = AVPlayerLayer(player: player) player.pause() c(player, playerLayer) } func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool { guard let drmSchemeUrl = loadingRequest.request.url, let originalHttpsUrl = self.originalHttpsUrl else { os_log("DRM: 请求URL或原始HTTPS地址无效", log: .default, type: .error) loadingRequest.finishLoading(with: NSError(domain: domain, code: -1, userInfo: nil)) return false } // 第一步:处理m3u8/ts媒体文件请求——转换回HTTPS协议获取内容 if drmSchemeUrl.pathExtension == "m3u8" || drmSchemeUrl.pathExtension == "ts" { var originalRequestComponents = URLComponents(url: originalHttpsUrl, resolvingAgainstBaseURL: false)! originalRequestComponents.path = drmSchemeUrl.path originalRequestComponents.query = drmSchemeUrl.query guard let httpsRequestUrl = originalRequestComponents.url else { loadingRequest.finishLoading(with: NSError(domain: domain, code: -2, userInfo: nil)) return false } var httpsRequest = URLRequest(url: httpsRequestUrl) httpsRequest.allHTTPHeaderFields = loadingRequest.request.allHTTPHeaderFields let session = URLSession(configuration: .default) let task = session.dataTask(with: httpsRequest) { data, response, error in if let error = error { os_log("DRM: 获取媒体内容失败:%@", log: .default, type: .error, error.localizedDescription) loadingRequest.finishLoading(with: error as NSError) return } guard let response = response, let data = data else { os_log("DRM: 媒体请求无响应或数据", log: .default, type: .error) loadingRequest.finishLoading(with: NSError(domain: self.domain, code: -3, userInfo: nil)) return } // 把HTTPS获取到的内容返回给AVFoundation loadingRequest.contentInformationProvider?.contentType = (response as? HTTPURLResponse)?.mimeType loadingRequest.contentInformationProvider?.contentLength = response.expectedContentLength loadingRequest.dataRequest?.respond(with: data) loadingRequest.finishLoading() } task.resume() return true } // 第二步:处理DRM密钥请求(SPC -> CKC) guard let drmData = drmData else { os_log("DRM: DRM配置数据缺失", log: .default, type: .error) loadingRequest.finishLoading(with: NSError(domain: domain, code: -4, userInfo: nil)) return false } do { guard let certificateData = getCertificateFromServer() else { os_log("DRM: 加载证书失败", log: .default, type: .error) loadingRequest.finishLoading(with: NSError(domain: domain, code: -5, userInfo: nil)) return false } let contentId = drmData.idVideo guard let contentIdData = contentId.data(using: .utf8), let spcData = try? loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData, options: nil), let dataRequest = loadingRequest.dataRequest else { os_log("DRM: 生成SPC数据失败", log: .default, type: .error) loadingRequest.finishLoading(with: NSError(domain: domain, code: -6, userInfo: nil)) return false } guard let ksmServerUrl = URL(string: drmData.proxyFairPlay) else { os_log("DRM: KSM服务器地址无效", log: .default, type: .error) loadingRequest.finishLoading(with: NSError(domain: domain, code: -7, userInfo: nil)) return false } // 注意:多数KSM服务器要求用POST传递SPC数据,原代码用GET可能有问题,根据你的KSM配置调整 var ksmRequest = URLRequest(url: ksmServerUrl) ksmRequest.httpMethod = "POST" ksmRequest.httpBody = spcData let session = URLSession(configuration: .default) let task = session.dataTask(with: ksmRequest) { data, response, error in if let error = error { os_log("DRM: 获取CKC密钥失败:%@", log: .default, type: .error, error.localizedDescription) loadingRequest.finishLoading(with: error as NSError) return } guard let ckcData = data else { os_log("DRM: 未收到CKC数据", log: .default, type: .error) loadingRequest.finishLoading(with: NSError(domain: self.domain, code: -8, userInfo: nil)) return } dataRequest.respond(with: ckcData) loadingRequest.finishLoading() } task.resume() return true } catch { os_log("DRM: 处理DRM请求出错:%@", log: .default, type: .error, error.localizedDescription) loadingRequest.finishLoading(with: error as NSError) return false } } func getCertificateFromServer() -> Data? { // 修复原代码的路径错误:本地文件路径要用fileURLWithPath,不能用URL(string:) guard let filePath = Bundle.main.path(forResource: "privatekey", ofType: "pem"), let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else { os_log("DRM: 从Bundle加载证书失败", log: .default, type: .error) return nil } return data } }
关键修改说明
- 存储原始HTTPS URL:在初始化时保存视频的原始HTTPS地址,后续把自定义scheme的请求转换回HTTPS,让系统能正确获取m3u8和媒体片段
- 区分请求类型:通过文件扩展名判断是媒体请求还是DRM密钥请求,分别处理——媒体请求转发回HTTPS,密钥请求走原有DRM逻辑
- 修复证书加载路径:原代码用
URL(string: filePath!)加载本地文件是错误的,本地路径必须用URL(fileURLWithPath: filePath) - 调整KSM请求方法:多数KSM服务器要求用POST接收SPC数据,原代码的GET请求可能导致KSM无法解析,记得根据你的KSM配置确认
额外注意事项
- 测试时打开日志输出,方便定位每一步的问题
- 确认你的KSM服务器能正确处理SPC数据并返回CKC,可以先用Postman测试KSM接口
- 如果自定义scheme遇到权限问题,可在Info.plist的
LSApplicationQueriesSchemes中添加drm(一般AVAssetResourceLoader处理不需要,但保险起见可以加上)
内容的提问来源于stack exchange,提问作者pkesaj
相关产品推荐
相关产品推荐

