/** * KiraTV — helpers para escritorio (Electron o shell WebView2 + panel Win32). */ (function () { function showToast(msg, type, durationMs) { let container = document.getElementById('kiratv-desktop-toast'); if (!container) { container = document.createElement('div'); container.id = 'kiratv-desktop-toast'; Object.assign(container.style, { position: 'fixed', bottom: '24px', left: '50%', transform: 'translateX(-50%)', zIndex: '99999', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px', pointerEvents: 'none' }); document.body.appendChild(container); } const toast = document.createElement('div'); Object.assign(toast.style, { background: type === 'error' ? '#c0392b' : '#1a7f4a', color: '#fff', padding: '10px 20px', borderRadius: '8px', fontSize: '14px', fontFamily: 'sans-serif', boxShadow: '0 4px 16px rgba(0,0,0,.5)', opacity: '1', transition: 'opacity 0.4s' }); toast.textContent = msg; container.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 450); }, durationMs || 3500); } function unwrapDesktopWrapperUrl(urlLike) { try { const u = new URL(String(urlLike), window.location.origin); const wrappedPath = u.pathname; if ( wrappedPath === '/api/transcode' || wrappedPath === '/api/remux' || wrappedPath === '/api/proxy/stream' ) { const original = u.searchParams.get('url'); if (original) return original; } } catch (_) { /* */ } return urlLike; } function absolutePlaybackUrl(streamUrl) { if (!streamUrl) return ''; const raw = String(streamUrl).trim(); const s = String(unwrapDesktopWrapperUrl(raw)).trim(); if (/^https?:\/\//i.test(s)) return s; return window.location.origin + (s.startsWith('/') ? s : '/' + s); } async function resolveNativePlaybackUrl(streamUrl) { const abs = absolutePlaybackUrl(streamUrl); try { const u = new URL(abs); if (/\/api\/proxy\/xtream\/[^/]+\/stream\/[^/]+/i.test(u.pathname)) { const r = await fetch(abs, { credentials: 'include' }); if (r.ok) { const j = await r.json(); if (j && typeof j.url === 'string' && j.url.trim()) { return j.url.trim(); } } } } catch (_) { /* usar abs */ } return abs; } function authBearerPayload() { try { const t = localStorage.getItem('authToken'); return t && t.length > 0 ? { authorizationBearer: t } : {}; } catch (_) { return {}; } } function authBearerPayloadFor(url) { try { const u = new URL(url, window.location.origin); if (u.origin !== window.location.origin) return {}; } catch (_) { return {}; } return authBearerPayload(); } function elementToBounds(el) { if (!el) return null; const r = el.getBoundingClientRect(); return { left: Math.round(r.left), top: Math.round(r.top), width: Math.max(1, Math.round(r.width)), height: Math.max(1, Math.round(r.height)) }; } /** * Rect para Electron (--wid sobre Chromium). En WebView2+panel Win32 no se usa. */ function getEmbedBounds() { if (!window.kiratvDesktop?.supportsEmbeddedMpv) return null; if (window.kiratvDesktop.embedUsesNativePanel) return null; const watchPage = document.getElementById('page-watch'); if (watchPage && watchPage.classList.contains('active')) { const videoEl = document.getElementById('watch-video'); if (videoEl) return elementToBounds(videoEl); const section = document.querySelector('.watch-video-section'); if (section) return elementToBounds(section); } const livePage = document.getElementById('page-live'); if (livePage && livePage.classList.contains('active')) { const ps = document.querySelector('.player-section'); if (ps) return elementToBounds(ps); } const ps = document.querySelector('.player-section'); if (ps && ps.offsetParent !== null) return elementToBounds(ps); const wv = document.getElementById('watch-video'); if (wv && wv.offsetParent !== null) return elementToBounds(wv); return null; } function embedUsesNativePanel() { return !!(window.kiratvDesktop && window.kiratvDesktop.embedUsesNativePanel); } function setEmbeddedUiClass(active) { const native = embedUsesNativePanel(); document.body?.classList.toggle('kiratv-win-embedded', !!(active && !native)); document.body?.classList.toggle('kiratv-shell-wv2-playing', !!(active && native)); } /** * Modo cromo ventana: WebView2 oculta panel vídeo en rutas sin reproductor nativo. */ function shellChromeModeFromHash() { const raw = location.hash || '#home'; const h = raw.replace(/^#\//, '#').replace(/^#/, '').split(/[/?&]/)[0]; if (h === 'live') return 'liveSplit'; if (h === 'watch') return 'vodSplit'; return 'fullWeb'; } function syncShellChrome() { const d = window.kiratvDesktop; if (typeof d?.setShellChromeMode !== 'function') return; const mode = shellChromeModeFromHash(); d.setShellChromeMode({ mode }).catch(() => {}); if (embedUsesNativePanel() && mode === 'fullWeb') { d.stopExternalMpv?.().catch(() => {}); if (typeof window.kiratvDesktopClearEmbeddedChrome === 'function') { window.kiratvDesktopClearEmbeddedChrome(); } } } function initEmbedBoundsSync() { if (!window.kiratvDesktop?.supportsEmbeddedMpv) return; if (embedUsesNativePanel()) return; if (typeof window.kiratvDesktop?.setVideoHostBounds !== 'function') return; let t = null; function flushBounds() { clearTimeout(t); t = setTimeout(() => { const b = getEmbedBounds(); if (!b) return; window.kiratvDesktop.setVideoHostBounds(b).catch(() => {}); }, 72); } window.addEventListener('resize', flushBounds); if (typeof ResizeObserver !== 'undefined') { const ro = new ResizeObserver(flushBounds); document.querySelectorAll('.player-section, .watch-video-section, #watch-video').forEach((el) => { try { ro.observe(el); } catch (_) { /* */ } }); } window.addEventListener('hashchange', flushBounds); flushBounds(); } function useEmbeddedPlayback() { const d = window.kiratvDesktop; return !!(d?.supportsEmbeddedMpv && typeof d.playEmbeddedMpv === 'function'); } async function playInMpv(absUrl, extraArgs) { const d = window.kiratvDesktop; if (!d?.playExternalMpv) { showToast('Error: sin puente escritorio', 'error'); return { ok: false, error: 'Sin puente escritorio' }; } const nativeUrl = await resolveNativePlaybackUrl(absUrl); if (useEmbeddedPlayback()) { const native = embedUsesNativePanel(); let bounds = null; if (!native) { bounds = getEmbedBounds(); if (!bounds || bounds.width < 2 || bounds.height < 2) { showToast('No hay área de vídeo visible — cambia de página o redimensiona', 'error'); return { ok: false, error: 'bounds vídeo vacíos' }; } showToast('▶ Reproduciendo en la misma ventana (mpv)', 'ok', 2800); try { await d.setVideoHostBounds(bounds); } catch (_) { /* */ } } else { showToast('▶ Reproduciendo en panel nativo (mpv)', 'ok', 2800); } setEmbeddedUiClass(true); const payload = { url: nativeUrl, extraArgs: extraArgs || [], ...authBearerPayloadFor(nativeUrl) }; if (!native && bounds) payload.bounds = bounds; const result = await d.playEmbeddedMpv(payload); if (!result?.ok) { setEmbeddedUiClass(false); const msg = result?.error || 'mpv no pudo arrancar'; console.error('[KiraTV Desktop]', msg); showToast('⚠ ' + msg, 'error', 6000); } return result; } showToast('▶ Abriendo en reproductor nativo…', 'ok', 2500); const result = await d.playExternalMpv({ url: nativeUrl, extraArgs: extraArgs || [], ...authBearerPayloadFor(nativeUrl) }); if (!result?.ok) { const msg = result?.error || 'mpv no pudo arrancar'; console.error('[KiraTV Desktop]', msg); showToast( '⚠ ' + msg + (msg.includes('mpv') ? '' : ' — ¿mpv instalado?'), 'error', 6000 ); } return result; } function whenReady(cb) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', cb, { once: true }); } else { cb(); } } whenReady(() => { if (window.kiratvDesktop?.isDesktopApp) { document.body.classList.add('kiratv-desktop-app'); } if (window.kiratvDesktop?.supportsEmbeddedMpv) { initEmbedBoundsSync(); } window.addEventListener('hashchange', syncShellChrome); setTimeout(syncShellChrome, 0); }); window.kiratvDesktopClearEmbeddedChrome = function () { setEmbeddedUiClass(false); }; window.kiratvAbsolutePlaybackUrl = absolutePlaybackUrl; window.kiratvEmbedBoundsFlush = () => getEmbedBounds(); window.kiratvPlayLiveInNativePlayer = function (absoluteUrl) { return playInMpv(absoluteUrl); }; window.kiratvPlayVodInNativePlayer = function (absoluteUrl, extraArgs) { return playInMpv(absoluteUrl, extraArgs); }; })();