日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

AVPlayer 音视频缓存方案

發(fā)布時(shí)間:2023/12/31 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 AVPlayer 音视频缓存方案 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文的主要內(nèi)容?

  • 理解 AVAssetResourceLoaderDelegate 的使用?

  • 緩存下載的實(shí)現(xiàn)?

  • VIMediaCache 提供了哪些 API

接下來會(huì)介紹通過使用 AVAssetResourceLoader,在不改變 AVPlayer API 的情況下,對播放的音視頻進(jìn)行緩存。

前戲

現(xiàn)在市場上各種各樣的應(yīng)用,充滿了多媒體信息,而聲音和視頻又是體積最大的文件,如果直接使用 URL 通過 AVPlayer 播放,系統(tǒng)并不會(huì)做緩存處理,等下次再播又要重新下載,對網(wǎng)絡(luò)狀況差的用戶來說這就是災(zāi)難。若是下載好再播,同樣要等待全部下載完成,也是很痛苦。

我們最理想的緩存方案是:邊播放,邊緩存。

我在早期加入美拍團(tuán)隊(duì)的時(shí)候,實(shí)際上已經(jīng)有了邊下邊播的功能,當(dāng)時(shí)選擇了使用 HTTPServer,在本地開啟一個(gè) http 服務(wù)器,把需要緩存的請求地址指向本地服務(wù)器,并帶上真正的 url 地址。

早期的美拍都是不到 20s 的短視頻,后面加長了視頻時(shí)間,但考慮到用戶設(shè)備容量問題,我們只對短視頻做視頻緩存。一直發(fā)展到現(xiàn)在,平臺(tái)上現(xiàn)在大多數(shù)的視頻都是長視頻,真正使用到緩存功能的頻率已經(jīng)很低。那么問題就來了,HTTPServer 不管我們有沒有使用緩存功能,都要在應(yīng)用打開的時(shí)候默默開啟,這真的是很浪費(fèi)了。并且我們引入 HTTPServer 庫也會(huì)增加一些包體積。

理解 AVAssetResourceLoaderDelegate 的使用

那么在一段尋覓之下,發(fā)現(xiàn)了最適合做邊下邊播緩存的工具。AVAssetResourceLoaderDelegate:一個(gè) iOS 6 就被開放出來,專門用來處理 AVAsset 加載的工具。

AVURLAsset *urlAsset = ... [urlAsset.resourceLoader setDelegate:<AVAssetResourceLoaderDelegate> queue:dispatch_get_main_queue()];

只要找一個(gè)對象實(shí)現(xiàn)了?AVAssetResourceLoaderDelegate?這個(gè)協(xié)議的方法,丟給 asset,再把 asset 丟給 AVPlayer,AVPlayer 在執(zhí)行播放的時(shí)候就會(huì)去問這個(gè) delegate:喂,你能不能播放這個(gè) url 啊?然后會(huì)觸發(fā)下面這個(gè)方法:

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest

我們在這個(gè)方法中看看 request 里面的 url 是不是我們支持的,如果能支持就返回 YES!然后就可以開心的一邊下視頻數(shù)據(jù),一邊塞數(shù)據(jù)給 AVPlayer 讓它顯示視頻畫面。

先不管下載和緩存,實(shí)現(xiàn)上,可以分為兩步:1. 需要知道如何請求數(shù)據(jù),url 是什么,下載多少數(shù)據(jù)。2. 下載好的數(shù)據(jù)怎么塞給 AVPlayer

1. 如何請求數(shù)據(jù)

在上面的回調(diào)方法中,會(huì)得到一個(gè)?AVAssetResourceLoadingRequest?對象,它里面的屬性和方法不多,為了減少干擾,我精簡了一下這個(gè)類的頭文件,只留下我們會(huì)用到以及需要解釋的屬性和方法:

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

在?AVAssetResourceLoadingRequest?里面,request?代表原始的請求,由于 AVPlayer 是會(huì)觸發(fā)分片下載的策略,還需要從dataRequest?中得到請求范圍的信息。有了請求地址和請求范圍,我們就可以重新創(chuàng)建一個(gè)設(shè)置了請求 Range 頭的 NSURLRequest 對象,讓下載器去下載這個(gè)文件的 Range 范圍內(nèi)的數(shù)據(jù)。

