直接用 ffmpeg 命令生成合规点播 M3U8:需指定 -hls_list_size 0、-hls_playlist_type vod、-hls_version 4,强制关键帧、验证文件完整性、设置正确 MIME 类型。

直接用 ffmpeg 命令生成合规点播 M3U8,别绕弯
PHP 本身不处理视频编解码或封装,所谓“PHP 实现 HLS 切片”,本质是 PHP 调用 ffmpeg 执行命令。试图用 PHP 自己拼 .ts 或写 .m3u8 文件,99% 会产出播放失败、拖动卡顿、iOS 不认的非标文件。
点播(VOD)场景下,必须确保:-hls_list_size 0(保留全部切片索引)、-hls_playlist_type vod(触发标准 VOD 头部字段如 #EXT-X-PLAYLIST-TYPE:VOD)、输出路径可写且目录已存在。
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -f hls -hls_time 10 -hls_list_size 0 -hls_playlist_type vod -hls_segment_filename "segments/seg_%03d.ts" output/index.m3u8- 务必提前
mkdir -p segments/,否则ffmpeg静默失败,只生成空index.m3u8 - 若源视频关键帧间隔 >10s(如监控录像),加
-force_key_frames "expr:gte(t,n_forced*10)"强制插 I 帧,避免切片时长严重偏离
PHP 封装调用时,必须捕获 stderr 并检查输出文件完整性
仅靠 exec() 返回值或 $process->getExitCode() === 0 完全不可靠:ffmpeg 在部分切片失败、磁盘满、权限不足等情况下仍返回 0,但 index.m3u8 可能为空,或只有前几个 .ts 被写入。
- 用
proc_open()捕获stderr输出,关键词匹配"Error"、"failed"、"Permission denied"、"No such file" - 执行后立即验证:
file_exists($m3u8File)&&filesize($m3u8File) > 100&&count(glob($outputDir . '/segments/seg_*.ts')) > 0 - 不要用
shell_exec()直接拼接用户上传的文件名——必须用escapeshellarg()过滤$inputFile和$outputDir
生成的 M3U8 被 iOS/Safari 拒绝?检查协议版本与绝对路径
iOS 15+ 和 Safari 对 HLS 协议版本更敏感,v3 默认不支持 #EXT-X-MAP,而某些转码配置会隐式启用它;同时,ffmpeg 生成的 segment_filename 若为相对路径,Nginx/Apache 可能无法正确解析 .ts 的实际 URL。
立即学习“PHP免费学习笔记(深入)”;
- 显式指定
-hls_version 4,强制生成 v4 格式(支持#EXT-X-MAP、#EXT-X-I-FRAME-STREAM-INF等) -
-hls_segment_filename改用绝对路径,例如"./output/segments/seg_%03d.ts",并确保 Web 服务器能通过 HTTP 访问该路径 - 生成后手动打开
index.m3u8,确认第一行是#EXTM3U,且所有seg_*.ts行路径与浏览器实际请求路径一致(可用 Chrome DevTools → Network 查看)
点播 HLS 必须禁用 -hls_wrap,且避免动态重写 playlist
-hls_wrap 是直播滚动覆盖设计,点播场景下若误设(如 -hls_wrap 5),会导致 index.m3u8 只保留最近 5 个切片,用户拖动到开头就 404。更隐蔽的问题是:某些 PHP 封装逻辑会在后台定时重执行切片命令,覆盖旧 .m3u8,造成前端播放中断。
- 点播场景下,彻底去掉
-hls_wrap参数(默认即为 0,不覆盖) - 确保 PHP 脚本对同一视频只执行一次切片;可用
file_exists($m3u8File)先判断,避免重复触发 - 若需多码率,用多个独立命令分别生成不同分辨率的
.m3u8,再由 PHP 合并为主播放列表(#EXT-X-STREAM-INF格式),不要让单条ffmpeg命令同时输出多路
最易被忽略的是:切片完成后,.ts 文件的 HTTP MIME 类型必须为 video/MP2T,Nginx 默认不识别,需手动添加 types { application/vnd.apple.mpegurl m3u8; video/MP2T ts; },否则 Safari 直接拒绝加载。



















