android视频缓存框架 [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache) 源码解析与评估

本文详细解析了Android视频缓存框架AndroidVideoCache的工作原理,包括HttpProxyCacheServer、ProxySelector、Pinger、GetRequest等关键类的功能。该框架支持媒体文件的本地缓存,离线播放,多客户端共享,并提供自定义缓存限制。文章还展示了数据展示Demo和对框架的评估。
摘要由CSDN通过智能技术生成

android视频缓存框架 AndroidVideoCache 源码解析与评估

引言

android中许多视频播放框架都会有切换清晰度的选项, 而最佳的播放清晰度和流畅度无非是本地播放视频了; AndroidVideoCache 允许添加缓存支持 VideoView/MediaPlayer,ExoPlayer,或其他单行播放器;

基本原理为: 通过在本地构建一个服务器,再使用socket连接,通过socket读取流数据;

特征:

  • 在加载流时缓存至本地中;
  • 缓存资源离线工作;
  • 部分加载;
  • 自定义缓存限制;
  • 同一个url多客户端支持;

该项目仅支持 直接url 媒体文件,并不支持如 DASH, SmoothStreaming, HLS等流媒体;

本次 代码解析版本为 com.danikula:videocache:2.7.1

使用方式

其中的一个使用方式

然后通过 String proxyUrl = ApplicationDemo.getProxy(mContext).getProxyUrl(VIDEO_URL); 获取代理后url用于视频播放;

关键类解析

HttpProxyCacheServer 代理缓存服务类

提供配置构造者,系统入口及功能整合;


	private static final Logger LOG = LoggerFactory.getLogger("HttpProxyCacheServer");
	//本地ip地址,用于构建本地socket;
    private static final String PROXY_HOST = "127.0.0.1";

	//client 的锁对象;
    private final Object clientsLock = new Object();
	//固定线程数线程池;
    private final ExecutorService socketProcessor = Executors.newFixedThreadPool(8);
	//client 的 线程安全容器,key 为 url;
    private final Map<String, HttpProxyCacheServerClients> clientsMap = new ConcurrentHashMap<>();
	//服务端socket,用于阻塞等待socket连入;
    private final ServerSocket serverSocket;
	//端口
    private final int port;
	//等待socket连接子线程;
    private final Thread waitConnectionThread;
	//server 构建配置;
    private final Config config;
	//ping 系统,用于判断是否连接;
    private final Pinger pinger;
	
	//>>>>>>>> 这里是初始化的入口: 
	public HttpProxyCacheServer(Context context) {
		//使用默认的配置构建server;
        this(new Builder(context).buildConfig());
    }

    private HttpProxyCacheServer(Config config) {
        this.config = checkNotNull(config);
        try {
            InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
			//todo 使用本地ip地址建立服务端socket;
            this.serverSocket = new ServerSocket(0, 8, inetAddress);
			//服务端端口;
            this.port = serverSocket.getLocalPort();
			//ProxySelector 关键类:为当前的socket的host和端口忽略默认代理;
            IgnoreHostProxySelector.install(PROXY_HOST, port);
			//信号量 (门闩),阻塞当前线程,收到通知后继续执行;
            CountDownLatch startSignal = new CountDownLatch(1);
            this.waitConnectionThread = new Thread(new WaitRequestsRunnable(startSignal));
            this.waitConnectionThread.start();
            startSignal.await(); // freeze thread, wait for server starts
			//Pinger 关键类:
            this.pinger = new Pinger(PROXY_HOST, port);
			//使用pinger 去判断ServerSocket是否存活;
            LOG.info("Proxy cache server started. Is it alive? " + isAlive());
        } catch (IOException | InterruptedException e) {
			//中断时直接shutdown线程池;
            socketProcessor.shutdown();
            throw new IllegalStateException("Error starting local proxy server", e);
        }
    }
	
	//子线程运行
	private final class WaitRequestsRunnable implements Runnable {

        private final CountDownLatch startSignal;

        public WaitRequestsRunnable(CountDownLatch startSignal) {
            this.startSignal = startSignal;
        }

		//线程运行时,countDownLatch打开,死循环等待外部socket接入;
        @Override
        public void run() {
			//notify freezed thread;
            startSignal.countDown();
            waitForRequest();
        }
    }


	private void waitForRequest() {
        try {
			//中断时结束循环
            while (!Thread.currentThread().isInterrupted()) {
				//阻塞当前子线程(waitConnectionThread)
                Socket socket = serverSocket.accept();
                LOG.debug("Accept new socket " + socket);
				//已接入一个外部socket,线程池运行runnable,调用`processSocket(socket);`
                socketProcessor.submit(new SocketProcessorRunnable(socket));
            }
        } catch (IOException e) {
            onError(new ProxyCacheException("Error during waiting connection", e));
        }
    }

	//线程池运行
	private void processSocket(Socket socket) {
        try {
			//读取socket中输入流; 记录range 和 url 等请求数据;
            GetRequest request = GetRequest.read(socket.getInputStream());
            LOG.debug("Request to cache proxy:" + request);
			//url Decode, 此url 为 URL中定位的资源,ping或者videoUrl;
            String url = ProxyCacheUtils.decode(request.uri);
			
			//如果输入流中url 为`ping`,则返回连接状态ok;
            if (pinger.isPingRequest(url)) {
                pinger.responseToPing(socket);
            } else {
				//建立client,响应请求;
                HttpProxyCacheServerClients clients = getClients(url);
				//使用与url绑定的client处理socket输入流; 此处获取真实加载的videoUrl处理;
                clients.processRequest(request, socket);
            }
        } catch (SocketException e) {
            // There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
            // So just to prevent log flooding don't log stacktrace
            LOG.debug("Closing socket… Socket is closed by client.");
        } catch (ProxyCacheException | IOException e) {
            onError(new ProxyCacheException("Error processing request", e));
        } finally {
            releaseSocket(socket);
            LOG.debug("Opened connections: " + getClientsCount());
        }
    }

	//获取HttpProxyCacheServerClients 对象;
	private HttpProxyCacheServerClients getClients(String url) throws ProxyCacheException {
        synchronized (clientsLock) {
            HttpProxyCacheServerClients clients = clientsMap.get(url);
            if (clients == null) {
                clients = new HttpProxyCacheServerClients(url, config);
                clientsMap.put(url, clients);
            }
            return clients;
        }
    }

	// >>>>>>>> 2.代理videoUrl的方法入口;
	public String getProxyUrl(String url) {
        return getProxyUrl(url, true);
    }

	public String getProxyUrl(String url, boolean allowCachedFileUri) {
		//isCached 使用url 和命名生成器 判断本地是否存在缓存文件;
        if (allowCachedFileUri && isCached(url)) {
            File cacheFile = getCacheFile(url);
			//如果存在,尝试用diskUsage 的lru算法保存文件;
            touchFileSafely(cacheFile);
			//此处意为,如果已经下载完成后,直接用本地缓存文件路径播放;
            return Uri.fromFile(cacheFile).toString();
        }
		//如果serverSocket存活状态, 拼接代理VideoUrl; 加载时触发 `processSocket `方法
        return isAlive() ? appendToProxyUrl(url) : url;
    }

	//使用ping-ping ok 系统判断本地ip是否能成功连通;
	private boolean isAlive() {
		//最大尝试数3次,每次重新尝试会翻倍timeout时间;
        return pinger.ping(3, 70);   // 70+140+280=max~500ms
    }

	//>>>>>>>>> 2. 核心处理videourl,使用本地代理ip; 请求时,获取GET 的包头信息 即videoUrl或者ping;
	private String appendToProxyUrl(String url) {
        return String.format(Locale.US, "http://%s:%d/%s", PROXY_HOST, port, ProxyCacheUtils.encode(url));
    }


