HardBirch

玩转 Android MediaPlayer之视频预加载

时间:12-08-09 栏目:安卓入门与提高 作者:张飞不张,文采横飞 评论:11 点击: 4,306 次


       本文是在《玩转 Android MediaPlayer之Media Proxy》基础上做更进一步的开发,实现一个视频客户端很常用的功能~~~预加载。要学会本文介绍的内容,强烈建议把《玩转 Android MediaPlayer之Media Proxy》看懂,由浅入深,你懂的。

预加载,分为两类,本文介绍的是“代理服务器”这种方式:

1.边存边播:下载多少播放多少。

优点:快速加载播放,实现简单;缺点:不能拖动未存区域;适合音频媒体

2.代理服务器:预先下载媒体的头部(头部Size为 s1 byte)->监听播放器的请求,当Request的是预加载的URL->代理把媒体头部作为Response返回给播放器,并改Ranage 为 s1 byte 发送Request->代理服务器纯粹作为透传。

优点:快速加载播放,支持拖动;缺点:实现非常复杂;适合视频媒体

      预加载不仅可以缩短视频媒体的加载过程,还为“分段拼接”提供支持......通俗地说,IOS的播放器是高帅富,支持2个播放器交替播放从而无缝播放分片视频;Android的播放器是男屌丝,只能有一个实例一个个播放,切换分片视频时有明显的蛋疼感......使用预加载可以缩短停顿的时间。

先来看看预加载的效果,预加载4000ms打开视频消耗1420ms,不用预加载打开视频消耗2633ms:

==================================================================================================

 

本文的源码可以到http://download.csdn.net/detail/hellogv/4486051下载,本文所用的MP4搜索自百度....

HttpGetProxy.java是本文的核心,代理服务器的主要实现,源码如下:

