function decodeJwt(token) { return JSON.parse(atob(token.split('.')[1])); } function getQueryParam(name) { const url = new URL(window.location.href); return url.searchParams.get(name); } function showAlert(message) { const el = document.getElementById('alert'); if (!el) return; el.textContent = message; // Ensure error styling (danger) when using the simple API. el.classList.remove('alert-primary', 'alert-secondary', 'alert-success', 'alert-danger', 'alert-warning', 'alert-info', 'alert-light', 'alert-dark'); el.classList.add('alert-danger'); el.classList.remove('d-none'); } function showAlertVariant(message, variant) { const el = document.getElementById('alert'); if (!el) return; el.textContent = message; el.classList.remove('d-none'); // Reset any prior bootstrap alert-* variants el.classList.remove('alert-primary', 'alert-secondary', 'alert-success', 'alert-danger', 'alert-warning', 'alert-info', 'alert-light', 'alert-dark'); const v = String(variant || 'danger').trim().toLowerCase(); el.classList.add(`alert-${v || 'danger'}`); } function setSourceLabel(text) { const el = document.getElementById('source-label'); if (!el) return; el.textContent = text || '—'; el.title = text || ''; } function setHeader(movie) { const titleEl = document.getElementById('movie-title'); const categoryEl = document.getElementById('movie-category'); const ratingEl = document.getElementById('movie-rating'); if (titleEl) titleEl.textContent = movie?.title || 'Player'; if (categoryEl) { const cat = movie?.category ? String(movie.category) : ''; categoryEl.textContent = cat ? `${cat} · ` : ''; } if (ratingEl) { const r = movie?.rating ? String(movie.rating) : ''; ratingEl.textContent = r ? `⭐ ${r}` : ''; } document.title = movie?.title ? `Player - ${movie.title}` : 'Player'; } function normalizeVodLink(raw) { const v = String(raw || '').trim(); if (!v) return null; // Support pasted iframe embed code (extract src) if (v.toLowerCase().includes(' { // If the browser can't play it (or CORS blocks), fall back to iframe. setSourceLabel('Embed (fallback)'); showEmbedPlayer(raw); }, { once: true }); try { videoEl.load(); } catch (_) {} return; } // HLS (.m3u8) if (looksLikeHls(raw)) { setSourceLabel('HLS stream (.m3u8)'); const videoEl = showVideoPlayer(); if (!videoEl) return; // Native HLS (Safari) first if (videoEl.canPlayType('application/vnd.apple.mpegurl')) { videoEl.src = raw; try { videoEl.load(); } catch (_) {} return; } // hls.js fallback (if supported) if (window.Hls && window.Hls.isSupported()) { const hls = new window.Hls({ enableWorker: true, }); hls.loadSource(raw); hls.attachMedia(videoEl); hls.on(window.Hls.Events.ERROR, function (_evt, data) { if (data?.fatal) { try { hls.destroy(); } catch (_) {} setSourceLabel('Embed (fallback)'); showEmbedPlayer(raw); } }); return; } // Last resort setSourceLabel('Embed (fallback)'); showEmbedPlayer(raw); return; } // Default: treat as embeddable URL setSourceLabel('Embed'); showEmbedPlayer(raw); } function cleanUrlValue(v) { const s = typeof v === 'string' ? v.trim() : ''; return s ? s : null; } function resolveMovieSource(movie) { const mode = String(movie?.player_mode || 'auto').toLowerCase(); const legacy = cleanUrlValue(movie?.vod_link); const video = cleanUrlValue(movie?.video_src); const embed = cleanUrlValue(movie?.embed_src); // Legacy should never embed in the player (open in new tab only). // So we only pick legacy if there is no other option, and mark it. let picked = null; if (mode === 'video') picked = video || null; else if (mode === 'embed') picked = embed || null; else picked = video || embed || null; // auto if (picked) return { src: picked, isLegacy: false }; if (legacy) return { src: legacy, isLegacy: true }; return { src: null, isLegacy: false }; } window.onload = async function () { // Basic auth UX: require a valid local token (like the rest of the app). const token = localStorage.getItem('auth_token'); if (!token) { window.location.href = '/'; return; } try { const decoded = decodeJwt(token); const currentTime = Math.floor(Date.now() / 1000); if (decoded.exp && decoded.exp < currentTime) { localStorage.removeItem('auth_token'); window.location.href = '/'; return; } if (decoded.mustChangePassword) { window.location.href = '/change-password.html'; return; } const nameEl = document.getElementById('user-name'); if (nameEl) nameEl.textContent = decoded.name || ''; if (decoded.admin) { const adminLink = document.getElementById('admin-control-link'); if (adminLink) adminLink.classList.remove('d-none'); } const logoffBtn = document.getElementById('log-off'); if (logoffBtn) { logoffBtn.addEventListener('click', function () { localStorage.removeItem('auth_token'); window.location.href = '/'; }); } } catch (_) { localStorage.removeItem('auth_token'); window.location.href = '/'; return; } const id = getQueryParam('id'); if (!id) { showAlert('Missing movie id.'); return; } try { const res = await fetch(`/api/movies/${encodeURIComponent(id)}`, { method: 'GET', headers: token ? { 'Authorization': `Bearer ${token}` } : undefined, }); const movie = await res.json().catch(() => ({})); if (!res.ok) { showAlert(movie?.message || movie?.error || `Failed to load movie (${res.status}).`); return; } setHeader(movie); const resolved = resolveMovieSource(movie); if (!resolved?.src) { setOpenOriginal(null); setSourceLabel('—'); hideAllPlayers(); showAlert('No playable source configured for this movie.'); return; } if (resolved.isLegacy) { // Legacy source: never embed it in the player. setOpenOriginal(resolved.src); setSourceLabel('Legacy link (opens in new tab)'); hideAllPlayers(); showAlertVariant('Legacy source: use “Open original” to watch in a new tab.', 'warning'); return; } await playVodLink(resolved.src); } catch (err) { showAlert(err.message || 'Network error.'); } };