iOS 視頻邊下邊播(緩存陨界,預(yù)加載)


背景

在有多個(gè)視頻鏈接需要連續(xù)切換播放時(shí)巡揍,視頻播放之前要等待視頻資源加載完成,切換視頻時(shí)需要等待很久菌瘪,已經(jīng)播放過的視頻也需要重新加載才能再次播放腮敌,影響用戶體驗(yàn)阱当。

優(yōu)化點(diǎn):

  • 邊下邊播:視頻播放時(shí),不受網(wǎng)絡(luò)狀況限制糜工,播放流暢
  • 緩存:已經(jīng)播放過的視頻弊添,將視頻資源緩存在本地,再次播放時(shí)直接讀取緩存
  • 預(yù)加載:切換視頻時(shí)捌木,無縫銜接油坝,視頻秒播

實(shí)現(xiàn)方案

本地代理服務(wù)器

在iOS本地開啟Local Server服務(wù),然后使用播放控件請(qǐng)求本地Local Server服務(wù)刨裆,本地的服務(wù)再不斷請(qǐng)求視頻地址獲取視頻流澈圈,本地服務(wù)請(qǐng)求的過程中把視頻緩存到本地。

唱吧開源庫:KTVHTTPCache

使用AVAssetResourceLoader回調(diào)下載

AVAssetResourceLoader通過提供的委托對(duì)象去調(diào)節(jié)AVURLAsset所需要的加載資源帆啃,同時(shí)可以進(jìn)行數(shù)據(jù)的緩存和讀取操作瞬女。大致流程如圖:


image.png

具體實(shí)現(xiàn)

1.給AVURLAsset設(shè)置資源加載代理

AVPlayer在執(zhí)行播放的時(shí)候,就回去問這個(gè)delegate努潘,是能能夠播放這個(gè)asset诽偷。于是就可以進(jìn)行自定義的一些操作

AVURLAsset *asset = [AVURLAsset URLAssetWithURL:assetURL options:nil];
//設(shè)置代理
[asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];

2.資源下載及數(shù)據(jù)填充

找一個(gè)對(duì)象實(shí)現(xiàn) AVAssetResourceLoaderDelegate 這個(gè)協(xié)議的方法

//在加載URLAsset資源時(shí)回調(diào)
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;

在加載資源的代理方法中看看 request 里面的 url 是不是我們支持的,如果能支持就返回 YES疯坤!然后就可以一邊下視頻數(shù)據(jù)报慕,一邊塞數(shù)據(jù)給 AVPlayer 讓它顯示視頻畫面。數(shù)據(jù)交互流程圖如下:


image.png
下載視頻數(shù)據(jù)

在上面的回調(diào)方法中压怠,得到了一個(gè)AVAssetResourceLoadingRequest對(duì)象眠冈,它的主要屬性和方法:

@interface AVAssetResourceLoadingRequest : NSObject 
 
 @property (nonatomic, readonly) NSURLRequest *request;
 
 @property (nonatomic, readonly, nullable) AVAssetResourceLoadingContentInformationRequest *contentInformationRequest;
 
 @property (nonatomic, readonly, nullable) AVAssetResourceLoadingDataRequest *dataRequest ;
 
 - (void)finishLoading;
 
 - (void)finishLoadingWithError:(nullable NSError *)error;
 
 @end 

在 AVAssetResourceLoadingRequest 里面,request 代表原始的請(qǐng)求刑峡。dataRequest是數(shù)據(jù)請(qǐng)求洋闽,包含數(shù)據(jù)起始偏移量,數(shù)據(jù)長(zhǎng)度等信息突梦。
AVPlayer 是會(huì)觸發(fā)分片下載的策略诫舅,需要從dataRequest 中得到請(qǐng)求范圍的信息。
有了請(qǐng)求地址和請(qǐng)求范圍宫患,我們就可以重新創(chuàng)建一個(gè)設(shè)置了請(qǐng)求 Range 頭的 NSURLRequest 對(duì)象刊懈,讓下載器去下載這個(gè)文件的 Range 范圍內(nèi)的數(shù)據(jù)。

塞數(shù)據(jù)給AVPLayer