2. 塞數(shù)據(jù)給 AVPlayer

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

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

獲取完視頻信息后,會(huì)收到剛才指定的 2 Byte 的 data 數(shù)據(jù),下載到的數(shù)據(jù)怎么辦? 可以塞給?AVAssetResourceLoadingRequest?里的?dataRequest?。?dataRequest?里面用?- (void)respondWithData:(NSData *)data;?專門用來接收下載的數(shù)據(jù),這個(gè)方法可以調(diào)用多次,接收增量連續(xù)的 data 數(shù)據(jù)。

當(dāng)?AVAssetResourceLoadingRequest?要求的所有數(shù)據(jù)都下載完畢,調(diào)用?- (void)finishLoading?完成下載,AVAssetResourceLoader?會(huì)繼續(xù)發(fā)起之后的數(shù)據(jù)片段的請求。如果本次請求失敗,可以直接調(diào)用?- (void)finishLoadingWithError:(nullable NSError *)error;?結(jié)束下載。

流程圖

完整實(shí)現(xiàn)的主流程是這樣的

重試機(jī)制

在實(shí)際的測試中,發(fā)現(xiàn)AVAssetResourceLoader?在執(zhí)行加載的時(shí)候,會(huì)時(shí)不時(shí)的觸發(fā)取消下載調(diào)用?- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest,然后重新發(fā)起加載請求的策略。如果下載了部分,那么重新發(fā)起的下載請求會(huì)從還沒有下載的部分開始。

AVAssetResourceLoaderDelegate?中還有 3 個(gè)方法可以針對特殊場景做處理,不過在目前的環(huán)境中都用不到所以可以選擇不實(shí)現(xiàn)這些方法。

緩存下載的實(shí)現(xiàn)

我們已經(jīng)知道 AVAssetResourceLoaderDelegate 的實(shí)現(xiàn)機(jī)制,當(dāng) AVAsset 需要加載數(shù)據(jù)時(shí)會(huì)通過 delegate 告訴外部,外部接管整個(gè)視頻下載過程。

接管了視頻下載,便可以對視頻數(shù)據(jù)做任何事情。比如:緩存、記錄下載速度、獲得下載進(jìn)度等等。

實(shí)現(xiàn)一個(gè)下載器,就是用 URLSession 開啟一個(gè) DataTask 請求數(shù)據(jù),把接收到的數(shù)據(jù)塞給 DataRequest 并寫入本地磁盤。在實(shí)現(xiàn)下載器時(shí)主要有三個(gè)注意的點(diǎn):1. Range 請求??2. 可取消下載??3. 分片緩存

1. Range 請求

每次得到的 LoadingRequest 帶有請求數(shù)據(jù)范圍的信息,比如期望請求第 100 字節(jié)到 500 字節(jié),在創(chuàng)建 URLRequest 時(shí)需要設(shè)置 HTTPHeader 的 Range 值。

NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", fromOffset, endOffset]; [request setValue:range forHTTPHeaderField:@"Range"];

2. 可取消下載

AVAsset 在加載視頻時(shí),經(jīng)常會(huì)在某次數(shù)據(jù)請求還沒有完成時(shí)觸發(fā)取消下載,然后發(fā)起一個(gè)新的 LoadingReqeust。這個(gè)機(jī)制是 AVAsset 里的黑盒,具體邏輯無法得知,比較像是 AVAsset 的一種重試機(jī)制。 作為下載器,在收到取消通知時(shí),需要立刻停止下載。由于 DataRequest 的 cancel 操作是異步的,就有可能在 cancel 還未完成時(shí),下一個(gè) LoadingRequest 就已經(jīng)到來,所以還需要需要保證同一個(gè) URL 只能同時(shí)存在一個(gè)下載器在下載,否則會(huì)出現(xiàn)數(shù)據(jù)混亂的問題。

3. 分片緩存

如果只是單純的下載視頻,數(shù)據(jù)單調(diào)遞增,緩存處理還是比較容易。然而現(xiàn)實(shí)是用戶對 player 的 seek 操作給視頻的緩存管理帶來了巨大的挑戰(zhàn),一旦涉及到用戶操作,可能性就越多,復(fù)雜度也會(huì)越高。

