采用系統(tǒng)AVPlayer組件來(lái)播放,對(duì)于需要手動(dòng)處理加密的hls視頻檩小,通過(guò)AVAssetResourceLoader攔截代理方法來(lái)手動(dòng)代理系統(tǒng)的m3u8文件下載、解密key下載等妆够。
蘋果的hls有自己的標(biāo)準(zhǔn)识啦,注意參考標(biāo)準(zhǔn)來(lái)實(shí)現(xiàn),否則可能播不出來(lái)神妹,而且很難排查原因颓哮,參考文檔:https://developer.apple.com/documentation/http-live-streaming/hls-authoring-specification-for-apple-devices?language=objc
我們?nèi)绾稳ブ鲃?dòng)控制播放過(guò)程中m3u8文件下載、解密key下載等邏輯呢鸵荠?
- 通常情況下冕茅,只要你設(shè)置了正確的播放url,AVAssetResourceLoader怎么都不會(huì)調(diào)用下面的代理方法蛹找,即使url報(bào)錯(cuò)也不會(huì)姨伤,無(wú)法訪問(wèn)也不會(huì)。不知道為什么這么設(shè)計(jì)??
想要觸發(fā)下面的代理調(diào)用庸疾,只有把設(shè)置進(jìn)去的url改成非url的形式才行乍楚,比如去掉url前面http/https,這樣系統(tǒng)就會(huì)調(diào)用下面代理方法了届慈,就有操作空間了
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool
- 通過(guò)let url = loadingRequest.request.url?.absoluteString拿到系統(tǒng)當(dāng)前要執(zhí)行的url徒溪,判斷url的后綴來(lái)實(shí)現(xiàn)邏輯扭轉(zhuǎn)。
- 判斷如果是播放url
// 自己下載m3u8文件金顿,
下載完成block里面的代碼: {
// 下載完成后塞給系統(tǒng)
loadingRequest.dataRequest?.respond(with: newData!)
// 通知系統(tǒng)請(qǐng)求結(jié)束
loadingRequest.finishLoading()
}
// 通知系統(tǒng)等待
return true
如果你的hls視頻加密是自定義的臊泌,比如秘鑰下載完是加密的,那就要在m3u8文件里面#EXT-X-KEY:METHOD:后面的URI給改成非url揍拆,這樣系統(tǒng)就會(huì)調(diào)用這個(gè)代理渠概,你自己去處理秘鑰下載然后解密的邏輯,自己下載完以上面同樣方式塞給系統(tǒng)嫂拴。
視頻切片文件下載的邏輯不能用上面方式來(lái)實(shí)現(xiàn)播揪,采用同樣方式,塞Data給系統(tǒng)筒狠,系統(tǒng)也不會(huì)播放剪芍,原因未知。系統(tǒng)只接受url重定向窟蓝。
因此碰到因?yàn)橐粢曨l編碼問(wèn)題,直接播放視頻切片文件播放不了的時(shí)候,目前我采用的是本地服務(wù)器的形式實(shí)現(xiàn)运挫,即把切片的url給改成非url状共,然后采用上面同樣的方式,手動(dòng)把視頻下載下來(lái)谁帕,如果加密了還需要解密峡继,然后使用ffmpeg轉(zhuǎn)碼,轉(zhuǎn)碼完成后匈挖,如果m3u8包含加密的碾牌,還需要再加密回去,(因?yàn)锳VAssetResourceLoader解密邏輯是內(nèi)部進(jìn)行的)儡循,然后啟動(dòng)本地服務(wù)器舶吗,把url重定向到本地視頻切片文件url
// 下載切片文件完成回調(diào): {
let localUrl = "http://localhost:port/" + fileName
if let u = URL(string: localUrl) {
let redirect = URLRequest(url: u)
loadingRequest.redirect = redirect
//設(shè)置需要重定向
loadingRequest.response = HTTPURLResponse(url: u, statusCode: 301, httpVersion: nil, headerFields: nil)
loadingRequest.finishLoading()
}
}