[ 'method' => 'GET', 'header' => implode("\r\n", $headers), 'follow_location' => 1, 'timeout' => 30, ]]); $handle = @fopen($url, 'rb', false, $opts); if (!$handle) { http_response_code(502); die('Cannot open stream'); } // Récupérer le Content-Type de la réponse $ct = 'video/MP2T'; foreach (($http_response_header ?? []) as $h) { if (preg_match('/^Content-Type:\s*(.+)/i', $h, $m)) { $ct = trim($m[1]); break; } } header('Content-Type: ' . $ct); header('X-Accel-Buffering: no'); // Désactiver le buffering PHP if (ob_get_level()) ob_end_clean(); while (!feof($handle)) { $chunk = fread($handle, 65536); // 64KB par chunk if ($chunk === false || $chunk === '') break; echo $chunk; flush(); } fclose($handle); } else { // Playlist .m3u8 — fetch complet puis réécriture $opts = stream_context_create(['http' => [ 'method' => 'GET', 'header' => implode("\r\n", $headers), 'follow_location' => 1, 'timeout' => 15, 'ignore_errors' => true, ]]); $body = @file_get_contents($url, false, $opts); if ($body === false) { http_response_code(502); die('Cannot reach upstream'); } $status_line = $http_response_header[0] ?? 'HTTP/1.1 200 OK'; preg_match('#HTTP/\S+\s+(\d+)#', $status_line, $sm); if ((int)($sm[1] ?? 200) >= 400) { http_response_code((int)$sm[1]); die('Upstream error'); } header('Content-Type: application/vnd.apple.mpegurl'); $proxy_base = (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . strtok($_SERVER['REQUEST_URI'], '?') . '?url='; $lines = explode("\n", $body); $out = []; foreach ($lines as $line) { $line = rtrim($line); if ($line === '' || $line[0] === '#') { $line = preg_replace_callback('/URI="([^"]+)"/', function($m) use ($base, $proxy_base) { $seg = strpos($m[1], 'http') === 0 ? $m[1] : $base . $m[1]; return 'URI="' . $proxy_base . urlencode($seg) . '"'; }, $line); $out[] = $line; } else { $seg = strpos($line, 'http') === 0 ? $line : $base . $line; $out[] = $proxy_base . urlencode($seg); } } echo implode("\n", $out); }