You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP无目录暴露传输视频:流媒体播放异常问题排查

解决Apache视频流媒体播放与权限控制问题

嘿,我来帮你梳理下问题所在,以及怎么修复它。你的核心问题是没处理HTTP Range请求——这是浏览器实现流式播放视频的关键机制,另外还有几个细节需要调整,咱们一步步来:

为什么现有代码不行?

  1. 缺少Range请求支持:浏览器播放视频时,不会一次性请求整个文件,而是发送Range头来分段获取文件内容(比如先拿前1MB加载播放)。你的代码直接返回完整文件,浏览器无法进行流式加载,要么提示"No supported video media"(因为它期望分段响应),要么得等整个文件下载完才能播放。
  2. 路径安全与正确性问题:你要把视频移到Apache目录外,直接用$_GET['req']会找不到文件,还存在路径遍历风险(比如有人恶意传req=../../secret/file)。
  3. 响应头细节错误:Content-Disposition设成filename="..."默认触发下载(attachment),应该设为inline让浏览器直接播放;另外mime_content_type()已经过时,兼容性不佳。

修复后的完整代码

下面是调整后的代码,包含Range请求处理、路径安全、正确的响应头和错误处理:

<?php
// 替换成你实际的外部视频存储路径(比如/var/www/external_videos/)
$external_video_dir = '/path/to/your/external/videos/';

// 过滤请求的文件名,防止路径遍历攻击
$requested_file = basename($_GET['req'] ?? '');
$full_file_path = $external_video_dir . $requested_file;

// 检查文件是否存在且可读
if (!file_exists($full_file_path) || !is_readable($full_file_path)) {
    http_response_code(404);
    echo "Requested video not found";
    exit;
}

// 获取文件基本信息
$file_size = filesize($full_file_path);
// 用finfo替代过时的mime_content_type,获取正确的MIME类型
$mime_detector = new finfo(FILEINFO_MIME_TYPE);
$mime_type = $mime_detector->file($full_file_path);

// 处理HTTP Range请求(流式播放核心)
$range_header = $_SERVER['HTTP_RANGE'] ?? '';
$start = 0;
$end = $file_size - 1;

if (preg_match('/bytes=(\d+)-(\d+)?/', $range_header, $matches)) {
    $start = intval($matches[1]);
    $end = isset($matches[2]) ? intval($matches[2]) : $file_size - 1;

    // 处理超出文件范围的请求
    if ($start >= $file_size) {
        http_response_code(416); // 请求范围不满足
        header("Content-Range: bytes */$file_size");
        exit;
    }
    $end = min($end, $file_size - 1);
    $response_length = $end - $start + 1;

    // 返回部分内容的响应头
    http_response_code(206); // Partial Content
    header("Accept-Ranges: bytes");
    header("Content-Range: bytes $start-$end/$file_size");
    header("Content-Length: $response_length");
} else {
    // 完整文件请求的响应头
    http_response_code(200);
    header("Accept-Ranges: bytes");
    header("Content-Length: $file_size");
}

// 通用响应头设置
header("Content-Type: $mime_type");
// inline让浏览器直接播放,而不是下载
header("Content-Disposition: inline; filename=\"$requested_file\"");

// 输出文件内容(分段或完整)
$file_handle = fopen($full_file_path, 'rb');
fseek($file_handle, $start);
echo fread($file_handle, $response_length ?? $file_size);
fclose($file_handle);
exit;
?>

关键修复点说明

  • Range请求处理:解析浏览器的分段请求,返回对应的文件片段,让浏览器可以边下载边播放,实现流媒体效果;
  • 路径安全:用basename()过滤请求的文件名,确保只能访问指定外部目录下的视频,避免恶意路径遍历;
  • 响应头优化:用inline替代默认的下载行为,用finfo获取更准确的MIME类型;
  • 错误处理:针对文件不存在、范围无效的情况返回正确的HTTP状态码,符合规范。

测试步骤

  1. 把test.mp4移到你设置的$external_video_dir路径下;
  2. 访问play.php?req=test.mp4,现在应该可以直接流式播放,不需要等待整个文件下载完成。

内容的提问来源于stack exchange,提问作者Timmy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:31:20