本文來自http://blog.csdn.net/hellogv/ ,引用必須註明出處!
本文是在《玩轉 Android MediaPlayer之Media Proxy》基礎上做更進一步的開發,實現一個視頻用戶端很常用的功能~~~預先載入。要學會本文介紹的內容,強烈建議把《玩轉 Android MediaPlayer之Media Proxy》看懂,由淺入深,你懂的。
預先載入,分為兩類,本文介紹的是“Proxy 伺服器”這種方式:
1.邊存邊播:下載多少播放多少。
優點:快速載入播放,實現簡單;缺點:不能拖動未存地區;適合音頻媒體
2.Proxy 伺服器:預先下載媒體的頭部(頭部Size為 s1 byte)->監聽播放器的請求,當Request的是預先載入的URL->代理把媒體頭部作為Response返回給播放器,並改Ranage 為 s1 byte 發送Request->Proxy 伺服器純粹作為透傳。
優點:快速載入播放,支援拖動;缺點:實現非常複雜;適合視頻媒體
預先載入不僅可以縮短視頻媒體的載入過程,還為“分段拼接”提供支援......通俗地說,IOS的播放器是高帥富,支援2個播放器交替播放從而無縫播放分區視頻;Android的播放器是男屌絲,只能有一個執行個體一個個播放,切換分區視頻時有明顯的蛋疼感......使用預先載入可以縮短停頓的時間。
先來看看預先載入的效果,預先載入4000ms開啟視頻消耗1420ms,不用預先載入開啟視頻消耗2633ms:
==================================================================================================
本文的源碼可以到http://download.csdn.net/detail/hellogv/4486051下載,本文所用的MP4搜尋自百度....
HttpGetProxy.java是本文的核心,Proxy 伺服器的主要實現,源碼如下:
/** * Proxy 伺服器類 * @author hellogv * */public class HttpGetProxy{final static public String TAG = "HttpGetProxy";/** 連結帶的連接埠 */private int remotePort=-1;/** 遠程伺服器位址 */private String remoteHost;/** Proxy 伺服器使用的連接埠 */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;/** * 初始化Proxy 伺服器 * * @param localport Proxy 伺服器監聽的連接埠 */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);// ----擷取對應本地Proxy 伺服器的連結----//String localUrl = null;URI originalURI = URI.create(targetUrl);remoteHost = originalURI.getHost();if (originalURI.getPort() != -1) {// URL帶Portaddress = new InetSocketAddress(remoteHost, originalURI.getPort());// 使用預設連接埠remotePort = originalURI.getPort();// 儲存連接埠,中轉時替換localUrl = targetUrl.replace(remoteHost + ":" + originalURI.getPort(), localHost + ":"+ localPort);} else {// URL不帶Portaddress = new InetSocketAddress(remoteHost, C.HTTP_PORT);// 使用80連接埠remotePort = -1;localUrl = targetUrl.replace(remoteHost, localHost + ":"+ localPort);}String[] result= new String[]{targetUrl,localUrl};return result;}/** * 非同步啟動Proxy 伺服器 * * @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之前關閉過去的Socketif (sckPlayer != null)sckPlayer.close();if (sckServer != null)sckServer.close();} catch (IOException e1) {}try {// --------------------------------------// 監聽MediaPlayer的請求,MediaPlayer->Proxy 伺服器// --------------------------------------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,網路伺服器->Proxy 伺服器->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 mediaplayersendToMP(httpResponse.get(0));String responseStr = new String(httpResponse.get(0));Log.e(TAG+"<---", responseStr);}if (enablePrebuffer) {//send prebuffer to mediaplayerint fileBufferSize = sendPrebufferToMP(request._prebufferFilePath);if (fileBufferSize > 0) {//重新發送請求到伺服器String newRequestStr = httpParser.modifyRequestRange(request._body,fileBufferSize);Log.e(TAG + "-pre->", newRequestStr);enablePrebuffer = false;// 下次不處理response的http headersentToServer(newRequestStr);enableSendHeader = false;hasResponseHeader = false;continue;}}//發送剩餘資料if (httpResponse.size() == 2) {sendToMP(httpResponse.get(1));}}}}Log.e(TAG, ".........over..........");// 關閉 2個SOCKETsckPlayer.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();}}