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

項目介紹

唱吧 iOS 團隊爲了解決音視頻在線播放的緩存問題,開發了 KTVHTTPCache 這個框架。設計之初是爲了解決音視頻在線播放的緩存問題,但其本質是對 HTTP 請求進行緩存,對傳輸內容並無限制,所以應用場景不限於音視頻在線播放,也能夠用於文件下載、圖片加載、普通網絡請求等場景。前端

技術背景

對於有重度音視頻在線播放需求的應用,緩存無疑是必不可少的功能。目前經常使用的方案有 Local HTTP Server 和 AVAssetResourceLoader 兩種。兩者實現及原理雖有不一樣,但本質都是要 Hook 到播放器資源加載的請求,從而接管資源加載邏輯。根據緩存狀態,自行決定是否須要經過網絡加載資源。從應用場景的角度看,兩者有一個比較大的差別是前者能夠搭配任意前端播放器,然後者只能配合 AVPlayer 使用。git

我的認爲,因爲 AVAssetResourceLoader 是黑盒且會干預 AVPlayer 自己的播放邏輯,致使坑多且難排查。而且不一樣的版本之間會有行爲差別(例如近期發如今最新的 iOS 11 系統中,本來工做正常的代碼,由於一個細小的行爲變化,引起了一個 Bug),去適配它的邏輯會有不小的工做量。相反 Local HTTP Server 是徹底 Open Source,咱們可以全面接管資源加載邏輯,能夠儘量的規避緩存策略的引入帶來的風險。github

功能特色

  • 支持相同 URL 併發操做且線程安全。
  • 全路徑 Log,支持控制檯打印和輸出到文件,可準肯定位問題。
  • 細粒度的緩存管理,可精確查看指定 URL 的完整緩存信息。
  • 模塊相互獨立,提供使用不一樣 Level 的接口。
  • 下載層高度可配置。
  • 低耦合,集成簡單。

結構設計 & 工做流程

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

結構及工做流程圖以下:

KTVHTTPCache Flow Chart
KTVHTTPCache Flow Chart

下面簡述一下工做流程:
  1. Client 發出的請求被 HTTP Srever 接收到,HTTP Server 經過分析 HTTP Request 建立用於訪問 Data Storage 的 Data Request 對象。
  2. HTTP Server 使用 Data Request 建立 Data Reader,並以此做爲從 Data Storage 獲取數據的通道。
  3. Data Reader 分析 Data Request 中的 Range 建立對應的網絡數據源 Data Network Source 和文件數據源 Data File Source,並經過 Data Sourcer 進行管理。
  4. Data Sourcer 開始加載數據。
  5. Data Reader 從 Data Sourcer 讀取數據並經過 HTTP Server 回傳給 Client。

緩存策略

以網絡使用最小化爲原則,設計了分片加載數據的功能。有 Network Source 和 File Source 兩種用於加載數據的 Source,分別用於下載網絡數據和讀取本地數據。經過分析 Data Request 的 Range 和本地緩存狀態來對應建立。安全

例如一次請求的 Range 爲 0-999,本地緩存中已有 200-499 和 700-799 兩段數據。那麼會對應生成 5 個 Source,分別是:bash

  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 進行管理,對外僅暴露一個 Read Data 的接口,根據當前的 Read Offset 自行選擇向外界提供數據的 Source。網絡

使用示例

// 使用簡單,基本能夠忽略集成成本

// 啓動(全局啓動一次便可)
NSError * error;
[KTVHTTPCache proxyStart:&error];

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

唱吧的實踐過程

方案演進

在音視頻緩存上,咱們一共採用過以下 4 個方案:併發

  1. AVPlayer 純在線播放。
  2. AVPlayer + AVAssetResourceLoader + 下載模塊。
  3. AVPlayer + 一個開源的緩存項目(一樣基於 AVAssetResourceLoader + 下載模塊)。
  4. AVPlayer + KTVHTTPCache。
  • 方案 1 簡單直接,缺點也沒必要多說。
  • 方案 2 的下載模塊設計的比較簡單,只能順序下載,不支持分片。致使只能 Seek 到已下載完的地方,在用戶體驗上會有較大的缺陷。
  • 方案 3 在功能上已經能夠知足需求,但在使用中問題較多,咱們在源碼基礎上作了不少修改來填坑。但穩定性依然不是很理想,上線不長時間就將該功能下掉了。
  • 方案 4 是唱吧如今的線上方案,目前在咱們的使用場景中尚未發現問題。除穩定性的提高外,比較大的改進是增長了全路徑的 Log 模塊。若用戶或測試同窗遇到問題,只需簡單描述並回傳 Log,就能夠快速定位到緣由,大大提升了調試效率。

踩過的坑

1. Content-Type 和 Path Extension

AVPlayer 在播放時會優先根據 Response Header 中的 Content-Type 判斷當前資源是否能夠播放。當 Content-Type 沒法給出有效信息時再去判斷 URL 中的 Path Extension。app

對應關係以下:框架

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

所以要想讓 AVPlayer 正常播放,Content-Type 和 Path Extension 中至少能提供一個有效信息,不然將直接報 Error。

  • 發現這一問題是由於在作 Original URL -> Proxy URL 映射時,將 Original URL 中的 Path Extension 信息在 URL Encode 時丟失了,再碰上某些狀況 CDN 返回的 Content-Type 是 application/octet-stream 而不是 video/mp4 之類的確切類型時,AVPlayer 會直接報 Error。

2. 鎖屏後 Server Socket 失效

在本地 Server 中有一個 Socket 用於接收 AVPlayer 發出的請求。若是在 AVPlayer 爲非播放狀態時鎖屏,一段時間後再喚起 App,FD 雖然還在,但 Listen 的端口會被回收,致使 FD 接收不到事件,AVPlayer 發出的請求也就沒法被本地 Server 接收到。咱們的解決辦法是在作 URL 映射時 Ping 一下本地 Server,若是 Ping 不通,會重啓本地 Server。

最後

項目已經開源,GitHub 地址: github.com/ChangbaDevs…

對重度影音類應用而言,音視頻緩存屬於比較重要的一環,對穩定性也有比較高的要求,咱們在這上走過一些彎路、踩過一些坑。但願 KTVHTTPCache 的開源能給你們帶來一些幫助。也很是歡迎你們在項目中使用,若是遇到問題能夠在 GitHub 提 Issue 給我。

相關文章
相關標籤/搜索