唱吧 iOS 音視頻緩存處理框架

項(xiàng)目介紹

唱吧 iOS 團(tuán)隊(duì)為了解決音視頻在線播放的緩存問(wèn)題,開發(fā)了 KTVHTTPCache 這個(gè)框架。設(shè)計(jì)之初是為了解決音視頻在線播放的緩存問(wèn)題,但其本質(zhì)是對(duì) HTTP 請(qǐng)求進(jìn)行緩存,對(duì)傳輸內(nèi)容并沒(méi)有限制,因此應(yīng)用場(chǎng)景不限于音視頻在線播放,也可以用于文件下載、圖片加載、普通網(wǎng)絡(luò)請(qǐng)求等場(chǎng)景。

技術(shù)背景

對(duì)于有重度音視頻在線播放需求的應(yīng)用,緩存無(wú)疑是必不可少的功能。目前常用的方案有 Local HTTP Server 和 AVAssetResourceLoader 兩種。二者實(shí)現(xiàn)及原理雖有不同,但本質(zhì)都是要 Hook 到播放器資源加載的請(qǐng)求,從而接管資源加載邏輯。根據(jù)緩存狀態(tài),自行決定是否需要通過(guò)網(wǎng)絡(luò)加載資源。從應(yīng)用場(chǎng)景的角度看,二者有一個(gè)比較大的差異是前者可以搭配任意前端播放器,而后者只能配合 AVPlayer 使用。

個(gè)人認(rèn)為,由于 AVAssetResourceLoader 是黑盒且會(huì)干預(yù) AVPlayer 本身的播放邏輯,導(dǎo)致坑多且難排查。并且不同的版本之間會(huì)有行為差異(例如近期發(fā)現(xiàn)在最新的 iOS 11 系統(tǒng)中,原本工作正常的代碼,因?yàn)橐粋€(gè)細(xì)小的行為變化,引發(fā)了一個(gè) Bug),去適配它的邏輯會(huì)有不小的工作量。相反 Local HTTP Server 是完全 Open Source,我們能夠全面接管資源加載邏輯,可以盡可能的規(guī)避緩存策略的引入帶來(lái)的風(fēng)險(xiǎn)。

功能特點(diǎn)

  • 支持相同 URL 并發(fā)操作且線程安全。
  • 全路徑 Log,支持控制臺(tái)打印和輸出到文件,可準(zhǔn)確定位問(wèn)題。
  • 細(xì)粒度的緩存管理,可精確查看指定 URL 的完整緩存信息。
  • 模塊相互獨(dú)立,提供使用不同 Level 的接口。
  • 下載層高度可配置。
  • 低耦合,集成簡(jiǎn)單。

結(jié)構(gòu)設(shè)計(jì) & 工作流程

KTVHTTPCache 由 HTTP Server 和 Data Storage 兩大模塊組成。前者負(fù)責(zé)與 Client 交互,后者負(fù)責(zé)資源加載及緩存處理。為方便拓展,Data Storage 為獨(dú)立模塊,也可直接與 Client 交互(例如可與 AVAssetResourceLoader 配合使用)。

結(jié)構(gòu)及工作流程圖如下:
KTVHTTPCache Flow Chart
下面簡(jiǎn)述一下工作流程:
  1. Client 發(fā)出的請(qǐng)求被 HTTP Srever 接收到,HTTP Server 通過(guò)分析 HTTP Request 創(chuàng)建用于訪問(wèn) Data Storage 的 Data Request 對(duì)象。
  2. HTTP Server 使用 Data Request 創(chuàng)建 Data Reader,并以此作為從 Data Storage 獲取數(shù)據(jù)的通道。
  3. Data Reader 分析 Data Request 中的 Range 創(chuàng)建對(duì)應(yīng)的網(wǎng)絡(luò)數(shù)據(jù)源 Data Network Source 和文件數(shù)據(jù)源 Data File Source,并通過(guò) Data Sourcer 進(jìn)行管理。
  4. Data Sourcer 開始加載數(shù)據(jù)。
  5. Data Reader 從 Data Sourcer 讀取數(shù)據(jù)并通過(guò) HTTP Server 回傳給 Client。

緩存策略

以網(wǎng)絡(luò)使用最小化為原則,設(shè)計(jì)了分片加載數(shù)據(jù)的功能。有 Network Source 和 File Source 兩種用于加載數(shù)據(jù)的 Source,分別用于下載網(wǎng)絡(luò)數(shù)據(jù)和讀取本地?cái)?shù)據(jù)。通過(guò)分析 Data Request 的 Range 和本地緩存狀態(tài)來(lái)對(duì)應(yīng)創(chuàng)建。

例如一次請(qǐng)求的 Range 為 0-999,本地緩存中已有 200-499 和 700-799 兩段數(shù)據(jù)。那么會(huì)對(duì)應(yīng)生成 5 個(gè) Source,分別是:

  1. Data Network Source: 0-199
  2. Data File Source: 200-499
  3. Data Network Source: 500-699
  4. Data File Source: 700-799
  5. Data Network Source: 800-999

它們由 Data Sourcer 進(jìn)行管理,對(duì)外僅暴露一個(gè) Read Data 的接口,根據(jù)當(dāng)前的 Read Offset 自行選擇向外界提供數(shù)據(jù)的 Source。

使用示例

// 使用簡(jiǎn)單,基本可以忽略集成成本