/** * 代理服务器类 * @author hellogv * */ public class HttpGetProxy{ final static public String TAG = "HttpGetProxy"; /** 链接带的端口 */ private int remotePort=-1; /** 远程服务器地址 */ private String remoteHost; /** 代理服务器使用的端口 */ private int localPort; /** 本地服务器地址 */ private String localHost; private ServerSocket localServer = null; /** 收发Media Player请求的Socket */ private Socket sckPlayer = null; /** 收发Media Server请求的Socket */ private Socket sckServer = null; private SocketAddress address; /**下载线程*/ private DownloadThread download = null; /** * 初始化代理服务器 * * @param localport 代理服务器监听的端口 */ public HttpGetProxy(int localport) { try { localPort = localport; localHost = C.LOCAL_IP_ADDRESS; localServer = new ServerSocket(localport, 1,InetAddress.getByName(localHost)); } catch (Exception e) { System.exit(0); } } /** * 把URL提前下载在SD卡,实现预加载 * @param urlString * @return 返回预加载文件名 * @throws Exception */ public String prebuffer(String urlString,int size) throws Exception{ if(download!=null && download.isDownloading()) download.stopThread(true); URI tmpURI=new URI(urlString); String fileName=ProxyUtils.urlToFileName(tmpURI.getPath()); String filePath=C.getBufferDir()+"/"+fileName; download=new DownloadThread(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) { // ----排除HTTP特殊----// String targetUrl = ProxyUtils.getRedirectUrl(urlString); // ----获取对应本地代理服务器的链接----// String localUrl = null; URI originalURI = URI.create(targetUrl); remoteHost = originalURI.getHost(); if (originalURI.getPort() != -1) {// URL带Port address = new InetSocketAddress(remoteHost, originalURI.getPort());// 使用默认端口 remotePort = originalURI.getPort();// 保存端口,中转时替换 localUrl = targetUrl.replace( remoteHost + ":" + originalURI.getPort(), localHost + ":" + localPort); } else {// URL不带Port address = new InetSocketAddress(remoteHost, C.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; int bytes_read; boolean enablePrebuffer=false;//必须放在这里 byte[] local_request = new byte[1024]; byte[] remote_reply = new byte[1024]; while (true) { boolean hasResponseHeader = 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); 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(); enablePrebuffer = isExists && request._isReqRange0;//两者具备才能使用预加载 Log.e(TAG,"enablePrebuffer:"+enablePrebuffer); sentToServer(request._body); // ------------------------------------------------------ // 把网络服务器的反馈发到MediaPlayer,网络服务器->代理服务器->MediaPlayer // ------------------------------------------------------ boolean enableSendHeader=true; while ((bytes_read = sckServer.getInputStream().read(remote_reply)) != -1) { byte[] tmpBuffer = new byte[bytes_read]; System.arraycopy(remote_reply, 0, tmpBuffer, 0, tmpBuffer.length); if(hasResponseHeader){ sendToMP(tmpBuffer); } else{ List<byte[]> httpResponse=httpParser.getResponseBody(remote_reply, bytes_read); if(httpResponse.size()>0){ hasResponseHeader = true; if (enableSendHeader) { // send http header to mediaplayer sendToMP(httpResponse.get(0)); String responseStr = new String(httpResponse.get(0)); Log.e(TAG+"<---", responseStr); } if (enablePrebuffer) {//send prebuffer to mediaplayer int fileBufferSize = sendPrebufferToMP(request._prebufferFilePath); if (fileBufferSize > 0) {//重新发送请求到服务器 String newRequestStr = httpParser.modifyRequestRange(request._body, fileBufferSize); Log.e(TAG + "-pre->", newRequestStr); enablePrebuffer = false; // 下次不处理response的http header sentToServer(newRequestStr); enableSendHeader = false; hasResponseHeader = false; continue; } } //发送剩余数据 if (httpResponse.size() == 2) { sendToMP(httpResponse.get(1)); } } } } Log.e(TAG, ".........over.........."); // 关闭 2个SOCKET sckPlayer.close(); sckServer.close(); } catch (Exception e) { Log.e(TAG,e.toString()); Log.e(TAG,ProxyUtils.getExceptionMessage(e)); } } } private int sendPrebufferToMP(String fileName) throws IOException { int fileBufferSize=0; byte[] file_buffer = new byte[1024]; int bytes_read = 0; FileInputStream fInputStream = new FileInputStream(fileName); while ((bytes_read = fInputStream.read(file_buffer)) != -1) { fileBufferSize += bytes_read; byte[] tmpBuffer = new byte[bytes_read]; System.arraycopy(file_buffer, 0, tmpBuffer, 0, bytes_read); sendToMP(tmpBuffer); } fInputStream.close(); Log.e(TAG,"读取完毕...下载:"+download.getDownloadedSize()+",读取:"+fileBufferSize); return fileBufferSize; } private void sendToMP(byte[] bytes) throws IOException{ sckPlayer.getOutputStream().write(bytes); sckPlayer.getOutputStream().flush(); } private void sentToServer(String requestStr) throws IOException{ try { if(sckServer!=null) sckServer.close(); } catch (Exception ex) {} sckServer = new Socket(); sckServer.connect(address); sckServer.getOutputStream().write(requestStr.getBytes());// 发送MediaPlayer的请求 sckServer.getOutputStream().flush(); } }

 

 

声明: 本文由( 张飞不张,文采横飞 )原创编译,转载请保留链接: 玩转 Android MediaPlayer之视频预加载

玩转 Android MediaPlayer之视频预加载:目前有11 条留言

  1. 11楼
    kf156:

    顶虫哥!

    2012-08-09 09:37 [回复]
  2. 10楼
    xuyan87101:

    顶!

    2012-08-09 09:37 [回复]
  3. 顶gv哥。

    2012-08-09 09:38 [回复]
  4. 8楼
    s278777851:

    神作, 标记一下, 稍后再看

    2012-08-09 09:40 [回复]
  5. 7楼
    jj120522:

    大力支持.

    2012-08-09 09:42 [回复]
  6. 6楼
    iveshuang:

    顶一个

    2012-08-09 09:48 [回复]
  7. 5楼
    hmc1985:

    不错!

    2012-08-09 10:37 [回复]
  8. 4楼
    nszkadrgg:

    写的好!!

    2012-08-09 13:13 [回复]
  9. 地板
    firstests:

    121

    2012-08-21 22:50 [回复]
  10. 板凳
    czqwust:

    很好,楼主很强大!

    2012-08-26 14:46 [回复]
  11. 沙发
    philofly:

    博主,IOS的播放器是高帅富,支持2个播放器交替播放从而无缝播放分片视频;Android的播放器是男屌丝,只能有一个实例一个个播放,切换分片视频时有明显的蛋疼感。

    现在android上使用m3u8播放也不会存在这个问题了。你的信息过时了。

    2012-08-30 13:27 [回复]

发表评论


QQ群互动

Linux系统与内核学习群:194051772

WP建站技术学习交流群:194062106

魔豆之路QR

魔豆的Linux内核之路

魔豆的Linux内核之路

优秀工程师当看优秀书籍

优秀程序员,要看优秀书!

赞助商广告

友荐云推荐