PHP无目录暴露传输视频:流媒体播放异常问题排查
解决Apache视频流媒体播放与权限控制问题
嘿,我来帮你梳理下问题所在,以及怎么修复它。你的核心问题是没处理HTTP Range请求——这是浏览器实现流式播放视频的关键机制,另外还有几个细节需要调整,咱们一步步来:
为什么现有代码不行?
- 缺少Range请求支持:浏览器播放视频时,不会一次性请求整个文件,而是发送
Range头来分段获取文件内容(比如先拿前1MB加载播放)。你的代码直接返回完整文件,浏览器无法进行流式加载,要么提示"No supported video media"(因为它期望分段响应),要么得等整个文件下载完才能播放。 - 路径安全与正确性问题:你要把视频移到Apache目录外,直接用
$_GET['req']会找不到文件,还存在路径遍历风险(比如有人恶意传req=../../secret/file)。 - 响应头细节错误:
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状态码,符合规范。
测试步骤
- 把
test.mp4移到你设置的$external_video_dir路径下; - 访问
play.php?req=test.mp4,现在应该可以直接流式播放,不需要等待整个文件下载完成。
内容的提问来源于stack exchange,提问作者Timmy
相关产品推荐
相关产品推荐

