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本地视频推流的资料请关注代码网其它相关文章!
发表评论