iOS邊下邊播總結(jié)

概述

最近修改了項(xiàng)目中的視頻播放功能, 由之前的全量下載完再播, 改為了邊下邊播的方式. 由于我們項(xiàng)目中的視頻在發(fā)出時(shí)都進(jìn)行了加密, 所以整個(gè)過程其實(shí)就是邊下載邊解密邊播放.

邊下邊播的技術(shù)方案, 網(wǎng)上的博客很容易搜到, 不外乎兩種方式, 內(nèi)置本地代理服務(wù)器AVAssetResourceLoader. 我們采取了系統(tǒng)提供的AVAssetResourceLoader這一方案.

方案原理

具體的AVAssetResourceLoader實(shí)現(xiàn)原理網(wǎng)上可以找到很多邏輯圖, 如下圖(來自網(wǎng)絡(luò))所示.

原理圖.png

這里結(jié)合我們的實(shí)際代碼簡單的介紹一個(gè)這個(gè)圖片.

在平時(shí)使用AVPlayer播放url時(shí), 我們會(huì)這樣創(chuàng)建一個(gè)播放器(簡略)

let videoAsset = AVURLAsset(url: "http://resource_url/xxxxx")
let item = AVPlayerItem(asset: videoAsset)
let player = AVPlayer(playerItem: item)

如果我們這樣設(shè)置播放, 整個(gè)播放的內(nèi)部流程其實(shí)都我們都是不可見的, 視頻的下載和緩存等, 我們只能通過已知的一些方法,來控制播放器的播放暫停等.

如果想要實(shí)現(xiàn)我們項(xiàng)目中想要的效果, 邊下載邊播放, 同時(shí), 我們可能需要接手視頻的緩存這一模塊, 所以我們就必須得能進(jìn)入到整個(gè)播放流程中, AVAssetResourceLoader其實(shí)就算是蘋果給我們留的一個(gè)小口子, 然后通過設(shè)置遵守AVAssetResourceLoaderDelegate這一協(xié)議的代理對象, 接手?jǐn)?shù)據(jù)處理的這一過程(包括獲取數(shù)據(jù)和向播放器填充數(shù)據(jù)).

videoAsset.resourceLoader.setDelegate(self, queue: queue)

注意事項(xiàng)

  1. 要進(jìn)入到 AVAssetResourceLoader的代理回調(diào), 除了要給videoAsset.resourceLoader設(shè)置delegate之外, 還需要把我們的url改為不能識別的scheme. 我們一遍的資源路徑都是http或者h(yuǎn)ttps, 我們需要把url的scheme改為不能識別的(私有的), 比如http://resource/xxx/xxx.mp4改為http-prefix://reource/xxxx/xxx.mp4
  2. url路徑的最后必須要有視頻的后綴, 類似.mp4, 我之前使用的資源路徑是沒有后綴的, 導(dǎo)致了播放器無法起播.

AVAssetResourceLoaderDelegate

AVAssetResourceLoaderDelegate有兩個(gè)常用的回調(diào)方法如下

// MARK: - AVAssetResourceLoaderDelegate
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {}

func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {}

當(dāng)播放器開始播放的時(shí)候, 會(huì)通過shouldWaitForLoadingOfRequestedResource這個(gè)回調(diào)方法向我們索要數(shù)據(jù), 具體所要數(shù)據(jù)的信息細(xì)節(jié)都封裝在loadingRequest里面.
因?yàn)檫@個(gè)回調(diào)會(huì)走很多次, 上圖中表示的是要保存起來每一次的loadingRequest, 但在實(shí)際項(xiàng)目中, 我使用了不太一樣的策略, 我把每一次loadingRequest都對應(yīng)一個(gè)worker對象來處理, 這樣每次索要數(shù)據(jù), 都有一個(gè)單獨(dú)的worker來處理相對應(yīng)的網(wǎng)絡(luò)請求(暫不考慮緩存), 這樣比較條理. 同時(shí)我們也需要保存起我們的worker, 因?yàn)槿绻シ牌餍枰С诌M(jìn)度條拖動(dòng)時(shí), 需要手動(dòng)seek到某一個(gè)位置, 這樣會(huì)觸發(fā)didCancel這個(gè)回調(diào), 所以我們也需要把我們對應(yīng)的worker內(nèi)部停掉.

原理圖mark.png

回調(diào)處理

當(dāng)我們收到一個(gè)回調(diào)時(shí), 我們主要關(guān)注這個(gè)AVAssetResourceLoadingRequest類型的loadingRequest.

他內(nèi)部有一個(gè)dataRequest屬性, dataRequest中有requestedOffset, requestedLength等一些有用信息. 我們通過requestedOffset和requestedLength構(gòu)建出我們的Range, 塞到請求頭里面去, 獲取相應(yīng)range的數(shù)據(jù).

當(dāng)我們的player開始播放時(shí), 收到的第一個(gè)回調(diào), requestedOffset=0,requestedLength=2, 也就是索要0-1這兩個(gè)字節(jié), 這次請求其實(shí)可以理解為一個(gè)嗅探請求, 目的是為了得到視頻的相關(guān)信息, 文件大小, 類型等.

guard request.contentInformationRequest == nil else {
    if request.dataRequest?.requestsAllDataToEndOfResource == false {
        request.contentInformationRequest?.contentLength = totalLen
    } else {
        request.contentInformationRequest?.contentLength = Int64(data.count)
   }
   request.contentInformationRequest?.isByteRangeAccessSupported = true
   request.contentInformationRequest?.contentType = "video/mp4"
   request.finishLoading()
   return
}

