Spring Boot接收直播视频流并转发至React前端的实现方案咨询
哈哈,我完全懂你搜不到合适教程的苦恼——毕竟普通的文件视频和直播流根本不是一回事!我来给你梳理一套从直播流接入Spring Boot到转发到React前端的完整方案,咱们一步一步来,顺便补点关键术语,以后搜资料就顺畅多了:
既然你能决定推送格式,咱挑最顺手、生态最成熟的来:
- HLS (HTTP Live Streaming):首推!基于HTTP协议,前端支持拉满(大部分浏览器直接兼容,不用额外插件),Spring Boot处理起来也简单,临时存储也方便(生成的小分片文件好管理),唯一缺点是延迟大概10-30秒,适合普通直播场景
- WebRTC:追求低延迟(几十毫秒级)的话选它,比如互动直播、连麦场景,但前后端处理相对复杂,需要额外的信令服务
- RTMP:传统直播协议,推流效率高,但前端原生不支持,得转成HLS或者用flv.js这类库播放,适合已有RTMP推流源的场景
1. 接收直播流:两种方案任你选
Spring Boot本身不是专门的媒体服务器,小流量可以直接扛,大流量建议搭配专门的媒体服务器(比如Nginx-RTMP)更稳定。
方案A:Spring Boot直接接推流(小流量场景)
核心思路是把推流过来的字节流,用FFmpeg转成HLS格式的分片文件。先给你个伪代码参考:
@RestController @RequestMapping("/live") public class LiveStreamController { // 分片文件存储路径,你可以换成对象存储地址 private final String HLS_STORAGE = "/tmp/hls/"; @PostMapping("/push") public ResponseEntity<String> receiveStream(HttpServletRequest request) throws IOException, InterruptedException { // 读取推流的原始字节流 InputStream inputStream = request.getInputStream(); // 调用FFmpeg把输入流转成HLS分片 ProcessBuilder ffmpegCmd = new ProcessBuilder( "ffmpeg", "-i", "-", "-c:v", "libx264", "-c:a", "aac", "-f", "hls", "-hls_time", "10", "-hls_list_size", "5", // 每10秒一个分片,保留最近5个 HLS_STORAGE + "live.m3u8" ); Process process = ffmpegCmd.start(); // 把推流数据写入FFmpeg的标准输入 try (OutputStream os = process.getOutputStream()) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } } process.waitFor(); return ResponseEntity.ok("直播流已接收并转码"); } }
注意:服务器上要先装FFmpeg,或者用Java封装的FFmpeg库(比如ffmpeg-java)。
方案B:搭配Nginx-RTMP接收推流(大流量推荐)
Nginx-RTMP是专门的媒体服务器,接推流效率比Spring Boot高N倍,还能自动生成HLS文件。Spring Boot只需要做权限控制、地址分发就行。
先配Nginx的RTMP模块:
rtmp { server { listen 1935; # RTMP默认端口 chunk_size 4096; application live { live on; # 开启直播模式 hls on; # 开启HLS转码 hls_path /tmp/hls; # HLS文件存储路径 hls_fragment 10s; # 每10秒一个分片 hls_playlist_length 60s; # 播放列表保留60秒的内容 } } }
再配Nginx的HTTP服务,让前端能访问HLS文件:
http { server { listen 8080; location /hls { root /tmp; add_header Cache-Control no-cache; # 禁止缓存,保证直播实时性 add_header Access-Control-Allow-Origin *; # 解决跨域问题 } } }
最后Spring Boot提供一个接口返回HLS播放地址(可以加权限验证):
@GetMapping("/live/play-url") public ResponseEntity<String> getPlayUrl() { // 这里可以加用户权限校验 String hlsUrl = "http://你的服务器IP:8080/hls/live.m3u8"; return ResponseEntity.ok(hlsUrl); }
2. 临时存储直播流
用HLS的话,临时存储就是保存生成的.m3u8索引文件和.ts分片文件,你可以存在本地磁盘,或者MinIO、阿里云OSS这类对象存储里。
可以给Spring Boot加个定时任务,自动清理旧的分片文件,避免占满磁盘:
@Scheduled(fixedRate = 3600000) // 每小时执行一次 public void cleanOldHlsFiles() { File hlsDir = new File(HLS_STORAGE); if (!hlsDir.exists()) return; File[] files = hlsDir.listFiles(); if (files == null) return; // 清理1小时前的文件 long cutoffTime = System.currentTimeMillis() - 3600000; for (File file : files) { if (file.lastModified() < cutoffTime) { file.delete(); } } }
3. 转发流到前端
其实HLS本身就是通过HTTP拉取分片文件,所以前端直接访问.m3u8地址就能播放,Spring Boot主要负责权限控制和地址分发;如果用WebRTC,Spring Boot需要做信令转发(交换两端的连接信息)。
1. HLS播放(简单易上手)
大部分现代浏览器原生支持HLS,也可以用hls.js库兼容更多浏览器,给你个组件示例:
import React, { useEffect, useRef } from 'react'; import Hls from 'hls.js'; const LivePlayer = () => { const videoRef = useRef(null); useEffect(() => { if (!videoRef.current) return; const video = videoRef.current; if (Hls.isSupported()) { const hls = new Hls(); // 从后端获取播放地址,这里可以换成接口请求 hls.loadSource('http://你的服务器IP:8080/hls/live.m3u8'); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () => { video.play(); }); // 组件卸载时清理资源 return () => hls.destroy(); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Safari原生支持HLS,直接设置地址就行 video.src = 'http://你的服务器IP:8080/hls/live.m3u8'; video.play(); } }, []); return ( <div className="live-player"> <video ref={videoRef} controls width="100%" /> </div> ); }; export default LivePlayer;
2. WebRTC播放(低延迟场景)
如果需要低延迟互动,WebRTC是首选,前端需要通过Spring Boot的信令服务和推流端交换连接信息,示例代码大概是这样:
import React, { useEffect, useRef } from 'react'; const WebRTCPlayer = () => { const videoRef = useRef(null); const peerConn = useRef(null); useEffect(() => { const initWebRTC = async () => { // 创建PeerConnection,用谷歌的公共STUN服务器 peerConn.current = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }); // 接收推流端的媒体流 peerConn.current.ontrack = (event) => { if (videoRef.current) { videoRef.current.srcObject = event.streams[0]; } }; // 从后端获取推流端的SDP Offer const offerRes = await fetch('/live/webrtc/offer', { method: 'POST' }); const offer = await offerRes.json(); await peerConn.current.setRemoteDescription(new RTCSessionDescription(offer)); // 创建Answer并发送给后端 const answer = await peerConn.current.createAnswer(); await peerConn.current.setLocalDescription(answer); await fetch('/live/webrtc/answer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(answer) }); // 发送ICE候选到后端,转发给推流端 peerConn.current.onicecandidate = (event) => { if (event.candidate) { fetch('/live/webrtc/ice', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(event.candidate) }); } }; }; initWebRTC(); return () => peerConn.current?.close(); }, []); return ( <div className="webrtc-player"> <video ref={videoRef} autoPlay playsInline width="100%" /> </div> ); }; export default WebRTCPlayer;
后端需要维护推流端和播放端的连接,转发SDP和ICE候选信息。
- 推流:把直播源的音视频数据发送到服务器的过程(比如用OBS推流到你的服务器)
- 拉流:前端从服务器获取音视频数据并播放的过程
- HLS分片:HLS把长视频切成10-30秒的小
.ts文件,通过.m3u8索引文件管理,前端按需拉取 - 媒体服务器:专门处理音视频流的服务器(比如Nginx-RTMP、SRS),比Spring Boot更适合高并发场景
- 信令服务:WebRTC里用来交换连接信息(SDP、ICE)的服务,Spring Boot可以兼任这个角色
- 高并发场景别让Spring Boot直接接推流,用专门的媒体服务器扛,Spring Boot只做业务逻辑
- HLS延迟是硬伤,如果需要低延迟优先选WebRTC
- 前端播放一定要处理跨域,Nginx或Spring Boot记得配置CORS
- 临时存储的分片文件一定要定时清理,不然磁盘很快就满了
内容的提问来源于stack exchange,提问作者BladesV

