$end || $start >= $size) { http_response_code(416); header("Content-Range: bytes */$size"); exit; } http_response_code(206); header("Content-Range: bytes $start-$end/$size"); } else { http_response_code(200); } session_write_close(); // don't block other requests from this user ignore_user_abort(false); set_time_limit(0); // The bytes must reach the socket as they are read: an output buffer // (php.ini's output_buffering=4096 is common) holds them back, and transparent // compression would change the length Content-Range and Content-Length promise. while (ob_get_level() > 0) { ob_end_clean(); } ini_set('zlib.output_compression', '0'); header('Content-Type: video/mp4'); header('Accept-Ranges: bytes'); header('Content-Length: ' . ($end - $start + 1)); header('Cache-Control: private, max-age=3600'); if ($_SERVER['REQUEST_METHOD'] === 'HEAD') { exit; } $fp = fopen($file, 'rb'); fseek($fp, $start); $remaining = $end - $start + 1; while ($remaining > 0 && !feof($fp) && !connection_aborted()) { $chunk = fread($fp, min(1024 * 512, $remaining)); if ($chunk === false) break; echo $chunk; flush(); $remaining -= strlen($chunk); } fclose($fp);