您的位置:首页 > 移动开发 > Android开发

android mediaplayer 实现歌曲边播放边下载

2015-12-04 16:38 423 查看
做音乐播放器,有时候会用到系统自带的mediaplayer播放器,这个播放器底层是在linux上面,封装了一些api供使用者调用,由于网络HTTP请求歌曲流这一块的都已经被封装了,所以要想实现歌曲边下载同时还能缓存到我们的文件夹中,需要做的就是从请求过程中间再开辟一个中间代理,通过代理去处理之间的输入输出流,同时在代理中实现本文的功能,这样就能实现我们想要的边下边播了。

本文的采用的就是利用创建本地socket代理,通过代理mediaplayer发送的请求,之后由代理去发送真实的请求,获取真实地址返回数据,然后再由代理写回给mediaplayer,同时写给本地去缓存。

小坑:

1、只采用一个socket代理+ 一个http的模式,歌曲可以播放正常,但是mediaplayer的seekTo方法失效,原因:分析mediaplayer的请求抓包,少了一些请求数据,导致mediaPlayer的duration一直为0。

2、碰到服务端返回采用了ngnix的redirect 也就是302跳转,导致本地文件会写的特别小,因为mediaplayer会根据302跳转标识去解析redirect的地址然后重新发送绕过代理的新请求。

所以本文最后采用的是一个本地socket,专门负责代理mediaplayer的请求和回写,通过捕获mediaplayer请求,替换掉http中的请求地址,然后通过另一个socket代理去处理发送至真实请求地址,根据请求回来的返回流,分析是否是歌曲,是歌曲流返回给mediaplayer,不是歌曲流,解析出里面的真实地址,再次发送远程socket,继续迭代分析,最终获取歌曲流并解析。也就是说1个socket与mediaplayer打交道,一个与网络打交道。

逻辑上就是上面了,接下来就是实现之(准备了一份demo,因为和太多demo放一起了,此次暂不放出,之后会统一放入github上)

主要代码:

初始化本地代理:

public void init() {
isThreadRun = true;
try {
localSocket = new ServerSocket(LOCAL_PORT, 0, InetAddress.getByName(LOCAL_IP_ADDRESS));//监听本地端口和IP
localSocket.setSoTimeout(TIME_OUT);
} catch (Exception e) {
MLog.error(this, "Can`t create a local serverSocket to listener local port");
}
}


重点代理类在线程中运行:

@Override
public void run() {
while (isThreadRun) {
try {
Socket client = localSocket.accept();//本地socket代理监听本地
if (client == null) {
continue;
}
newSocket = client;
MLog.debug(this, "socket,get it!");
MediaPlayerRequestInfo info = readRequest(client);//获取之前的请求并转换成新的请求
Socket remoteSocket = sendRemoteRequest(info);//将新的数据请求协议发送出去
if (remoteSocket == null) {
continue;
}
processRequest(remoteSocket, client, info);//代理获取信息后开始中转
} catch (IOException e) {

}
}

}


主要处理数据类:

该类用于读取之前的协议,然后替换成真实的请求地址,然后给后续发送

