編輯:Android開發實例
本文是在 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上要實現類似Launch的抽屜效果,大家一定首先會想起SlidingDrawer。SlidingDrawer是android官方控件之一,但是
JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個
很多程序在完全載入之前往往會先顯示一個載入界面,提示用戶等待,這樣對提高用戶體
如上一節中所講的那樣創建了ApiDemo工程後,我們就可以進行每個示例代碼的分