<?php
// BEZWZGLĘDNA BLOKADA CACHE - Gwarantuje prawidłowe wczytanie czasu po powrocie ze strzałki Wstecz
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

setlocale(LC_CTYPE, "pl_PL.UTF-8", "en_US.UTF-8", "C.UTF-8");
require_once 'functions.php';

$shareHash = $_GET['h'] ?? '';
$isPublicShare = false;
$cfg = cfg();

if ($shareHash !== '') {
    if (!preg_match('/^(?:[a-f0-9]{12}|[a-f0-9]{32})$/', $shareHash)) die('Błędny lub wygasły link.');
    $db = getDb();
    if (!isset($db['shares'][$shareHash])) die('Błędny lub wygasły link.');

    $share = $db['shares'][$shareHash];
    if (time() > (int)($share['expires'] ?? 0)) die('Ten link tymczasowy wygasł (minęły 24h).');

    $folderKey = $share['folder'] ?? '';
    $subPath = $share['path'] ?? '';
    $fileName = $share['file'] ?? '';
    $resolved = resolveAllowedFile($folderKey, $subPath, $fileName);
    if ($resolved === false) die('Plik usunięty, przeniesiony lub znajduje się poza dozwolonym folderem.');

    $isPublicShare = true;
    $csrfToken = '';
    session_write_close();
} else {
    checkAuth();
    $csrfToken = csrfToken();
    session_write_close();

    $folderKey = $_GET['folder'] ?? '';
    $subPath = $_GET['path'] ?? '';
    $fileName = $_GET['file'] ?? '';
    $resolved = resolveAllowedFile($folderKey, $subPath, $fileName);
    if ($resolved === false) die('Odmowa dostępu lub plik nie istnieje.');
}

$subPath = $resolved['path'];
$fileName = $resolved['file'];
$basePath = $resolved['base'];
$filePath = $resolved['full'];
$fileSignature = (@filemtime($filePath) ?: 0) . '|' . (@filesize($filePath) ?: 0);

// --- AGRESYWNE MINIATURKI I OCHRONA PROCESORA ---
$thumbDir = __DIR__ . '/data/thumbnails';
if (!is_dir($thumbDir)) @mkdir($thumbDir, 0777, true);
if ($isPublicShare) {
    $spriteUrl = "play.php?h=".urlencode($shareHash)."&sprite=1";
    $spriteSheetUrl = "play.php?h=".urlencode($shareHash)."&sprite_sheet=1";
} else {
    $spriteUrl = "play.php?folder=".urlencode($folderKey)."&path=".urlencode($subPath)."&file=".urlencode($fileName)."&sprite=1";
    $spriteSheetUrl = "play.php?folder=".urlencode($folderKey)."&path=".urlencode($subPath)."&file=".urlencode($fileName)."&sprite_sheet=1";
}

$metaHash = md5('video-meta-v1|' . $filePath . '|' . $fileSignature);
$metaFile = $thumbDir . '/meta_' . $metaHash . '.json';
$probeData = readJsonFile($metaFile, []);
if (empty($probeData)) {
    $probeCmd = "/usr/bin/ffprobe -v error -print_format json -show_entries format=duration:stream=codec_type,codec_name:stream_tags=language,title " . escapeshellarg($filePath);
    $probeData = json_decode((string)@shell_exec($probeCmd), true);
    if (!is_array($probeData)) $probeData = [];
    if (!empty($probeData)) writeJsonFile($metaFile, $probeData);
}
$duration = isset($probeData['format']['duration']) ? (float)$probeData['format']['duration'] : 0;

if (isset($_GET['sprite_sheet'])) {
    set_time_limit(15);
    $startSheetGeneration = isset($_GET['generate']) && $_GET['generate'] === '1';

    $sheetDuration = $duration;

    if ($sheetDuration <= 0) {
        http_response_code(204);
        header('Cache-Control: no-store');
        exit;
    }

    // Plansza jest generowana tylko raz dla filmu. Maksymalnie 200 równomiernie
    // rozłożonych klatek zapewnia płynny podgląd bez tworzenia tysięcy plików.
    $sheetInterval = max(2, (int)ceil($sheetDuration / 200));
    $sheetFrameCount = min(200, max(1, (int)ceil($sheetDuration / $sheetInterval)));
    $sheetColumns = 10;
    $sheetRows = max(1, (int)ceil($sheetFrameCount / $sheetColumns));

    $sheetHash = md5('preview-sheet-v10|' . $filePath . '|' . $fileSignature . '|' . $sheetInterval . '|' . $sheetFrameCount);
    $sheetFile = $thumbDir . '/' . $sheetHash . '.jpg';
    $sheetTmp = $sheetFile . '.tmp';
    $sheetPart = $thumbDir . '/' . $sheetHash . '.part.jpg';
    $sheetLog = $thumbDir . '/' . $sheetHash . '.log';
    $sheetFailed = $thumbDir . '/' . $sheetHash . '.failed';

    if (is_file($sheetFile) && filesize($sheetFile) === 0) @unlink($sheetFile);
    if (is_file($sheetTmp) && time() - filemtime($sheetTmp) > 900) @unlink($sheetTmp);
    if (is_file($sheetPart) && time() - filemtime($sheetPart) > 900) @unlink($sheetPart);
    if (is_file($sheetFailed) && time() - filemtime($sheetFailed) > 21600) @unlink($sheetFailed);

    if ($startSheetGeneration && !is_file($sheetFile) && !is_file($sheetTmp) && !is_file($sheetFailed)) {
        $lockHandle = @fopen($sheetTmp, 'x');

        if ($lockHandle !== false) {
            fclose($lockHandle);
            @unlink($sheetPart);

            // Odciążenie serwera: kompletna plansza powstaje w jednym procesie
            // FFmpeg z niskim priorytetem i jednym wątkiem, a potem jest używana z cache.
            $sheetFilter = "fps=1/{$sheetInterval},scale=160:90:force_original_aspect_ratio=decrease,pad=160:90:(ow-iw)/2:(oh-ih)/2,tile={$sheetColumns}x{$sheetRows}:nb_frames={$sheetFrameCount}:padding=0:margin=0:color=black";
            $ioNiceCmd = "";
            if (is_executable('/usr/bin/ionice')) {
                $ioniceOutput = [];
                $ioniceCode = 1;
                @exec('/usr/bin/ionice -c3 /bin/true 2>/dev/null', $ioniceOutput, $ioniceCode);
                if ($ioniceCode === 0) $ioNiceCmd = "/usr/bin/ionice -c3 ";
            }
            $sheetCmd = "/usr/bin/timeout --kill-after=10 600 " . $ioNiceCmd . "/usr/bin/nice -n 19 /usr/bin/ffmpeg -y -nostdin -hide_banner -loglevel error -i " . escapeshellarg($filePath) . " -an -sn -threads 1 -vf " . escapeshellarg($sheetFilter) . " -frames:v 1 -q:v 6 " . escapeshellarg($sheetPart);
            $backgroundCmd = $sheetCmd . " 2>" . escapeshellarg($sheetLog)
                . "; status=$?; if [ \$status -eq 0 ] && [ -s " . escapeshellarg($sheetPart) . " ]; then mv " . escapeshellarg($sheetPart) . " " . escapeshellarg($sheetFile)
                . "; rm -f " . escapeshellarg($sheetFailed)
                . "; else rm -f " . escapeshellarg($sheetPart) . "; date +%s > " . escapeshellarg($sheetFailed) . "; fi; rm -f " . escapeshellarg($sheetTmp);
            @exec("nohup /bin/sh -c " . escapeshellarg($backgroundCmd) . " >/dev/null 2>&1 < /dev/null &");
        }
    }

    clearstatcache(true, $sheetFile);
    $sheetInfo = is_file($sheetFile) ? @getimagesize($sheetFile) : false;
    if (is_file($sheetFile) && $sheetInfo === false) {
        @unlink($sheetFile);
        @file_put_contents($sheetFailed, (string)time(), LOCK_EX);
    }
    if (is_file($sheetFile) && filesize($sheetFile) > 0 && $sheetInfo !== false) {
        @touch($sheetFile);
        header('Content-Type: image/jpeg');
        header('Content-Length: ' . filesize($sheetFile));
        header('Cache-Control: private, max-age=86400');
        readfile($sheetFile);
    } elseif (is_file($sheetFailed)) {
        http_response_code(409);
        header('X-Sprite-Status: failed');
        header('Cache-Control: no-store');
    } else {
        http_response_code(204);
        header('Cache-Control: no-store');
    }
    exit;
}

