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上)
主要代码:
初始化本地代理:
重点代理类在线程中运行:
主要处理数据类:
该类用于读取之前的协议,然后替换成真实的请求地址,然后给后续发送
该类用于对转发后的socket读取到的数据,同时写给mediaplayer和本地
模拟mediaplayer发送协议
入口就是把datasource的请求url地址改成localIp和port就可以了,记得要把真实的请求地址也给过去,否则后续在代理中给远程socket使用的替换操作无法执行
重点的类应该就这些了,什么本地文件缓存管理,文件折半删除啊什么的就不放在一起讨论了
转载请注明:iamwsbear@gmail.com
///----------------------------博客空了好久了,之间写了好多demo,要放入博客,需要整理整理,以后会把代码统一放入github上------------------------
本文的采用的就是利用创建本地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上------------------------
相关文章推荐
- android学习笔记(七):Handler消息传递机制
- android开发之软键盘控制
- Android之View基础总结(View的事件体系一)
- Android开发周报:弹幕源码开放送
- android launcher3拖放功能深入剖析
- 【转载】Android样式的开发:View Animation篇
- Android 记住密码和自动登录界面的实现(SharedPreferences 的用法)
- 给 Android 开发者的 RxJava 详解
- Android开发之Tools使用
- Android动画解析2-帧动画实现
- Android Studio的使用02-Can't resolve the symblo异常
- Android调用WebService(图文教程)
- Android URI简介
- android(12)(简单学生管理系统,包括文件写入SD卡,pull解析xml文件等)
- Android 对话框(Dialog)大全
- Android调用系统自带的文件管理器进行文件选择
- Android 控件GridView的使用
- Camera和Photo相关
- LinearGradient在android开发中实现字体渐变效果实例
- Android Studio 操作记录