Java在线直播平台源码解析:HLS直播链路从推流到播放的完整实践
简介:这是一份基于Java开发的在线直播平台完整项目源码,适配期末大作业、毕业设计或课程设计场景,适合需要快速搭建直播类Web应用并理解其前后端协作的Java学习者。项目由高分通过的期末大作业提炼而成,下载后简单配置部署即可运行。压缩包共137个文件,其中55个Java文件承载后端业务逻辑,20个HTML页面、12个CSS样式表与7个JavaScript脚本共同构建前端展示与交互,17张PNG和17张JPG图片提供界面素材,另有properties、conf、xml等文件负责参数配置,整体仅7.46MB,结构清晰,便于按模块查阅和二次修改。包内还包含nginx与srs相关流媒体配置,并集成DPlayer、video.js等播放组件,可帮助读者掌握在线直播平台从推流、分发到页面播放的完整链路。已有194人学习/下载,适合作为JavaWeb综合实践、毕业设计原型或直播功能模块复现的参考。
1. 期末大作业名头之下,真正值得拆的是 HLS 这条链路
压缩包名字写着“期末大作业”,但把文件铺开看,它不是普通的增删改查演示:mvnw.cmd 说明后端是 Maven 工程,nginx.live.conf 和 srs.http.hls.conf 说明媒体部分用了 SRS 做 HLS 直播,DPlayer、Video.js 说明播放端走浏览器拉流,chatMain.css、loginStyle.css 说明聊天和用户面板已经切好。把这份 Java 在线直播平台源码跑起来,最大的收获不是交一份作业,而是把“推流 → 切片 → 分发 → 播放 → 聊天”这条完整链路拆清楚。适合正在准备毕业设计、期末大作业的 Java 方向学生,也适合后端工程师想给现有项目快速加直播模块时做参照。
2. 从 mvnw.cmd 到 Spring Boot 进程:后端先从压缩包变成可运行服务
2.1 mvnw.cmd 解决了构建环境不一致的问题
压缩包里出现 mvnw.cmd,说明这个 Java 项目使用 Maven Wrapper 管理构建。Wrapper 的价值在于锁定 Maven 版本:它读取 .mvn/wrapper/maven-wrapper.properties 里的 distributionUrl,在没有预装 Maven 的机器上也能拉取指定版本执行构建。对答辩和演示场景来说,这比要求现场装 Maven 可靠得多。
拿到压缩包后第一件事不是改代码,而是验证构建工具本身能跑通。Windows 下用 mvnw.cmd ,Linux 或 macOS 下用 ./mvnw ,两者逻辑一致。
cd online-live
mvnw.cmd -v
mvnw.cmd clean package -DskipTests -Dfile.encoding=UTF-8
java -jar target/online-live-0.0.1-SNAPSHOT.jar --server.port=8080
第一条命令如果打印出完整的 Maven 和 JDK 版本,说明 wrapper 正常,接着谈编译才有意义。第二条 clean package 把清理、编译、打包合并成一步; -DskipTests 跳过测试类,很多课程设计的测试类并不适配演示环境,跳过能少一个失败点; -Dfile.encoding=UTF-8 避免 Windows 控制台把源码里的中文注释显示成乱码。第三条 java -jar 启动 Spring Boot 的可执行 jar, --server.port=8080 用命令行参数覆盖配置文件端口,端口被占用时改成 --server.port=8081 即可。具体 jar 包名要看 pom.xml 里的 artifactId,可以用 ls target 确认。
提示:
mvnw.cmd -v报错时,先查 JAVA_HOME 是否指向 JDK 安装目录,再看配置里的 distributionUrl 是否可达,这两个是 wrapper 无法自行绕过的问题。
2.2 后端进程起来之后,先确认它给直播链路提供什么
Java 后端在这个项目里负责业务面,不负责媒体面。它要管三件事:用户登录、聊天消息、直播流地址查询。登录接口负责把前端页面和用户身份关联起来;直播流地址接口告诉播放器 m3u8 在哪;聊天消息通过 WebSocket 或轮询推到前端。
与直播相关的配置通常会集中在 application 配置里。下面是一个与这套源码场景匹配的典型写法:
server:
port: 8080
live:
push-url: rtmp://127.0.0.1:1935/live/live
hls-url: http://127.0.0.1:8088/live/live.m3u8
chat:
path: /ws/chat
enabled: true
live.push-url 是给推流工具填的 RTMP 地址,对应 SRS 的 1935 端口,OBS 或 ffmpeg 推流时填这一条。 live.hls-url 是给浏览器播放器用的 HLS 地址,走 Nginx 的 8088 端口。 chat.path 是聊天消息通道,前后端约定同一个路径即可。注意推流端口和播放端口很可能不一致,这是直播项目的正常状态,推流走 RTMP,播放走 HTTP。
2.3 端口规划直接决定演示现场是否翻车
这个项目同时存在 Java 进程、SRS 进程和 Nginx 进程,三张端口网互相叠加,最常见的演示事故就是端口冲突导致某个进程静默失败。建议把端口关系在部署第一天就固定下来。
| 进程 | 默认端口 | 在项目里的角色 | 冲突时优先改谁 |
|---|---|---|---|
| Spring Boot 后端 | 8080 | 登录、聊天、业务 API | 改成 8081 |
| SRS 主服务 | 1935 | 接收 RTMP 推流 | 保持 1935 不动 |
| SRS HTTP API | 1985 | 查询推流状态 | 与前端无关,可改 |
| Nginx 播放入口 | 8088 | 对外提供 m3u8 和 ts | 尽量保持 8088 不变 |
建议把后端端口改成 8081,SRS 的 1935 推流口和 1985 API 口保留,播放统一从 Nginx 的 8088 出口。这样做的好处是浏览器页面里只有一个媒体源地址,m3u8 和页面同源,跨域问题降到最低。别等到答辩前夜再调端口,三个进程同时跑起来以后再改配置,很容易漏改一处导致播放器黑屏。
3. srs.http.hls.conf 与 nginx.live.conf:推流、切片与播放出口
3.1 SRS 和 Nginx 在链路里的分工
srs.http.hls.conf 是 SRS 流媒体服务器的实例配置,不是前端文件。它的任务是接收推流端的 RTMP 信号,按设定参数把视频切片成 ts 分片,并生成 m3u8 播放列表。Nginx 的 nginx.live.conf 则把切片目录以静态文件方式暴露给浏览器,前端播放器拿到的其实是 Nginx 输出的 HTTP 地址。
为什么不在 Java 代码里直接做切片?切片涉及磁盘写入、缓存控制、时序管理,用 Java 实现成本高,稳定性也比不上成熟的流媒体进程。一个课程设计级别的直播系统,把媒体面交给 SRS,Java 只写业务接口,这是最合理的边界划分。
| 链路环节 | 使用的协议 | 对应配置 |
|---|---|---|
| 推流入场 | RTMP | SRS listen 1935 |
| 视频切片 | HLS | srs.http.hls.conf 的 hls 段 |
| 文件输出 | HTTP 静态 | nginx.live.conf 的 location |
| 浏览器播放 | HLS | m3u8 + ts |
3.2 srs.http.hls.conf 的 HLS 参数决定延迟和磁盘占用
SRS 配置语法接近 nginx 风格,核心是 vhost 下的 hls 段。这套源码对应的典型写法如下:
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
hls {
enabled on;
hls_path ./objs/nginx/html;
hls_fragment 4;
hls_window 30;
hls_cleanup on;
}
}
listen 1935 是 RTMP 推流入口,ffmpeg 或 OBS 推流时连接这个端口。 http_server 段把 ./objs/nginx/html 目录暴露成静态文件服务,m3u8 和 ts 都落在这个目录。 hls_path 必须与 Nginx 的 alias 指向同一个目录,否则播放器拿到 m3u8 也读不到 ts 分片。 hls_fragment 4 表示每 4 秒生成一个 ts 分片,数值越小延迟越低,但分片文件数量会变多; hls_window 30 表示播放列表保留最近 30 秒内容; hls_cleanup on 自动清理过期分片,防止演示机器磁盘被写满。
注意:SRS 的 hls_path 目录必须真实存在,否则启动后分片写入失败,日志里有报错但进程看起来还在运行。
3.3 nginx.live.conf 用静态文件输出,避免浏览器跨域
nginx.live.conf 承担的是播放出口。它把 /live/ 路径直接映射到 SRS 的切片目录,不做业务判断,下面是常见的静态输出写法:
server {
listen 8088;
server_name localhost;
location /live/ {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
alias /usr/local/srs/objs/nginx/html/live/;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
}
location /live/ 把所有以 /live/ 开头的请求都收进来。 types 块让 m3u8 返回 application/vnd.apple.mpegurl ,ts 返回 video/mp2t ,这两个 MIME 类型缺失时,浏览器会把文件当 text/plain 直接拒播,控制台报错往往是媒体加载失败。 alias 把 URL 前缀映射到磁盘路径,注意 alias 与 root 的差异,alias 是整段替换,末尾路径要和 SRS 的 hls_path 结构一致。 Cache-Control no-cache 让播放器每次刷新都重新请求 m3u8,直播列表才能实时更新。 Access-Control-Allow-Origin * 是跨域报错的解药,页面和播放入口不在同源时必须有这一行。
4. DPlayer、Video.js 与 chatMain.css:播放页和聊天区怎么组织起来
4.1 样式引用顺序和页面骨架
前端页面的文件顺序直接决定播放器能否正常渲染。bootstrap.min.css 是基础样式,要放最前面;video-js.min.css 和 DPlayer.min.css 是播放器组件样式,排在 bootstrap 之后;chatMain.css、user-right.css、loginStyle.css 属于业务页面样式,放最后负责覆盖细节。如果一个页面同时出现 DPlayer 和 Video.js,只保留其中一套初始化逻辑,两套播放器同时初始化会争抢同一个 video 容器。
一个典型页面引用片段如下:
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/video-js.min.css">
<link rel="stylesheet" href="css/DPlayer.min.css">
<link rel="stylesheet" href="css/chatMain.css">
<link rel="stylesheet" href="css/user-right.css">
<script src="js/DPlayer.min.js"></script>
<script src="js/video.min.js"></script>
CSS 放在 head 里,JS 放在 body 底部,这样播放器脚本执行时 DOM 已经解析完, document.getElementById 一定能拿到容器。如果脚本写在 head 里,需要包裹 DOMContentLoaded 事件,否则初始化时容器还不存在,播放器会静默失败。
4.2 DPlayer 播放 HLS 的实例参数
DPlayer 内置 HLS 解析能力,直接把 m3u8 地址交给 video 配置即可。下面是播放器初始化代码:
const dp = new DPlayer({
container: document.getElementById('livePlayer'),
autoplay: false,
preload: 'auto',
theme: '#ff5f33',
video: {
url: 'http://localhost:8088/live/live.m3u8',
type: 'hls'
}
});
container 指定播放器挂载到哪个 DOM 节点。 autoplay 设成 false,因为浏览器对带声音的自动播放有限制,用户没有交互直接 play 会被拦截。 preload 选 auto,让浏览器提前加载视频元数据。 video.url 就是上一章 Nginx 输出的播放地址, type 为 hls 时,播放器内部通过 MediaSource 模式拉取 m3u8 并读取 ts 分片。如果改成 flv 类型,需要额外引入 flv.js。
播放器初始化顺利不代表直播链路没隐患,建议加一个错误回调:
dp.on('error', function (err) {
console.error('播放错误:', err);
});
回调本身只打印日志,真正的价值在于区分两类问题:地址 404 优先查 Nginx 的 alias 路径是否和 SRS 的 hls_path 一致;跨域报错查 Access-Control-Allow-Origin 头是否存在。浏览器控制台的错误信息比画面黑屏更早暴露问题位置。
4.3 chatMain.css 对应的聊天消息渲染
chatMain.css 解决的是聊天区布局,不是后端消息逻辑,通常固定聊天室的位置、高度和滚动行为。对应的结构一般长这样:
<div class="chat-main">
<div class="chat-head">聊天室</div>
<div class="chat-body" id="chatBody"></div>
<div class="chat-footer">
<input class="chat-input" id="chatInput" placeholder="说点什么">
<button class="chat-send" id="chatSend">发送</button>
</div>
</div>
chat-main 作为最外层容器撑满父级高度,chat-body 设置 flex: 1 和 overflow-y: auto ,新消息进入时聊天区域独立滚动。chat-footer 固定在聊天区底部,输入框占剩余宽度,发送按钮固定宽度,这是 chatMain.css 里最常见的 flex 布局手段。消息渲染不依赖框架,收到数据后向 chat-body 追加一个节点即可:
function appendChatMessage(msg) {
const item = document.createElement('div');
item.className = 'chat-item';
item.innerHTML = '<span class="chat-nick">' + msg.nick + '</span> ' + msg.content;
document.getElementById('chatBody').appendChild(item);
}
appendChatMessage 只做 DOM 追加,不整体重写 chatBody 的 innerHTML,避免直播间消息刷屏时页面频繁重排。nick 和 content 分别落在样式类上,与 user-right.css 里的用户信息展示互不干扰。
播放器与聊天模块的关键参数如下:
| 配置项 | 取值 | 作用 |
|---|---|---|
| video.type | hls | 让 DPlayer 按 HLS 协议解析 |
| autoplay | false | 规避浏览器自动播放限制 |
| chat-body overflow-y | auto | 消息多时独立滚动 |
| chat-footer | flex 布局 | 输入框与发送按钮固定底部 |
5. 验证与演示排错:先看切片,再看浏览器控制台
5.1 用 curl 判断链路通到哪一段
启动后不急着开浏览器,先用 curl 确定性验证。播放地址走 Nginx 的 8088,就请求 m3u8:能看到分片列表,说明 SRS 在切、Nginx 也在正常输出文件。
curl -s http://127.0.0.1:8088/live/live.m3u8
curl -s http://127.0.0.1:1985/api/v1/streams
第一条命令期望看到以 #EXTM3U 开头、后续跟着 ts 文件名的内容。如果返回 404,去 SRS 的 ./objs/nginx/html/live 目录下看文件是否存在;文件存在但 404,问题就在 Nginx 的 alias 路径。第二条命令请求 SRS 的 HTTP API,返回结果里能看到 live 流,说明推流端和 SRS 的连接还活着。这两条命令分别覆盖“切片是否生成”和“推流是否在线”,哪一步失败,就把排查范围缩到对应进程。
5.2 三个演示现场最高频的翻车点
第一是端口冲突。SRS 内置 HTTP 服务和 Spring Boot 都默认监听 8080,后启动的进程会报 bind failed。建议按第二章的表格提前分配端口,在后端启动命令里显式写 --server.port=8081 。
第二是 MIME 类型缺失。播放器拿到 m3u8 却报 MEDIA_ERR_SRC_NOT_SUPPORTED,多半是 Nginx 没配置 m3u8 和 ts 的 types。把 application/vnd.apple.mpegurl m3u8 和 video/mp2t ts 加上后重启 Nginx 即可解决。
第三是 m3u8 缓存导致直播画面卡住。Nginx 默认对静态文件启用缓存,直播播放列表更新不及时,画面会停在十几秒前。更精细的做法是把 m3u8 和 ts 分开处理:
location ~* \.m3u8$ {
add_header Cache-Control no-cache;
}
location ~* \.ts$ {
add_header Cache-Control max-age=30;
}
m3u8 每次请求都回到服务器拿最新播放列表,ts 分片因为是已生成的静态文件,缓存 30 秒不会造成明显延迟,同时减少重复请求量。这个区分让直播进度更新及时,已切片文件的缓存能力也保留下来。
演示现场最容易被忽略的是推流源本身。答辩时如果用 ffmpeg 推一个循环视频,文件读完后画面会停在最后一帧。提前用循环参数推流,并把推流命令、后端启动、curl 检查顺序在终端里按序跑一遍,能省掉现场一半以上的调试时间。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)