// 啟動(dòng)(全局啟動(dòng)一次即可)
NSError * error;
[KTVHTTPCache proxyStart:&error];

// 使用
NSString * URLString = [KTVHTTPCache proxyURLStringWithOriginalURLString:@"原始 URL"];
AVPlayer * player = [AVPlayer playerWithURL:[NSURL URLWithString:URLString]];

唱吧的實(shí)踐過(guò)程

方案演進(jìn)

在音視頻緩存上,我們一共采用過(guò)如下 4 個(gè)方案:

  1. AVPlayer 純?cè)诰€播放。
  2. AVPlayer + AVAssetResourceLoader + 下載模塊。
  3. AVPlayer + 一個(gè)開源的緩存項(xiàng)目(同樣基于 AVAssetResourceLoader + 下載模塊)。
  4. AVPlayer + KTVHTTPCache。
  • 方案 1 簡(jiǎn)單直接,缺點(diǎn)也不必多說(shuō)。
  • 方案 2 的下載模塊設(shè)計(jì)的比較簡(jiǎn)單,只能順序下載,不支持分片。導(dǎo)致只能 Seek 到已下載完的地方,在用戶體驗(yàn)上會(huì)有較大的缺陷。
  • 方案 3 在功能上已經(jīng)可以滿足需求,但在使用中問(wèn)題較多,我們?cè)谠创a基礎(chǔ)上做了很多修改來(lái)填坑。但穩(wěn)定性依然不是很理想,上線不長(zhǎng)時(shí)間就將該功能下掉了。
  • 方案 4 是唱吧現(xiàn)在的線上方案,目前在我們的使用場(chǎng)景中還沒(méi)有發(fā)現(xiàn)問(wèn)題。除穩(wěn)定性的提升外,比較大的改進(jìn)是增加了全路徑的 Log 模塊。若用戶或測(cè)試同學(xué)遇到問(wèn)題,只需簡(jiǎn)單描述并回傳 Log,就可以快速定位到原因,大大提高了調(diào)試效率。

踩過(guò)的坑

1. Content-Type 和 Path Extension

AVPlayer 在播放時(shí)會(huì)優(yōu)先根據(jù) Response Header 中的 Content-Type 判斷當(dāng)前資源是否可以播放。當(dāng) Content-Type 無(wú)法給出有效信息時(shí)再去判斷 URL 中的 Path Extension。

對(duì)應(yīng)關(guān)系如下:

URL Content-Type 是否可播
http://changba.com/video.mp4 video/mp4 YES
http://changba.com/video.mp4 application/octet-stream YES
http://changba.com/video video/mp4 YES
http://changba.com/video application/octet-stream NO

因此要想讓 AVPlayer 正常播放,Content-Type 和 Path Extension 中至少能提供一個(gè)有效信息,否則將直接報(bào) Error。

  • 發(fā)現(xiàn)這一問(wèn)題是因?yàn)樵谧?Original URL -> Proxy URL 映射時(shí),將 Original URL 中的 Path Extension 信息在 URL Encode 時(shí)丟失了,再碰上某些情況 CDN 返回的 Content-Type 是 application/octet-stream 而不是 video/mp4 之類的確切類型時(shí),AVPlayer 會(huì)直接報(bào) Error。

2. 鎖屏后 Server Socket 失效

在本地 Server 中有一個(gè) Socket 用于接收 AVPlayer 發(fā)出的請(qǐng)求。如果在 AVPlayer 為非播放狀態(tài)時(shí)鎖屏,一段時(shí)間后再喚起 App,F(xiàn)D 雖然還在,但 Listen 的端口會(huì)被回收,導(dǎo)致 FD 接收不到事件,AVPlayer 發(fā)出的請(qǐng)求也就無(wú)法被本地 Server 接收到。我們的解決辦法是在做 URL 映射時(shí) Ping 一下本地 Server,如果 Ping 不通,會(huì)重啟本地 Server。

最后

項(xiàng)目已經(jīng)開源,GitHub 地址: https://github.com/ChangbaDevs/KTVHTTPCache

對(duì)重度影音類應(yīng)用而言,音視頻緩存屬于比較重要的一環(huán),對(duì)穩(wěn)定性也有比較高的要求,我們?cè)谶@上走過(guò)一些彎路、踩過(guò)一些坑。希望 KTVHTTPCache 的開源能給大家?guī)?lái)一些幫助。也非常歡迎大家在項(xiàng)目中使用,如果遇到問(wèn)題可以在 GitHub 提 Issue 給我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 群里要求寫自己最喜歡的電影。腦袋里面過(guò)了一遍,最喜歡的沒(méi)能確定,但是第一時(shí)間想起幾年前看過(guò)的《白日夢(mèng)想家》這部電影...
    夢(mèng)不落的腳印閱讀 605評(píng)論 0 0
  • http://www.itdecent.cn/p/cd1bc0e82f4d CABasicAnimation動(dòng)畫的...
    frankisbaby閱讀 404評(píng)論 0 0
  • 先更五十個(gè),覺(jué)得長(zhǎng)姿勢(shì)了就點(diǎn)喜歡吧 2、劉禪是曹操的兒子 3、中國(guó)也有一個(gè)機(jī)密”13區(qū)”,在羅布泊 4、中國(guó)的龍?jiān)?..
    生七閱讀 354評(píng)論 0 0

友情鏈接更多精彩內(nèi)容