当前位置: 代码网 > it编程>编程语言>Java > 基于SpringBoot+FFmpeg+ZLMediaKit实现本地视频推流

基于SpringBoot+FFmpeg+ZLMediaKit实现本地视频推流

2025年11月03日 Java 我要评论
1. 环境准备1.1 zlmediakit 安装配置下载安装# 拉取镜像docker pull zlmediakit/zlmediakit:master# 启动docker run -d \ --n

1. 环境准备

1.1 zlmediakit 安装配置

下载安装

# 拉取镜像
docker pull zlmediakit/zlmediakit:master

# 启动
docker run -d \
  --name zlm-server \
  -p 1935:1935 \
  -p 8099:80 \
  -p 8554:554 \
  -p 10000:10000 \
  -p 10000:10000/udp \
  -p 8000:8000/udp \
  -v /docker-volumes/zlmediakit/conf/config.ini:/opt/media/conf/config.ini \
  zlmediakit/zlmediakit:master

配置文件 (config.ini)

[hls]
broadcastrecordts=0
deletedelaysec=300    # 推流的视频保存多久(5分钟)
filebufsize=65536
filepath=./www    # 保存路径
segdur=2    # 单个.ts 切片时长(秒)。
segnum=1000  # 直播时.m3u8 里最多同时保留多少个切片。
segretain=9999    # 磁盘上实际保留多少个历史切片

启动服务

# 查看启动状态
docker logs -f zlm-server

1.2 ffmpeg 安装

# 下载路径
https://www.gyan.dev/ffmpeg/builds/

这两个都可以选

配置环境变量

c:\ffmpeg\ffmpeg-7.0.2-essentials_build\bin

找到 bin 目录,将其配到 path 环境变量中。

出来版本就成功了。

2. spring boot 后端实现

2.1 添加依赖

<dependencies>
    
    <!-- 进程管理 -->
    <dependency>
        <groupid>org.apache.commons</groupid>
        <artifactid>commons-exec</artifactid>
        <version>1.3</version>
    </dependency>
    
</dependencies>

2.2 推流配置类

package com.lyk.plugflow.config;

import lombok.data;
import org.springframework.boot.context.properties.configurationproperties;
import org.springframework.stereotype.component;

@data
@component
@configurationproperties(prefix = "stream")
public class streamconfig {

    /**
     * zlmediakit服务地址
     */
    private string zlmhost;

    /**
     * rtmp推流端口
     */
    private integer rtmpport;

    /**
     * http-flv拉流端口
     */
    private integer httpport;

    /**
     * ffmpeg可执行文件路径
     */
    private string ffmpegpath;

    /**
     * 视频存储路径
     */
    private string videopath;

}

2.3 推流服务类

package com.lyk.plugflow.service;

import com.lyk.plugflow.config.streamconfig;
import lombok.extern.slf4j.slf4j;
import org.apache.commons.exec.*;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;

import java.io.bytearrayoutputstream;
import java.io.file;
import java.util.map;
import java.util.concurrent.concurrenthashmap;

@slf4j
@service
public class streamservice {

    @autowired
    private streamconfig streamconfig;

    // 存储推流进程
    private final map<string, defaultexecutor> streamprocesses = new concurrenthashmap<>();

    // 添加手动停止标记
    private final map<string, boolean> manualstopflags = new concurrenthashmap<>();

    /**
     * 开始推流
     */
    public boolean startstream(string videopath, string streamkey) {
        try {
            // 检查视频文件是否存在
            file videofile = new file(videopath);
            if (!videofile.exists()) {
                log.error("视频文件不存在: {}", videopath);
                returnfalse;
            }

            // 构建rtmp推流地址
            string rtmpurl = string.format("rtmp://%s:%d/live/%s",
                    streamconfig.getzlmhost(), streamconfig.getrtmpport(), streamkey);

            // 构建ffmpeg命令
            commandline cmdline = getcommandline(videopath, rtmpurl);

            // 创建执行器
            defaultexecutor executor = new defaultexecutor();
            executor.setexitvalue(0);

            // 设置watchdog用于进程管理
            executewatchdog watchdog = new executewatchdog(executewatchdog.infinite_timeout);
            executor.setwatchdog(watchdog);

            // 设置输出流
            bytearrayoutputstream outputstream = new bytearrayoutputstream();
            pumpstreamhandler streamhandler = new pumpstreamhandler(outputstream);
            executor.setstreamhandler(streamhandler);

            // 异步执行
            executor.execute(cmdline, new executeresulthandler() {
                @override
                public void onprocesscomplete(int exitvalue) {
                    log.info("推流完成, streamkey: {}, exitvalue: {}", streamkey, exitvalue);
                    streamprocesses.remove(streamkey);
                }

                @override
                public void onprocessfailed(executeexception e) {
                    boolean ismanualstop = manualstopflags.remove(streamkey);
                    if (ismanualstop) {
                        log.info("推流已手动停止, streamkey: {}", streamkey);
                    } else {
                        log.error("推流失败, streamkey: {}, error: {}", streamkey, e.getmessage());
                    }
                    streamprocesses.remove(streamkey);
                }
            });

            // 保存进程引用
            streamprocesses.put(streamkey, executor);

            log.info("开始推流, streamkey: {}, rtmpurl: {}", streamkey, rtmpurl);
            returntrue;

        } catch (exception e) {
            log.error("推流启动失败", e);
            returnfalse;
        }
    }

