You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用FFMPEG与PHP实时转码HLS视频,网页video标签无法播放求助

问题分析与解决方案

核心问题

你的脚本存在两个关键问题:

  1. HLS协议本质限制:HLS是基于分片的流媒体协议,需要生成.m3u8播放列表和多个.ts分片文件。但你用-让ffmpeg输出到stdout,这种模式下ffmpeg无法正确生成分片数据——VLC能播放是因为它做了兼容处理(直接识别后续流为TS格式),但浏览器的video标签严格遵循HLS规范,无法处理这种不完整的输出。
  2. 浏览器兼容性:除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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 03:03:18