當(dāng) AVPlayer 觸發(fā)下載時(shí)娃闲,總是會(huì)先發(fā)起一個(gè) Range 為 0-2 的數(shù)據(jù)請(qǐng)求虚汛,這個(gè)請(qǐng)求的作用其實(shí)是用來確認(rèn)視頻數(shù)據(jù)的信息,如文件類型皇帮、文件數(shù)據(jù)長(zhǎng)度卷哩。當(dāng)下載器發(fā)起這個(gè)請(qǐng)求,收到服務(wù)端返回的 response 后属拾,我們要把視頻的信息填充到 AVAssetResourceLoadingRequest 的 contentInformationRequest 屬性中将谊,告知下載的視頻格式以及視頻長(zhǎng)度冷溶。

獲取完視頻信息后,AVAssetResourceLoader 會(huì)繼續(xù)發(fā)起之后的數(shù)據(jù)片段的請(qǐng)求尊浓,下載到的數(shù)據(jù)就可以塞給 AVAssetResourceLoadingRequest 里的 dataRequest 逞频。 dataRequest 調(diào)動(dòng)下面的方法接收下載的數(shù)據(jù),這個(gè)方法可以調(diào)用多次栋齿,接收增量連續(xù)的 data 數(shù)據(jù)苗胀。與此同時(shí)對(duì)下載數(shù)據(jù)進(jìn)行本地緩存。

 - (void)respondWithData:(NSData *)data;

當(dāng) AVAssetResourceLoadingRequest 要求的所有數(shù)據(jù)都下載完畢瓦堵,調(diào)用 - (void)finishLoading 完成下載基协。如果本次請(qǐng)求失敗,可以直接調(diào)用 - (void)finishLoadingWithError:(NSError *)error; 結(jié)束下載谷丸。

AVAssetResourceLoadingRequest 在 - (void)finishLoading 的時(shí)候堡掏,會(huì)根據(jù) contentInformationRequest 中的信息,去判斷接下去要怎么處理刨疼。例如:下載 AVURLAsset 中 URL 指向的文件,獲取到的文件的 contentType 是系統(tǒng)不支持的類型鹅龄,這個(gè) AVURLAsset 將無法正常播放揩慕。

下載重試
//在取消加載資源后回調(diào)
- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest;

AVAssetResourceLoader 在執(zhí)行加載的時(shí)候,會(huì)時(shí)不時(shí)的觸發(fā)取消下載扮休,在這個(gè)回調(diào)里面迎卤,需要取消當(dāng)前正在進(jìn)行中的下載任務(wù)。然后重新發(fā)起加載請(qǐng)求的策略玷坠。如果下載了部分蜗搔,那么重新發(fā)起的下載請(qǐng)求會(huì)從還沒有下載的部分開始肘交。

3.緩存

根據(jù)上面的 AVAssetResourceLoaderDelegate 的實(shí)現(xiàn)機(jī)制抢腐,當(dāng) AVAsset 需要加載數(shù)據(jù)時(shí)會(huì)通過 delegate 告訴外部,外部接管整個(gè)視頻下載過程薄坏。
當(dāng)我們接管了視頻下載兄渺,便可以對(duì)視頻數(shù)據(jù)做任何事情缝龄。比如:緩存、記錄下載速度挂谍、獲得下載進(jìn)度等等叔壤。

實(shí)現(xiàn)一個(gè)下載器,用 URLSession 開啟一個(gè) DataTask 請(qǐng)求數(shù)據(jù)口叙,把接收到的數(shù)據(jù)塞給 DataRequest 并寫入本地磁盤炼绘。

分片下載

在每次的loadingRequest中,都包含著本次加載請(qǐng)求的dataRequest妄田,他是一個(gè)AVAssetResourceLoadingDataRequest對(duì)象俺亮,看下他的屬性:

@interface AVAssetResourceLoadingDataRequest : NSObject

@property (nonatomic, readonly) long long requestedOffset;
@property (nonatomic, readonly) NSInteger requestedLength;

- (void)respondWithData:(NSData *)data;

@end

根據(jù)dataRequest中的信息驮捍,在創(chuàng)建下載數(shù)據(jù)的 URLRequest 時(shí)需要設(shè)置 HTTPHeader 的 Range 值

NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", fromOffset, (fromOffset + length -1)];
[request setValue:range forHTTPHeaderField:@"Range"];
取消下載

AVAsset 在加載視頻時(shí),經(jīng)常會(huì)在某次數(shù)據(jù)請(qǐng)求還沒有完成時(shí)觸發(fā)取消下載铅辞,然后發(fā)起一個(gè)新的 LoadingReqeust厌漂。所以在接到取消下載的代理回調(diào)時(shí),需要立刻停止當(dāng)前正在進(jìn)行中的下載斟珊。由于 DataRequest 的 cancel 操作是異步的苇倡,就有可能在 cancel 還未完成時(shí),下一個(gè) LoadingRequest 就已經(jīng)到來囤踩,所以還需要需要保證同一個(gè) URL 同時(shí)只存在一個(gè)下載器在下載旨椒,否則會(huì)出現(xiàn)數(shù)據(jù)混亂的問題。

