PHP不应直接输出视频,而应由Nginx/Apache接管传输,PHP仅负责鉴权、跳转与任务调度;FFmpeg需异步执行,视频须推CDN并配置合理缓存。

直接用 PHP 读取并输出视频文件(比如 readfile())必然卡顿、超时、内存爆掉——这不是 PHP 的问题,而是你没绕过它的执行模型限制。真正的高效处理,核心是“不讓 PHP 干视频的事”,只让它调度、控制、兜底。
PHP 不该直接输出视频流
常见错误现象:页面加载几秒后空白、iOS Safari 拒绝播放、Nginx 返回 504、max_execution_time 被频繁触发。
根本原因在于 PHP 是同步阻塞模型,readfile() 或 fread() + flush() 会独占进程,大文件下内存持续增长,且无法真正释放连接。
- 永远不要在 Web 请求中用
readfile()输出 >10MB 的视频 - 即使加了
ob_end_clean()和ini_set('output_buffering', 'Off'),也挡不住底层 TCP 连接被 PHP 进程锁死 - 浏览器发起的
Range请求若由 PHP 处理,逻辑稍有偏差(比如字节计算错、头没写全),就会退化成整文件下载
用 Web 服务器接管静态视频传输
让 Nginx 或 Apache 直接服务视频文件,PHP 只负责鉴权和跳转,这是最轻量、最稳定的方案。
立即学习“PHP免费学习笔记(深入)”;
Nginx 配置示例(启用 X-Accel-Redirect):
location /protected/ {
internal;
alias /var/www/storage/videos/;
}
PHP 中只需:
header('X-Accel-Redirect: /protected/' . $filename);
header('Content-Type: video/mp4');
header('Content-Disposition: inline; filename="' . $filename . '"');
exit;
-
X-Accel-Redirect是 Nginx 原生机制,不走 PHP 文件 I/O,零内存拷贝 - 路径
/protected/必须声明为internal,防止用户直连 - 确保
alias结尾无多余斜杠,否则 404 - Apache 对应方案是
X-Sendfile,需启用mod_xsendfile
FFmpeg 调用必须脱离请求生命周期
所有转码任务(如生成 HLS、缩略图、格式转换)都不能在 HTTP 请求中同步执行——哪怕加了 set_time_limit(0),也扛不住高并发下的进程堆积。
正确做法是把命令交给后台进程,PHP 只做三件事:入队、返回任务 ID、提供状态查询接口。
- 用
proc_open()启动 FFmpeg 时,务必重定向stdin/stdout/stderr,否则子进程可能因管道满而挂起 - 避免用
exec()+&后台运行,它无法可靠捕获退出码和错误日志 - 生产环境优先用消息队列(RabbitMQ/Kafka),Worker 进程调用
shell_exec()执行 FFmpeg 命令 - 关键参数必须显式指定:
-threads 2(防 CPU 过载)、-preset ultrafast(直播场景)、-movflags +faststart(MP4 边下边播)
缓存与 CDN 是性能放大器,不是可选项
视频一旦生成,就该立刻推送到 CDN;PHP 层唯一要做的,是生成带签名的临时 URL(如 https://cdn.example.com/v.mp4?expires=1718323200&sig=abc),而不是自己拼接路径。
- CDN 缓存策略必须设为
Cache-Control: public, max-age=31536000,避免回源压垮 PHP - 对未登录用户,直接返回 CDN 地址;对需要鉴权的用户,用
X-Accel-Redirect+ CDN 回源鉴权(Nginxauth_request) - 切片类输出(如 HLS 的
.m3u8和.ts)必须设置短缓存(max-age=1),否则播放器无法感知新切片 - 别忘了用 FFmpeg 提前生成首帧缩略图:
ffmpeg -i input.mp4 -ss 00:00:01 -vframes 1 -q:v 2 thumbnail.jpg,前端可先加载小图提升感知速度
最容易被忽略的一点:FFmpeg 命令里看似无关的参数(比如 -hls_list_size 0 或 -movflags +faststart)直接影响播放器能否启动、拖动是否生效、首帧加载多快——这些不是“锦上添花”,而是播放链路里不可绕过的硬性节点。



















