如何用PHP从远程服务器直接流式传输MP3、MP4等媒体文件?
实现PHP跨服务器流式传输媒体文件的正确姿势
你遇到的问题本质是全量下载后转发导致的延迟和资源浪费,正确的做法是让服务器A作为「代理」,把服务器B的文件分段流式传递给客户端,不用把整个文件存在A上。下面是具体的实现方案:
核心思路
服务器A向服务器B发起请求,同时把收到的文件数据立刻转发给客户端,全程保持数据流的开放,不缓存完整文件。还要支持浏览器的Range请求(媒体播放时的断点续传、进度拖动都靠这个)。
完整代码示例
<?php // 1. 安全验证:这里可以加你的权限逻辑,比如token校验、IP白名单等 $allowed = false; // 示例:假设通过GET参数传递验证token if (isset($_GET['token']) && $_GET['token'] === 'your_secure_token') { $allowed = true; } if (!$allowed) { http_response_code(403); exit('Forbidden'); } // 2. 目标文件在服务器B的地址(可以是内部IP/域名,确保A能访问B) $remoteFileUrl = 'http://server-b.example.com/media/your-file.mp3'; // 3. 先获取远程文件的元信息(大小、类型),支持Range请求 $ch = curl_init($remoteFileUrl); curl_setopt($ch, CURLOPT_NOBODY, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, true); $headers = curl_exec($ch); curl_close($ch); // 解析头部信息 $contentLength = 0; $contentType = 'application/octet-stream'; foreach (explode("\r\n", $headers) as $header) { if (strpos($header, 'Content-Length:') === 0) { $contentLength = (int)trim(substr($header, 15)); } elseif (strpos($header, 'Content-Type:') === 0) { $contentType = trim(substr($header, 13)); } } // 4. 处理客户端的Range请求(媒体播放必备) $range = ''; if (isset($_SERVER['HTTP_RANGE'])) { $range = $_SERVER['HTTP_RANGE']; // 解析Range,比如 "bytes=0-1023" preg_match('/bytes=(\d+)-(\d+)?/', $range, $matches); $start = (int)$matches[1]; $end = isset($matches[2]) ? (int)$matches[2] : $contentLength - 1; $length = $end - $start + 1; // 返回206 Partial Content http_response_code(206); header("Content-Range: bytes $start-$end/$contentLength"); header("Content-Length: $length"); } else { // 正常请求返回200 http_response_code(200); header("Content-Length: $contentLength"); } // 5. 设置必要的响应头部,让浏览器识别为媒体文件 header("Content-Type: $contentType"); header("Accept-Ranges: bytes"); header("Cache-Control: public, max-age=31536000"); // 可选:设置缓存时间 header("Content-Disposition: inline; filename=\"" . basename($remoteFileUrl) . "\""); // 让浏览器直接播放,不是下载 // 6. 流式转发远程文件 $ch = curl_init($remoteFileUrl); if (!empty($range)) { curl_setopt($ch, CURLOPT_RANGE, $range); } curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) { echo $data; ob_flush(); flush(); return strlen($data); }); curl_exec($ch); curl_close($ch); exit; ?>
关键细节说明
- 安全验证:一定要加!比如token校验、用户权限检查,防止别人通过你的PHP脚本随意访问服务器B的文件。
- Range请求支持:没有这个的话,用户拖动媒体进度条会失效,浏览器会重新下载整个文件。
- 流式传输:用
CURLOPT_WRITEFUNCTION实时把收到的字节输出给客户端,配合ob_flush()和flush()确保数据立刻发送,不会被PHP缓存。 - 服务器B的访问权限:确保服务器A可以访问服务器B的文件(比如内部网络打通,或者B的防火墙允许A的IP访问),如果B需要认证,可以在cURL里加
CURLOPT_USERPWD或者其他认证方式。 - 避免内存溢出:这种方式不会把整个文件加载到PHP内存里,所以不用担心大文件导致的内存不足问题,比
file_get_contents更可靠。
替代方案:用Apache/Nginx反向代理
如果你的服务器用的是Apache或Nginx,其实可以直接用反向代理来实现,性能比PHP更好:
- Nginx示例:在A的配置里加一个location,把请求转发到B,同时支持Range请求:
location /media/ { proxy_pass http://server-b.example.com/media/; proxy_set_header Range $http_range; proxy_set_header If-Range $http_if_range; proxy_redirect off; proxy_buffering off; # 禁用缓存,实现流式传输 }
这种方式性能更高,不用PHP处理,适合高并发场景,但需要你有服务器配置权限。
内容的提问来源于stack exchange,提问作者Saeid Rad
相关产品推荐
相关产品推荐