上述代碼就是第一個(gè)嗅探請求的處理方式, 通過request.contentInformationRequest==nil, 判斷出是第一個(gè)嗅探請求, 然后我們需要填充request的contentInformationRequest, 然后填充信息結(jié)束調(diào)用finishLoading(), 當(dāng)前的loadingRequest就結(jié)束了.

第一個(gè)嗅探請求結(jié)束后, 如果我們返回的沒有問題, 那播放器會(huì)立刻進(jìn)行下一個(gè)回調(diào), 開始所要視頻數(shù)據(jù), 我在項(xiàng)目中測試時(shí), 第二個(gè)請求一般都是0-xxx(文件大小-1), 索要整個(gè)文件, 這時(shí)我們dataTask類型的請求,等待服務(wù)器一片一片的返回?cái)?shù)據(jù), 沒收到一部分?jǐn)?shù)據(jù)后調(diào)用dataRequest.respond(with: data), 全部收取完畢之后調(diào)用request.finishLoading().

其實(shí)這就是最基本的數(shù)據(jù)填充的邏輯, 除了第一個(gè)嗅探請求特殊處理一下, 后面的就是收到數(shù)據(jù), 就填充回dataRequest, 索要的數(shù)據(jù)全部填充完畢, 調(diào)用finishLoading.

在我們請求整個(gè)文件的過程中, 有時(shí)候會(huì)發(fā)現(xiàn)一種現(xiàn)象, 就是respond一部分?jǐn)?shù)據(jù)之后, loadingRequest被cancel了, 然后又開始索要很后面的range的數(shù)據(jù), 其實(shí)這可以理解為一個(gè)尋找文件的moov的過程, 文件的moov可能在文件頭, 也可能在文件尾部.moov里面定義視頻的時(shí)間尺度,時(shí)長,顯示特性以及每個(gè)軌道信息等, 這一部分可以通過了解mp4文件頭格式來多做一下了解.

我們不管他索要的是那一部分?jǐn)?shù)據(jù), 只要我們請求到對應(yīng)的數(shù)據(jù), respond回去就沒問題.

補(bǔ)充

那這么簡單的邏輯對于我們自己的項(xiàng)目來說難點(diǎn)是什么呢,這里簡單描述一下.

前面有說到我們項(xiàng)目中的資源都是經(jīng)過加密的, 使用了AES的加密算法, 這樣我們在接受到數(shù)據(jù)之后, 是不能直接返回給dataRequest的, 需要我們先解密, 然后簡單的說我們使用的加密策略是每16字節(jié)是一個(gè)加密片段, 但請求返回的數(shù)據(jù)并不能保證每次都是16倍數(shù), 所以我們處理16的倍數(shù)才能進(jìn)行解密這一個(gè)問題, 然后還有一個(gè)range的修正問題, 打比方我們需要1-10這10個(gè)字節(jié)的數(shù)據(jù), 但是我請求頭的range是不能直接寫1-10的, 因?yàn)榘凑瘴覀兠?6個(gè)字節(jié)是一個(gè)加密片段, 我們需要的1-10, 在0-15這個(gè)片段中, 所以我們必須要先請求下來0-15這一個(gè)片段, 然后解密, 再從中拿出1-10, 填充回去. 當(dāng)然了還有一些細(xì)節(jié)就不展開敘述了, 等有機(jī)會(huì)結(jié)合項(xiàng)目單獨(dú)聊一聊AES的加解密方法.

總結(jié)

上面就是在實(shí)現(xiàn)邊下邊播過程中總結(jié)到的一些小點(diǎn), 當(dāng)然每個(gè)人在實(shí)際項(xiàng)目可能會(huì)遇到不一樣的問題. 同時(shí)本文沒有涉及到數(shù)據(jù)的緩存, github上也有很多不錯(cuò)的緩存方案, 大家可以看看.

感謝閱讀.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市院喜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖邦投,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熄云,死亡現(xiàn)場離奇詭異艺糜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)赶熟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陷嘴,“玉大人映砖,你說我怎么就攤上這事≡职ぃ” “怎么了邑退?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長劳澄。 經(jīng)常有香客問我地技,道長,這世上最難降的妖魔是什么秒拔? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任莫矗,我火速辦了婚禮,結(jié)果婚禮上砂缩,老公的妹妹穿的比我還像新娘作谚。我一直安慰自己,他們只是感情好庵芭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布妹懒。 她就那樣靜靜地躺著,像睡著了一般双吆。 火紅的嫁衣襯著肌膚如雪眨唬。 梳的紋絲不亂的頭發(fā)上会前,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機(jī)與錄音单绑,去河邊找鬼回官。 笑死,一個(gè)胖子當(dāng)著我的面吹牛搂橙,可吹牛的內(nèi)容都是我干的歉提。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼区转,長吁一口氣:“原來是場噩夢啊……” “哼苔巨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起废离,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤侄泽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蜻韭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悼尾,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年肖方,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闺魏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俯画,死狀恐怖析桥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情艰垂,我是刑警寧澤泡仗,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站猜憎,受9級特大地震影響娩怎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拉宗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一峦树、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧旦事,春花似錦魁巩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至卖鲤,卻和暖如春肾扰,著一層夾襖步出監(jiān)牢的瞬間畴嘶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工集晚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窗悯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓偷拔,卻偏偏與公主長得像蒋院,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子莲绰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容