HLS 动态推流技术文档
本系统旨在实现客户端上传由 Manim 生成的多个 MP4 场景文件,服务端利用内部的 native‑media 项目(替代 ffmpeg)对这些文件进行转换、分段处理,并动态更新 HLS 播放列表(m3u8 文件)。客户端只需获取持续更新的 m3u8 地址,即可实现无缝、低延迟播放整个场景视频流。
1. 概述
系统核心模块包括:
- 客户端上传模块:负责将带有唯一编号的 MP4 文件上传到服务器;
- 服务端处理模块:利用 native‑media 替代 ffmpeg 对 MP4 文件进行转换和分段处理,并管理播放列表;
- 动态播放列表模块:实时维护 m3u8 文件(包括 EXT‑X‑MEDIA‑SEQUENCE、EXTINF、EXT‑X‑TARGETDURATION 等标签),对外提供可播放的 HLS 流。
此外,为保证整个视频流的完整性,还设计了开始和结束接口,确保在会话初始化、文件上传、播放及结束过程中接口调用顺序严格且编号明确。
2. 系统架构
整体架构示意如下:
┌────────────┐
│ 客户端 │
│(上传、播放HLS)│
└─────┬──────┘
│ HTTP
┌─────▼──────┐
│ Java服务端 │
│ ───────── │
│ • 文件接收 │
│ • 转换调度 │
│ • 播放列表管理│
│ • API接口 │
└─────┬──────┘
│ 内部调用native‑media API
┌─────▼──────┐
│ native‑media │
│ (音视频处理库)│
└────────────┘
3. 客户端交互流程
3.1 接口调用顺序与整体流程
会话初始化(开始接口 /hls/start)
客户端在上传第一个场景文件前,调用/start
接口通知服务器创建新会话,并返回对应的播放列表 URL。上传 MP4 场景文件(上传接口 /hls/upload)
客户端依次将由 Manim 生成的每个场景的 MP4 文件上传至服务器,上传时需附带唯一会话编号(session_id)和(可选)场景序号。获取播放地址(查询接口 /hls/stream)
在上传第一个场景文件后,客户端调用/stream
接口获取最新的 m3u8 播放列表地址,播放器开始拉取并定时刷新列表。实时更新播放列表
服务端在每次完成转换和分段后,将生成的 TS 分段信息追加到内存中的播放列表,并写入磁盘供 HTTP 服务器访问。客户端播放器通过刷新 m3u8 文件获取新内容,实现无缝播放。会话结束(结束接口 /hls/finish)
当所有场景文件上传并处理完毕后,客户端调用/finish
接口通知服务器结束会话。服务器在最后一次更新播放列表时追加EXT‑X‑ENDLIST
标签,标识播放流已结束。上传 MP4(/hls/upload/video)
客户端依次将由 Manim 生成的每个片段的 MP4 文件上传至服务器,上传时需附带唯一会话编号(session_id)和序号。注意,片段需要需要时有序的上传 MP3(/hls/upload/audio)
客户端依次将由 Manim 生成的每个片段的 MP3 文件上传至服务器,上传时需附带唯一会话编号(session_id)和序号。注意,片段需要需要时有序的
4. 服务端接口设计与参数
服务端采用 RESTful API 设计,以下为主要接口及其参数示例:
4.1 开始接口 /hls/start
接口目的
- 会话初始化:客户端在上传第一个场景前调用该接口,提交唯一的 session_id,雪花格式,服务器据此创建新的播放会话,包括初始化一个空的播放列表文件。
- 返回播放地址:返回对应的 HLS 播放列表 URL。
接口 URL
POST /hls/start
请求参数
session_id
(必填):字符串,客户端生成的唯一标识.雪花 id 格式timestamp
(可选):UTC 毫秒时间戳(System.currentTimeMillis())。
请求示例(JSON 格式):
{ "session_id": "1743829002012", "timestamp": "1743829002012" }
返回值
{ "data": { "stream_url": "/hls/498090097324138496/playlist.m3u8" }, "msg": null, "ok": true, "error": null, "code": 1 }
服务端内部逻辑
- 根据
session_id
在预定义目录下创建会话文件夹(例如./data/hls/{session_id}/
)。 - 初始化新的 m3u8 播放列表,写入必要的 HLS 头信息(如 EXT‑X-VERSION、EXT‑X-TARGETDURATION、初始 EXT‑X‑MEDIA‑SEQUENCE 设为 0)。
- 记录会话状态,后续用于管理上传的场景、转换任务和播放列表更新。
- 根据
4.2 上传接口 /hls/upload
接口 URL scene_index
POST /hls/upload
请求参数
session_id
(必填):客户端生成的唯一编号,用于标识会话/流。scene_index
(可选):当前场景的序号,便于服务器按顺序处理(若不传,服务器可根据上传时间排序)。- 文件数据:Content-Type 为 multipart/form-data,包含 MP4 文件。
请求示例:
POST /upload?session_id={uniqueId} Content-Type: multipart/form-data Body: [场景文件]
返回值
{ "data": { "session_id": "", "scene_index": 1 }, "msg": null, "ok": true, "error": null, "code": 1 }
4.3 获取播放列表接口 /stream
接口 URL
GET /hls/stream
请求参数
session_id
(必填):对应的会话编号。
返回值
{ "data": { "stream_url": "/hls/498090097324138496/playlist.m3u8" }, "msg": null, "ok": true, "error": null, "code": 1 }
4.4 查询处理状态接口 /status
接口 URL
GET /hls/status
请求参数
session_id
(必填)
返回值
{ "data": { "stream_url": "/hls/498090097324138496/playlist.m3u8" }, "msg": null, "ok": true, "error": null, "code": 1 }
4.5 结束接口 /finish
接口目的
当客户端确定所有场景文件上传完毕,不再有新内容追加时,调用该接口通知服务器结束会话。服务器收到后,在最新更新的播放列表中追加EXT‑X‑ENDLIST
标签,明确标识播放流已结束。接口 URL
POST /hls/finish
请求参数
session_id
(必填):当前播放流的唯一标识,与上传接口中一致。finish_time
(可选):事件事件戳。
请求示例(JSON 格式):
{ "session_id": "abc123-xxxx", "finish_time": "" }
返回值
{ "data":null "msg": null, "ok": true, "error": null, "code": 1 }
服务端内部逻辑
- 接收结束通知,根据
session_id
定位到对应会话的播放列表管理模块。 - 检查当前播放列表中是否已有
EXT‑X‑ENDLIST
标签;若没有,则追加该标签,并更新会话状态为 FINISHED。 - 记录结束操作日志,通知相关监控模块或推送给客户端。
- 播放器在刷新播放列表时读取到
EXT‑X‑ENDLIST
标签,从而判断流已结束。
- 接收结束通知,根据
4.6 上传接口 /hls/upload/video
接口 URL scene_index
POST /hls/upload/video
请求参数
session_id
(必填):客户端生成的唯一编号,用于标识会话/流。scene_index
(可选):当前场景的序号,便于服务器按顺序处理(若不传,服务器可根据上传时间排序)。- 文件数据:Content-Type 为 multipart/form-data,包含 MP4 文件。
请求示例:
POST /upload?session_id={uniqueId} Content-Type: multipart/form-data Body: video
返回值
{ "data": { "session_id": "", "scene_index": 1 }, "msg": null, "ok": true, "error": null, "code": 1 }
4.2 上传接口 /hls/upload/audio
接口 URL scene_index
POST /hls/upload/audio
请求参数
session_id
(必填):客户端生成的唯一编号,用于标识会话/流。scene_index
(可选):当前场景的序号,便于服务器按顺序处理(若不传,服务器可根据上传时间排序)。- 文件数据:Content-Type 为 multipart/form-data,包含 MP4 文件。
请求示例:
POST /upload?session_id={uniqueId} Content-Type: multipart/form-data Body: audio
返回值
{ "data": { "session_id": "", "scene_index": 1 }, "msg": null, "ok": true, "error": null, "code": 1 }
5. 服务端内部逻辑
5.1 文件接收与归档
唯一标识管理
根据上传请求中的session_id
,将同一会话的 MP4 文件存放到特定目录或队列中,例如:./data/hls/{session_id}/scene_{scene_index}.mp4
队列管理
将上传成功的文件记录在内部任务队列中,等待后续转换处理。
5.2 转换与分段处理
调用 native‑media API
接收到 MP4 文件后,服务端调用 native‑media 的转换接口,将 MP4 文件转换为 HLS 分段(TS 或 fMP4)。转换参数包括:- 固定的分段时长(例如 10 秒,相当于
-hls_time 10
的效果); - 指定输出的起始编号(第一场景起始编号为 0,后续场景根据前一会话最大分段编号设置
-start_number
参数)。
示例调用(伪代码):
// nativeMedia.convertToHLS(inputFile, outputDir, startNumber, hlsTime); NativeMedia.convertToHLS("./data/uploads/abc123/scene_1.mp4","./data/hls/abc123/",0, 10);
- 固定的分段时长(例如 10 秒,相当于
分段连续性
每个转换任务生成的 TS 文件名称格式为 "segment_%03d.ts",同时生成一个 m3u8 播放列表片段。服务端解析该 m3u8 文件中的分段信息和 EXTINF 值,将新条目追加到全局播放列表中。
5.3 播放列表动态更新
内部播放列表管理
服务器维护一个内存中的播放列表对象,每次转换完成后,将新生成的 TS 分段条目追加到该列表中,并动态更新以下标签:EXT-X-MEDIA-SEQUENCE
:设为当前播放列表中第一个分段的编号;EXT-X-TARGETDURATION
:设置为所有分段中最大时长;- 每个分段的
EXTINF
标签根据实际分段时长更新。
列表文件更新
定时或实时将内存中的播放列表写入磁盘(例如./data/hls/{session_id}/playlist.m3u8
),供外部 HTTP 服务器访问,或通过 API 将最新列表返回给客户端。
5.4 与 native‑media 集成
替代 ffmpeg 的关键点
native‑media 项目作为内部音视频处理库,其 API 应支持:- MP4 到 HLS(TS 或 fMP4)转换;
- 分段参数设置(起始编号、分段时长等);
- 生成播放列表信息(包含分段文件名、EXTINF 等)。
定制改造
可根据需求在 native‑media 中扩展接口,如支持动态追加分段、获取转换进度等,以便与服务端播放列表管理逻辑配合工作。
5.5 异常处理与日志记录
转换错误
若转换失败或时间戳不连续,服务端应进行重试,或记录错误信息并通知上层监控模块。并发处理
对于同一会话中多个场景文件并发上传和转换,需设计并发控制机制(例如基于队列或线程池)确保转换顺序和播放列表的连续性。日志记录
对每个转换任务和播放列表更新记录详细日志,便于后续调试和性能监控。
6. native‑media 项目说明
6.1 项目概述
native‑media 是内部开发的音视频处理库,用以替代 ffmpeg。主要特点包括:
- 支持常用格式转换(MP4、TS、fMP4、HLS);
- 可配置转换参数(如分段时长、起始编号);
- 提供 API 接口供 Java 服务端调用,支持批量处理和动态追加。
6.2 API 调用示例
示例接口(伪代码):
// 将 MP4 文件转换为 HLS 分段
HLSConversionResult result = nativeMedia.convertToHLS(
inputFilePath, // MP4 文件路径
outputDirectory, // 输出目录
startSegmentNumber,// 分段起始编号
segmentDuration // 分段时长(秒)
);
if (result.isSuccess()) {
// 返回 TS 文件列表及生成的播放列表片段
List<TSSegment> segments = result.getSegments();
String partialM3U8 = result.getM3U8Content();
// 将这些数据交给播放列表管理模块追加更新
}
扩展点包括:
- 支持回调通知转换进度;
- 支持直接输出 fMP4 分段;
- 提供错误码和详细日志输出接口。
7. 总结与扩展
本文档描述了一个基于 Java 服务器和内部 native‑media 库实现的 HLS 动态推流系统设计方案。系统流程涵盖:
- 客户端通过调用
/start
接口初始化会话并获取播放列表 URL; - 客户端依次上传带有唯一标识的 MP4 场景文件(调用
/upload
接口),服务端利用 native‑media 进行转换分段,并动态更新 m3u8 播放列表(通过/stream
接口供播放器获取); - 当所有场景文件上传完毕后,客户端调用
/finish
接口,服务器在播放列表末尾追加EXT‑X‑ENDLIST
标签,标识流播放结束; - (可选)通过
/status
接口查询当前处理状态。
播放结束判定说明
在 HLS 规范中,播放结束通常由服务器端通过播放列表来控制,而非客户端主动发送结束标识,具体包括:
EXT‑X‑ENDLIST 标签
服务器确定整个流结束时,在 m3u8 播放列表末尾添加EXT‑X‑ENDLIST
标签。播放器读取该标签后,会认为流已结束,从而停止请求新分段。客户端判断
- 客户端无需主动发送“结束”信号,可根据播放列表中是否包含
EXT‑X‑ENDLIST
标签判断流结束; - 或者根据播放列表在一定时间内不再更新(连续多次刷新无新分段)的情况判断播放结束。
- 客户端无需主动发送“结束”信号,可根据播放列表中是否包含
方案建议
- 主动结束:设计结束接口(如
/finish
),当全部场景上传完毕后,客户端主动通知服务器。 - 被动检测:若无主动结束信号,服务器在预设超时周期内无新场景数据时自动追加
EXT‑X‑ENDLIST
标签。
- 主动结束:设计结束接口(如
未来扩展
- 支持 fMP4(CMAF)输出以降低延迟;
- 增加转换进度回调和监控接口;
- 进一步优化并发处理和错误恢复策略。
通过上述完整接口体系和内部逻辑设计,整个系统能够实现从会话初始化、文件上传、转换分段到播放结束的全流程管理,确保客户端播放体验无缝且实时。希望本技术文档能为工程师开发提供充分的技术依据,并根据项目实际情况进一步细化接口和参数设置。
C 端代码实现
参考我的 native-media 章节