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

本文介绍如何利用Android MediaPlayer实现歌曲边播放边下载。通过创建本地Socket代理,处理MediaPlayer的请求和回写,解析HTTP响应,实现边下边播。遇到的问题包括MediaPlayer的seekTo方法失效和302重定向导致缓存错误。解决方案是使用两个Socket,一个与MediaPlayer交互,一个处理网络请求,确保数据正确流转。

做音乐播放器,有时候会用到系统自带的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上------------------------



评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值