本文目录导读:

在 PHP 中实现流媒体点播(Video on Demand,简称 VOD),核心目标不是把整个视频文件一次性传给用户,而是支持拖拽(Seek)、节省服务器带宽以及保护资源。
这通常需要前端播放器(如 Video.js、HLS.js)与后端 PHP 配合,以下是几种由简到繁的实现方案:
最简单的原生支持(伪流媒体 / HTTP Range)
这是最基础的方式,PHP 不需要做太多事情,只需正确设置响应头,并支持 HTTP Range 请求(即告诉服务器客户端需要文件的哪一段)。
适用场景:MP4 文件(且编码为 H.264 + AAC,moov 元数据在前),浏览器原生播放器即可播放并支持拖拽。
核心代码示例(video.php):
<?php
$filePath = '/path/to/your/video.mp4'; // 这里是服务器上的实际路径
if (!file_exists($filePath)) {
http_response_code(404);
exit('File not found');
}
$fp = fopen($filePath, 'rb');
$size = filesize($filePath);
$length = $size; // 默认发送整个文件
$start = 0;
$end = $size - 1;
// 解析 Range 头
if (isset($_SERVER['HTTP_RANGE'])) {
// 格式:bytes=start-end
preg_match('/bytes=(\d+)-(\d*)/', $_SERVER['HTTP_RANGE'], $matches);
$start = intval($matches[1]);
if (!empty($matches[2])) {
$end = intval($matches[2]);
}
// 限制结束位置不能超过文件大小
if ($end > $size - 1) $end = $size - 1;
$length = $end - $start + 1;
// 设置 206 状态码(Partial Content)
http_response_code(206);
header('Content-Range: bytes ' . $start . '-' . $end . '/' . $size);
} else {
http_response_code(200);
header('Content-Length: ' . $size);
}
// 设置必要的响应头
header('Content-Type: video/mp4');
header('Accept-Ranges: bytes');
header('Content-Length: ' . $length);
// 移动指针并输出内容
fseek($fp, $start);
$chunkSize = 8192; // 8KB 块,防止一次性内存溢出
while ($length > 0 && !feof($fp)) {
$read = min($chunkSize, $length);
echo fread($fp, $read);
$length -= $read;
flush(); // 确保数据发送到客户端
}
fclose($fp);
exit;
?>
前端使用:
<video controls src="video.php" style="width:100%"></video>
优点:实现简单,支持拖拽(取决于 MP4 是否包含 moov 元数据)。 缺点:如果视频很大,且 MP4 元数据在文件尾部,拖拽会非常慢(需下载整个文件才能解析),且不支持 HLS 加密。
使用 HLS 协议(推荐用于点播)
HLS(HTTP Live Streaming)是苹果推出的协议,现在基本是所有流媒体的标准,它将视频分割成很多 .ts(或 .m4s)切片,通过 .m3u8 索引文件进行播放。拖拽体验最好,支持最高码率自适应。
注意:PHP 通常不负责视频切片(那是 FFmpeg 的活),而是负责提供切片文件或动态生成播放列表。
步骤 1:预处理视频(用 FFmpeg 切分)
在本地或服务器上执行:
# 将 input.mp4 转成 HLS 格式,生成 video.m3u8 和 多个 .ts 文件 ffmpeg -i input.mp4 -codec copy -hls_time 10 -hls_list_size 0 -hls_segment_filename public/media/segment_%03d.ts public/media/video.m3u8
注意:这一步通常不是 PHP 做的,而是在部署时用命令完成,或者通过 PHP 的 shell_exec 调用来完成(但需注意性能)。
步骤 2:PHP 提供支持(处理 .m3u8 和 .ts)
PHP 可以把这些文件放在公共目录,或者通过一个控制器来分发(用于权限控制)。
示例:player.php(返回播放列表)
<?php
// 假设你的 .m3u8 文件在 /var/www/html/media/video.m3u8
$playlistPath = __DIR__ . '/media/video.m3u8';
if (file_exists($playlistPath)) {
header('Content-Type: application/vnd.apple.mpegurl'); // 标准 MIME 类型
header('Cache-Control: no-cache');
// 读取并输出内容,同时可能修改内部路径以适应你的 URL 规则
$content = file_get_contents($playlistPath);
// 如果内部是相对路径,一般无需修改,浏览器会基于当前 URL 解析
echo $content;
} else {
http_response_code(404);
}
?>
前端使用:
<!-- 使用 hls.js 库(现代浏览器)+ HLS 原生支持(Safari) -->
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<video id="video" controls></video>
<script>
var video = document.getElementById('video');
var videoSrc = 'player.php'; // 你的 PHP 接口
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// 兼容 Safari
video.src = videoSrc;
}
</script>
优点:拖拽秒开、支持动态码率、安全(可配合 Token 认证)。 缺点:需要预先用 FFmpeg 切片,存储占用稍大。
大文件 + 权限控制(受保护的多媒体)
如果你不想文件直接在 public/media 目录下暴露(防止被盗链),可以放在 PHP 可以访问但外部无法直接访问的目录,通过 PHP 代理读取。
安全策略:签名 URL + 过期时间
<?php // 生成播放链接(比如在用户点击播放时) $videoId = 123; // 生成一个 10 分钟有效的签名 $expires = time() + 600; $token = md5($videoId . $expires . SECRET_KEY); // SECRET_KEY 是自定义常量 // 输出链接给前端 echo "play.php?id=$videoId&expires=$expires&token=$token"; ?>
play.php 校验签名后再输出视频流(结合方案一或方案二)。
关键性能建议
-
开启
gzip?:不要开启 gzip!二进制视频流(MP4/TS)压缩率极低且浪费 CPU,应让 Nginx/Apache 跳过这些扩展名的压缩。 -
使用
X-Sendfile(强烈推荐):PHP 逐块读取并输出文件非常消耗内存和 CPU(每次请求都要跑 PHP 进程)。- Nginx 使用
X-Accel-Redirect。 - Apache 使用
mod_xsendfile。 - 原理:PHP 只负责验证权限和设置 Header,然后告诉 Nginx 返回这个文件,Nginx 直接读取磁盘文件,速度和性能最高。
Nginx 示例(在 Nginx 配置中设置):
location /protected_videos/ { internal; # 表示外部无法直接访问 alias /var/www/files/; }PHP 代码:
<?php // 权限验证通过后 header('Content-Type: video/mp4'); header('X-Accel-Redirect: /protected_videos/' . $filename); // 不输出任何内容,直接结束 exit; ?> - Nginx 使用
-
APCu / Redis 缓存切片列表:如果你动态生成
.m3u8播放列表,建议缓存几分钟,避免大并发时 MySQL/文件系统压力过大。
- 普通 MP4(体积 < 2GB,不追求极致体验):用 方案一(Range + 原生 Video) 最快,但需注意前端加载速度,建议安装
moov元数据前置工具(如qt-faststart,或 FFmpeg 参数-movflags +faststart)。 - 企业级 / 大型点播:用 方案二(HLS + 切片),配合 CDN(内容分发网络),性能最稳。
- 内存/性能优化:放弃
readfile()或fread,改用 X-Sendfile 交给 Web 服务器处理。