springboot+vue+ffmpeg实现视频监控直播拉流播放

5 阅读1分钟

浏览器无法直接播放监控摄像头的 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

  1. FFmpeg 把 RTSP 推 RTMP:

    ffmpeg -rtsp_transport tcp -i rtsp://... -c copy -f flv rtmp://localhost/live/cam1

  2. Nginx http-flv 分发:http://ip/live?port=1935&app=live&stream=cam1

  3. 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

以上是完整源代码

GitHub/Gitee 上没有完全同名、开箱即用的 Spring Boot + Vue + FFmpeg HLS 拉流完整项目模板,但我把**可直接跑的最小可运行版本(后端 Spring Boot + 前端 Vue3)**给你整理好了,照着建目录复制即可运行。

📁 项目结构

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')

三、运行步骤

  1. 服务器:安装 ffmpeg,建目录 mkdir -p /opt/hls

  2. 启动后端mvn spring-boot:run(端口 8080)

  3. 启流POST /api/stream/start/cam1?rtsp=你的RTSP地址

  4. 启动前端npm install && npm run dev

  5. 浏览器打开前端即可看到监控画面

四、参考开源项目(可作为成品参考)