if (isset($_GET['sprite'])) {
    set_time_limit(90);

    $spriteTime = isset($_GET['time']) ? max(0, floor(floatval($_GET['time']) / 5) * 5) : 0;
    $thumbHash = md5('preview-v7|' . $filePath . '|' . $fileSignature . '|' . $spriteTime);
    $spriteFile = $thumbDir . '/' . $thumbHash . '.jpg';
    $spriteTmp = $spriteFile . '.tmp';
    $spritePart = $thumbDir . '/' . $thumbHash . '.part.jpg';
    $spriteLog = $thumbDir . '/' . $thumbHash . '.log';

    if (is_file($spriteFile) && filesize($spriteFile) === 0) @unlink($spriteFile);
    if (is_file($spriteTmp) && time() - filemtime($spriteTmp) > 300) @unlink($spriteTmp);
    if (is_file($spritePart) && time() - filemtime($spritePart) > 300) @unlink($spritePart);

    if (!is_file($spriteFile)) {
        $lockHandle = @fopen($spriteTmp, 'x');

        if ($lockHandle !== false) {
            fclose($lockHandle);
            @unlink($spritePart);

            // Odciążenie serwera: pojedyncza klatka jest pobierana szybkim seekiem,
            // z nice -n 19 i jednym wątkiem, tylko dla miejsca wskazanego na pasku.
            $spriteFilter = "scale=160:90:force_original_aspect_ratio=decrease,pad=160:90:(ow-iw)/2:(oh-ih)/2";
            $ffmpegCmd = "/usr/bin/timeout --kill-after=5 60 /usr/bin/nice -n 19 /usr/bin/ffmpeg -y -nostdin -hide_banner -loglevel error -ss " . escapeshellarg((string)$spriteTime) . " -i " . escapeshellarg($filePath) . " -map 0:v:0 -an -sn -threads 1 -vf " . escapeshellarg($spriteFilter) . " -frames:v 1 -q:v 6 " . escapeshellarg($spritePart);
            $output = [];
            $returnCode = 1;
            @exec($ffmpegCmd . " 2>" . escapeshellarg($spriteLog), $output, $returnCode);

            $imageInfo = is_file($spritePart) ? @getimagesize($spritePart) : false;
            if ($returnCode === 0 && is_file($spritePart) && filesize($spritePart) > 0 && $imageInfo !== false) {
                @rename($spritePart, $spriteFile);
                @unlink($spriteLog);
            } else {
                @unlink($spritePart);
            }

            @unlink($spriteTmp);
        }
    }

    clearstatcache(true, $spriteFile);
    if (is_file($spriteFile) && filesize($spriteFile) > 0) {
        @touch($spriteFile);
        header('Content-Type: image/jpeg');
        header('Content-Length: ' . filesize($spriteFile));
        header('Cache-Control: private, max-age=86400');
        readfile($spriteFile);
    } else {
        http_response_code(204);
        header('Cache-Control: no-store');
    }
    exit;
}

if ($isPublicShare) recordDownload($resolved['mega']);

$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$isNative = in_array($ext, ['mp4', 'webm']);

$aid = isset($_GET['aid']) ? intval($_GET['aid']) : 0;

// Pobieranie ścieżek ZAWSZE (Względne indeksowanie jest najbardziej niezawodne dla FFmpeg)
$audioTracks = [];
$aIdx = 0;

if (isset($probeData['streams'])) {
    foreach ($probeData['streams'] as $st) {
        if ($st['codec_type'] === 'audio') {
            $lang = (string)($st['tags']['language'] ?? 'unk');
            $title = (string)($st['tags']['title'] ?? "Ścieżka " . ($aIdx + 1));
            $audioTracks[] = ['id' => $aIdx, 'name' => strtoupper($lang) . " - " . $title];
            $aIdx++;
        }
    }
}

if (isset($_GET['aid'])) {
    $isNative = false; 
}

// Dodano parametr "cb" (Cache-Buster), by powstrzymać przeglądarkę przed odtwarzaniem starego audio z pamięci cache
if ($isPublicShare) {
    $streamUrl = $isNative
        ? "share.php?h=".urlencode($shareHash)."&action=media"
        : "stream.php?h=".urlencode($shareHash)."&aid=".$aid."&cb=".time();
} else {
    $streamUrl = $isNative 
        ? "view.php?folder=".urlencode($folderKey)."&path=".urlencode($subPath)."&file=".urlencode($fileName)
        : "stream.php?folder=".urlencode($folderKey)."&path=".urlencode($subPath)."&file=".urlencode($fileName)."&aid=".$aid."&cb=".time();
}

