168 lines
4.8 KiB
PHP
168 lines
4.8 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* serve_media.php - Serves media files with proper headers for streaming
|
||
|
|
*/
|
||
|
|
|
||
|
|
// Get the requested file path
|
||
|
|
$requestedFile = isset($_GET['file']) ? $_GET['file'] : '';
|
||
|
|
|
||
|
|
if (empty($requestedFile)) {
|
||
|
|
header('HTTP/1.1 400 Bad Request');
|
||
|
|
echo 'No file specified';
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
// IMPORTANT: Set base directory to where media actually lives
|
||
|
|
// Using home directory + GDrive instead of __DIR__
|
||
|
|
$homeDir = getenv('HOME') ?: (getenv('USERPROFILE') ?: '/home/' . get_current_user());
|
||
|
|
$baseDir = $homeDir . '/GDrive';
|
||
|
|
|
||
|
|
// Construct full path
|
||
|
|
$filePath = $baseDir . DIRECTORY_SEPARATOR . $requestedFile;
|
||
|
|
|
||
|
|
// Security: Prevent directory traversal attacks
|
||
|
|
$realBase = realpath($baseDir);
|
||
|
|
$realFile = realpath($filePath);
|
||
|
|
|
||
|
|
// Additional debug info (remove in production)
|
||
|
|
if ($realFile === false) {
|
||
|
|
header('HTTP/1.1 404 Not Found');
|
||
|
|
echo 'File not found. Debug info:' . "\n";
|
||
|
|
echo 'Base dir: ' . $baseDir . "\n";
|
||
|
|
echo 'Requested: ' . $requestedFile . "\n";
|
||
|
|
echo 'Full path: ' . $filePath . "\n";
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (strpos($realFile, $realBase) !== 0) {
|
||
|
|
header('HTTP/1.1 403 Forbidden');
|
||
|
|
echo 'Access denied - path outside base directory';
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if file exists
|
||
|
|
if (!file_exists($filePath) || !is_file($filePath)) {
|
||
|
|
header('HTTP/1.1 404 Not Found');
|
||
|
|
echo 'File not found';
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get file information
|
||
|
|
clearstatcache(true, $filePath); // Clear file stat cache
|
||
|
|
$fileSize = filesize($filePath);
|
||
|
|
$fileExtension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
|
||
|
|
|
||
|
|
// Disable output buffering to prevent Content-Length mismatches
|
||
|
|
if (ob_get_level()) {
|
||
|
|
ob_end_clean();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set content type based on extension
|
||
|
|
$mimeTypes = [
|
||
|
|
// Video
|
||
|
|
'mp4' => 'video/mp4',
|
||
|
|
'mkv' => 'video/x-matroska',
|
||
|
|
'avi' => 'video/x-msvideo',
|
||
|
|
'mov' => 'video/quicktime',
|
||
|
|
'wmv' => 'video/x-ms-wmv',
|
||
|
|
'flv' => 'video/x-flv',
|
||
|
|
'webm' => 'video/webm',
|
||
|
|
'm4v' => 'video/x-m4v',
|
||
|
|
// Audio
|
||
|
|
'mp3' => 'audio/mpeg',
|
||
|
|
'wav' => 'audio/wav',
|
||
|
|
'ogg' => 'audio/ogg',
|
||
|
|
'flac' => 'audio/flac',
|
||
|
|
'm4a' => 'audio/mp4',
|
||
|
|
'aac' => 'audio/aac',
|
||
|
|
'wma' => 'audio/x-ms-wma',
|
||
|
|
'opus' => 'audio/opus'
|
||
|
|
];
|
||
|
|
|
||
|
|
$contentType = isset($mimeTypes[$fileExtension]) ? $mimeTypes[$fileExtension] : 'application/octet-stream';
|
||
|
|
|
||
|
|
// Handle range requests for seeking
|
||
|
|
$range = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : '';
|
||
|
|
|
||
|
|
if (!empty($range)) {
|
||
|
|
// Parse range header
|
||
|
|
list($unit, $range) = explode('=', $range, 2);
|
||
|
|
|
||
|
|
if ($unit === 'bytes') {
|
||
|
|
// Parse range values
|
||
|
|
list($start, $end) = explode('-', $range, 2);
|
||
|
|
$start = intval($start);
|
||
|
|
$end = !empty($end) ? intval($end) : $fileSize - 1;
|
||
|
|
|
||
|
|
// Validate range
|
||
|
|
if ($start > $end || $start < 0 || $end >= $fileSize) {
|
||
|
|
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
||
|
|
header("Content-Range: bytes */$fileSize");
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
$length = $end - $start + 1;
|
||
|
|
|
||
|
|
// Set headers for partial content
|
||
|
|
header('HTTP/1.1 206 Partial Content');
|
||
|
|
header("Content-Range: bytes $start-$end/$fileSize");
|
||
|
|
header("Content-Length: $length");
|
||
|
|
header("Content-Type: $contentType");
|
||
|
|
header('Accept-Ranges: bytes');
|
||
|
|
|
||
|
|
// Open file and seek to start position
|
||
|
|
$file = fopen($filePath, 'rb');
|
||
|
|
if ($file === false) {
|
||
|
|
header('HTTP/1.1 500 Internal Server Error');
|
||
|
|
echo 'Failed to open file';
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
fseek($file, $start);
|
||
|
|
|
||
|
|
// Output the requested range
|
||
|
|
$buffer = 8192; // 8KB chunks
|
||
|
|
$bytesRemaining = $length;
|
||
|
|
|
||
|
|
while ($bytesRemaining > 0 && !feof($file)) {
|
||
|
|
$bytesToRead = min($buffer, $bytesRemaining);
|
||
|
|
$data = fread($file, $bytesToRead);
|
||
|
|
if ($data === false) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
echo $data;
|
||
|
|
$bytesRemaining -= strlen($data);
|
||
|
|
if (connection_aborted()) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fclose($file);
|
||
|
|
exit; // Important: exit after streaming
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Normal full file response
|
||
|
|
header('HTTP/1.1 200 OK');
|
||
|
|
header("Content-Type: $contentType");
|
||
|
|
header("Content-Length: $fileSize");
|
||
|
|
header('Accept-Ranges: bytes');
|
||
|
|
header('Cache-Control: public, max-age=3600');
|
||
|
|
|
||
|
|
// Stream file in chunks to avoid memory issues
|
||
|
|
$file = fopen($filePath, 'rb');
|
||
|
|
if ($file === false) {
|
||
|
|
header('HTTP/1.1 500 Internal Server Error');
|
||
|
|
echo 'Failed to open file';
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
$buffer = 8192; // 8KB chunks
|
||
|
|
while (!feof($file) && !connection_aborted()) {
|
||
|
|
echo fread($file, $buffer);
|
||
|
|
}
|
||
|
|
|
||
|
|
fclose($file);
|
||
|
|
exit; // Important: exit after streaming
|
||
|
|
}
|
||
|
|
?>
|
||
|
|
|