转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/54234583
前言:视频的预加载是提高用户体验的重要因素。预加载成为网络视频播放不可或缺的一个技术环节。看下Agenda:
- 预加载的形式
- 影响预加载的因素
- 预加载场景
- 一张图看清本地代理
- 数据预加载
- 效果图
- HttpProxy
- 1.边存边播:下载多少播放多少。 优点:快速加载播放,实现简单;缺点:不能拖动未存区域;适合音频媒体
- 2.代理服务器:预先下载媒体的头部(头部Size为 s1 byte)->监听播放器的请求,当Request的是预加载的URL->代理把媒体头部作为Response返回给播放器,并改Ranage 为 s1 byte 发送Request->代理服务器纯粹作为透传。 优点:快速加载播放,支持拖动;缺点:实现非常复杂;适合视频媒体
- 网络状态
- 缓冲文件大小
- 视频码率
码率低、网速快的情况没必要使用预加载,码率中等、网速一般的情况合适使用。另外,缓冲文件也不能设置太大:过大的缓冲区会刷爆MediaPlayer内置的缓冲区,影响正常播放;再者,读取缓冲文件也耗时。
预加载场景:电视剧播放第1集时,在快要结束前5分钟,开始加载第2集。播放时就会连贯起来。缩短加载时间,如当系统检测您的网络环境较差时,会提示您开启预加载模式,开启预加载模式后,系统将为您缓冲整部影片。为了您流畅观看视频,建议您在开启预加载模式5分钟后,再次点击播放按钮,开始播放。这时就不会卡在缓冲中的状态。就是在视频播放的时候,在播放器通过本地URL,请求视频数据时,本地代理截取这次请求,经过本地代理逻辑,向服务器或本地缓存请求数据。本地代理在获得视频数据后,将数据转给播放器,实现播放。相比直接请求视频数据,本地代理的优势在于,buffer由本地控制,提大提升视频播放速度。提高用户体验。本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/54234583
一张图看清本地代理:
不同rom上的MediaPlayer是不同的,会有一些差异,取决rom多媒体团队对MediaPlayer的定制程度,例如:有些MediaPlayer首次播放从头buffer,有些MdiaPlayer首次播放会多次Request,Range到网络媒体文件的头部、中间和文件尾,再从指定位置buffer。系统播放器需要下载5s的数据才开始把buffer进行播放出来。5s的数据,如果网络差的话,就处于是buffering中,一旦有5s的数据,就先播起来,而后,再在背后预加载,如一般播放panel的seekbar,有两种颜色,一种颜色(下图蓝色)是当前正在播的位置,还是一种颜色(灰色)走在前面,就是加载好的数据。本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/54234583
效果图1:
HttpProxy,就是启动一个本地代理,用127.0.0.1来替换视频源的服务器地址。
package com.hejunlin.videopreloaded; import android.util.Log; public class HttpProxy { public static final int SIZE = (int) (5 * 1024 * 1024); public static final String TAG = HttpProxy.class.getSimpleName(); private int remotePort = -1; private String remoteHost; private int localPort; private String localHost; private ServerSocket localServer = null; private Socket sckPlayer = null; private Socket sckServer = null; private SocketAddress serverAddress; private HandleDownLoad download = null; /** * 初始化代理服务器 * * @param localport * 代理服务器监听的端口 */ public HttpProxy(int localport) { try { localPort = localport; localHost = Contants.LOCAL_IP_ADDRESS; localServer = new ServerSocket(localport, 1, InetAddress.getByName(localHost)); } catch (Exception e) { System.exit(0); } URI tmpURI = new URI(urlString); String fileName = Utils.urlToFileName(tmpURI.getPath()); String filePath = Contants.getBufferDir() + "/" + fileName; download = new HandleDownLoad(urlString, filePath, size); download.startThread(); return filePath; } /** * 把网络URL转为本地URL,127.0.0.1替换网络域名 * * @param url网络URL * @return [0]:重定向后MP4真正URL,[1]:本地URL */ public String[] getLocalURL(String urlString) { String targetUrl = Utils.getRedirectUrl(urlString); // ----获取对应本地代理服务器的链接----// String localUrl = null; URI originalURI = URI.create(targetUrl); remoteHost = originalURI.getHost(); if (originalURI.getPort() != -1) {// URL带Port serverAddress = new InetSocketAddress(remoteHost, originalURI.getPort());// 使用默认端口 remotePort = originalURI.getPort();// 保存端口,中转时替换 localUrl = targetUrl.replace( remoteHost + ":" + originalURI.getPort(), localHost + ":" + localPort); } else {// URL不带Port serverAddress = new InetSocketAddress(remoteHost, Contants.HTTP_PORT);// 使用80端口 remotePort = -1; localUrl = targetUrl.replace(remoteHost, localHost + ":" + localPort); } String[] result = new String[] { targetUrl, localUrl }; return result; } /** * 异步启动代理服务器 * * @throws IOException */ public void asynStartProxy() { new Thread() { public void run() { startProxy(); } }.start(); } private void startProxy() { HttpParser httpParser = null; HttpProxyUtils utils = null; int bytes_read; byte[] local_request = new byte[1024]; byte[] remote_reply = new byte[1024]; while (true) { boolean sentResponseHeader = false; try {// 开始新的request之前关闭过去的Socket if (sckPlayer != null) sckPlayer.close(); if (sckServer != null) sckServer.close(); } catch (IOException e1) { } try { // -------------------------------------- // 监听MediaPlayer的请求,MediaPlayer->代理服务器 // -------------------------------------- sckPlayer = localServer.accept(); Log.e(TAG, "------------------------------------------------------------------"); if (download != null && download.isDownloading()) download.stopThread(false); httpParser = new HttpParser(remoteHost, remotePort, localHost, localPort); utils = new HttpProxyUtils(sckPlayer, sckServer, serverAddress); HttpParser.ProxyRequest request = null; while ((bytes_read = sckPlayer.getInputStream().read( local_request)) != -1) { byte[] buffer = httpParser.getRequestBody(local_request, bytes_read); if (buffer != null) { request = httpParser.getProxyRequest(buffer); break; } } boolean isExists = new File(request._prebufferFilePath) .exists(); if (isExists) Log.e(TAG, ">> prebuffer size:" + download.getDownloadedSize()); sckServer = utils.sentToServer(request._body); // ------------------------------------------------------ // 把网络服务器的反馈发到MediaPlayer,网络服务器->代理服务器->MediaPlayer // ------------------------------------------------------ while ((bytes_read = sckServer.getInputStream().read( remote_reply)) != -1) { if (sentResponseHeader) { try {// 拖动进度条时,容易在此异常,断开重连 utils.sendToMP(remote_reply, bytes_read); } catch (Exception e) { break;// 发送异常直接退出while } continue;// 退出本次while } List<byte[]> httpResponse = httpParser.getResponseBody( remote_reply, bytes_read); if (httpResponse.size() == 0) continue;// 没Header则退出本次循环 sentResponseHeader = true; String responseStr = new String(httpResponse.get(0)); Log.e(TAG, ">> responseStr " + responseStr); // send http header to mediaplayer utils.sendToMP(httpResponse.get(0)); if (isExists) {// 需要发送预加载到MediaPlayer isExists = false; int sentBufferSize = 0; try { sentBufferSize = utils.sendPrebufferToMP( request._prebufferFilePath, request._rangePosition); } catch (Exception ex) { break; } if (sentBufferSize > 0) {// 成功发送预加载,重新发送请求到服务器 int newRange = (int) (sentBufferSize + request._rangePosition); String newRequestStr = httpParser .modifyRequestRange(request._body, newRange); Log.e(TAG + "-pre->", newRequestStr); // 修改Range后的Request发送给服务器 sckServer = utils.sentToServer(newRequestStr); // 把服务器的Response的Header去掉 utils.removeResponseHeader(httpParser); continue; } } // 发送剩余数据 if (httpResponse.size() == 2) { utils.sendToMP(httpResponse.get(1)); } } Log.e(TAG, ">> preloaded over"); // 关闭 2个SOCKET sckPlayer.close(); sckServer.close(); } catch (Exception e) { Log.e(TAG, e.toString()); Log.e(TAG, Utils.getExceptionMessage(e)); } } } }
第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。