能否用.ts分片创建HLS主播放列表?Nginx+FFmpeg相关技术问询
嘿,我来帮你理清这个HLS主播放列表的问题~
一、直接用不同播放列表的.ts分片创建主播放列表?不可行!
首先得明确HLS的核心规范:主播放列表(Master Playlist)的设计目的是指向变体播放列表(Variant Playlist)——也就是那些包含对应码率/分辨率.ts分片的.m3u8文件,而不是直接引用单个.ts分片。
这是因为HLS客户端的逻辑是:先拉取主列表,再根据当前带宽选择合适的变体列表,最后从变体列表里逐个拉取.ts分片播放。如果主列表直接放.ts文件,完全不符合RFC 8216的标签规则,客户端根本无法识别这种结构,直接会播放失败。
举个正确的主列表结构例子:
#EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=1280x720 720p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=500000,RESOLUTION=640x360 360p.m3u8
你要是把里面的.m3u8换成.ts,播放器直接会报错。
二、如何在Nginx收到首个.ts分片时立即生成主播放列表?
虽然直接用.ts分片做主列表不行,但我们有几种办法能实现“首个分片出现就生成主列表”的需求,下面是具体方案:
1. 用Shell脚本+文件监听自动生成
Nginx的ngx_http_hls_module在收到FFmpeg推流的第一个.ts分片时,会自动生成对应的变体播放列表(比如720p.m3u8)。我们可以用inotifywait监听变体列表的创建事件,一旦检测到第一个变体列表出现,就自动生成主列表:
#!/bin/bash # 监听的HLS目录,改成你的Nginx web目录下的HLS路径 MONITOR_DIR="/usr/share/nginx/html/hls" MASTER_PLAYLIST="$MONITOR_DIR/master.m3u8" # 等待第一个变体列表创建 inotifywait -e create "$MONITOR_DIR" | while read -r dir events filename; do # 只处理变体m3u8,跳过主列表本身 if [[ $filename == *.m3u8 && $filename != "master.m3u8" ]]; then # 初始化主列表 echo "#EXTM3U" > "$MASTER_PLAYLIST" # 遍历所有变体列表,添加到主列表 for variant in "$MONITOR_DIR"/*.m3u8; do if [[ $(basename "$variant") != "master.m3u8" ]]; then # 从文件名提取分辨率(比如720p.m3u8),计算带宽 res=$(echo "$filename" | grep -oP '\d+p' | sed 's/p//') bandwidth=$((res * 1000)) # 转换成bps单位 resolution="${res}x$((res*9/16))" # 16:9比例计算高度 echo "#EXT-X-STREAM-INF:BANDWIDTH=$bandwidth,RESOLUTION=$resolution" >> "$MASTER_PLAYLIST" echo "$(basename "$variant")" >> "$MASTER_PLAYLIST" fi done # 生成一次就退出,或者去掉break持续监听更新 break fi done
把这个脚本做成系统服务(比如systemd),或者在Nginx启动时自动运行,就能实现首个分片出现时自动生成主列表。
2. 用Nginx Lua模块动态生成主列表
如果你的Nginx安装了ngx_http_lua_module,可以写个Lua脚本,在客户端请求master.m3u8时实时扫描变体列表并生成主列表内容——这样不需要提前生成文件,只要有至少一个变体列表存在(也就是已经有.ts分片了),就能返回有效的主列表:
首先创建Lua脚本/etc/nginx/lua/gen_master.lua:
local hls_dir = "/usr/share/nginx/html/hls" local master_content = "#EXTM3U\n" -- 遍历目录下的变体m3u8文件 local handle = io.popen("ls -1 " .. hls_dir .. "/*.m3u8 | grep -v master.m3u8") for filename in handle:lines() do local base_name = string.match(filename, "/([^/]+)$") -- 从文件名提取分辨率(比如720p.m3u8) local res = string.match(base_name, "(%d+)p") if res then local bandwidth = tonumber(res) * 1000 -- 假设720p对应720000 bps local resolution = res .. "x" .. math.floor(tonumber(res)*9/16) master_content = master_content .. "#EXT-X-STREAM-INF:BANDWIDTH=" .. bandwidth .. ",RESOLUTION=" .. resolution .. "\n" master_content = master_content .. base_name .. "\n" end end handle:close() -- 设置响应头并返回主列表内容 ngx.header.content_type = "application/vnd.apple.mpegurl" ngx.print(master_content)
然后在Nginx配置里添加这个location:
location /master.m3u8 { content_by_lua_file /etc/nginx/lua/gen_master.lua; }
这样客户端请求master.m3u8时,只要有一个变体列表存在(即首个.ts分片已生成),就会动态生成并返回主列表,非常灵活。
3. FFmpeg推流时同步生成主列表
如果你的推流场景是固定码率的变体流,可以在FFmpeg推流命令执行后,延迟几秒(等首个分片生成)直接生成主列表:
# 推720p流到后台 ffmpeg -i input.mp4 -c:v libx264 -c:a aac -f hls -hls_list_size 0 http://your-nginx/hls/720p.m3u8 & # 推360p流到后台 ffmpeg -i input.mp4 -c:v libx264 -c:a aac -vf scale=640:360 -f hls -hls_list_size 0 http://your-nginx/hls/360p.m3u8 & # 等待1秒让首个分片生成,然后写入主列表 sleep 1 && echo -e "#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=720000,RESOLUTION=1280x720\n720p.m3u8\n#EXT-X-STREAM-INF:BANDWIDTH=360000,RESOLUTION=640x360\n360p.m3u8" > /usr/share/nginx/html/hls/master.m3u8
这种方法最简单,但需要预先知道所有变体流的参数,适合固定场景。
内容的提问来源于stack exchange,提问作者Pallav

