本文目录导读:

在 PHP 中处理和生成视频流(Video Streaming),核心在于服务器端控制 HTTP 响应头,以支持范围请求(Range Requests),这使得用户可以拖动进度条、从中间开始播放,而不是一次性下载整个文件。
根据你的具体场景(是处理上传/转码后的流,还是直接提供本地视频文件),有以下几种主要方式:
直接提供本地视频文件(最常用)
如果你的视频已经存储在服务器上,并且用户想看,应该使用分块流式传输并支持 Range 请求。
<?php
// stream.php
$file = '/path/to/your/video.mp4'; // 确保文件路径正确
if (!file_exists($file)) {
header('HTTP/1.1 404 Not Found');
exit;
}
// 获取文件大小
$size = filesize($file);
$mimeType = mime_content_type($file); // 或手动指定 'video/mp4'
// 定义响应头
header('Content-Type: ' . $mimeType);
header('Accept-Ranges: bytes'); // 告知客户端支持范围请求
header('Content-Length: ' . $size); // 初始长度(可能被后面的代码修改)
// --- 处理 Range (分块请求) ---
$start = 0;
$end = $size - 1;
$isPartial = false;
if (isset($_SERVER['HTTP_RANGE'])) {
// 解析 Range 头, bytes=100-599
preg_match('/bytes=(\d+)-(\d*)/', $_SERVER['HTTP_RANGE'], $matches);
$start = intval($matches[1]);
$end = $matches[2] === '' ? $size - 1 : intval($matches[2]);
// 验证范围合法性
if ($start > $end || $start >= $size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header('Content-Range: bytes */' . $size);
exit;
}
$isPartial = true;
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $start . '-' . $end . '/' . $size);
header('Content-Length: ' . ($end - $start + 1));
}
// --- 输出文件内容 ---
$fp = fopen($file, 'rb');
if ($fp === false) {
header('HTTP/1.1 500 Internal Server Error');
exit;
}
// 移动指针到开始位置
fseek($fp, $start);
$bufferSize = 8192; // 8KB 块大小
$remaining = $end - $start + 1;
set_time_limit(0); // 防止大文件超时
while (!feof($fp) && $remaining > 0) {
$readSize = min($bufferSize, $remaining);
echo fread($fp, $readSize);
$remaining -= $readSize;
flush(); // 立刻发送到客户端(关键)
ob_flush(); // 如果开启了输出缓冲
}
fclose($fp);
exit;
?>
使用方式: 在 HTML5 <video> 标签中直接引用:
<video controls> <source src="stream.php" type="video/mp4"> </video>
处理实时/动态生成的视频流(如摄像头、转码输出)
如果你需要从外部进程实时获取数据并推送给用户(ffmpeg 实时转码),需要使用分块传输编码(Chunked Transfer Encoding)。
<?php
// realtime_stream.php
header('Content-Type: video/mp4'); // 或其他类型
header('Transfer-Encoding: chunked');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
set_time_limit(0);
ob_implicit_flush(true);
// 示例:调用 ffmpeg 推送实时视频
$cmd = 'ffmpeg -i rtmp://your_source -f mp4 pipe:1 2>/dev/null';
$handle = popen($cmd, 'r');
if ($handle === false) {
header('HTTP/1.1 500 Internal Server Error');
exit;
}
while (!feof($handle)) {
$buffer = fread($handle, 8192);
if ($buffer !== false && strlen($buffer) > 0) {
// 发送分块
echo dechex(strlen($buffer)) . "\r\n";
echo $buffer . "\r\n";
flush();
}
}
// 发送结束块
echo "0\r\n\r\n";
pclose($handle);
?>
注意: 此方法不支持进度条跳跃(因为没有 Range 支持),仅适用于直播或无法确定内容总长度的场景。
使用 PHP 框架或专用方案(推荐)
如果不想手写上述逻辑,可以使用现成方案:
-
Symfony HTTP Foundation:提供了
BinaryFileResponse,支持Range请求。use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; $response = new BinaryFileResponse('/path/to/video.mp4'); $response->setContentDisposition('inline'); return $response; -
Laravel:返回文件流。
return response()->file('/path/to/video.mp4'); -
Nginx 内部流(性能最优):让 Nginx 直接处理静态文件,PHP 只负责权限验证。
# nginx 配置 location /videos/ { internal; # 只允许内部重定向访问 alias /var/www/storage/videos/; }// PHP 代码 header('Content-Type: video/mp4'); header('X-Accel-Redirect: /videos/' . $filename); // 由 nginx 直接发送 exit;
| 特性 | 普通文件流(支持拖动) | 实时流(不支持拖动) |
|---|---|---|
| HTTP协议 | Range 头 + 206 Partial Content |
Transfer-Encoding: chunked |
| 内存占用 | 低(分块读取) | 较低 |
| 进度条跳跃 | ✅ 支持 | ❌ 不支持 |
| 典型应用 | 点播视频、课程视频 | 摄像头直播、动态转码 |
| 性能瓶颈 | fread/fwrite |
外部进程(ffmpeg)I/O |
避坑指南
- 不要用
readfile()或file_get_contents()直接返回大视频:它们会把整个文件载入内存,导致服务器 OOM(内存溢出),且不支持Range。 flush()非常重要:如果不调用flush(),PHP 会缓冲到脚本结束才发送,失去流式传输的意义。- 浏览器缓存:对于点播视频,可以配置
Cache-Control: public和ETag让浏览器减少请求。 - 安全:如果视频需要权限控制,务必在 PHP 脚本中进行身份验证(如 Session、JWT),不要将视频目录直接暴露给公网。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播