$nextFile = '';
$nextUrl = '';
if (!$isPublicShare) {
    $dirPath = dirname($filePath);
    $dirFiles = scandir($dirPath);
    $videos = [];
    $supportedExts = ['mkv', 'avi', 'ts', 'mts', 'mp4', 'webm'];

    foreach($dirFiles as $f) {
        if($f === '.' || $f === '..') continue;
        if(is_file($dirPath . '/' . $f)) {
            if(in_array(strtolower(pathinfo($f, PATHINFO_EXTENSION)), $supportedExts)) {
                $videos[] = $f;
            }
        }
    }

    natsort($videos); 
    $videos = array_values($videos); 
    $currentIndex = array_search($fileName, $videos);

    if($currentIndex !== false && isset($videos[$currentIndex + 1])) {
        $nextFile = $videos[$currentIndex + 1];
        $nextUrl = "play.php?folder=".urlencode($folderKey)."&path=".urlencode($subPath)."&file=".urlencode($nextFile);
    }
}

// LINK 24H
// Link jest tworzony dopiero po kliknięciu przycisku, aby otwieranie filmu nie zapisywało nieużywanych linków.

// --- ODCZYT POSTĘPU Z SERWERA ---
$savedTime = $isPublicShare ? 0 : getProgressTime($folderKey, $subPath, $fileName);
$initialStart = ($savedTime > 15) ? floor($savedTime) : 0;

