Files
IM-web-new/dist/api/media_stream.php
T

72 lines
2.4 KiB
PHP

<?php
declare(strict_types=1);
const WEB_MEDIA_STREAM_ROOT = '/opt/im-server/media';
function failMediaStream(int $status, string $message): never
{
http_response_code($status);
header('Content-Type: text/plain; charset=utf-8');
header('Cache-Control: no-store');
echo $message;
exit;
}
$url = trim((string)($_GET['url'] ?? ''));
if ($url === '') {
failMediaStream(400, '缺少视频地址');
}
$path = rawurldecode((string)(parse_url($url, PHP_URL_PATH) ?: ''));
if (!preg_match('#^/fs/[0-9]+/[A-Za-z0-9_./ -]+$#', $path) || str_contains($path, '..')) {
failMediaStream(400, '视频地址不合法');
}
$root = realpath(WEB_MEDIA_STREAM_ROOT);
$fullPath = realpath(rtrim(WEB_MEDIA_STREAM_ROOT, '/') . $path);
if ($root === false || $fullPath === false || !str_starts_with($fullPath, $root . DIRECTORY_SEPARATOR) || !is_file($fullPath)) {
failMediaStream(404, '视频文件不存在');
}
$size = (int)filesize($fullPath);
$mime = (string)(mime_content_type($fullPath) ?: 'video/mp4');
if (!str_starts_with($mime, 'video/')) {
$extension = strtolower((string)pathinfo($fullPath, PATHINFO_EXTENSION));
$mime = $extension === 'webm' ? 'video/webm' : ($extension === 'mov' ? 'video/quicktime' : 'video/mp4');
}
$start = 0;
$end = max(0, $size - 1);
$range = trim((string)($_SERVER['HTTP_RANGE'] ?? ''));
if ($range !== '' && preg_match('/^bytes=(\d*)-(\d*)$/', $range, $matches)) {
if ($matches[1] !== '') $start = (int)$matches[1];
if ($matches[2] !== '') $end = min($end, (int)$matches[2]);
if ($start < 0 || $start > $end || $start >= $size) {
header('Content-Range: bytes */' . $size);
failMediaStream(416, '请求范围无效');
}
http_response_code(206);
header('Content-Range: bytes ' . $start . '-' . $end . '/' . $size);
}
$length = $end - $start + 1;
header('Content-Type: ' . $mime);
header('Content-Length: ' . $length);
header('Accept-Ranges: bytes');
header('Content-Disposition: inline');
header('Cache-Control: public, max-age=86400');
header('X-Content-Type-Options: nosniff');
$stream = fopen($fullPath, 'rb');
if ($stream === false) failMediaStream(500, '视频文件无法读取');
fseek($stream, $start);
$remaining = $length;
while ($remaining > 0 && !feof($stream)) {
$chunk = fread($stream, min(1024 * 1024, $remaining));
if ($chunk === false || $chunk === '') break;
echo $chunk;
$remaining -= strlen($chunk);
flush();
}
fclose($stream);