private MediaPlayerRequestInfo readRequest(Socket client) {//从本地代理中读取请求信息,然后加工替换为新的请求流,用于远程socket转发
int readBytes;
MediaPlayerRequestInfo info = new MediaPlayerRequestInfo();//自己封装的类,主要放入实际上要请求的地址以及歌曲的相关状态值
try {
InputStream localInputStream = client.getInputStream();
String infactRequest = "";
while ((readBytes = localInputStream.read(buffer)) != -1) {
String str = new String(buffer);
infactRequest += str;
if (infactRequest.contains("GET") && infactRequest.contains("\r\n\r\n")) {//找到请求头中的get和\r\n
infactRequest = infactRequest.replaceAll(LOCAL_IP_ADDRESS + ":" + LOCAL_PORT, remoteHost);//替换为真实的请求地址
info.realRequest = infactRequest;
//----以下代码用于本地文件创建,不属于此次所述内容,可忽略,部分已删除

File file = new File(DownloadMusicFileUtils.getMusicCacheDownloadPath() + File.separator +********));
if (!file.exists()) {
file.mkdirs();
}
//----------------------------
 if (infactRequest.contains("Range")) {//如果用户拖动了进度条,删除本地文件,因为拖动了滚动条还有Range则表示本地歌曲还未缓存完,不保存
MLog.debug(this, infactRequest);
MLog.debug(this, "this is range socket ,so do not need to write on local storage, and need to delete the file from local");
deleteFile(info.fileName);
info.shouldWrite = false;//提示该应用这次的请求都不用去管了,毕竟是range的,不是完整歌曲
}
break;
}
}
} catch (IOException e) {
MLog.debug(this, "this request info is error");
return info;
}
return info;
}


该类用于对转发后的socket读取到的数据,同时写给mediaplayer和本地

private void processRequest(Socket remoteSocket, Socket client, MediaPlayerRequestInfo info) throws IllegalStateException, IOException {
InputStream realInputStream = remoteSocket.getInputStream();//获取真实返回数据
if (realInputStream == null) {
return;
}
OutputStream localOutputStream = client.getOutputStream();//向本地播放代理写
FileOutputStream cacheOut = null;//写本地文件
if (info.shouldWrite) {
File file = new File(DownloadMusicFileUtils.getMusicCacheDownloadPath() + File.separator + info.fileName);
cacheOut = new FileOutputStream(file);
}
try {
int readBytes;
while (isThreadRun && (readBytes = realInputStream.read(buffer, 0, buffer.length)) != -1) {
if (newSocket != client) {
throw new Exception();
}
if (readBytes < 900) {//redirect 302 or last bytes
String redirectStr = new String(buffer);
if (redirectStr.contains("302") && redirectStr.contains("Location:")) {

//部分代码删除了,此处可以省略,该处主要是判断http302的时候跳转就需要重新组装请求,发送,把之前的socket关闭,系统会在写的时候发现,然后抛异常,在异常中删除之前创建的文件
 

Socket newRemoteSocket = sendRemoteRequest(info);//发送新请求
if (remoteSocket == null) {
return;
}
processRequest(newRemoteSocket, client, info);//调自己
return;
}
}
localOutputStream.write(buffer, 0, readBytes);
try {
if (info.shouldWrite) {
cacheOut.write(buffer, 0, readBytes);
}
}catch(Exception e){
info.shouldWrite = false;//文件写不进去的情况
}
}
} catch (Exception e) {
MLog.debug(this, "the socket is be closed,it means download is not finish and now should delete download file" + e.getMessage());
deleteFile(info.fileName);
info.shouldWrite = false;
} finally {
MLog.debug(this, "finish");
realInputStream.close();
localOutputStream.close();
if (cacheOut != null) {
cacheOut.close();
}
client.close();
remoteSocket.close();
}
}


模拟mediaplayer发送协议

private Socket sendRemoteRequest(MediaPlayerRequestInfo info) {
Socket remoteSocket = null;
try {
remoteSocket = new Socket();
remoteSocket.connect(new InetSocketAddress(remoteHost, HTTP_PORT));
remoteSocket.getOutputStream().write(info.realRequest.getBytes());
remoteSocket.getOutputStream().flush();
} catch (Exception e) {
return remoteSocket;
}
return remoteSocket;
}


入口就是把datasource的请求url地址改成localIp和port就可以了,记得要把真实的请求地址也给过去,否则后续在代理中给远程socket使用的替换操作无法执行

mediaplayer.setDataSource(context, url)


重点的类应该就这些了,什么本地文件缓存管理,文件折半删除啊什么的就不放在一起讨论了

转载请注明:iamwsbear@gmail.com

///----------------------------博客空了好久了,之间写了好多demo,要放入博客,需要整理整理,以后会把代码统一放入github上------------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: