zoukankan      html  css  js  c++  java
  • AndroidVideoCache源码浅析

    1. 边播放边缓存

      视频播放时边播放边缓存,这样用户再次播放时可以节省流量,提高用户体验,这是视频播放很常见的需求。但是,Android的VideoView是没有提供这样的功能的。

    有个开源库比较好用,github地址:https://github.com/danikula/AndroidVideoCache

    2. 简述一下AndroidVideoCache的大体实现原理

      大家都知道,VideoView.setVideoPath(proxyUrl);走的也是http请求,而http底层是走tcp协议的socket实现的。AndroidVideoCache的实现就是在tcp层架一个SocketServer的代理服务器,也就是说VideoView.setVideoPath(proxyUrl);的请求是先请求到代理服务器SocketServer,然后代理服务器SocketServer再去请求真正的服务器。这样,这个代理服务器SocketServer就可以去请求真正的服务器去下载视频(AndroidVideoCache是分段下载,也就是断点续传,每次8 * 1024大小),然后将视频响应给请求。当然代理服务器SocketServer还会先判断该视频是否已经下载缓存完,如果已经下载缓存完就直接使用本地的缓存视频。

    3. AndroidVideoCache源代码简述

      1)App全局架设一个本地Socket代理服务器 

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

      2)getProxyUrl方法,先判断是否已经缓存过了,如果是直接使用本地缓存。如果否,就判断代理服务器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了一下代理服务器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();
        }
      }

      

  • 相关阅读:
    vlc 学习网
    delphi vlc 安装bug 处理编译错误"0" is an invalid value for the "DebugInformation" parameter of the "DCC"
    检测一组电动车电瓶好坏要多久?
    通过VLC的ActiveX进行二次开发,实现一个多媒体播放器 2011-04-10 00:57:23
    最简单的基于libVLC的例子:最简单的基于libVLC的视频播放器
    把任意的EXE嵌入到自己程序中
    http代理工具delphi源码
    2.1.2 列表常用方法
    2.1.1 列表创建与删除
    第2章 Python序列
  • 原文地址:https://www.cnblogs.com/yongfengnice/p/9188700.html
Copyright © 2011-2022 走看看