PHP 怎么PHP 视频流

wen PHP项目 1

本文目录导读:

PHP 怎么PHP 视频流

  1. 场景一:直接提供本地视频文件(最常用)
  2. 场景二:处理实时/动态生成的视频流(如摄像头、转码输出)
  3. 场景三:使用 PHP 框架或专用方案(推荐)
  4. 避坑指南

在 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 框架或专用方案(推荐)

如果不想手写上述逻辑,可以使用现成方案:

  1. 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;
  2. Laravel:返回文件流。

    return response()->file('/path/to/video.mp4');
  3. 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

避坑指南

  1. 不要用 readfile()file_get_contents() 直接返回大视频:它们会把整个文件载入内存,导致服务器 OOM(内存溢出),且不支持 Range
  2. flush() 非常重要:如果不调用 flush(),PHP 会缓冲到脚本结束才发送,失去流式传输的意义。
  3. 浏览器缓存:对于点播视频,可以配置 Cache-Control: publicETag 让浏览器减少请求。
  4. 安全:如果视频需要权限控制,务必在 PHP 脚本中进行身份验证(如 Session、JWT),不要将视频目录直接暴露给公网。

抱歉,评论功能暂时关闭!