本文是在《玩转 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/hcb1230/6593541下载,本文所用的MP4搜索自百度....
HttpGetProxy.java是本文的核心,代理服务器的主要实现,源码如下:
<span style="font-family:Comic Sans MS;font-size:18px;">/**
* 代理服务器类
* @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();
}
}</span>