PHP无法直接将.php后缀变为.mp4,关键在于正确设置Content-Type: video/mp4、Accept-Ranges: bytes等响应头,并用readfile()或fpassthru()输出合法MP4二进制流;动态生成MP4应调用ffmpeg,非PHP原生实现。

PHP 文件后缀(.php)本身不能“变成”.mp4,它只是服务器端脚本;真正能被浏览器直接播放的,是 PHP 脚本输出的符合 MP4 格式规范的二进制流,且响应头设置正确。关键不在改后缀,而在控制 HTTP 响应行为。
PHP 输出 MP4 流必须设置的响应头
浏览器是否触发视频播放,取决于 Content-Type 和 Accept-Ranges 等头部,而非文件扩展名。漏掉任一关键头,可能导致下载、空白、或无法拖动进度条。
-
Content-Type: video/mp4—— 强制声明媒体类型 -
Accept-Ranges: bytes—— 支持分段请求(快进/拖拽必需) -
Content-Length(静态文件)或省略(流式传输时用Transfer-Encoding: chunked) - 若启用缓存,加
Cache-Control: public, max-age=31536000;若禁止缓存(如动态生成),用no-cache
用 readfile() 直接输出本地 MP4 文件(最常用)
适用于已存在磁盘上的 MP4 文件,PHP 充当“管道”。注意路径安全和 MIME 类型校验,避免任意文件读取漏洞。
header('Content-Type: video/mp4');
header('Accept-Ranges: bytes');
header('Content-Length: ' . filesize('/path/to/video.mp4'));
readfile('/path/to/video.mp4');
⚠️ 容易踩的坑:
- 路径未校验:用户传入
file=../../etc/passwd会导致敏感文件泄露 - 未检查文件是否存在或是否为真实 MP4(可用
finfo_file()验证) - 大文件下
readfile()占用内存高,建议配合set_time_limit(0)和ob_end_flush()
用 fpassthru() + fopen() 实现流式输出(适合大文件或需要权限拦截)
比 readfile() 更可控,支持边读边发,内存占用低,也便于在读取前插入鉴权逻辑。
$file = '/path/to/video.mp4';
if (!is_readable($file)) {
http_response_code(403);
exit;
}
$fp = fopen($file, 'rb');
header('Content-Type: video/mp4');
header('Accept-Ranges: bytes');
header('Content-Length: ' . filesize($file));
fpassthru($fp);
fclose($fp);
⚠️ 注意点:
- 必须用
rb模式打开,否则 Windows 下可能因换行符转换损坏二进制流 - 务必关闭
$fp,否则连接不释放,服务端资源泄漏 - 如果前端需支持拖动,
Accept-Ranges头必须有,且不能依赖Content-Length做范围判断——PHP 本身不处理Range请求,需自行解析并 fseek
PHP 动态生成 MP4?基本不可行
MP4 是复杂容器格式,含 moov、mdat、stts 等多个 box 结构,时间戳、索引、编码参数必须严格对齐。PHP 没有原生 MP4 编码能力,也不推荐用纯 PHP 构造。
- 真要动态合成(如加水印、拼接),调用
ffmpeg命令行更可靠:exec("ffmpeg -i input.mp4 -vf 'drawtext=...' -f mp4 -y output.mp4 2>&1", $out) - 生成完再用上面的
readfile()或fpassthru()输出,不要试图在 PHP 中“拼 MP4 字节” - 实时流媒体(如 HLS/DASH)更不该由 PHP 主导,应交由 Nginx 的
ngx_http_mp4_module或专用流媒体服务器(如 Wowza、Nginx-rtmp)处理
真正容易被忽略的是:浏览器对 MP4 的 range 请求默认会发两次(一次试探,一次正式),而很多 PHP 示例代码没处理 $_SERVER['HTTP_RANGE'],导致拖动失败——这不是 PHP 不能播 MP4,是它没按 HTTP 协议回应范围请求。











