直播短影片系統原始碼,一步步實現快取機制

云豹科技-苏凌霄發表於2024-11-30

直播短影片系統原始碼,一步步實現快取機制

1、邏輯衝突

設計的伊始談到,為了保證解耦, 我們希望快取機制 不能修改播放器原始碼 ,但 MediaPlayer 如何在不改原始碼的情況下,將自身的快取載入邏輯交給我們的 CacheService 呢?
如下述程式碼中所展示的,這種實現似乎無法避免:

public class MyMediaPlayer extends MediaPlayer {
  
  public final CacheService mProxy;
  
  @Override
  public void setDataSource(String url) {
    // super.setDataSource(url);
    mProxy.setDataSource(url);
  }
}

必須承認,這也是一種與播放器的耦合,不能修改播放器原始碼 的設定似乎並不符合常理。
這裡體現出了作者本身優秀的創造力,透過建立一個裝置的本地代理服務 CacheService,在將影片資源的url交給播放器之前,先進行本地的一次轉換,並將初始的url作為引數,拼接在本地代理的url上:

1.建立本地代理:比如 http://127.0.0.1:8090
2.拿到要快取的影片地址,比如 https://xxx.mp4
3.拼接為新的地址:http://127.0.0.1:8090/https://xxx.mp4

拿到新的 url 並交給任意播放器後,播放器的載入都指向本地服務的新地址——即透過 Socket 連線建立的本地服務 CacheService,後者透過解析出請求中真正的 https://xxx.mp4 地址,建立對應的下載任務,並從下載的檔案快取中,讀取 buffer 返回給播放器;同時,監控整個流程的 CacheService 響應式地回撥過程中所有大大小小的事件。
經過這樣設計,整個流程的呼叫變得非常簡單:

public class MainActivity extends Activity {
  
  public final MediaPlayer mPlayer;
  
  @Override
  public void playVideo(String url) {
    final String proxyUrl = VideoUtils.getProxyUrl(url);
    // url = https://xxx.mp4
    // proxyUrl = http://127.0.0.1:8090/https://xxx.mp4
    mPlayer.setDataSource(proxyUrl);
  }
}

2、建立代理伺服器

接下來,筆者透過虛擬碼的形式,簡單闡述下建立本地代理連線的過程。
上文提到的本地服務 CacheService在建立時,會自動初始化一個本地代理伺服器,配置ip和自動分配埠號,這之後,服務完成初步建立,並立即開啟一個執行緒,等待接收客戶端的後續連線。

// 實際類名 HttpProxyCacheServer.java
public final class CacheService {
  
  private CacheService(Config config) {
        // 初始化ip和埠號
        InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
        this.serverSocket = new ServerSocket(0, 8, inetAddress);
        this.port = serverSocket.getLocalPort();
        // 開啟新的執行緒,等待後續接收客戶端的連線
        this.waitConnectionThread = new Thread(new WaitRequestsRunnable());
        this.waitConnectionThread.start();
    }
}

3、處理快取請求

本地服務建立完畢,當使用者嘗試播放音影片時,播放器實際上訪問類似 http://127.0.0.1:8009/https://xxx.mp4 的地址,這時我們的 CacheService 中接到了對應的訊息。
針對每一次請求,我們都能解析到真實音影片檔案的地址(https://xxx.mp4),為了提高複用性,我們宣告一個HttpProxyCache類,為每一個音影片配置一個對應的 HttpProxyCache 以進行管理:

class HttpProxyCache extends ProxyCache {
    // 影片資源的url地址
    private final HttpUrlSource source;
    // 影片資源的本地檔案資訊
    private final FileCache cache;
}

實際上還不夠,我們還需要針對每個音影片快取過程的回撥進行管理,因此,基於此再封裝一層,使用 HttpProxyCacheServerClients 管理一個音影片資源:

final class HttpProxyCacheServerClients {
    private final String url;       // 影片資源url
    private volatile HttpProxyCache proxyCache;  // 快取資訊
    private final List<CacheListener> listeners = new CopyOnWriteArrayList<>(); // 快取監聽
}

簡單概括一下,針對一次新的音影片資源載入,會構建一個新的 HttpProxyCacheServerClients,內部除了相關資訊的成員,還包含了 HttpProxyCache 物件用於讀取和載入快取。

4、遠端載入流程

抽象地看待音影片的源,分為 遠端音影片資源 和 本地音影片資源,當不使用快取時,必然會從遠端進行下載,並不斷將音影片的流透過 Socket 向播放器傳輸。
這裡我們將 源 抽象為 Source:

public interface Source {

    // 建立開啟資源
    void open(long offset) throws ProxyCacheException;

    // 獲取音影片的長度
    long length() throws ProxyCacheException;

    // 不斷讀取音影片資料
    int read(byte[] buffer) throws ProxyCacheException;

    // 關閉釋放資源
    void close() throws ProxyCacheException;
}

對於遠端載入的完整流程,本質上就是建立、開啟、讀取和關閉一個遠端連線 HttpURLConnection的過程,核心程式碼如下:

public class HttpUrlSource implements Source {
    @Override
    public void open(long offset){
      HttpURLConnection connection = openConnection(offset, -1);
    }
    
    @Override
    public int read(byte[] buffer){
      return inputStream.read(buffer, 0, buffer.length);
    }
    // ...
}

5、快取載入流程

更多的時候,無論音影片資源是否已下載,我們都希望透過快取統一載入管理:
1、檔案已下載:直接讀取本地檔案,將資料透過Socket不斷傳回給播放器;
2、檔案未下載:新建一個本地檔案,並開啟遠端下載任務,下載過程中,資料流不斷湧入本地檔案,本地檔案大小、下載進度的變更都會響應式通知上層;除此之外,新的音影片流資料會透過Socket不斷傳回給播放器,播放器也會不斷的推進播放進度。
由此可見,無論檔案是否下載,快取流程都是圍繞 本地快取檔案 進行的,這也符合軟體開發中的 唯一可信源 的概念。

以上就是直播短影片系統原始碼,一步步實現快取機制, 更多內容歡迎關注之後的文章

相關文章