沒有 seek 的情況:網(wǎng)速正常時(shí)緩存數(shù)據(jù)比播放時(shí)間走得開,正常播放;網(wǎng)速慢時(shí),播放器 loading,直到有足夠的數(shù)據(jù)量進(jìn)行播放,如果網(wǎng)速一直很慢就會(huì)播幾秒卡一下。

當(dāng)加入 seek 后會(huì)有三種可能:

  • 視頻完全下載好,這時(shí) seek 只需讀取相應(yīng)緩存

  • 視頻下載一半,用戶 seek 到未下載部分,LoadingRequest 請求的部分全部都是未下載的數(shù)據(jù)。這時(shí)需要取消正在下載的數(shù)據(jù),然后從 seek 的點(diǎn)開始下載數(shù)據(jù)。為了支持 seek 操作,下載器就需要支持分片緩存。目前使用的解決方案是下載的視頻數(shù)據(jù)會(huì)根據(jù)請求的 Range 值,把數(shù)據(jù)存儲(chǔ)到文件中對應(yīng)的偏移值位置,并且每個(gè)視頻文件都會(huì)另外再保存一個(gè)與之對應(yīng)的下載信息文件。這個(gè)信息文件會(huì)記錄當(dāng)前下載了多少數(shù)據(jù),總共有多少數(shù)據(jù),下載了哪些片段的數(shù)據(jù)等信息,之后的緩存管理會(huì)非常依賴這個(gè)配置文件。

  • 視頻被 seek 了多次,用戶 seek 到一個(gè)時(shí)間點(diǎn),LoadingRequest 請求的部分包含了已下載和未下載的部分。

這種情況是最復(fù)雜的!簡單的做法是,當(dāng)成上面的情況來處理,全部都重新下載,雖然邏輯簡單,但這個(gè)方案會(huì)下載多次同樣的數(shù)據(jù),不是最最優(yōu)解。?

我的目標(biāo)當(dāng)然是做最優(yōu)的解決方案,但也是復(fù)雜高很多的解決方案。

在收到 LoadingRequest 的請求范圍后,下載器會(huì)先獲取已經(jīng)下載的數(shù)據(jù)信息,把已下載的分片信息分別創(chuàng)建一個(gè) action,再把需要遠(yuǎn)程下載的分片數(shù)據(jù)分別創(chuàng)建一個(gè) action。最終組合就可能是 LocalAction(50-100 bytes) + RemoteAction(101-200 bytes) + LocalAction(201-300 bytes) + RemoteAction(300-400 bytes)。每一個(gè) action 會(huì)按順序獲取數(shù)據(jù)再返回給 LoadingRequest。

VIMediaCache 提供了哪些 API

基本使用

VIMediaCache?主要提供了?VIResourceLoaderManager,這個(gè)類實(shí)現(xiàn)了?AVAssetResourceLoaderDelegate,并且提供了初始化一個(gè)?AVPlayerItem?的方法,平時(shí)使用時(shí),只需用?VIResourceLoaderManager?創(chuàng)建一個(gè)?AVPlayerItem?,AVPlayer?再用這個(gè)?playerItem?初始化,AVPlayer?在播放的時(shí)候就會(huì)自動(dòng)緩存了。

VIResourceLoaderManager *resourceLoaderManager = [VIResourceLoaderManager new]; self.resourceLoaderManager = resourceLoaderManager; AVPlayerItem *playerItem = [resourceLoaderManager playerItemWithURL:url]; AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];

緩存管理

所有緩存相關(guān)的信息都在?VICacheManager?類中。目前提供了下載進(jìn)度通知、修改緩存目錄、根據(jù) url 獲取緩存地址、根據(jù) url 獲取緩存信息、計(jì)算緩存大小、清除緩存等功能。詳情可看頭文件

錯(cuò)誤回調(diào)?

在下載視頻時(shí),出現(xiàn)錯(cuò)誤無法正常下載是比較容易出現(xiàn)的。我們自己實(shí)現(xiàn)了?AVAssetResourceLoaderDelegate?在第一次請求就拋出錯(cuò)誤的話,播放器會(huì)馬上提示錯(cuò)誤狀態(tài),而如果是已經(jīng)響應(yīng)了部分?jǐn)?shù)據(jù),再拋錯(cuò)誤,AVAssetResourceLoader?會(huì)忽略錯(cuò)誤而一直處于 loading,直到超時(shí)。這種情況就比較尷尬,所以?VIResourceLoaderManager?提供了 delegate,如果內(nèi)部出現(xiàn)錯(cuò)誤,就會(huì)拋出錯(cuò)誤,再又外部業(yè)務(wù)決定是如何處理。

