326 lines
11 KiB
JavaScript
326 lines
11 KiB
JavaScript
/**
|
|
* 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);
|
|
};
|
|
})();
|