AndroidVideoCache源碼淺析

來源:互聯網
上載者:User

標籤:deb   uri   connect   string   代理服務   tostring   rom   tar   socket   

1. 邊播放邊緩衝

  視頻播放時邊播放邊緩衝,這樣使用者再次播放時可以節省流量,提高使用者體驗,這是視頻播放很常見的需求。但是,Android的VideoView是沒有提供這樣的功能的。

有個開源庫比較好用,github地址:https://github.com/danikula/AndroidVideoCache

2. 簡述一下AndroidVideoCache的大體實現原理

  大家都知道,VideoView.setVideoPath(proxyUrl);走的也是http請求,而http底層是走tcp協議的socket實現的。AndroidVideoCache的實現就是在tcp層架一個SocketServer的Proxy 伺服器,也就是說VideoView.setVideoPath(proxyUrl);的請求是先請求到Proxy 伺服器SocketServer,然後Proxy 伺服器SocketServer再去請求真正的伺服器。這樣,這個Proxy 伺服器SocketServer就可以去請求真正的伺服器去下載視頻(AndroidVideoCache是分段下載,也就是斷點續傳,每次8 * 1024大小),然後將視頻響應給請求。當然Proxy 伺服器SocketServer還會先判斷該視頻是否已經下載快取完,如果已經下載快取完就直接使用本地的緩衝視頻。

3. AndroidVideoCache原始碼簡述

  1)App全域架設一個本地SocketProxy 伺服器 

    InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
    this.serverSocket = new ServerSocket(0, 8, inetAddress);

  2)getProxyUrl方法,先判斷是否已經緩衝過了,如果是直接使用本機快取。如果否,就判斷Proxy 伺服器SocketServer是否可用

    public String getProxyUrl(String url, boolean allowCachedFileUri) {
      if (allowCachedFileUri && isCached(url)) {  //緩衝ok
        File cacheFile = getCacheFile(url);
        touchFileSafely(cacheFile);  //touch一下檔案,讓該檔案時間最新,用於LRUcache緩衝,LRUcache緩衝是按照時間排序的。
        return Uri.fromFile(cacheFile).toString();
      }
        return isAlive() ? appendToProxyUrl(url) : url;  //isAlive()方法裡就是ping了一下Proxy 伺服器SocketServer,看看是否可用?是,封裝成proxyURL,否,使用來源的url,不緩衝
      }

  3)processSocket處理所有的請求進來的Socket,包括ping的和VideoView.setVideoPath(proxyUrl)的Socket

      private void processSocket(Socket socket) {
        GetRequest request = GetRequest.read(socket.getInputStream());
        LOG.debug("Request to cache proxy:" + request);
        String url = ProxyCacheUtils.decode(request.uri);
        if (pinger.isPingRequest(url)) {  //如果是ping的,返回ping響應
          pinger.responseToPing(socket);
        } else {  //視頻的Socket,啟動對應的Client去處理
          HttpProxyCacheServerClients clients = getClients(url);
          clients.processRequest(request, socket);
        }
      }

  4)clients.processRequest(request, socket);方法的實現

    public void processRequest(GetRequest request, Socket socket) throws ProxyCacheException, IOException {
      startProcessRequest();  //這個方法其實就是擷取一個proxyCache對應
      try {
        clientsCount.incrementAndGet();
        proxyCache.processRequest(request, socket);  //交給proxyCache處理
      } finally {
        finishProcessRequest();
      }
    }

    private synchronized void startProcessRequest() throws ProxyCacheException {
      proxyCache = proxyCache == null ? newHttpProxyCache() : proxyCache;  //newHttpProxyCache() 這個方法準備一下url,緩衝file路徑,監聽器等等。。。
    }

  5)proxyCache.processRequest(request, socket);方法的實現

    public void processRequest(GetRequest request, Socket socket) throws IOException, ProxyCacheException {
      OutputStream out = new BufferedOutputStream(socket.getOutputStream());  //建立一個Socket的響應流
      String responseHeaders = newResponseHeaders(request);
      out.write(responseHeaders.getBytes("UTF-8"));

      long offset = request.rangeOffset;
      if (isUseCache(request)) {  //判斷是否使用緩衝?是
        responseWithCache(out, offset);  //緩衝並響應  
      } else {  //不使用緩衝
        responseWithoutCache(out, offset);  //不緩衝並響應
      }
    }

  6)responseWithCache(out, offset);方法的實現

    while (!cache.isCompleted() && cache.available() < (offset + length) && !stopped) {
      readSourceAsync();  //非同步請求真正的伺服器讀取該段資料,讀取完會通知
      waitForSourceData();  //加鎖等待readSourceAsync()的通知
      checkReadSourceErrorsCount();  //校正錯誤
    }

  6-1)readSourceAsync();方法最後回調用readSource()方法 

    private void readSource() {
      long sourceAvailable = -1;
      long offset = 0;
      try {
        offset = cache.available();
        source.open(offset);
        sourceAvailable = source.length();
        byte[] buffer = new byte[ProxyCacheUtils.DEFAULT_BUFFER_SIZE];  //每次讀取這麼多資料ProxyCacheUtils.DEFAULT_BUFFER_SIZE
        int readBytes;
        while ((readBytes = source.read(buffer)) != -1) {
          synchronized (stopLock) {
            if (isStopped()) {
            return;
          }
          cache.append(buffer, readBytes);  //追加到cache
        }
        offset += readBytes;
        notifyNewCacheDataAvailable(offset, sourceAvailable);  //通知有資料可用,也就是喚醒waitForSourceData()方法,讓while (!cache.isCompleted() && cache.available() < (offset + length) && !stopped)繼續判斷執行下去
      }
        tryComplete();  //這個方法判斷檔案是否已經下載完成了。cache的檔案是一個臨時的.download為尾碼的檔案,分段緩衝完成整個視頻檔案後,修改檔案名稱為與請求url關聯的檔案名稱
        onSourceRead();
      } catch (Throwable e) {
        readSourceErrorsCount.incrementAndGet();
        onError(e);
      } finally {
        closeSource();
        notifyNewCacheDataAvailable(offset, sourceAvailable);
      }
    }

  7)responseWithoutCache(out, offset);方法的實現

    private void responseWithoutCache(OutputStream out, long offset) throws ProxyCacheException, IOException {
      HttpUrlSource newSourceNoCache = new HttpUrlSource(this.source);
      try {
        newSourceNoCache.open((int) offset);  //這個方法裡面開啟HttpURLConnection
        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
        int readBytes;
        while ((readBytes = newSourceNoCache.read(buffer)) != -1) {  //read讀取HttpURLConnection的getInputStream()的資料
        out.write(buffer, 0, readBytes);
        offset += readBytes;
      }
      out.flush();
    } finally {
      newSourceNoCache.close();
    }
  }

  

AndroidVideoCache源碼淺析

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.