分片緩存

由于AVAsset請(qǐng)求資源數(shù)據(jù)的時(shí)候堵漱,不是完整的視頻數(shù)據(jù)综慎,但是為了方便數(shù)據(jù)管理和魂村讀取,對(duì)于同一個(gè)視頻URL的數(shù)據(jù)我們應(yīng)該緩存到同一個(gè)文件中勤庐,根據(jù)range將下載到的數(shù)據(jù)拼接完整即可示惊。
對(duì)于更復(fù)雜的場(chǎng)景,比如用戶seek操作愉镰,還要處理播放進(jìn)度和已緩存數(shù)據(jù)以及還未緩存的遠(yuǎn)程數(shù)據(jù)之間的協(xié)調(diào)米罚。(我們的業(yè)務(wù)暫時(shí)不涉及到此場(chǎng)景,具體的處理方案可參考:VIMediaCache文檔

4.預(yù)加載

在當(dāng)前視頻播放時(shí)丈探,開啟下載任務(wù)录择,提前將后面的視頻資源下載并緩存到本地。需要切換視頻時(shí)碗降,根據(jù)loadingRequest的url判斷本地是否已經(jīng)緩存了這個(gè)視頻的數(shù)據(jù)隘竭,根據(jù)range從本地讀取數(shù)據(jù)填充到dataRequest中。如果本地沒有緩存讼渊,從上面第2步动看,走邊下邊播邏輯。

不足與展望

現(xiàn)在的預(yù)加載處理方式是精偿,提前下載后續(xù)幾條視頻完整的視頻數(shù)據(jù)弧圆,因此預(yù)加載的任務(wù)量大,耗時(shí)長(zhǎng)笔咽。切換視頻時(shí)搔预,可能預(yù)加載的任務(wù)還沒有完成就被提前終止,然后又開始新的預(yù)加載叶组。
最好的處理方式是拯田,預(yù)加載的視頻,只下載開頭的一部分?jǐn)?shù)據(jù)緩存甩十,到播放這條視頻的時(shí)候再邊下邊播剩余的數(shù)據(jù)船庇。這里就涉及到這樣一個(gè)場(chǎng)景吭产,如下圖示:


image.png

對(duì)于這次的loadingRequest,我們需要從本地緩存中讀取一段數(shù)據(jù)鸭轮,再從遠(yuǎn)端下載一部分?jǐn)?shù)據(jù)臣淤,最后將兩部分?jǐn)?shù)據(jù)合并填充給dataRequest。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窃爷,一起剝皮案震驚了整個(gè)濱河市邑蒋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌按厘,老刑警劉巖医吊,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異逮京,居然都是意外死亡卿堂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門懒棉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來草描,“玉大人,你說我怎么就攤上這事策严√罩椋” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵享钞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我诀蓉,道長(zhǎng)栗竖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任渠啤,我火速辦了婚禮狐肢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沥曹。我一直安慰自己份名,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布妓美。 她就那樣靜靜地躺著僵腺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪壶栋。 梳的紋絲不亂的頭發(fā)上辰如,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音贵试,去河邊找鬼琉兜。 笑死凯正,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的豌蟋。 我是一名探鬼主播廊散,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼梧疲!你這毒婦竟也來了允睹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤往声,失蹤者是張志新(化名)和其女友劉穎擂找,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浩销,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贯涎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了慢洋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塘雳。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖普筹,靈堂內(nèi)的尸體忽然破棺而出败明,到底是詐尸還是另有隱情,我是刑警寧澤太防,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布妻顶,位于F島的核電站,受9級(jí)特大地震影響蜒车,放射性物質(zhì)發(fā)生泄漏讳嘱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一酿愧、第九天 我趴在偏房一處隱蔽的房頂上張望沥潭。 院中可真熱鬧,春花似錦嬉挡、人聲如沸钝鸽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拔恰。三九已至,卻和暖如春焊夸,著一層夾襖步出監(jiān)牢的瞬間仁连,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留饭冬,地道東北人使鹅。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像昌抠,于是被迫代替她去往敵國和親患朱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345