PHP无法直接将.php后缀变为.mp4,关键在于正确设置Content-Type: video/mp4、Accept-Ranges: bytes等响应头,并用readfile()或fpassthru()输出合法MP4二进制流;动态生成MP4应调用ffmpeg,非PHP原生实现。
PHP 文件后缀(.php)本身不能“变成”.mp4,它只是服务器端脚本;真正能被浏览器直接播放的,是 PHP 脚本输出的符合 MP4 格式规范的二进制流,且响应头设置正确。关键不在改后缀,而在控制 HTTP 响应行为。
浏览器是否触发视频播放,取决于 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');
⚠️ 容易踩的坑:
立即学习“PHP免费学习笔记(深入)”;
file=../../etc/passwd 会导致敏感文件泄露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 请求,需自行解析并 fseekMP4 是复杂容器格式,含 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 字节”ngx_http_mp4_module 或专用流媒体服务器(如 Wowza、Nginx-rtmp)处理真正容易被忽略的是:浏览器对 MP4 的 range 请求默认会发两次(一次试探,一次正式),而很多 PHP 示例代码没处理 $_SERVER['HTTP_RANGE'],导致拖动失败——这不是 PHP 不能播 MP4,是它没按 HTTP 协议回应范围请求。