// --- AGRESYWNE MINIATURKI I OCHRONA PROCESORA ---
// Miniaturki są generowane na żądanie przez parametr sprite=1 dopiero podczas przewijania filmu.
?>
<!doctype html>
<html lang='pl'>
<head>
    <meta charset='utf-8'>
    <title>VOD: <?=htmlspecialchars($fileName)?></title>
    <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover'>
    <style>
        body, html { height: 100dvh; width: 100vw; margin: 0; padding: 0; background: #080808; overflow: hidden; font-family: -apple-system, sans-serif; touch-action: none; user-select: none; -webkit-user-select: none; }
        .player-wrapper { position: absolute; top:0; left:0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #000; }
        video { width: 100%; height: 100%; object-fit: contain; }
        
        #video-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 5; }

        .controls, .top-bar { position: absolute; left: 0; width: 100%; box-sizing: border-box; display: flex; transition: opacity 0.4s ease; z-index: 10; opacity: 1; pointer-events: none; }
        .player-wrapper.idle .controls, .player-wrapper.idle .top-bar { opacity: 0 !important; cursor: none; }
        body.idle { cursor: none; }
        .controls > *, .top-bar > * { pointer-events: auto; }

        .top-bar { top: 0; background: linear-gradient(rgba(0,0,0,0.9), transparent); padding: 15px; justify-content: space-between; align-items: flex-start; }
        .title-container { flex: 1; display: flex; flex-direction: column; overflow: hidden; margin-right: 15px; }
        .title { color: #fff; font-size: 15px; font-weight: bold; text-shadow: 1px 1px 3px #000; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        
        .controls { bottom: 0; background: linear-gradient(transparent, rgba(0,0,0,0.9)); padding: 40px 15px 15px 15px; gap: 10px; align-items: center; flex-wrap: wrap; }
        .btn-ctrl { background: none; border: none; color: #fff; cursor: pointer; font-size: 20px; padding: 5px; transition: color 0.2s; }
        .btn-ctrl:hover { color: #007bff; }
        .btn-link { background: rgba(40, 167, 69, 0.15); border: 1px solid rgba(40, 167, 69, 0.5); color: #28a745; font-size: 12px; font-weight: bold; border-radius: 4px; padding: 4px 8px; margin-right: 5px; transition: 0.2s; }

        .progress-container { flex-grow: 1; min-width: 30%; height: 35px; cursor: pointer; position: relative; display: flex; align-items: center; touch-action: none; }
        .progress-track { width: 100%; height: 6px; background: rgba(255,255,255,0.2); border-radius: 4px; position: relative; pointer-events: none; }
        .progress-bar { height: 100%; width: 0%; background: #6f42c1; border-radius: 4px; pointer-events: none; }
        
        /* Twarde display: block; wymuszające wyświetlanie pudełka miniatury od razu */
        #scrubBubble { position: absolute; bottom: 35px; transform: translateX(-50%); background: #080808; border: 2px solid #6f42c1; border-radius: 6px; padding: 5px; color: #fff; font-size: 13px; font-family: monospace; text-align: center; display: none; pointer-events: none; z-index: 50; box-shadow: 0 5px 15px rgba(0,0,0,0.8); }
        .sprite-view { width: 160px; height: 90px; background-repeat: no-repeat; margin-bottom: 5px; border-radius: 2px; display: none; background-color: #222; }

        .time-text { color: #fff; font-size: 12px; font-family: monospace; text-shadow: 1px 1px 2px #000; }
        
        /* NAPRAWA: Zmieniono link <a> na przycisk <button> odporny na bloki przeglądarki */
        .back-btn { border: none; cursor: pointer; font-family: inherit; color: #fff; text-decoration: none; font-size: 12px; background: rgba(255,255,255,0.1); padding: 5px 12px; border-radius: 20px; transition: 0.2s; flex-shrink: 0; z-index: 99999; }
        .back-btn:hover { background: rgba(255,255,255,0.3); }

        .loader { border: 4px solid rgba(255,255,255,0.1); border-top: 4px solid #6f42c1; border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; position: absolute; top: 50%; left: 50%; margin-top: -25px; margin-left: -25px; display: none; z-index: 5; }
        
        .seek-indicator { position: absolute; top: 50%; transform: translateY(-50%); background: rgba(0,0,0,0.6); padding: 15px 25px; border-radius: 50px; font-size: 20px; font-weight: bold; color: #fff; pointer-events: none; opacity: 0; transition: opacity 0.2s; z-index: 20; text-shadow: 1px 1px 3px #000; }
        
        @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
        @media (max-width: 600px) {
            .btn-ctrl { font-size: 18px; padding: 2px; }
            .btn-link { font-size: 10px; padding: 4px 6px; }
            .time-text { font-size: 10px; }
            .title { font-size: 13px; }
            .controls { padding-top: 30px; }
            #volSlider { display: none; }
.volume-container { margin-right: 0 !important; }
        }
        
        /* Modal wyboru ścieżek */
        .track-modal { display: none; position: absolute; bottom: 80px; right: 15px; background: rgba(20,20,20,0.95); border: 1px solid #444; border-radius: 8px; padding: 15px; z-index: 100; min-width: 200px; box-shadow: 0 5px 15px rgba(0,0,0,0.8); backdrop-filter: blur(4px); }
        .track-modal label { color: #ccc; font-size: 12px; display: block; margin-bottom: 5px; }
        .track-modal select { width: 100%; background: #333; color: #fff; border: 1px solid #555; border-radius: 4px; padding: 5px; margin-bottom: 15px; outline: none; }
        .track-modal button { width: 100%; background: #6f42c1; border: none; color: #fff; padding: 8px; border-radius: 4px; cursor: pointer; font-weight: bold; }
        .track-modal button:hover { background: #5a32a3; }
    </style>
</head>
<body>
    <div class="player-wrapper" id="player-wrapper">
        <div class="loader" id="loader" style="display: block;"></div>
        
        <div id="seekIndL" class="seek-indicator" style="left:10%;">⏪ -10s</div>
        <div id="seekIndR" class="seek-indicator" style="right:10%;">+10s ⏩</div>

        <video id="vid" autoplay playsinline webkit-playsinline x5-playsinline="true" x5-video-player-type="h5-page" preload="metadata"></video>
        
        <div id="video-overlay"></div>

        <div class="top-bar">
            <div class="title-container">
                <span class="title" title="<?=htmlspecialchars($fileName)?>"><?=htmlspecialchars($fileName)?></span>
                <span style="font-size:10px; color:#aaa; margin-top:3px;"><?=$isNative ? '(Natywny MP4)' : '(Transkodowanie Live)'?></span>
            </div>
            <button onclick="closePlayer()" class="back-btn">&#10006; Zamknij</button>
        </div>
        
        <div class="controls">
            <button class="btn-ctrl" id="playBtn">&#9658;</button>
            <div class="time-text" id="timeDisplay">00:00:00</div>
            
            <div class="progress-container" id="progress">
                <div class="progress-track">
                    <div class="progress-bar" id="progressBar"></div>
                </div>
                <div id="scrubBubble">
                    <div class="sprite-view" id="spriteView"></div>
                    <span id="scrubTime">00:00:00</span>
                </div>
            </div>
            
            <div class="time-text" id="durationDisplay"><?=gmdate("H:i:s", (int)$duration)?></div>
            
            <?php if (!$isPublicShare): ?>
            <button class="btn-ctrl btn-link" onclick="copyLink24()" title="Skopiuj ukryty link 24h">
                &#128279; Link
            </button>
            <?php endif; ?>

<div class="volume-container" style="display:flex; align-items:center; gap:5px; margin-right:10px;">
                <button class="btn-ctrl" id="muteBtn" style="font-size:16px;">&#128266;</button>
                <input type="range" id="volSlider" min="0" max="1" step="0.05" value="1" style="width: 70px; cursor: pointer;">
            </div>

<div class="track-modal" id="trackModal">
                <label>🔊 Ścieżka Audio:</label>
                <select id="audioSelect">
                    <?php foreach($audioTracks as $at): ?>
                        <option value="<?=$at['id']?>" <?=($aid == $at['id']) ? 'selected' : ''?>><?=htmlspecialchars($at['name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')?></option>
                    <?php endforeach; ?>
                </select>

                <button onclick="applyTracks()">Zastosuj</button>
            </div>
            <button class="btn-ctrl" id="tracksBtn" title="Ścieżka audio">&#9881;</button>

            <button class="btn-ctrl" id="fsBtn">&#9974;</button>
        </div>
    </div>

    <script>
        // NAPRAWA BŁĘDU ODŚWIEŻANIA (Wymusza pobranie z serwera, gdy użyto "wstecz")
        window.addEventListener("pageshow", function(event) {
            if (event.persisted) { window.location.reload(); }
        });

        // Funkcja zamknięcia odporna na blokady MI Browsera
        function closePlayer() {
            window.close();
            setTimeout(() => { window.history.back(); }, 200);
        }

        const csrfToken = <?=json_encode($csrfToken)?>;
        const isPublicShare = <?=$isPublicShare ? 'true' : 'false'?>;
        const playerFolder = <?=json_encode($folderKey, JSON_UNESCAPED_UNICODE)?>;
        const playerPath = <?=json_encode($subPath, JSON_UNESCAPED_UNICODE)?>;
        const playerFile = <?=json_encode($fileName, JSON_UNESCAPED_UNICODE)?>;

        function copyLink24() {
            let fd = new FormData();
            fd.append('action', 'share');
            fd.append('folder', playerFolder);
            fd.append('path', playerPath);
            fd.append('file', playerFile);
            fd.append('csrf_token', csrfToken);

            fetch('admin_ajax.php', { method: 'POST', body: fd })
                .then(r => r.json())
                .then(data => {
                    if (!data || data.status !== 'ok' || !data.link) throw new Error(data && data.msg ? data.msg : 'Nie udało się utworzyć linku.');
                    const url = data.link;
                    if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(url).then(() => alert('Skopiowano!'));
                    else { let dummy = document.createElement('input'); document.body.appendChild(dummy); dummy.value = url; dummy.select(); document.execCommand('copy'); document.body.removeChild(dummy); alert('Skopiowano!'); }
                })
                .catch(e => alert(e.message || 'Nie udało się utworzyć linku.'));
        }

        const vid = document.getElementById('vid');
        const playBtn = document.getElementById('playBtn');
        const progress = document.getElementById('progress');
        const progressBar = document.getElementById('progressBar');
        const scrubBubble = document.getElementById('scrubBubble');
        const spriteView = document.getElementById('spriteView');
        const scrubTime = document.getElementById('scrubTime');
        const timeDisplay = document.getElementById('timeDisplay');
        const fsBtn = document.getElementById('fsBtn');
        const loader = document.getElementById('loader');
        const playerWrapper = document.getElementById('player-wrapper');
        const videoOverlay = document.getElementById('video-overlay');
const muteBtn = document.getElementById('muteBtn');
        const volSlider = document.getElementById('volSlider');
        const seekIndL = document.getElementById('seekIndL');
        const seekIndR = document.getElementById('seekIndR');
        const tracksBtn = document.getElementById('tracksBtn');
        const trackModal = document.getElementById('trackModal');

        if (tracksBtn) {
            tracksBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                trackModal.style.display = trackModal.style.display === 'block' ? 'none' : 'block';
            });
        }
        
function applyTracks() {
            let aid = document.getElementById('audioSelect').value;
            let url = new URL(window.location.href);
            url.searchParams.set('aid', aid);
            
            saveProgress();
            // Wymuszenie twardego przeładowania w MI Browser (bez blokowanego setTimeout)
            window.location.assign(url.toString());
        }

        let isNative = <?=$isNative ? 'true' : 'false'?>;
        let totalDuration = <?=$duration?>;
        let currentOffset = 0;
        let isSeeking = false;
        let isDragging = false;
        let dragTime = 0;
        
        let lastTapTime = 0;
        let clickTimer = null;
        let ignoreNextClick = false;
        let touchControlsArmed = false;
        let autoplayBlocked = false;
        let seekAccumulator = 0;

        // --- PRZYWRÓCONE AGRESYWNE ŁADOWANIE MINIATUREK ---
        // Wersja podglądu v9: plansza klatek generowana raz w tle i natychmiastowe przewijanie po jej przygotowaniu.
        let spriteUrlBase = <?=json_encode($spriteUrl, JSON_UNESCAPED_SLASHES)?>;
        let spriteSheetUrlBase = <?=json_encode($spriteSheetUrl, JSON_UNESCAPED_SLASHES)?>;
        let spriteReady = false;
        let spriteTimer = null;
        let spriteLoading = 0;
        let spriteTargetTime = null;
        let spriteCurrentTime = null;
        let spriteCache = new Map();
        let spritePending = new Set();
        let spriteLastTargetTime = null;
        let spriteDirection = 0;
        let spriteMaxLoading = 2;
        let spriteSheetReady = false;
        let spriteSheetObjectUrl = null;
        let spriteSheetPollTimer = null;
        let spriteSheetAttempts = 0;
        let spriteSheetStarted = false;
        let spriteSheetChecked = false;
        let spriteSheetStartTimer = null;
        let spriteSheetFailed = false;
        let spriteSheetInterval = Math.max(2, Math.ceil(totalDuration / 200));
        let spriteSheetFrameCount = Math.min(200, Math.max(1, Math.ceil(totalDuration / spriteSheetInterval)));
        let spriteSheetColumns = 10;

        function showSpriteSheet(time) {
            if (!spriteSheetReady || !spriteSheetObjectUrl) return;

            let frameIdx = Math.floor(Math.max(0, time) / spriteSheetInterval);
            frameIdx = Math.max(0, Math.min(spriteSheetFrameCount - 1, frameIdx));
            let col = frameIdx % spriteSheetColumns;
            let row = Math.floor(frameIdx / spriteSheetColumns);

            spriteView.style.backgroundImage = "url('" + spriteSheetObjectUrl + "')";
            spriteView.style.backgroundPosition = `-${col * 160}px -${row * 90}px`;
            spriteView.style.display = 'block';
            spriteReady = true;
            spriteCurrentTime = frameIdx * spriteSheetInterval;
        }

        function checkSpriteSheet(startGeneration = false) {
            if (spriteSheetReady || spriteSheetFailed || spriteSheetAttempts >= 200) return;

            spriteSheetAttempts++;
            let sheetRequestUrl = spriteSheetUrlBase + (startGeneration ? '&generate=1' : '') + '&t=' + Date.now();
            fetch(sheetRequestUrl, { cache: 'no-store' })
                .then(response => {
                    if (response.status === 409 || response.headers.get('X-Sprite-Status') === 'failed') {
                        spriteSheetFailed = true;
                        clearTimeout(spriteSheetPollTimer);
                        throw new Error('Generowanie planszy miniaturek zostało wstrzymane po błędzie FFmpeg.');
                    }
                    if (response.status !== 200) throw new Error('Plansza jeszcze nie jest gotowa.');
                    return response.blob();
                })
                .then(blob => {
                    if (!blob || blob.size === 0) throw new Error('Pusta plansza miniaturek.');

                    let objectUrl = URL.createObjectURL(blob);
                    let img = new Image();
                    img.onload = () => {
                        if (spriteSheetObjectUrl) URL.revokeObjectURL(spriteSheetObjectUrl);
                        spriteSheetObjectUrl = objectUrl;
                        spriteSheetReady = true;
                        clearTimeout(spriteSheetPollTimer);

                        if (spriteTargetTime !== null) showSpriteSheet(spriteTargetTime);
                    };
                    img.onerror = () => {
                        URL.revokeObjectURL(objectUrl);
                        if (!spriteSheetFailed && (startGeneration || spriteSheetStarted)) spriteSheetPollTimer = setTimeout(() => checkSpriteSheet(true), 3000);
                    };
                    img.src = objectUrl;
                })
                .catch(() => {
                    if (!spriteSheetFailed && (startGeneration || spriteSheetStarted)) spriteSheetPollTimer = setTimeout(() => checkSpriteSheet(true), 3000);
                });
        }

        function showSprite(spriteObjectUrl, spriteTime) {
            spriteView.style.backgroundImage = "url('" + spriteObjectUrl + "')";
            spriteView.style.backgroundPosition = '0 0';
            spriteView.style.display = 'block';
            spriteReady = true;
            spriteCurrentTime = spriteTime;
        }

        function checkSprite(time) {
            if (isNaN(time) || time < 0) return;

            if (spriteSheetReady) {
                spriteTargetTime = time;
                showSpriteSheet(time);
                return;
            }

            let maxPreviewTime = totalDuration > 1 ? totalDuration - 1 : totalDuration;
            let spriteTime = Math.floor(Math.max(0, Math.min(time, maxPreviewTime)) / 5) * 5;

            if (spriteLastTargetTime !== null && spriteTime !== spriteLastTargetTime) {
                spriteDirection = spriteTime > spriteLastTargetTime ? 1 : -1;
            }
            spriteLastTargetTime = spriteTime;
            spriteTargetTime = spriteTime;

            if (spriteCache.has(spriteTime)) {
                showSprite(spriteCache.get(spriteTime), spriteTime);
                return;
            }

            let nearestTime = null;
            spriteCache.forEach((value, cachedTime) => {
                if (nearestTime === null || Math.abs(cachedTime - spriteTime) < Math.abs(nearestTime - spriteTime)) {
                    nearestTime = cachedTime;
                }
            });
            if (nearestTime !== null && (!spriteReady || Math.abs(nearestTime - spriteTime) < Math.abs(spriteCurrentTime - spriteTime))) {
                showSprite(spriteCache.get(nearestTime), nearestTime);
            }

            clearTimeout(spriteTimer);
            spriteTimer = setTimeout(() => loadSprite(spriteTime), 60);
        }

        function loadSprite(requestedTime = spriteTargetTime, isPrefetch = false) {
            if (spriteSheetReady) return;
            if (requestedTime === null || spriteCache.has(requestedTime) || spritePending.has(requestedTime)) return;
            if (spriteLoading >= spriteMaxLoading) return;

            let maxPreviewTime = totalDuration > 1 ? totalDuration - 1 : totalDuration;
            requestedTime = Math.max(0, Math.min(requestedTime, Math.floor(maxPreviewTime / 5) * 5));
            if (spriteCache.has(requestedTime) || spritePending.has(requestedTime)) return;

            let spriteRequestUrl = spriteUrlBase + '&time=' + encodeURIComponent(requestedTime) + '&t=' + Date.now();
            spriteLoading++;
            spritePending.add(requestedTime);

            let img = new Image();
            img.onload = () => {
                spriteCache.set(requestedTime, spriteRequestUrl);
                spritePending.delete(requestedTime);
                spriteLoading--;

                if (!spriteSheetReady && !isPrefetch && (!spriteReady || spriteTargetTime === requestedTime || Math.abs(spriteTargetTime - requestedTime) <= Math.abs(spriteTargetTime - spriteCurrentTime))) {
                    showSprite(spriteRequestUrl, requestedTime);
                }

                if (spriteTargetTime !== null && !spriteCache.has(spriteTargetTime) && !spritePending.has(spriteTargetTime)) {
                    loadSprite(spriteTargetTime);
                }

                if (!isPrefetch && spriteDirection !== 0 && spriteLoading < spriteMaxLoading) {
                    let prefetchTime = requestedTime + (spriteDirection * 5);
                    if (prefetchTime >= 0 && prefetchTime <= maxPreviewTime) {
                        loadSprite(prefetchTime, true);
                    }
                }
            };
            img.onerror = () => {
                spritePending.delete(requestedTime);
                spriteLoading--;

                if (spriteTargetTime !== null && !spriteCache.has(spriteTargetTime) && !spritePending.has(spriteTargetTime)) {
                    loadSprite(spriteTargetTime);
                }
            };
            img.src = spriteRequestUrl;
        }

        function scheduleSpriteSheet() {
            if (!spriteSheetChecked) {
                spriteSheetChecked = true;
                checkSpriteSheet(false);
            }

            if (spriteSheetReady || spriteSheetStarted || spriteSheetStartTimer !== null) return;

            spriteSheetStartTimer = setTimeout(() => {
                spriteSheetStartTimer = null;
                if (vid.paused || vid.readyState < 3) return;

                spriteSheetStarted = true;
                checkSpriteSheet(true);
            }, 15000);
        }

        function formatTime(seconds) {
            if (isNaN(seconds) || seconds < 0) return "00:00:00";
            let h = Math.floor(seconds / 3600); let m = Math.floor((seconds % 3600) / 60); let s = Math.floor(seconds % 60);
            return (h > 0 ? (h < 10 ? "0"+h : h) + ":" : "00:") + (m < 10 ? "0"+m : m) + ":" + (s < 10 ? "0"+s : s);
        }

        window.addEventListener('DOMContentLoaded', () => {
            if (isPublicShare) {
                currentOffset = 0;
                vid.src = '<?=$streamUrl?>' + (isNative ? '' : '&start=0');
                let playPromise = vid.play();
                if (playPromise !== undefined) {
                    playPromise.then(() => { loader.style.display = 'none'; }).catch((error) => {
                        loader.style.display = 'none';
                        if (error && error.name === 'NotAllowedError') autoplayBlocked = true;
                    });
                }
                return;
            }

            let fd = new FormData();
            fd.append('action', 'get_progress');
            fd.append('folder', playerFolder);
            fd.append('path', playerPath);
            fd.append('file', playerFile);
            fd.append('csrf_token', csrfToken);

            fetch('admin_ajax.php', { method: 'POST', body: fd })
                .then(r => r.json())
                .then(data => {
                    let startT = (data && data.status === 'ok' && data.time > 15) ? data.time : 0;
                    currentOffset = isNative ? 0 : startT;
                    vid.src = isNative ? '<?=$streamUrl?>' : ('<?=$streamUrl?>&start=' + startT);
                    
                    if (isNative && startT > 0) { vid.currentTime = startT; }

                    let playPromise = vid.play();
                    if (playPromise !== undefined) {
                        playPromise.then(() => { loader.style.display = 'none'; }).catch((error) => {
                            loader.style.display = 'none';
                            if (error && error.name === 'NotAllowedError') autoplayBlocked = true;
                        });
                    }
                }).catch(() => {
                    vid.src = '<?=$streamUrl?>' + (isNative ? '' : '&start=0');
                    vid.play().catch((error) => {
                        loader.style.display = 'none';
                        if (error && error.name === 'NotAllowedError') autoplayBlocked = true;
                    });
                });
        });

        let videoEnded = false;
        function saveProgress(forcedTime = null) {
            if (isPublicShare) return;
            if (videoEnded && forcedTime === null) return;
            if (forcedTime === null && vid.paused && vid.currentTime === 0) return;
            let realTime = forcedTime === null ? (isNative ? vid.currentTime : currentOffset + vid.currentTime) : forcedTime;
            let fd = new FormData();
            fd.append('action', 'save_progress');
            fd.append('folder', playerFolder);
            fd.append('path', playerPath);
            fd.append('file', playerFile);
            fd.append('time', realTime);
            fd.append('csrf_token', csrfToken);
            fetch('admin_ajax.php', { method: 'POST', body: fd }).catch(e=>console.log(e));
        }
        setInterval(saveProgress, 10000); 

        function getX(e) { return (e.touches && e.touches.length > 0) ? e.touches[0].clientX : e.clientX; }

        function startScrub(e) {
            if (totalDuration <= 0) return;
            isDragging = true;
            resetIdleTimer();
            updateScrub(getX(e));
        }

        function moveScrub(e) {
            if (!isDragging) return;
            e.preventDefault(); 
            resetIdleTimer();
            updateScrub(getX(e));
        }

        function endScrub(e) {
            if (!isDragging) return;
            isDragging = false;
            scrubBubble.style.display = 'none';
            triggerSeek(dragTime);
            resetIdleTimer();
        }

        progress.addEventListener('mousedown', startScrub);
        document.addEventListener('mousemove', moveScrub);
        document.addEventListener('mouseup', endScrub);
        progress.addEventListener('touchstart', startScrub, {passive: false});
        document.addEventListener('touchmove', moveScrub, {passive: false});
        document.addEventListener('touchend', endScrub);
        document.addEventListener('touchcancel', endScrub);

        function updateScrub(x) {
            let rect = progress.getBoundingClientRect();
            let perc = (x - rect.left) / rect.width;
            perc = Math.max(0, Math.min(1, perc));
            dragTime = perc * totalDuration;

            progressBar.style.width = (perc * 100) + '%';
            scrubBubble.style.display = 'block';
            scrubBubble.style.left = (perc * 100) + '%';
            scrubTime.innerText = formatTime(dragTime);

            checkSprite(dragTime);
        }

        videoOverlay.addEventListener('touchend', (e) => {
            // Na ekranach dotykowych obsługujemy dotknięcie tutaj i blokujemy późniejszy syntetyczny click.
            e.preventDefault();
            e.stopPropagation();
            ignoreNextClick = true;
            setTimeout(() => { ignoreNextClick = false; }, 700);

            let touch = e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches[0] : null;
            if (!touch) return;

            let now = Date.now();

            // Firefox blokuje automatyczne odtwarzanie strumienia z dźwiękiem.
            // Pierwsze dotknięcie ponawia play() bezpośrednio w geście użytkownika.
            if (autoplayBlocked && vid.paused) {
                clearTimeout(clickTimer);
                let playPromise = vid.play();
                if (playPromise !== undefined) {
                    playPromise.then(() => { autoplayBlocked = false; loader.style.display = 'none'; }).catch(() => { autoplayBlocked = true; loader.style.display = 'none'; });
                }
                touchControlsArmed = true;
                lastTapTime = 0;
                resetIdleTimer();
                return;
            }

            // Pierwsze dotknięcie tylko pokazuje i uzbraja kontrolki. Nie zatrzymuje filmu.
            if (!touchControlsArmed || playerWrapper.classList.contains('idle')) {
                clearTimeout(clickTimer);
                touchControlsArmed = true;
                lastTapTime = now;
                resetIdleTimer();
                return;
            }

            if (now - lastTapTime < 400) {
                clearTimeout(clickTimer);

                let rect = videoOverlay.getBoundingClientRect();
                let isLeft = (touch.clientX - rect.left) < (rect.width / 2);

                seekAccumulator += isLeft ? -10 : 10;

                if (isLeft) {
                    seekIndL.innerText = seekAccumulator + "s";
                    seekIndL.style.opacity = '1'; setTimeout(() => seekIndL.style.opacity = '0', 500);
                } else {
                    seekIndR.innerText = "+" + seekAccumulator + "s";
                    seekIndR.style.opacity = '1'; setTimeout(() => seekIndR.style.opacity = '0', 500);
                }

                clearTimeout(window.finalSeekTimer);
                window.finalSeekTimer = setTimeout(() => {
                    let targetTime = (isNative ? vid.currentTime : currentOffset + vid.currentTime) + seekAccumulator;
                    triggerSeek(targetTime);
                    seekAccumulator = 0;
                }, 500);
            } else {
                clickTimer = setTimeout(() => {
                    if (seekAccumulator === 0) {
                        if (vid.paused) vid.play(); else vid.pause();
                    }
                }, 400);
            }
            lastTapTime = now;
        }, {passive: false});

        videoOverlay.addEventListener('click', (e) => {
            // Dotknięcie telefonu generuje czasem dodatkowy click - nie może on ponownie przełączyć odtwarzania.
            if (ignoreNextClick) return;

            // Firefox blokuje automatyczne odtwarzanie strumienia z dźwiękiem.
            // Pierwsze kliknięcie ponawia play() natychmiast, zanim wygaśnie aktywacja użytkownika.
            if (autoplayBlocked && vid.paused) {
                clearTimeout(clickTimer);
                let playPromise = vid.play();
                if (playPromise !== undefined) {
                    playPromise.then(() => { autoplayBlocked = false; loader.style.display = 'none'; }).catch(() => { autoplayBlocked = true; loader.style.display = 'none'; });
                }
                lastTapTime = 0;
                resetIdleTimer();
                return;
            }

            // Gdy kontrolki są ukryte, pierwsze kliknięcie tylko je pokazuje.
            if (playerWrapper.classList.contains('idle')) {
                clearTimeout(clickTimer);
                lastTapTime = 0;
                resetIdleTimer();
                return;
            }

            let now = Date.now();
            if (now - lastTapTime < 400) {
                clearTimeout(clickTimer); 
                
                let rect = videoOverlay.getBoundingClientRect();
                let isLeft = (e.clientX - rect.left) < (rect.width / 2);
                
                seekAccumulator += isLeft ? -10 : 10;
                
                if (isLeft) {
                    seekIndL.innerText = seekAccumulator + "s";
                    seekIndL.style.opacity = '1'; setTimeout(() => seekIndL.style.opacity = '0', 500);
                } else {
                    seekIndR.innerText = "+" + seekAccumulator + "s";
                    seekIndR.style.opacity = '1'; setTimeout(() => seekIndR.style.opacity = '0', 500);
                }

                clearTimeout(window.finalSeekTimer);
                window.finalSeekTimer = setTimeout(() => {
                    let targetTime = (isNative ? vid.currentTime : currentOffset + vid.currentTime) + seekAccumulator;
                    triggerSeek(targetTime);
                    seekAccumulator = 0;
                }, 500);
                
            } else {
                clickTimer = setTimeout(() => {
                    if (seekAccumulator === 0) {
                        if (vid.paused) vid.play(); else vid.pause();
                    }
                }, 400);
            }
            lastTapTime = now;
        });

        function triggerSeek(newTime) {
            if (newTime < 0) newTime = 0;
            if (newTime > totalDuration) newTime = totalDuration;

            isSeeking = true; loader.style.display = 'block';
            currentOffset = isNative ? 0 : newTime;
            
            progressBar.style.width = ((newTime / totalDuration) * 100) + '%';
            timeDisplay.innerText = formatTime(newTime);
            
            if (isNative) {
                vid.currentTime = newTime;
                let playPromise = vid.play(); if (playPromise !== undefined) playPromise.catch(e => console.log(e));
                isSeeking = false;
            } else {
                vid.src = '<?=$streamUrl?>&start=' + newTime;
                let playPromise = vid.play(); if (playPromise !== undefined) playPromise.catch(e => console.log(e));
                isSeeking = false;
            }
        }

        function updateMuteIcon(vol) {
            if(vol == 0 || vid.muted) muteBtn.innerHTML = '&#128263;'; else if(vol < 0.5) muteBtn.innerHTML = '&#128265;'; else muteBtn.innerHTML = '&#128266;';
        }
        let savedVol = localStorage.getItem('vod_volume');
        if(savedVol !== null) { vid.volume = savedVol; volSlider.value = savedVol; updateMuteIcon(savedVol); }
        volSlider.addEventListener('input', (e) => { vid.volume = e.target.value; vid.muted = false; localStorage.setItem('vod_volume', vid.volume); updateMuteIcon(vid.volume); });
        muteBtn.addEventListener('click', () => { vid.muted = !vid.muted; if(vid.muted) { updateMuteIcon(0); volSlider.value = 0; } else { updateMuteIcon(vid.volume); volSlider.value = vid.volume; } });

        vid.addEventListener('timeupdate', () => {
            if ((totalDuration <= 0 || isNaN(totalDuration)) && vid.duration > 0 && vid.duration !== Infinity) {
                totalDuration = vid.duration;
                document.getElementById('durationDisplay').innerText = formatTime(totalDuration);
            }
            if (isSeeking || isDragging) return;
            let realTime = isNative ? vid.currentTime : currentOffset + vid.currentTime;
            if (realTime > totalDuration) realTime = totalDuration;
            
            if (totalDuration > 0) progressBar.style.width = ((realTime / totalDuration) * 100) + '%';
            timeDisplay.innerText = formatTime(realTime);
        });

        vid.addEventListener('loadedmetadata', () => {
            if (isNative) {
                if (totalDuration <= 0 || isNaN(totalDuration)) totalDuration = vid.duration;
                if (<?=$initialStart?> > 0 && vid.currentTime < 1) vid.currentTime = <?=$initialStart?>;
            }
        });

        vid.addEventListener('waiting', () => { loader.style.display = 'block'; });
        vid.addEventListener('playing', () => { loader.style.display = 'none'; scheduleSpriteSheet(); });
        playBtn.addEventListener('click', () => {
            if (vid.paused) {
                let playPromise = vid.play();
                if (playPromise !== undefined) playPromise.then(() => { autoplayBlocked = false; }).catch((error) => { if (error && error.name === 'NotAllowedError') autoplayBlocked = true; });
            } else vid.pause();
        });

        let idleTimeout;
        function resetIdleTimer() {
            playerWrapper.classList.remove('idle'); document.body.classList.remove('idle');
            clearTimeout(idleTimeout);
            if (!vid.paused && !isDragging) { idleTimeout = setTimeout(() => { touchControlsArmed = false; playerWrapper.classList.add('idle'); document.body.classList.add('idle'); }, 2500); }
        }
        document.addEventListener('mousemove', resetIdleTimer);
        document.addEventListener('touchstart', (e) => {
            if (e.target === videoOverlay && playerWrapper.classList.contains('idle')) return;
            resetIdleTimer();
        }, {passive: true});

        // Strzałki na klawiaturze przewijają film o 10 sekund.
        document.addEventListener('keydown', (e) => {
            let activeTag = document.activeElement ? document.activeElement.tagName : '';
            if (activeTag === 'INPUT' || activeTag === 'SELECT' || activeTag === 'TEXTAREA' || (document.activeElement && document.activeElement.isContentEditable)) return;
            if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
            if (e.repeat) return;

            e.preventDefault();
            let direction = e.key === 'ArrowLeft' ? -10 : 10;
            let targetTime = (isNative ? vid.currentTime : currentOffset + vid.currentTime) + direction;

            if (direction < 0) {
                seekIndL.innerText = '-10s';
                seekIndL.style.opacity = '1'; setTimeout(() => seekIndL.style.opacity = '0', 500);
            } else {
                seekIndR.innerText = '+10s';
                seekIndR.style.opacity = '1'; setTimeout(() => seekIndR.style.opacity = '0', 500);
            }

            triggerSeek(targetTime);
            resetIdleTimer();
        });
        
        vid.addEventListener('play', () => { autoplayBlocked = false; if (vid.currentTime < totalDuration - 1) videoEnded = false; playBtn.innerHTML = '&#10074;&#10074;'; resetIdleTimer(); });
        vid.addEventListener('pause', () => { playBtn.innerHTML = '&#9658;'; clearTimeout(idleTimeout); playerWrapper.classList.remove('idle'); document.body.classList.remove('idle'); });

        fsBtn.addEventListener('click', () => {
            let wrap = document.getElementById('player-wrapper');
            if (!document.fullscreenElement) { if (wrap.requestFullscreen) wrap.requestFullscreen(); } 
            else { if (document.exitFullscreen) document.exitFullscreen(); }
        });

        const nextUrl = "<?=str_replace('&amp;', '&', $nextUrl)?>";
        const nextFile = '<?=htmlspecialchars($nextFile, ENT_QUOTES)?>';

        vid.addEventListener('ended', () => {
            videoEnded = true;
            saveProgress(0);
            if (nextUrl) {
                if (document.fullscreenElement) { document.exitFullscreen().catch(err => console.log(err)); }
                let overlay = document.createElement('div');
                overlay.id = 'nextOverlay';
                overlay.style.cssText = 'position:absolute; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.95); display:flex; flex-direction:column; align-items:center; justify-content:center; z-index:9999; text-align:center; padding:20px;';
                overlay.innerHTML = `
                    <h2 style="color:#fff; margin-bottom:5px; font-size:24px;">Następny materiał:</h2>
                    <h3 style="color:#007bff; margin-top:0; font-size:18px; word-break:break-all;">${nextFile}</h3>
                    <p style="color:#bbb; font-size:16px;">Odtwarzanie za <span id="nextCd" style="font-weight:bold; color:#fff;">5</span>s...</p>
                    <div style="display:flex; gap:15px; margin-top:25px; flex-wrap:wrap; justify-content:center;">
                        <button onclick="window.location.assign('${nextUrl}')" style="background:#28a745; color:#fff; border:none; padding:12px 24px; font-size:16px; border-radius:6px; cursor:pointer; font-weight:bold; box-shadow:0 4px 10px rgba(0,0,0,0.5);">Odtwórz teraz</button>
                        <button onclick="cancelNext()" style="background:#444; color:#fff; border:none; padding:12px 24px; font-size:16px; border-radius:6px; cursor:pointer; font-weight:bold; box-shadow:0 4px 10px rgba(0,0,0,0.5);">Anuluj</button>
                    </div>
                `;
                document.getElementById('player-wrapper').appendChild(overlay);
                let countdown = 5;
                window.nextInterval = setInterval(() => {
                    countdown--; let cdSpan = document.getElementById('nextCd'); if(cdSpan) cdSpan.innerText = countdown;
                    if(countdown <= 0) { clearInterval(window.nextInterval); window.location.assign(nextUrl); }
                }, 1000);
            }
        });
        window.cancelNext = function() { if(window.nextInterval) clearInterval(window.nextInterval); let overlay = document.getElementById('nextOverlay'); if(overlay) overlay.remove(); }
    </script>
</body>
</html>
