springboot+vue+ffmpeg实现视频监控的直播拉流播放
作者:每日技术
浏览器无法直接播放监控摄像头的 RTSP/RTMP 流,标准做法是 Spring Boot 调用服务器端 FFmpeg 将 RTSP 转成 HLS(.m3u8) 或推送 RTMP→HTTP-FLV,再由 Vue 前端用 video.js / flv.js / hls.js 播放。下面给你一套较常用的 RTSP → HLS + Spring Boot 管理 + Vue 播放 方案,以及低延迟的 HTTP-FLV 备选方案。
一、整体架构(推荐 HLS 方案)
IPC摄像头(RTSP)
→ FFmpeg 拉流转 HLS(.m3u8 + .ts 切片) 输出到目录
→ Nginx/Spring Boot 静态资源暴露 http://ip/hls/xxx.m3u8
→ Vue + video.js / hls.js 播放
↑
Spring Boot 用 ProcessBuilder 启停 FFmpeg 进程
生产环境建议用 Nginx 托管 HLS 目录(配 CORS),Spring Boot 只负责启停 FFmpeg 和管理接口。
二、服务端 — Spring Boot 调用 FFmpeg
1. FFmpeg 转 HLS 命令
ffmpeg -rtsp_transport tcp \ -i "rtsp://admin:password@192.168.1.100:554/Streaming/Channels/101" \ -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/cam1_%03d.ts" \ /opt/hls/cam1.m3u8
参数说明:
-rtsp_transport tcp:比 UDP 稳定,减少花屏-hls_time 2:每片 2 秒,延迟约 4~8 秒delete_segments:自动删旧切片防磁盘爆满
2. Spring Boot 启停 FFmpeg 进程
@Service
@Slf4j
public class FfmpegService {
private final Map<String, Process> processMap = new ConcurrentHashMap<>();
public void startStream(String camId, String rtspUrl, String outM3u8) throws IOException {
if (processMap.containsKey(camId)) return;
List<String> cmd = Arrays.asList(
"ffmpeg",
"-rtsp_transport", "tcp",
"-i", rtspUrl,
"-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency",
"-c:a", "aac",
"-f", "hls",
"-hls_time", "2",
"-hls_list_size", "5",
"-hls_flags", "delete_segments",
outM3u8
);
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true);
Process process = pb.start();
processMap.put(camId, process);
log.info("FFmpeg started for camera: {}", camId);
}
public void stopStream(String camId) {
Process p = processMap.remove(camId);
if (p != null) {
p.destroy();
log.info("FFmpeg stopped for camera: {}", camId);
}
}
}
3. Controller 示例
@RestController
@RequestMapping("/api/stream")
public class StreamController {
@Autowired
private FfmpegService ffmpegService;
@PostMapping("/start/{camId}")
public String start(@PathVariable String camId, @RequestParam String rtsp) throws IOException {
ffmpegService.startStream(camId, rtsp, "/opt/hls/" + camId + ".m3u8");
return "started";
}
@PostMapping("/stop/{camId}")
public String stop(@PathVariable String camId) {
ffmpegService.stopStream(camId);
return "stopped";
}
}
4. 静态资源映射(开发测试用)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/hls/**")
.addResourceLocations("file:/opt/hls/");
}
}
正式环境推荐用 Nginx 托管 /opt/hls,加跨域头:
location /hls {
types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; }
root /opt/hls;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
三、前端 — Vue 播放 HLS 流
安装依赖
npm install video.js videojs-contrib-hls
Vue3 组件示例
<template>
<video ref="videoEl" class="video-js vjs-big-play-centered" style="width:640px"></video>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
const videoEl = ref(null)
let player = null
onMounted(() => {
player = videojs(videoEl.value, {
controls: true,
autoplay: true,
preload: 'auto',
sources: [{
type: 'application/x-mpegURL',
src: 'http://你的IP/hls/cam1.m3u8'
}]
})
})
onBeforeUnmount(() => {
player?.dispose()
})
</script>播放地址即:http://ip/hls/cam1.m3u8
四、低延迟方案 — RTSP→RTMP→HTTP-FLV(可选)
若 HLS 延迟(5~10s)不满足,可用 Nginx + nginx-http-flv-module:
- FFmpeg 把 RTSP 推 RTMP:
ffmpeg -rtsp_transport tcp -i rtsp://... -c copy -f flv rtmp://localhost/live/cam1 - Nginx http-flv 分发:
http://ip/live?port=1935&app=live&stream=cam1 - Vue 用 flv.js 播放:
if (flvjs.isSupported()) { const player = flvjs.createPlayer({ type: 'flv', url: 'http://ip/live?port=1935&app=live&stream=cam1' }) player.attachMediaElement(document.getElementById('video')) player.load() player.play() }
延迟一般可压到 1~3 秒。
五、常见问题与建议
| 问题 | 解决思路 |
|---|---|
| 播放无画面 | 确认 FFmpeg 已生成 .m3u8 文件;先用 VLC 测试 RTSP 流是否正常 |
| 跨域报错 | Nginx 添加 Access-Control-Allow-Origin * 响应头 |
| 磁盘满 | 务必添加 -hls_flags delete_segments 参数,启用切片自动删除 |
| 多路并发 | 不要全走 Spring Boot 代理;建议用 Nginx/SRS/ZLMediaKit 做流媒体分发层 |
| 超低延迟(<500ms) | 考虑 WebRTC 方案,搭配 ZLMediaKit 或 webrtc-streamer |
六、项目结构
video-stream-demo/
├── backend/
│ └── src/main/java/com/demo/videostream/
│ ├── VideoStreamApplication.java
│ ├── config/WebMvcConfig.java
│ ├── service/FfmpegService.java
│ └── controller/StreamController.java
│ └── pom.xml
└── frontend/
└── src/
├── components/LivePlayer.vue
├── App.vue
└── main.js
├── package.json
└── vite.config.js
七、完整代码
后端 — Spring Boot
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>video-stream-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>VideoStreamApplication.java
package com.demo.videostream;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class VideoStreamApplication {
public static void main(String[] args) {
SpringApplication.run(VideoStreamApplication.class, args);
}
}
FfmpegService.java(核心:ProcessBuilder 启停 FFmpeg)
package com.demo.videostream.service;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class FfmpegService {
private final Map<String, Process> processMap = new ConcurrentHashMap<>();
public synchronized void start(String camId, String rtspUrl, String outM3u8) throws IOException {
if (processMap.containsKey(camId)) return;
ProcessBuilder pb = new ProcessBuilder(
"ffmpeg",
"-rtsp_transport", "tcp",
"-i", rtspUrl,
"-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency",
"-c:a", "aac",
"-f", "hls",
"-hls_time", "2",
"-hls_list_size", "5",
"-hls_flags", "delete_segments",
outM3u8
);
Process p = pb.start();
processMap.put(camId, p);
}
public synchronized void stop(String camId) {
Process p = processMap.remove(camId);
if (p != null) p.destroy();
}
}
StreamController.java
package com.demo.videostream.controller;
import com.demo.videostream.service.FfmpegService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/stream")
@CrossOrigin
public class StreamController {
@Autowired
private FfmpegService ffmpegService;
// POST /api/stream/start/cam1?rtsp=rtsp://admin:123@192.168.1.100:554/Streaming/Channels/101
@PostMapping("/start/{camId}")
public String start(@PathVariable String camId, @RequestParam String rtsp) throws Exception {
ffmpegService.start(camId, rtsp, "/opt/hls/" + camId + ".m3u8");
return "started -> http://localhost:8080/hls/" + camId + ".m3u8";
}
@PostMapping("/stop/{camId}")
public String stop(@PathVariable String camId) {
ffmpegService.stop(camId);
return "stopped";
}
}
WebMvcConfig.java(静态资源暴露 HLS 目录)
package com.demo.videostream.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/hls/**")
.addResourceLocations("file:/opt/hls/");
}
}
服务器需提前建 /opt/hls目录,且安装 ffmpeg 并在 PATH 中。
前端 — Vue3 + video.js
package.json
{
"name": "frontend",
"dependencies": {
"vue": "^3.4.0",
"video.js": "^7.21.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"vite": "^5.0.0"
}
}LivePlayer.vue
<template>
<video ref="videoEl" class="video-js vjs-big-play-centered" style="width:100%;max-width:720px"></video>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
const props = defineProps({
src: { type: String, default: 'http://localhost:8080/hls/cam1.m3u8' }
})
const videoEl = ref(null)
let player = null
onMounted(() => {
player = videojs(videoEl.value, {
controls: true,
autoplay: true,
preload: 'auto',
sources: [{ type: 'application/x-mpegURL', src: props.src }]
})
})
onBeforeUnmount(() => player?.dispose())
</script>App.vue
<template>
<div style="padding:20px">
<h3>监控直播 — HLS 播放</h3>
<LivePlayer src="http://localhost:8080/hls/cam1.m3u8" />
</div>
</template>
<script setup>
import LivePlayer from './components/LivePlayer.vue'
</script>main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')运行步骤
- 服务器:安装 ffmpeg,建目录
mkdir -p /opt/hls - 启动后端:
mvn spring-boot:run(端口 8080) - 启流:
POST /api/stream/start/cam1?rtsp=你的RTSP地址 - 启动前端:
npm install && npm run dev - 浏览器打开前端即可看到监控画面
以上就是springboot+vue+ffmpeg实现视频监控的直播拉流播放的详细内容,更多关于springboot vue视频播放的资料请关注脚本之家其它相关文章!