    private commandline getcommandline(string videopath, string rtmpurl) {
        commandline cmdline = new commandline(streamconfig.getffmpegpath());
        cmdline.addargument("-re"); // 按原始帧率读取
        cmdline.addargument("-i");
        cmdline.addargument(videopath);
        cmdline.addargument("-c:v");
        cmdline.addargument("libx264"); // 视频编码
        cmdline.addargument("-c:a");
        cmdline.addargument("aac"); // 音频编码
        cmdline.addargument("-f");
        cmdline.addargument("flv"); // 输出格式
        cmdline.addargument("-flvflags");
        cmdline.addargument("no_duration_filesize");
        cmdline.addargument(rtmpurl);
        return cmdline;
    }

    /**
     * 停止推流
     */
    public boolean stopstream(string streamkey) {
        try {
            defaultexecutor executor = streamprocesses.get(streamkey);
            if (executor != null) {
                // 设置手动停止标记
                manualstopflags.put(streamkey, true);

                executewatchdog watchdog = executor.getwatchdog();
                if (watchdog != null) {
                    watchdog.destroyprocess();
                } else {
                    log.warn("进程没有watchdog,无法强制终止, streamkey: {}", streamkey);
                }
                streamprocesses.remove(streamkey);
                log.info("停止推流成功, streamkey: {}", streamkey);
                returntrue;
            }
            returnfalse;
        } catch (exception e) {
            log.error("停止推流失败", e);
            returnfalse;
        }
    }

    /**
     * 获取拉流地址
     */
    public string getplayurl(string streamkey, string protocol) {
        return switch (protocol.tolowercase()) {
            case"flv" -> string.format("http://%s:%d/live/%s.live.flv",
                    streamconfig.getzlmhost(), streamconfig.gethttpport(), streamkey);
            case"hls" -> string.format("http://%s:%d/live/%s/hls.m3u8",
                    streamconfig.getzlmhost(), streamconfig.gethttpport(), streamkey);
            default -> null;
        };
    }

    /**
     * 检查推流状态
     */
    public boolean isstreaming(string streamkey) {
        return streamprocesses.containskey(streamkey);
    }
}

2.4 配置文件

stream:
  zlm-host: 192.168.159.129
  rtmp-port: 1935
  http-port: 8099
  ffmpeg-path: ffmpeg
  video-path: \videos\

# 文件上传配置
spring:
  servlet:
    multipart:
      max-file-size: 1gb
      max-request-size: 1gb

3. 使用说明

3.1 推流流程

  • • 启动 zlmediakit 服务
  • • 上传视频文件到服务器
  • • 调用推流接口,指定视频路径和推流密钥
  • • spring boot 执行 ffmpeg 命令推流到 zlmediakit

3.2 播放流程

  • • 获取推流地址(http-flv 或 hls)
  • • 支持实时播放和回放
ffmpeg -re -i "c:\users\lyk19\videos\8月9日.mp4" -c:v libx264 -preset ultrafast -tune zerolatency -c:a aac -ar 44100 -b:a 128k -f flv rtmp://192.168.159.129:1935/live/stream
  • • 前端播放
