浏览器无法直接播放 RTSP/RTMP 等监控流协议,典型做法是:Spring Boot 调用服务器上安装的 FFmpeg,把拉到的流转成 HLS(.m3u8+.ts),Spring Boot 静态资源暴露地址,Vue 前端用 video.js 或 hls.js 播放。下面给你完整可落地的示例。
一、整体架构
bash
摄像头RTSP → FFmpeg(转HLS) → 输出目录(/hls/xxx.m3u8 + .ts)
↑
Spring Boot ProcessBuilder 启动/停止
↓
Spring Boot 静态资源映射 → http://ip:port/hls/xxx.m3u8
↓
Vue + video.js / hls.js 播放 HLS
⚠️ 建议生产环境用 Nginx 托管 HLS 目录并做跨域,Spring Boot 只负责启停 FFmpeg 进程和接口。
二、服务端 --- Spring Boot 调用 FFmpeg
1. FFmpeg 转 HLS 核心命令
perl
ffmpeg -rtsp_transport tcp \
-i "rtsp://admin:密码@192.168.1.100:554/stream1" \
-c:v libx264 -preset ultrafast -tune zerolatency \
-c:a aac \
-f hls \
-hls_time 2 \
-hls_list_size 5 \
-hls_flags delete_segments \
-hls_segment_filename "/opt/hls/stream_%03d.ts" \
/opt/hls/stream.m3u8
-
-rtsp_transport tcp:比 UDP 稳定,防花屏 -
hls_time 2:每片 2 秒,延迟约 4~8 秒属正常 -
delete_segments:自动删旧切片防磁盘爆满
2. FFmpeg 服务类(启停进程)
java
@Service
public class FfmpegService {
private Process ffmpegProcess;
private static final String HLS_DIR = "/opt/hls/";
// Windows 改成 "C:\\ffmpeg\\bin\\ffmpeg.exe"
public synchronized void startStream(String rtspUrl) throws IOException {
stopStream(); // 先停旧进程
new File(HLS_DIR).mkdirs();
List<String> cmd = new ArrayList<>();
cmd.add("ffmpeg");
cmd.addAll(List.of("-rtsp_transport", "tcp"));
cmd.addAll(List.of("-i", rtspUrl));
cmd.addAll(List.of("-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency"));
cmd.addAll(List.of("-c:a", "aac"));
cmd.addAll(List.of("-f", "hls", "-hls_time", "2", "-hls_list_size", "5"));
cmd.addAll(List.of("-hls_flags", "delete_segments"));
cmd.addAll(List.of("-hls_segment_filename", HLS_DIR + "stream_%03d.ts"));
cmd.add(HLS_DIR + "stream.m3u8");
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true);
ffmpegProcess = pb.start();
// 异步消费 FFmpeg 输出,防止阻塞
new Thread(() -> {
try (BufferedReader br = new BufferedReader(
new InputStreamReader(ffmpegProcess.getInputStream()))) {
while (br.readLine() != null) {}
} catch (IOException ignored) {}
}).start();
}
public synchronized void stopStream() {
if (ffmpegProcess != null && ffmpegProcess.isAlive()) {
ffmpegProcess.destroyForcibly();
}
}
}
3. Controller + 静态资源映射
typescript
@RestController
@RequestMapping("/api/stream")
public class StreamController {
@Autowired
private FfmpegService ffmpegService;
@PostMapping("/start")
public String start(@RequestParam String rtsp) throws IOException {
ffmpegService.startStream(rtsp);
return "started";
}
@PostMapping("/stop")
public String stop() {
ffmpegService.stopStream();
return "stopped";
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 访问 http://ip:8080/hls/stream.m3u8
registry.addResourceHandler("/hls/**")
.addResourceLocations("file:/opt/hls/");
}
}
三、前端 --- Vue 播放 HLS
安装依赖
css
npm install video.js hls.js
Vue 3 组件示例
xml
<template>
<div>
<video ref="videoEl" id="my-video" class="video-js vjs-default-skin"
controls width="800" height="450">
</video>
<button @click="play">开始播放</button>
</div>
</template>
<script setup>
import { ref, onBeforeUnmount } from 'vue'
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
const videoEl = ref(null)
let player = null
const play = () => {
if (player) player.dispose()
player = videojs(videoEl.value, {
sources: [{
src: 'http://localhost:8080/hls/stream.m3u8',
type: 'application/x-mpegURL'
}]
})
}
onBeforeUnmount(() => {
player?.dispose()
})
</script>
Safari 原生支持 HLS,Chrome/Firefox 靠 video.js 内部集成 hls.js 自动处理。
四、常见问题排查
问题
原因与解决
播放器 404
HLS 目录未被 Spring 静态资源映射或 Nginx 未配 alias
能下载 m3u8 但播不出
.ts 切片路径不对,确认 segment_filename 在同一目录
花屏/断流
RTSP 改用 -rtsp_transport tcp,检查网络
多路流 CPU 飙高
FFmpeg 转码耗 CPU,可用 -c:v copy(不重编码,需源流 H.264+AAC)或用 ZLMediaKit/SRS 替代
CORS 报错
Spring Boot 加 CORS 或 Nginx 加 Access-Control-Allow-Origin *
五、进阶建议
-
轻量场景:上述 FFmpeg + Spring Boot 够用
-
多路监控/低延迟 :推荐用 ZLMediaKit 或 **SRS(Simple Realtime Server)** 做流媒体服务器,Spring Boot 只做信令控制,Vue 播放 HTTP-FLV 或 WebRTC,延迟可压到 1 秒内