AVPlayer播放受保护HLS流时令牌过期该如何更新?
AVPlayer 短有效期令牌HLS流401中断问题修复方案
原有实现失效核心原因
- 未正确触发
AVAssetResourceLoader回调:默认http/httpsscheme的HLS资源,AVPlayer会走系统默认加载逻辑,不会调用你实现的代理方法,修改header自然不生效。必须将初始AVURLAsset的scheme替换为自定义scheme,系统才会把所有资源请求交给自定义代理处理。 - 未完整填充
AVAssetResourceLoadingRequest的必填字段:你只返回了请求数据,没有填充内容信息相关字段,AVPlayer会忽略自定义请求的结果,继续走默认请求流程,必然返回401。 - 重复创建URLSession、每次请求都强制刷新令牌属于不必要的性能开销,会提升请求失败概率。
修复步骤
第一步:初始化AVURLAsset时替换为自定义scheme
// 原始播放地址 let originalURL = URL(string: "https://your-protected-hls-url.m3u8")! // 替换为自定义scheme var components = URLComponents(url: originalURL, resolvingAgainstBaseURL: false) components?.scheme = "custom-https" let customURL = components?.url! // 初始化asset并设置代理 let asset = AVURLAsset(url: customURL!) asset.resourceLoader.setDelegate(self, queue: DispatchQueue(label: "com.yourproject.resourceLoaderQueue"))
第二步:修正resourceLoader代理实现
// 缓存当前有效的令牌,避免每次请求都刷新 private var validTokenCookie: String? private let sharedSession = URLSession(configuration: .default) func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool { // 把自定义scheme换回真实的https guard let customURL = loadingRequest.request.url, var components = URLComponents(url: customURL, resolvingAgainstBaseURL: false) else { loadingRequest.finishLoading(with: NSError(domain: "ResourceLoaderError", code: -1, userInfo: nil)) return false } components.scheme = "https" guard let realURL = components.url else { loadingRequest.finishLoading(with: NSError(domain: "ResourceLoaderError", code: -2, userInfo: nil)) return false } var request = URLRequest(url: realURL) request.allHTTPHeaderFields = loadingRequest.request.allHTTPHeaderFields // 按需刷新令牌,这里可以加令牌有效期判断逻辑,避免重复刷新 let needRefreshToken = (validTokenCookie == nil || isTokenExpired()) let requestBlock = { [weak self] in guard let self = self else { return } request.setValue(self.validTokenCookie, forHTTPHeaderField: "Cookie") self.sharedSession.dataTask(with: request) { data, response, error in if let error = error { loadingRequest.finishLoading(with: error) return } guard let httpResponse = response as? HTTPURLResponse, let data = data else { loadingRequest.finishLoading(with: NSError(domain: "ResourceLoaderError", code: -3, userInfo: nil)) return } // 处理401情况,刷新令牌后重试 if httpResponse.statusCode == 401 { self.validTokenCookie = nil service.networkService.refreshToken { result in // 刷新成功后更新令牌,重新执行请求 if let newCookie = result.cookie { self.validTokenCookie = newCookie requestBlock() } else { loadingRequest.finishLoading(with: NSError(domain: "ResourceLoaderError", code: -4, userInfo: nil)) } } return } // 填充必填的content信息 if let infoRequest = loadingRequest.contentInformationRequest { infoRequest.contentType = httpResponse.mimeType infoRequest.expectedContentLength = httpResponse.expectedContentLength infoRequest.isByteRangeAccessSupported = httpResponse.allHeaderFields["Accept-Ranges"] as? String == "bytes" } // 返回数据 loadingRequest.dataRequest?.respond(with: data) loadingRequest.finishLoading() }.resume() } if needRefreshToken { service.networkService.refreshToken { [weak self] result in guard let self = self, let newCookie = result.cookie else { loadingRequest.finishLoading(with: NSError(domain: "ResourceLoaderError", code: -4, userInfo: nil)) return } self.validTokenCookie = newCookie requestBlock() } } else { requestBlock() } return true }
注意事项
- 所有HLS相关请求包括播放列表刷新、分片请求、加密密钥请求都会自动走这个代理,不需要手动修改asset内部的任何参数,所有请求都会自动携带最新的有效令牌,不会再出现401中断问题。
- 如果服务端返回302重定向,你需要自行处理重定向逻辑,拿到最终响应数据后再返回给loadingRequest。
- 可以根据业务需要调整令牌刷新的判断逻辑,比如提前30秒刷新令牌,避免请求到一半才发现令牌过期导致卡顿。
内容的提问来源于stack exchange,提问作者Abyss Lurker
相关产品推荐
相关产品推荐