<!doctype html>
<html lang="zh-cn">

  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>flv直播播放器</title>
    <style>
      body {
        margin: 0;
        padding: 20px;
        font-family: arial, sans-serif;
        background-color: #f0f0f0;
      }

      .player-container {
        max-width: 800px;
        margin: 0 auto;
        background: white;
        border-radius: 8px;
        padding: 20px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
      }

      #videoelement {
        width: 100%;
        height: 450px;
        background-color: #000;
        border-radius: 4px;
      }

      .controls {
        margin-top: 15px;
        text-align: center;
      }

      button {
        padding: 10px 20px;
        margin: 0 5px;
        border: none;
        border-radius: 4px;
        background-color: #007bff;
        color: white;
        cursor: pointer;
        font-size: 14px;
      }

      button:hover {
        background-color: #0056b3;
      }

      button:disabled {
        background-color: #ccc;
        cursor: not-allowed;
      }

      .status {
        margin-top: 10px;
        padding: 10px;
        border-radius: 4px;
        text-align: center;
      }

      .status.success {
        background-color: #d4edda;
        color: #155724;
      }

      .status.error {
        background-color: #f8d7da;
        color: #721c24;
      }

      .status.info {
        background-color: #d1ecf1;
        color: #0c5460;
      }
    </style>
  </head>

  <body>
    <div class="player-container">
      <h1>flv直播播放器</h1>
      <video id="videoelement" controls muted>
        您的浏览器不支持视频播放
      </video>

      <div class="controls">
        <button id="playbtn">播放</button>
        <button id="pausebtn" disabled>暂停</button>
        <button id="stopbtn" disabled>停止</button>
        <button id="mutebtn">静音</button>
      </div>

      <div id="status" class="status info">
        准备就绪,点击播放开始观看直播
      </div>
    </div>

    <!-- 使用flv.js库 -->
    <script src="https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js"></script>

    <script>
      let flvplayer = null;
      const videoelement = document.getelementbyid('videoelement');
      const playbtn = document.getelementbyid('playbtn');
      const pausebtn = document.getelementbyid('pausebtn');
      const stopbtn = document.getelementbyid('stopbtn');
      const mutebtn = document.getelementbyid('mutebtn');
      const statusdiv = document.getelementbyid('status');

      // 你的流地址
      const streamurl = 'http://192.168.159.129:8099/live/stream.live.flv';

      function updatestatus(message, type) {
        statusdiv.textcontent = message;
        statusdiv.classname = `status ${type}`;
          console.log(`[${type.touppercase()}] ${message}`);
        }

        function updatebuttons(playenabled, pauseenabled, stopenabled) {
            playbtn.disabled = !playenabled;
            pausebtn.disabled = !pauseenabled;
            stopbtn.disabled = !stopenabled;
        }

        // 检查浏览器支持
        if (!flvjs.issupported()) {
            updatestatus('您的浏览器不支持flv播放,请使用chrome、firefox或edge浏览器', 'error');
            playbtn.disabled = true;
        }

        // 播放功能
        playbtn.addeventlistener('click', function () {
            try {
                if (flvplayer) {
                    flvplayer.destroy();
                }

                // 创建flv播放器
                flvplayer = flvjs.createplayer({
                    type: 'flv',
                    url: streamurl,
                    islive: true
                }, {
                    enableworker: false,
                    lazyload: true,
                    lazyloadmaxduration: 3 * 60,
                    deferloadaftersourceopen: false,
                    autocleanupsourcebuffer: true,
                    enablestashbuffer: false
                });

                flvplayer.attachmediaelement(videoelement);
                flvplayer.load();

                // 监听事件
                flvplayer.on(flvjs.events.error, function (errortype, errordetail, errorinfo) {
                    console.error('flv播放器错误:', errortype, errordetail, errorinfo);
                    updatestatus(`播放错误: ${errordetail}`, 'error');
                });

                flvplayer.on(flvjs.events.loading_complete, function () {
                    updatestatus('流加载完成', 'success');
                });

                flvplayer.on(flvjs.events.recovered_early_eof, function () {
                    updatestatus('从早期eof恢复', 'info');
                });

                // 开始播放
                videoelement.play().then(() => {
                    updatestatus('正在播放直播流', 'success');
                    updatebuttons(false, true, true);
                }).catch(error => {
                    console.error('播放失败:', error);
                    updatestatus('播放失败: ' + error.message, 'error');
                });

            } catch (error) {
                console.error('创建播放器失败:', error);
                updatestatus('创建播放器失败: ' + error.message, 'error');
            }
        });

        // 暂停功能
        pausebtn.addeventlistener('click', function () {
            if (videoelement && !videoelement.paused) {
                videoelement.pause();
                updatestatus('播放已暂停', 'info');
                updatebuttons(true, false, true);
            }
        });

        // 停止功能
        stopbtn.addeventlistener('click', function () {
            if (flvplayer) {
                flvplayer.pause();
                flvplayer.unload();
                flvplayer.destroy();
                flvplayer = null;
            }

            videoelement.src = '';
            videoelement.load();

            updatestatus('播放已停止', 'info');
            updatebuttons(true, false, false);
        });

        // 静音功能
        mutebtn.addeventlistener('click', function () {
            videoelement.muted = !videoelement.muted;
            mutebtn.textcontent = videoelement.muted ? '取消静音' : '静音';
            updatestatus(videoelement.muted ? '已静音' : '已取消静音', 'info');
        });

        // 视频事件监听
        videoelement.addeventlistener('loadstart', function () {
            updatestatus('开始加载视频流...', 'info');
        });

        videoelement.addeventlistener('canplay', function () {
            updatestatus('视频流已准备就绪', 'success');
        });

        videoelement.addeventlistener('playing', function () {
            updatestatus('正在播放直播流', 'success');
            updatebuttons(false, true, true);
        });

        videoelement.addeventlistener('pause', function () {
            updatestatus('播放已暂停', 'info');
            updatebuttons(true, false, true);
        });

        videoelement.addeventlistener('error', function (e) {
            updatestatus('视频播放出错', 'error');
            updatebuttons(true, false, false);
        });
    </script>
</body>

</html>

以上就是基于springboot+ffmpeg+zlmediakit实现本地视频推流的详细内容,更多关于springboot ffmpeg本地视频推流的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com