注意:同一時(shí)間同一個(gè) url 不能有多次下載: 由于緩存內(nèi)部實(shí)現(xiàn)是對每一個(gè) url 都共用同一個(gè)下載配置文件,如果同時(shí)有多次對同一個(gè) url 進(jìn)行下載,這個(gè)文件下載信息會(huì)被同時(shí)修改,下載信息會(huì)變得混亂。 MediaCache 內(nèi)部做了簡單的處理,如果正在下載某 url,這時(shí)再想嘗試下載同樣的 url 會(huì)直接拋出錯(cuò)誤,提示無法開始下載。

已知問題

播到一半聲音停了,視頻正常播

比較低概率,在美拍上測試時(shí)有短視頻會(huì)出現(xiàn)

弱網(wǎng)下一直loading到超時(shí),但是文件都是已經(jīng)下載好了

沒有調(diào)用 AVPlayer 的 play 在弱網(wǎng)下會(huì)造成,AVPlayerLayer 一直無法達(dá)到 readyForDisplay 的情況

以上問題暫時(shí)沒有很好的解決方案,因?yàn)?ResourceLoader 的實(shí)現(xiàn)只能做到控制緩存,但 AVPlayer 內(nèi)的具體實(shí)現(xiàn)機(jī)制并不清楚,在緩存沒有問題的情況下出現(xiàn)問題,很難去追根溯源尋找問題的根本原因。

吐槽

在實(shí)現(xiàn)?AVAssetResourceLoaderDelegate?的時(shí)候,文檔非常少,幾乎只能一邊看頭文件中的文檔一邊運(yùn)行測試才能知道??AVAssetResourceLoaderDelegate?真正的運(yùn)行機(jī)制。

另外最大的坑是?AVAssetResourceLoaderDelegate?的內(nèi)部機(jī)制是個(gè)沙盒, 因?yàn)檫@個(gè)沙盒里面做了很多視頻播放處理,導(dǎo)致遇到播放時(shí)出問題很難排查是什么原因引起,只能不斷嘗試去找規(guī)律....

小結(jié)

回顧全文,理解?AVAssetResourceLoaderDelegate?的原理和實(shí)現(xiàn)機(jī)制,再到自己實(shí)現(xiàn)一個(gè) Downloader,講了會(huì)遇到的幾個(gè)坑以及如何解決,最后簡單介紹了 MTMediaCache 如何使用。

完整的項(xiàng)目地址:https://github.com/vitoziv/VIMediaCache

更全的文章,里面有多做方案的對比,寫的很好?:?https://blog.csdn.net/kyl282889543/article/details/104354112


DNS對AVPlayer的解析, 部分地址是https的,替換成ip后會(huì)變成https://192.168.1.1/xxx.mp3,?系統(tǒng)校驗(yàn)https的證書校驗(yàn)不通過, 自動(dòng)攔截不安全的請求 ,

可以給AVPlayer設(shè)置請求頭(https://blog.csdn.net/weixin_33872660/article/details/86085136), 然后在自定義https的驗(yàn)證(https://blog.csdn.net/kyl282889543/article/details/104354112), https://blog.csdn.net/kyl282889543/article/details/102877244 ,

然后經(jīng)過檢查,發(fā)現(xiàn)HTTP的請求, 會(huì)被系統(tǒng)屏蔽,無法進(jìn)入到delegate中 (https://stackoverflow.com/questions/60179072/avplayer-url-custom-loading-for-https , https://www.it1352.com/918282.html), 如果還想要進(jìn)入到AVAssetResourceLoaderDelegate的delegate中, 就需要把請求的https改成自定義的scheme,比如https://xxx.mp3 -> ULhttps://xxx.mp3, 這樣是太麻煩而且改動(dòng)太大,所以就去掉AVPlayer下https情況下的DNS解析功能.

總結(jié)

以上是生活随笔為你收集整理的AVPlayer 音视频缓存方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。