Build a Live Streaming Service with ZLMediaKit, FFmpeg, and Spring Boot
This tutorial walks through setting up ZLMediaKit via Docker, installing and configuring FFmpeg, creating a Spring Boot backend with stream configuration and service classes, and explains how to push and play live streams using RTMP, HTTP‑FLV, and HLS protocols.
1. Environment Preparation
1.1 ZLMediaKit Installation
Download and run the ZLMediaKit Docker image:
# Pull image
docker pull zlmediakit/zlmediakit:master
# Run container
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:masterConfiguration file (config.ini) example:
[hls]
broadcastRecordTs=0
deleteDelaySec=300
fileBufSize=65536
filePath=./www
segDur=2
segNum=1000
segRetain=99991.2 FFmpeg Installation
Download FFmpeg from https://www.gyan.dev/ffmpeg/builds/ and add its bin directory to the system PATH.
2. Spring Boot Backend Implementation
2.1 Add Dependencies
<dependencies>
<!-- Process management -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
</dependency>
</dependencies>2.2 Stream Configuration Class
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 service address */
private String zlmHost;
/** RTMP port */
private Integer rtmpPort;
/** HTTP‑FLV port */
private Integer httpPort;
/** FFmpeg executable path */
private String ffmpegPath;
/** Video storage path */
private String videoPath;
}2.3 Stream Service Class
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.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);
return false;
}
String rtmpUrl = String.format("rtmp://%s:%d/live/%s",
streamConfig.getZlmHost(), streamConfig.getRtmpPort(), streamKey);
CommandLine cmdLine = getCommandLine(videoPath, rtmpUrl);
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValue(0);
ExecuteWatchdog watchdog = new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT);
executor.setWatchdog(watchdog);
executor.setStreamHandler(new PumpStreamHandler(new java.io.ByteArrayOutputStream()));
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);
return true;
} catch (Exception e) {
log.error("推流启动失败", e);
return false;
}
}
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);
return true;
}
return false;
} catch (Exception e) {
log.error("停止推流失败", e);
return false;
}
}
public String getPlayUrl(String streamKey, String protocol) {
switch (protocol.toLowerCase()) {
case "flv":
return String.format("http://%s:%d/live/%s.live.flv",
streamConfig.getZlmHost(), streamConfig.getHttpPort(), streamKey);
case "hls":
return String.format("http://%s:%d/live/%s/hls.m3u8",
streamConfig.getZlmHost(), streamConfig.getHttpPort(), streamKey);
default:
return null;
}
}
public boolean isStreaming(String streamKey) {
return streamProcesses.containsKey(streamKey);
}
}2.4 Application Configuration
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: 1GB3. Usage Instructions
3.1 Push Flow
Start the ZLMediaKit service.
Upload video files to the server.
Call the push API with the video path and a stream key.
Spring Boot executes FFmpeg to push the stream to ZLMediaKit.
3.2 Play Flow
Obtain the playback URL (HTTP‑FLV or HLS).
Use a compatible player to watch live or on‑demand streams.
Example FFmpeg command for manual testing:
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/streamFrontend playback can be implemented with flv.js; the core logic involves creating a player, attaching it to a video element, and handling play/pause/stop events.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
