如何从其他Android设备流式传输音频?本地URL服务器搭建及问题排查
方案一:在Android设备搭建轻量HTTP服务器共享音频文件
这种方式最直接,因为MediaPlayer原生支持HTTP URL数据源,只要把音频文件通过HTTP服务暴露出去,其他设备就能直接用setDataSource("http://[设备IP]:[端口]/[文件名]")播放。
实现步骤:
添加轻量HTTP服务器依赖(比如NanoHTTPD,体积小无需额外权限)
在app的build.gradle中添加:dependencies { implementation 'org.nanohttpd:nanohttpd:2.3.1' }编写自定义HTTP服务器类
继承NanoHTTPD,处理文件请求:public class AudioFileServer extends NanoHTTPD { private File audioDir; public AudioFileServer(int port, File audioDir) { super(port); this.audioDir = audioDir; } @Override public Response serve(IHTTPSession session) { String uri = session.getUri(); // 简化处理,直接根据URI匹配音频文件 File audioFile = new File(audioDir, uri.substring(1)); // 去掉开头的/ if (audioFile.exists() && audioFile.isFile()) { try { return newFixedLengthResponse(Response.Status.OK, "audio/mpeg", new FileInputStream(audioFile), audioFile.length()); } catch (FileNotFoundException e) { return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "File not found"); } } else { return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "File not found"); } } }启动服务器
在Activity或Service中初始化并启动:// 假设音频文件存在手机的外部存储音乐目录下 File audioDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath()); AudioFileServer server = new AudioFileServer(8080, audioDir); try { server.start(); Log.d("Server", "HTTP server started at http://" + getLocalIpAddress() + ":8080"); } catch (IOException e) { e.printStackTrace(); }获取本地IP地址的工具方法
private String getLocalIpAddress() { try { for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { NetworkInterface intf = en.nextElement(); for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { InetAddress inetAddress = enumIpAddr.nextElement(); if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { return inetAddress.getHostAddress(); } } } } catch (SocketException ex) { ex.printStackTrace(); } return null; }其他设备播放
拿到服务器设备的IP后,直接在客户端设备调用:MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource("http://192.168.1.100:8080/test.mp3"); // 替换成实际IP和文件名 mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(MediaPlayer::start);
注意:要申请READ_EXTERNAL_STORAGE(Android 12及以下)或READ_MEDIA_AUDIO(Android 13+)权限,以及INTERNET权限。
方案二:通过Socket实时流式传输音频数据
如果不需要持久化文件共享,而是要实时传输音频(比如实时录音并传输),可以用Socket直接传输音频字节流。但MediaPlayer不能直接用Socket输入流,需要通过管道流做适配:
发送端(音频源设备)
读取音频文件的字节,分块发送到Socket:
// 启动Socket服务端,放在子线程执行 new Thread(() -> { try { ServerSocket serverSocket = new ServerSocket(8080); Socket clientSocket = serverSocket.accept(); OutputStream outputStream = clientSocket.getOutputStream(); File audioFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC), "test.mp3"); FileInputStream fis = new FileInputStream(audioFile); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); outputStream.flush(); } fis.close(); outputStream.close(); clientSocket.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } }).start();
接收端(播放设备)
通过Socket接收字节流,用PipeInputStream和PipeOutputStream桥接,让MediaPlayer可以读取:
// 放在子线程执行,避免阻塞主线程 new Thread(() -> { try { Socket socket = new Socket("192.168.1.100", 8080); // 替换成发送端IP InputStream inputStream = socket.getInputStream(); PipeInputStream pipeIn = new PipeInputStream(); PipeOutputStream pipeOut = new PipeOutputStream(pipeIn); // 启动子线程把Socket输入流写入管道 new Thread(() -> { byte[] buffer = new byte[4096]; int bytesRead; try { while ((bytesRead = inputStream.read(buffer)) != -1) { pipeOut.write(buffer, 0, bytesRead); pipeOut.flush(); } pipeOut.close(); inputStream.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } }).start(); // 用MediaPlayer播放管道流 MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(pipeIn); mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(MediaPlayer::start); } catch (IOException e) { e.printStackTrace(); } }).start();
关于你之前的Socket.io问题
MediaPlayer只支持HTTP、RTSP、本地文件等标准数据源,Socket.io是基于WebSocket的自定义协议,MediaPlayer无法识别这种URL格式,所以直接设置setDataSource("IP:8080/路径")会报错,必须用上述两种适配方式。
内容的提问来源于stack exchange,提问作者goodjamal