**java.net.ProxySelector ** 代理选择

{@link ProxySelector} that ignore system default proxies for concrete host.
ProxySelector 用于为具体的host忽略系统默认的代理;

IgnoreHostProxySelector extends ProxySelector 修改系统默认proxySelector 忽略本地ip;


	//ProxySelector.java 静态代码块中会进行初始化
	public abstract class ProxySelector {
		...
		static {
	        try {
	            Class var0 = Class.forName("sun.net.spi.DefaultProxySelector");
	            if (var0 != null && ProxySelector.class.isAssignableFrom(var0)) {
	                theProxySelector = (ProxySelector)var0.newInstance();
	            }
	        } catch (Exception var1) {
	            theProxySelector = null;
	        }
	
	    }

		public static ProxySelector getDefault() {
	        SecurityManager var0 = System.getSecurityManager();
	        if (var0 != null) {
	            var0.checkPermission(SecurityConstants.GET_PROXYSELECTOR_PERMISSION);
	        }
	
	        return theProxySelector;
	    }	
	
		public static void setDefault(ProxySelector var0) {
	        SecurityManager var1 = System.getSecurityManager();
	        if (var1 != null) {
	            var1.checkPermission(SecurityConstants.SET_PROXYSELECTOR_PERMISSION);
	        }
	
	        theProxySelector = var0;
	    }	
	}
	

	class IgnoreHostProxySelector extends ProxySelector {

	    private static final List<Proxy> NO_PROXY_LIST = Arrays.asList(Proxy.NO_PROXY);
	
	    private final ProxySelector defaultProxySelector;
	    private final String hostToIgnore;
	    private final int portToIgnore;
	
	    IgnoreHostProxySelector(ProxySelector defaultProxySelecto
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值