如何把 ZLMediaKit 接入你的 Spring Boot 系统:从搭建流媒体服务器到跑通直播的完整指南
【免费下载链接】ZLMediaKitWebRTC/RTSP/RTMP/HTTP/HLS/HTTP-FLV/WebSocket-FLV/HTTP-TS/HTTP-fMP4/WebSocket-TS/WebSocket-fMP4/GB28181/SRT/STUN/TURN server and client framework based on C++11项目地址: https://gitcode.com/GitHub_Trending/zl/ZLMediaKit
本文以 C++ 编写的流媒体服务器 ZLMediaKit 为例,演示如何用 Spring Boot 对接它,实现 RTMP 推流、HLS 播放、WebHook 回调和流媒体 API 调用。适合想在业务系统里加入流媒体能力的 Java 开发者和新手读者。
它替开发者省掉了什么
自己写流媒体服务器,意味着要亲自实现 RTSP、RTMP、HLS 等一堆协议,每种协议都有自己的连接协商、分包、重传机制,还要处理时间戳对齐和权限校验。这套脏活累活,正是 C++ 写的流媒体引擎该干的事。ZLMediaKit 负责协议适配、帧处理和一转多协议分发:一路 RTMP 推进来,就能转成 RTSP、HTTP-FLV、HLS、WebRTC 等多种形式播出去。
Java 这边剩下的就是业务逻辑。通常有三类问题是留给 Spring Boot 层处理的:
- 鉴权:这个用户能不能推流、能不能播放,由业务系统说了算,媒体服务器只负责"问一句"
- 状态同步:流创建、销毁时,数据库和前端要实时知道
- 生命周期管理:按需拉流、无人观看自动关闭、断流恢复
所以两者的分工很清晰:ZLMediaKit 管"流怎么进出",Spring Boot 管"谁能看、什么时候看、看完记什么"。它们之间的所有通信都走 HTTP,且只有两种形式——你调它的接口,它回调你。
一条从克隆仓库到跑通第一条流的完整路径
克隆并编译流媒体服务器
克隆仓库后在 Linux 环境(需要 cmake 和 C++11 工具链)下编译,产物是MediaServer可执行文件:
git clone https://gitcode.com/GitHub_Trending/zl/ZLMediaKit cd ZLMediaKit mkdir build && cd build cmake .. && make -j4编译完成后文件位于release/linux/Debug/。注意进程默认加载的是同目录下的config.ini(cmake 会从 conf/config.ini 拷贝过去),直接改conf/里的文件不会生效。配置里先认准两项:
api.secret:敏感接口的访问密钥,通过 127.0.0.1 本地访问时可不带hook.*:所有 WebHook 回调地址,默认全部关闭
推一条流、播一条流
启动进程后,用一次最轻的 API 调用确认服务活着:
curl "http://127.0.0.1/index/api/getServerConfig"返回一份包含全部配置的 JSON,说明服务正常。接着用 FFmpeg 在本地推一条流(RTMP 是推流领域的事实标准协议,绝大多数推流软件都用它):
ffmpeg -re -stream_loop -1 -i test.mp4 -c copy -f flv rtmp://127.0.0.1/live/streamA推流地址由app/stream两段组成,这里live是应用名、streamA是流名。推上去之后,浏览器播放器打开http://127.0.0.1/live/streamA.flv就能播 HTTP-FLV;或者用任意支持 m3u8 的播放器播 HLS(基于切片文件的渐进式播放协议)地址http://127.0.0.1/index.hls/live/streamA.m3u8。
把 Spring Boot 接到媒体服务器 API 上
引擎侧就绪后,Java 侧只需要一个能访问http://127.0.0.1:80/index/api的 HTTP 客户端。建议把地址和密钥收进application.properties统一管理:
media.api.base=http://127.0.0.1:80/index/api media.secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc到这里整体形态就出来了:MediaServer 是常驻进程,负责流处理;Spring Boot 是业务入口,负责鉴权与状态记录,前者完全由后者通过 HTTP 驱动。
请求-响应 与 事件驱动:两种交互模式拆解
同步调用:Spring Boot 主动调流媒体 API
Java 主动发起的操作都是"请求-响应"模式:发一个 HTTP 请求,同步拿回 JSON 结果。接口定义在 server/WebApi.cpp,常用的是这几个:
getMediaList:列出当前所有流addStreamProxy:让服务器去拉一个外部源delStreamProxy:停掉一路拉流代理getThreadsLoad/getStatistic:线程负载与统计,做监控用
把最典型的拉流接口包成一个 Service,其他接口的调法和它一致:
@Service public class MediaService { @Value("${media.api.base}") private String apiBase; @Value("${media.secret}") private String secret; private final RestTemplate http = new RestTemplate(); // 让媒体服务器拉取外部源(rtsp/rtmp)并注册为本地流 public String pullStream(String stream, String url) { MultiValueMap<String, String> form = new LinkedMultiValueMap<>(); form.add("secret", secret); form.add("stream", stream); form.add("url", url); return http.postForObject(apiBase + "/addStreamProxy", form, String.class); } }请求体是普通表单参数,响应里code=0表示成功。需要完整字段说明时,看 www/swagger/openapi.json 这份 OpenAPI 文档,导入 swagger-ui 可以直接浏览。
异步回调:配置 WebHook 回调地址
另一个方向是"事件驱动":你不发请求,媒体服务器有事发生时就 POST 到你预先配置的地址。这种回调机制叫 WebHook,在 conf/config.ini 的hook.*下填地址即可启用:
[hook] enable=1 on_publish=http://127.0.0.1:8080/hook/on_publish on_play=http://127.0.0.1:8080/hook/on_play on_stream_changed=http://127.0.0.1:8080/hook/on_stream_changedHook 分两种。裁决型如on_publish、on_play:媒体服务器要等你的答复才继续,你必须回allow=1操作才能放行;通知型如on_stream_changed:只需要收到并处理,不用做决策。一个裁决型接口长这样:
/** 媒体服务器接收 RTMP 推流前调用,返回 allow=1 表示放行 */ @PostMapping("/hook/on_publish") public Map<String, Object> onPublish(@RequestParam Map<String, String> req) { boolean allowed = authService.hasPublishRight(req.get("stream")); Map<String, Object> resp = new HashMap<>(); resp.put("code", 0); resp.put("allow", allowed ? 1 : 0); return resp; }注意hook.timeoutSec(默认 10 秒):业务系统超时无应答会被判定为拒绝。所以鉴权逻辑要做得简单快速,发短信、记账单这类重活交给异步线程。
跟着一条直播流的完整生命周期走一遍
沿着一条叫streamA的流,把两个系统里实际发生的事理一遍 📺
第一步,推流。主播用推流软件推rtmp://你的域名/live/streamA。媒体服务器发现配置了on_publish,把这次推流的app、stream、来源ip等字段 POST 给 Spring Boot,自己先暂停处理,等待答复。
第二步,鉴权。前面的on_publish接口去查库:推流者是否注册、时段是否合法。回allow=1后媒体服务器才正式收流。注意这里的边界:鉴权只是一次短命的 HTTP 请求,而推流本身是长连接的 RTMP 会话,两种交互模式在这里交汇。
第三步,注册上报。流建立后,媒体服务器触发on_stream_changed(reg=1表示注册,reg=0表示销毁)。这个事件是流状态的唯一事实来源:Spring Boot 在此写库或写 Redis,前端的"直播中/已结束"标签也靠它刷新。
第四步,播放。观众在网页打开 HTTP-FLV 地址,或拿 m3u8 地址播 HLS。如果配了on_play,每次播放会先经过一次鉴权——挂计费逻辑的常见位置。还有个容易忽略的机制:maxStreamWaitMS(默认 15 秒)允许"先播放再推流",观众比主播早到页面不会立刻报错,会等流到位。
第五步,结束。推流断开,或拉流代理的流无人观看超过streamNoneReaderDelayMS(默认 20 秒),媒体服务器回调on_stream_changed(reg=0),业务侧据此做收尾。对拉流代理,还可以用on_stream_none_readerhook 自主决定关不关这路流。
把这条链路走通后能看清算法:媒体帧数据从不经过 Spring Boot,Java 层只接触元数据和决策。这正是这样拆分的意义——业务进程不用扛视频流的负载。
上生产之前必须处理的几件事
按严重程度和出问题的频率排一下序。
更换默认密钥。示例配置里的api.secret是公开的,知道它的人就能调用敏感接口增删流。上线前必改,并确保 Spring Boot 侧的media.secret与之同步。
让 hook 处理器幂等且快。hook 段里retry=1表示回调失败会重试一次,叠加网络抖动,同一个事件可能到达多次。on_stream_changed这类状态写入要做幂等处理;裁决型接口必须在timeoutSec内返回,超时就当拒绝。
给 RestTemplate 配上连接池和超时。默认的单机连接、无限等待在生产环境很脆,下面这份配置可直接套用:
@Bean public RestTemplate restTemplate() { HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory( HttpClientBuilder.create() .setMaxConnTotal(100) .setMaxConnPerRoute(20).build()); factory.setConnectTimeout(3000); factory.setReadTimeout(10000); return new RestTemplate(factory); }警惕状态不一致的坑。生产上最常见的现象是"前端显示直播中,实际已经结束了"。根源几乎都是前端状态没有以on_stream_changed事件为准,而是隔一段时间查一次库。让事件成为状态的单一来源,能消掉大部分此类问题。
定时任务盯住负载。周期调getThreadsLoad和getStatistic,前者返回各线程的 CPU 占用,后者返回在线流与协议统计,配一条阈值告警,比等用户报障早得多。排查 hook 行为异常时,把apiDebug=1打开,媒体服务器侧会打印每次 API 调用的内容和回复。
接下来可以继续探索的方向
"推流、播放、鉴权、状态"这条主线走通之后,可以按业务形态继续挖:
- WebRTC 低延迟播放:完整的 DTLS/ICE/SRTP 协议栈实现在 webrtc/ 目录,接上后浏览器端可以拿到亚秒级延迟
- GB28181 安防对接:接收国标监控设备流的 SIP/RTP 处理在 src/Rtp/,适合监控回传场景
- SRT 传输:面向专线和大带宽场景的私有传输协议,实现见 srt/
- 集群与多机部署:配置文件的
cluster段支持源站按需拉流,适合源站与边缘节点分离的架构 - 非 Java 语言接入:api/ 下封装了 C 接口,其他语言的服务可以用同样的方式控制媒体服务器
动手调试时,tests/ 目录里的示例代码覆盖了 HTTP 客户端、WebRTC 回归等场景,照着改比从零写快得多。协议细节和部署方式的权威说明,看项目根的 README.md 以及 conf/readme.md。
【免费下载链接】ZLMediaKitWebRTC/RTSP/RTMP/HTTP/HLS/HTTP-FLV/WebSocket-FLV/HTTP-TS/HTTP-fMP4/WebSocket-TS/WebSocket-fMP4/GB28181/SRT/STUN/TURN server and client framework based on C++11项目地址: https://gitcode.com/GitHub_Trending/zl/ZLMediaKit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考