使用FFMPEG与PHP实时转码HLS视频,网页video标签无法播放求助
问题分析与解决方案
核心问题
你的脚本存在两个关键问题:
- HLS协议本质限制:HLS是基于分片的流媒体协议,需要生成
.m3u8播放列表和多个.ts分片文件。但你用-让ffmpeg输出到stdout,这种模式下ffmpeg无法正确生成分片数据——VLC能播放是因为它做了兼容处理(直接识别后续流为TS格式),但浏览器的video标签严格遵循HLS规范,无法处理这种不完整的输出。 - 浏览器兼容性:除Safari外,Chrome、Firefox等主流浏览器原生不支持HLS,需要借助第三方库实现播放。
解决方案一:预转码并保存到服务器(推荐)
先将视频转码为标准HLS格式并保存到服务器可访问目录,再让video标签指向.m3u8文件。
修改PHP转码脚本
<?php $input_video = 'whatever.mp4'; // 定义输出目录(确保服务器有写入权限) $output_dir = __DIR__ . '/hls_stream/'; // 创建目录(如果不存在) if (!is_dir($output_dir)) { mkdir($output_dir, 0755, true); } $output_m3u8 = $output_dir . 'stream.m3u8'; // 构造ffmpeg命令:生成标准HLS分片和播放列表 $ffmpeg_command = sprintf( 'ffmpeg -i %s ' . '-map 0:v -c:v copy ' . '-map 0:a:m:language:eng -c:a aac -ac 6 -b:a 320k ' . '-f hls -hls_time 30 -hls_list_size 120 -start_number 0 ' . '-hls_segment_filename "%ssegment_%%03d.ts" ' . '%s', escapeshellarg($input_video), $output_dir, escapeshellarg($output_m3u8) ); // 执行转码命令 exec($ffmpeg_command, $exec_output, $return_code); if ($return_code === 0) { // 转码成功,返回播放列表的可访问URL $m3u8_public_url = '/hls_stream/stream.m3u8'; // 根据服务器配置调整路径 header("Location: {$m3u8_public_url}"); exit; } else { // 转码失败,输出错误信息 echo "转码失败:" . implode('<br>', $exec_output); } ?>
HTML播放代码(兼容多浏览器)
使用hls.js库兼容非Safari浏览器:
<video id="hls-video" controls width="640" height="360"></video> <script> const video = document.getElementById('hls-video'); const m3u8Url = '/hls_stream/stream.m3u8'; // 对应上面的公开URL // 检测浏览器播放支持情况 if (window.Hls) { const hls = new Hls(); hls.loadSource(m3u8Url); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () => { video.play(); }); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Safari原生支持HLS video.src = m3u8Url; } else { alert('当前浏览器不支持HLS视频播放'); } </script> <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
解决方案二:实时转码并流式传输(适合大文件/按需转码)
如果需要实时转码不保存文件,需要让ffmpeg生成的播放列表指向动态获取分片的PHP脚本,同时处理分片交付:
1. 生成带动态分片URL的m3u8(encode.php)
<?php $input_video = 'whatever.mp4'; $temp_dir = __DIR__ . '/temp_hls/'; mkdir($temp_dir, 0755, true); // 构造ffmpeg命令:分片保存到临时目录,播放列表指向分片获取脚本 $ffmpeg_command = sprintf( 'ffmpeg -i %s ' . '-map 0:v -c:v copy ' . '-map 0:a:m:language:eng -c:a aac -ac 6 -b:a 320k ' . '-f hls -hls_time 30 -hls_list_size 120 -start_number 0 ' . '-hls_segment_filename "%ssegment_%%03d.ts" ' . '-hls_segment_url "/get_segment.php?seg=segment_%%03d.ts" ' . '-', escapeshellarg($input_video), $temp_dir ); // 设置HTTP头 header('Content-Type: application/vnd.apple.mpegurl'); header('Cache-Control: no-cache'); header('Access-Control-Allow-Origin: *'); // 输出播放列表 $handle = popen($ffmpeg_command, 'rb'); fpassthru($handle); pclose($handle); ?>
2. 分片获取脚本(get_segment.php)
<?php $temp_dir = __DIR__ . '/temp_hls/'; $seg_name = $_GET['seg'] ?? ''; // 验证文件名,防止路径遍历 if (preg_match('/^segment_\d{3}\.ts$/', $seg_name)) { $seg_path = $temp_dir . $seg_name; if (file_exists($seg_path)) { header('Content-Type: video/MP2T'); header('Cache-Control: public, max-age=3600'); readfile($seg_path); // 可选:删除1小时前的过期分片节省空间 if (time() - filemtime($seg_path) > 3600) { unlink($seg_path); } exit; } } http_response_code(404); echo '分片不存在'; ?>
3. HTML播放代码
和解决方案一的HTML代码一致,只需将m3u8Url指向你的转码脚本URL(比如/encode.php)。
额外注意事项
- 服务器权限:确保PHP对输出目录/临时目录有读写权限。
- 缓存控制:m3u8文件要设置
no-cache,避免浏览器缓存旧的播放列表;TS分片可设置合理的缓存时间。 - 性能优化:实时转码对服务器CPU和带宽要求较高,大文件建议使用预转码方案,或借助CDN分发HLS流。
内容的提问来源于stack exchange,提问作者mrsmith
相关产品推荐
相关产品推荐

