时间:2021-12-04 09:32:58 | 栏目:Android代码 | 点击:次
实现思路
红色框的 ProxyServer就是需要实现的一个代理服务器。 当客户端拿到一个视频的url(mp4或者m3u8)时,通过proxyServer转化为一个代理的url,然后请求代理服务器;代理服务器接收到客户端的请求后,先查看本地是否存在缓存,如果不存在则向真实服务器发送请求,拿到结果后再存到本地。
实现重点
缓存是一个代理服务器的主要部分,所以这部分是一个重点。本设计的缓存是一个分片的LRU缓存。分片的好处是灵活方便做LRU。当真实服务器返回一个大文件时,我们在进行切割后缓存在本地,并返回给客户端,不用等待所有数据返回后再返回给客户端。
使用方式
在app初始化的时候 创建代理服务器
public class APP extends Application { private static VideoCacheServer videoCacheServer; @Override public void onCreate() { super.onCreate(); if (videoCacheServer == null) { // 缓存路径 String cachePath = getCacheDir().getAbsolutePath(); // 缓存大小 1024 * 1024 * 500 videoCacheServer = new VideoCacheServer(cachePath, 1024 * 1024 * 500); } } public static VideoCacheServer getVideoProxyServer() { return videoCacheServer; } }
代理服务建立好了 ,使用的时候只需要将真实url转换为代理url就好了
String proxyUrl = APP.getVideoProxyServer().getLocalProxyUrl("https://sina.com-h-sina.com/20181024/21342_8f737b71/1000k/hls/index.m3u8"); videoView.setVideoPath(proxyUrl);
转换的规则即讲https的请求转换为http的请求 ,并且替换域名为代理服务器的地址,将真实服务器的地址作为参数添加到代理url的后面。
例如 sina.com-h-sina.com/20181024/21… 地址转换后变成了 https://127.0.0.1:3260/20181024/21342_8f737b71/1000k/hls/index.m3u8?RealHostParam=sina.com-h-sina.com 3260是VideoCacheServer监听的端口
实现细节
代理服务器的建立
public class VideoCacheServer{ private ExecutorService pool = Executors.newFixedThreadPool(20); public int start() { if (isRunning) { return curPort; } curPort = new Random().nextInt(65535); try { final ServerSocket server = new ServerSocket(curPort); isRunning = true; singleService.submit(new Runnable() { @Override public void run() { while (isRunning) { try { Socket connection = server.accept(); connection.setKeepAlive(true); pool.submit(new ProxyHandler(connection)); } catch (IOException ex) { if (Constant.enableLog) { logger.log(Level.WARNING, "Exception accepting connection", ex); } } catch (Exception ex) { if (Constant.enableLog) { logger.log(Level.SEVERE, "Unexpected error", ex); } } } } }); return curPort; } catch (IOException e) { e.printStackTrace(); return start(); } } }
通过socket实现端口的监听,当请求到来时,使用ProxyHandler来处理。
public class ProxyHandler implements Runnable { private Socket realClientSocket; ProxyHandler(Socket realClientSocket) { this.realClientSocket = realClientSocket; } @Override public void run() { try { BufferedOutputStream outputStream = new BufferedOutputStream(realClientSocket.getOutputStream()); BufferedInputStream inputStream = new BufferedInputStream(realClientSocket.getInputStream()); HttpRequest realRequest = HttpRequest.parse(inputStream); HttpResponse response = getResponseWithInterceptorChain(realRequest); writeResponseAndClose(response, outputStream); } catch (Exception e) { if (Constant.enableLog) { logger.log(Level.SEVERE, "error proxy ", e); } } finally { CloseUtil.close(realClientSocket); } } private HttpResponse getResponseWithInterceptorChain(HttpRequest realRequest) { List<Interceptor> interceptors = new ArrayList<>(); interceptors.add(new VideoTypeInterceptor()); interceptors.add(new HostFilterInterceptor(curPort)); interceptors.add(new CacheInterceptor(diskCache)); interceptors.add(new ConnectInterceptor()); InterceptorChain interceptorChain = new InterceptorChain(interceptors, realRequest, 0); return interceptorChain.proceed(realRequest); } }
ProxyHandler中使用拦截器的模式,将请求分部处理
VideoTypeInterceptor 将代理的url 还原为真实的url
CacheInterceptor 用于缓存
ConnectInterceptor 建立代理服务器与真实服务器的连接
VideoTypeInterceptor 主要是针对m3u8类型,因为m3u8会先返回一个m3u8的文件,文件里面记录了每个ts的地址,VideoTypeInterceptor就是将返回的文件中的ts地址转换为代理服务器的地址