![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/.cursor-server/data/User/History/2d16b85/ |
<?php
// Audio proxy endpoint - hides actual MP3 file paths
// Uses track_id instead of task_id for community pages
// Now includes signed token validation to prevent URL sharing
error_reporting(0);
ini_set('display_errors', 0);
ob_start();
session_start();
// Include token validation
require_once __DIR__ . '/audio_token.php';
/**
* Log access violation and display a styled error page (bilingual FR/EN)
* Logs attempt to database and invites user to review terms
*
* @param string $reason Reason for denial (for logging)
* @param int|string $trackId The track ID attempted
* @param string $token The token used (if any)
* @param int $expires Token expiration (if any)
*/
function showAccessDeniedPage($reason, $trackId = null, $token = null, $expires = null) {
// Collect violation data
$violationData = [
'reason' => $reason,
'track_id' => $trackId,
'token' => $token ? substr($token, 0, 8) . '...' : null, // Partial token for privacy
'expires' => $expires,
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
'referrer' => $_SERVER['HTTP_REFERER'] ?? 'direct',
'request_uri' => $_SERVER['REQUEST_URI'] ?? '',
'user_id' => $_SESSION['user_id'] ?? null,
'session_id' => session_id(),
'timestamp' => date('Y-m-d H:i:s')
];
// Log to error log
error_log("AUDIO ACCESS VIOLATION: " . json_encode($violationData));
// Try to log to database
try {
require_once __DIR__ . '/../config/database.php';
$pdo = getDBConnection();
if ($pdo) {
// Create table if not exists (first time only)
$pdo->exec("
CREATE TABLE IF NOT EXISTS audio_access_violations (
id INT AUTO_INCREMENT PRIMARY KEY,
reason VARCHAR(255) NOT NULL,
track_id INT NULL,
token_partial VARCHAR(20) NULL,
ip_address VARCHAR(45) NOT NULL,
user_agent TEXT,
referrer TEXT,
request_uri TEXT,
user_id INT NULL,
session_id VARCHAR(128),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_ip (ip_address),
INDEX idx_created (created_at),
INDEX idx_reason (reason)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
// Insert violation record
$stmt = $pdo->prepare("
INSERT INTO audio_access_violations
(reason, track_id, token_partial, ip_address, user_agent, referrer, request_uri, user_id, session_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$reason,
$trackId,
$violationData['token'],
$violationData['ip_address'],
substr($violationData['user_agent'], 0, 500),
substr($violationData['referrer'], 0, 500),
substr($violationData['request_uri'], 0, 500),
$violationData['user_id'],
$violationData['session_id']
]);
}
} catch (Exception $e) {
error_log("Failed to log audio violation to database: " . $e->getMessage());
}
// Detect language (from session, cookie, or browser)
$lang = $_SESSION['lang'] ?? $_COOKIE['lang'] ?? 'en';
if ($lang !== 'fr') {
// Check browser Accept-Language header
$acceptLang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
if (stripos($acceptLang, 'fr') === 0) {
$lang = 'fr';
}
}
$isFrench = ($lang === 'fr');
// Bilingual content
$texts = [
'title' => $isFrench ? 'Accès Refusé' : 'Access Denied',
'page_title' => $isFrench ? 'Accès Refusé - Sound Studio Pro' : 'Access Denied - Sound Studio Pro',
'message' => $isFrench
? 'Ce contenu audio est protégé. L\'accès direct aux fichiers audio n\'est pas autorisé afin de protéger les droits de nos créateurs et d\'assurer une licence appropriée.'
: 'This audio content is protected. Direct access to audio files is not permitted to protect the rights of our creators and ensure proper licensing.',
'why_title' => $isFrench ? 'Pourquoi je vois ceci ?' : 'Why am I seeing this?',
'why_message' => $isFrench
? 'Ce lien n\'est plus valide. Il a peut-être expiré ou été utilisé depuis un autre appareil.'
: 'This link is no longer valid. It may have expired or been used from another device.',
'terms' => $isFrench ? '📜 Conditions d\'utilisation' : '📜 Terms of Service',
'privacy' => $isFrench ? '🔐 Politique de confidentialité' : '🔐 Privacy Policy',
'home_btn' => $isFrench ? '🏠 Retour à Sound Studio Pro' : '🏠 Return to Sound Studio Pro',
'need_help' => $isFrench ? 'Besoin d\'aide ?' : 'Need help?',
'contact' => $isFrench ? 'Contacter le support' : 'Contact Support',
'copyright' => $isFrench ? 'Tous droits réservés.' : 'All rights reserved.'
];
// Clear any output buffers
while (ob_get_level()) {
ob_end_clean();
}
// Send 403 status
http_response_code(403);
header('Content-Type: text/html; charset=UTF-8');
header('Cache-Control: no-store, no-cache, must-revalidate');
$htmlLang = $isFrench ? 'fr' : 'en';
// Display styled error page with site header
echo '<!DOCTYPE html>
<html lang="' . $htmlLang . '">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>' . htmlspecialchars($texts['page_title']) . '</title>
<link rel="icon" type="image/png" href="/assets/images/favicon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/assets/fontawesome/fontawesome-free-6.5.1-web/css/all.min.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0f23 100%);
min-height: 100vh;
color: #fff;
}
/* Site Header */
.site-header {
background: rgba(26, 26, 46, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255,255,255,0.1);
padding: 15px 30px;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
text-decoration: none;
color: #fff;
}
.logo-icon {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.logo-text {
font-size: 20px;
font-weight: 700;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header-nav {
display: flex;
gap: 20px;
align-items: center;
}
.header-nav a {
color: rgba(255,255,255,0.7);
text-decoration: none;
font-size: 14px;
padding: 8px 16px;
border-radius: 8px;
transition: all 0.3s;
}
.header-nav a:hover {
color: #fff;
background: rgba(255,255,255,0.1);
}
.header-nav .lang-switch-btn {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(118, 75, 162, 0.2));
border: 1px solid rgba(102, 126, 234, 0.3);
color: #fff;
font-weight: 600;
}
.header-nav .lang-switch-btn:hover {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.35), rgba(118, 75, 162, 0.35));
border-color: rgba(102, 126, 234, 0.5);
}
.header-nav .lang-switch-btn i {
animation: globeRotate 3s linear infinite;
}
@keyframes globeRotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.header-nav .btn-login {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
.header-nav .btn-login:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}
/* Main Content */
.main-content {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 100px 20px 40px;
}
.container {
max-width: 520px;
text-align: center;
background: rgba(255,255,255,0.05);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
border: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 25px 50px rgba(0,0,0,0.5);
}
.icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #e74c3c, #c0392b);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 25px;
font-size: 40px;
}
h1 {
font-size: 28px;
margin-bottom: 15px;
color: #fff;
}
.message {
color: rgba(255,255,255,0.7);
line-height: 1.6;
margin-bottom: 30px;
}
.info-box {
background: rgba(231, 76, 60, 0.1);
border: 1px solid rgba(231, 76, 60, 0.3);
border-radius: 10px;
padding: 15px;
margin-bottom: 25px;
font-size: 14px;
color: rgba(255,255,255,0.8);
text-align: left;
}
.info-box strong {
color: #e74c3c;
}
.links {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 25px;
}
.links a {
color: #667eea;
text-decoration: none;
padding: 10px 20px;
border: 1px solid #667eea;
border-radius: 8px;
transition: all 0.3s;
font-size: 14px;
}
.links a:hover {
background: #667eea;
color: #fff;
}
.home-btn {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 12px 30px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: transform 0.3s, box-shadow 0.3s;
}
.home-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4);
}
.footer-text {
margin-top: 30px;
font-size: 12px;
color: rgba(255,255,255,0.4);
}
.footer-text a {
color: rgba(255,255,255,0.6);
text-decoration: none;
}
.footer-text a:hover {
color: #667eea;
}
@media (max-width: 600px) {
.site-header {
padding: 12px 15px;
}
.logo-text {
font-size: 16px;
}
.header-nav a:not(.btn-login) {
display: none;
}
.container {
padding: 25px;
}
h1 {
font-size: 22px;
}
}
</style>
</head>
<body>
<!-- Site Header -->
<header class="site-header">
<a href="/" class="logo">
<div class="logo-icon"><i class="fas fa-music"></i></div>
<span class="logo-text">Sound Studio Pro</span>
</a>
<nav class="header-nav">
<a href="/community_fixed.php"><i class="fas fa-fire"></i> ' . ($isFrench ? 'Découvrir' : 'Discover') . '</a>
<a href="/lang_switch.php?lang=' . ($isFrench ? 'en' : 'fr') . '&return=' . urlencode('/') . '" class="lang-switch-btn">
<i class="fas fa-globe"></i> ' . ($isFrench ? 'EN' : 'FR') . '
</a>
<a href="/auth/login.php" class="btn-login"><i class="fas fa-sign-in-alt"></i> ' . ($isFrench ? 'Connexion' : 'Login') . '</a>
</nav>
</header>
<!-- Main Content -->
<main class="main-content">
<div class="container">
<div class="icon">🔒</div>
<h1>' . htmlspecialchars($texts['title']) . '</h1>
<p class="message">' . htmlspecialchars($texts['message']) . '</p>
<div class="info-box">
<strong>' . htmlspecialchars($texts['why_title']) . '</strong><br><br>
' . htmlspecialchars($texts['why_message']) . '
</div>
<div class="links">
<a href="/terms.php">' . $texts['terms'] . '</a>
<a href="/privacy.php">' . $texts['privacy'] . '</a>
</div>
<a href="/" class="home-btn">' . $texts['home_btn'] . '</a>
<div class="footer-text">
<p>' . htmlspecialchars($texts['need_help']) . ' <a href="/contact.php">' . htmlspecialchars($texts['contact']) . '</a></p>
<p style="margin-top: 10px;">© ' . date('Y') . ' Sound Studio Pro. ' . htmlspecialchars($texts['copyright']) . '</p>
</div>
</div>
</main>
</body>
</html>';
exit;
}
// Get track ID and optional variation from URL
$trackId = $_GET['id'] ?? '';
$variationIndex = isset($_GET['variation']) ? (int)$_GET['variation'] : null;
$token = $_GET['token'] ?? '';
$expires = isset($_GET['expires']) ? (int)$_GET['expires'] : 0;
// CRITICAL: Block direct URL access - only allow when coming from proper pages or Range requests (playback)
// This prevents people from pasting URLs directly in browser
$referrer = $_SERVER['HTTP_REFERER'] ?? '';
$isRangeRequest = isset($_SERVER['HTTP_RANGE']); // Range requests are from audio player (playback)
$isPageLoad = !$isRangeRequest && ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'GET';
// Allowed pages that can generate valid audio URLs
$allowedPages = [
'track.php',
'community_fixed.php',
'create_music.php', // In case users preview tracks
'radio/', // Radio pages
];
$isFromValidPage = false;
// Check if request is coming from a valid page on our domain
if (!empty($referrer)) {
// Extract the path from the referrer URL
$referrerPath = parse_url($referrer, PHP_URL_PATH);
if ($referrerPath) {
$referrerPath = basename($referrerPath);
// Check if referrer is from our domain
$referrerHost = parse_url($referrer, PHP_URL_HOST);
$allowedDomains = [
$_SERVER['HTTP_HOST'] ?? 'soundstudiopro.com',
'soundstudiopro.com',
'www.soundstudiopro.com'
];
if (in_array($referrerHost, $allowedDomains)) {
// Check if it's from an allowed page
foreach ($allowedPages as $allowedPage) {
if (strpos($referrer, $allowedPage) !== false) {
$isFromValidPage = true;
break;
}
}
}
}
}
// Block direct access for page loads (not Range requests) if not from valid page
// Range requests are always allowed (they're from audio player, not direct browser access)
if ($isPageLoad && !$isFromValidPage) {
showAccessDeniedPage('Direct URL access - no valid referrer', $trackId, $token, $expires);
}
ob_clean();
if (empty($trackId) || !is_numeric($trackId)) {
http_response_code(400);
header('Content-Type: text/plain');
echo "Invalid track ID";
exit;
}
require_once __DIR__ . '/../config/database.php';
ob_clean();
$pdo = getDBConnection();
if (!$pdo) {
http_response_code(500);
header('Content-Type: text/plain');
echo "Database error";
exit;
}
// Get current user and session
$user_id = $_SESSION['user_id'] ?? null;
$session_id = session_id();
// Get track and verify access
$stmt = $pdo->prepare("SELECT id, task_id, audio_url, is_public, user_id, status, metadata, selected_variation FROM music_tracks WHERE id = ? AND status = 'complete'");
$stmt->execute([$trackId]);
$track = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$track) {
http_response_code(404);
header('Content-Type: text/plain');
echo "Track not found";
exit;
}
// SECURITY: Check access
$isOwner = ($user_id && $track['user_id'] == $user_id);
$isPublic = ($track['is_public'] == 1);
// For public tracks, token is optional (for backward compatibility)
// For private tracks or owners, token is required
if ($isPublic) {
// Public track - if token is provided, it MUST be valid for this track and session
// This prevents URL sharing across browsers/sessions
if (!empty($token) && !empty($expires)) {
$tokenValid = validateAudioToken($trackId, $variationIndex, $token, $expires, $user_id, $session_id);
if (!$tokenValid) {
// Try with null user_id (guest mode)
$tokenValid = validateAudioToken($trackId, $variationIndex, $token, $expires, null, $session_id);
}
// CRITICAL: If token is provided, it MUST be valid - prevents cross-browser sharing
if (!$tokenValid) {
showAccessDeniedPage('Invalid token for session (possible URL sharing)', $trackId, $token, $expires);
}
}
// Public track - allow access (with valid token or without token)
} else {
// Private track - require valid token
if (empty($token) || empty($expires)) {
showAccessDeniedPage('Private track - missing authentication token', $trackId, $token, $expires);
}
// Validate token
$tokenValid = validateAudioToken($trackId, $variationIndex, $token, $expires, $user_id, $session_id);
if (!$tokenValid && $isOwner) {
// Owner might have different session, try with null user_id
$tokenValid = validateAudioToken($trackId, $variationIndex, $token, $expires, null, $session_id);
}
if (!$tokenValid) {
showAccessDeniedPage('Invalid or expired token', $trackId, $token, $expires);
}
}
// CRITICAL: One-time use enforcement - check BEFORE serving, mark on page loads only
// First page load consumes the use, refresh is blocked
// Range requests (playback/seeking) don't consume uses - they're part of the same session
$isPageLoad = !isset($_SERVER['HTTP_RANGE']) && ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'GET';
if (!empty($token) && !empty($expires)) {
// Check if token has already been used (blocks refresh)
$usage = checkTokenUsage($token, $trackId);
if ($usage['used'] && $usage['expired']) {
// Token already used - block immediately
showAccessDeniedPage('Token already used (possible replay attack)', $trackId, $token, $expires);
}
// Mark token as used ONLY on page loads (not Range requests)
// First page load consumes the use immediately, refresh is blocked
// Range requests (playback/seeking) don't consume uses - allows normal playback
if ($isPageLoad) {
markTokenUsed($token, $trackId);
}
}
// If no variation parameter provided, check for selected variation in metadata
if ($variationIndex === null) {
// Check metadata for selected_variation
if (!empty($track['metadata'])) {
$metadata = is_string($track['metadata']) ? json_decode($track['metadata'], true) : $track['metadata'];
if (isset($metadata['selected_variation'])) {
$variationIndex = (int)$metadata['selected_variation'];
}
}
// Also check selected_variation column if it exists
if ($variationIndex === null && isset($track['selected_variation']) && $track['selected_variation'] !== null) {
$variationIndex = (int)$track['selected_variation'];
}
}
// Get audio URL - check for specific variation first, then main track
$audioUrl = '';
if ($variationIndex !== null) {
// Get specific variation
$stmt = $pdo->prepare("SELECT audio_url FROM audio_variations WHERE track_id = ? AND variation_index = ?");
$stmt->execute([$trackId, $variationIndex]);
$variation = $stmt->fetch(PDO::FETCH_ASSOC);
if ($variation) {
$audioUrl = $variation['audio_url'] ?? '';
}
}
// If no variation or variation not found, use main track
if (empty($audioUrl)) {
$audioUrl = $track['audio_url'] ?? '';
// If still empty, try first variation as fallback
if (empty($audioUrl)) {
$stmt = $pdo->prepare("SELECT audio_url FROM audio_variations WHERE track_id = ? ORDER BY variation_index ASC LIMIT 1");
$stmt->execute([$trackId]);
$variation = $stmt->fetch(PDO::FETCH_ASSOC);
if ($variation) {
$audioUrl = $variation['audio_url'] ?? '';
}
}
}
if (empty($audioUrl)) {
http_response_code(404);
header('Content-Type: text/plain');
echo "Audio not available";
exit;
}
// SECURITY: If it's a local file, validate path before serving
if (strpos($audioUrl, '/audio_files/') === 0 || strpos($audioUrl, '/uploads/') === 0) {
// Use file security utility to validate path (defense in depth)
require_once __DIR__ . '/../includes/file_security.php';
$audio_validation = validateAudioUrl($audioUrl);
if ($audio_validation['type'] !== 'local' || !$audio_validation['path']) {
// Path validation failed - security violation
error_log("SECURITY: Invalid audio path in play_audio.php: " . htmlspecialchars($audioUrl, ENT_QUOTES, 'UTF-8'));
showAccessDeniedPage('Security violation - invalid path', $trackId, $token, $expires);
}
$localPath = $audio_validation['path'];
if (file_exists($localPath)) {
$fileSize = filesize($localPath);
$fileType = mime_content_type($localPath) ?: 'audio/mpeg';
while (ob_get_level()) {
ob_end_clean();
}
header('Content-Type: ' . $fileType);
header('Accept-Ranges: bytes');
header('Cache-Control: public, max-age=3600');
// Handle Range requests for seeking support
if (isset($_SERVER['HTTP_RANGE'])) {
// Parse the Range header
$range = $_SERVER['HTTP_RANGE'];
if (preg_match('/bytes=(\d*)-(\d*)/', $range, $matches)) {
$start = $matches[1] === '' ? 0 : intval($matches[1]);
$end = $matches[2] === '' ? $fileSize - 1 : intval($matches[2]);
// Validate range
if ($start > $end || $start >= $fileSize || $end >= $fileSize) {
http_response_code(416); // Range Not Satisfiable
header("Content-Range: bytes */$fileSize");
exit;
}
$length = $end - $start + 1;
http_response_code(206); // Partial Content
header("Content-Range: bytes $start-$end/$fileSize");
header("Content-Length: $length");
// Serve the requested range
$fp = fopen($localPath, 'rb');
fseek($fp, $start);
$remaining = $length;
while ($remaining > 0 && !feof($fp)) {
$chunk = min(8192, $remaining);
echo fread($fp, $chunk);
$remaining -= $chunk;
flush();
}
fclose($fp);
exit;
}
}
// No Range header - serve full file
header('Content-Length: ' . $fileSize);
readfile($localPath);
exit;
}
}
// If it's an external URL, proxy it with Range support
if (strpos($audioUrl, 'http') === 0) {
while (ob_get_level()) {
ob_end_clean();
}
// Build headers to forward to external server
$requestHeaders = [
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
];
// Forward Range header if present (for seeking support)
if (isset($_SERVER['HTTP_RANGE'])) {
$requestHeaders[] = 'Range: ' . $_SERVER['HTTP_RANGE'];
}
$ch = curl_init($audioUrl);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $requestHeaders);
// Capture response headers to forward them
$responseHeaders = [];
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = trim($header);
if (empty($header)) return $len;
// Parse header
$parts = explode(':', $header, 2);
if (count($parts) == 2) {
$name = strtolower(trim($parts[0]));
$value = trim($parts[1]);
$responseHeaders[$name] = $value;
} elseif (strpos($header, 'HTTP/') === 0) {
// Status line - extract status code
if (preg_match('/HTTP\/\d\.?\d?\s+(\d+)/', $header, $matches)) {
$responseHeaders['_status'] = intval($matches[1]);
}
}
return $len;
});
// Buffer the response to get headers first, then stream
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$body = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
http_response_code(500);
header('Content-Type: text/plain');
echo "Failed to stream audio";
exit;
}
// Set appropriate status code (206 for partial content, 200 for full)
if ($httpCode == 206) {
http_response_code(206);
}
// Forward relevant headers
header('Content-Type: ' . ($responseHeaders['content-type'] ?? 'audio/mpeg'));
header('Accept-Ranges: bytes');
header('Cache-Control: public, max-age=3600');
if (isset($responseHeaders['content-length'])) {
header('Content-Length: ' . $responseHeaders['content-length']);
}
if (isset($responseHeaders['content-range'])) {
header('Content-Range: ' . $responseHeaders['content-range']);
}
// Output the body
echo $body;
exit;
}
http_response_code(404);
header('Content-Type: text/plain');
echo "Audio not available";
exit;
?>