(() => { const cap = window.Capacitor || {}; const N = (cap.Plugins && cap.Plugins.VylaNative) || cap.registerPlugin('VylaNative'); window.API_BASE = 'http://127.0.0.1:38417'; const webFetch = window.fetch.bind(window); const dec = new TextDecoder('utf-8'); const bytesOf = s => { const b = atob(s), u = new Uint8Array(b.length); for (let i = 0; i < b.length; i++) u[i] = b.charCodeAt(i); return u }; const abortErr = () => new DOMException('The operation was aborted', 'AbortError'); const wrap = r => { const bytes = bytesOf(r.body || ''), h = r.headers || {}; const text = async () => dec.decode(bytes); return { ok: r.status >= 200 && r.status < 300, status: r.status, statusText: '', url: r.url, headers: { get: k => { const v = h[String(k).toLowerCase()]; return v === undefined ? null : v }, has: k => String(k).toLowerCase() in h }, body: { getReader: () => { let d = false; return { read: async () => d ? { done: true, value: undefined } : (d = true, { done: false, value: bytes }) } }, cancel: async () => { } }, text, json: async () => JSON.parse(await text()), arrayBuffer: async () => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) }; }; window.fetch = (input, init = {}) => { const isReq = typeof input !== 'string'; const url = (isReq ? (input && input.url) : input) || ''; if (!/^https?:\/\//i.test(url) || url.startsWith(window.API_BASE) || /^https:\/\/(api|image)\.themoviedb\.org|^https:\/\/image\.tmdb\.org/i.test(url)) return webFetch(input, init); const hd = {}; new Headers(init.headers || (isReq ? input.headers : undefined)).forEach((v, k) => { hd[k] = v }); const sig = init.signal; if (sig && sig.aborted) return Promise.reject(abortErr()); const call = N.fetch({ url, method: String(init.method || (isReq ? input.method : 'GET')).toUpperCase(), headers: hd, body: init.body == null ? undefined : String(init.body), timeout: init.timeout || 120000, redirect: init.redirect || 'follow', maxBytes: init.maxBytes || 0 }).then(wrap); if (!sig) return call; return Promise.race([call, new Promise((_, rej) => sig.addEventListener('abort', () => rej(abortErr()), { once: true }))]); }; let booted = false; const boot = () => { if (!booted) { VylaLocal.init(K); booted = true } }; const opt = v => v === undefined || v === null || v === '' || v === 'null' || v === 'undefined' ? undefined : String(v); const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'; const OKS = [200, 206, 304]; const SEG = { recap: 'Skip Recap', intro: 'Skip Intro', preview: 'Skip Preview', credits: 'Skip Credits' }; const probeGet = (u, h, max) => fetch(u, { headers: { 'User-Agent': UA, Accept: '*/*', 'Accept-Language': 'en-US,en;q=0.9', ...h, Range: 'bytes=0-1024' }, timeout: 6000, maxBytes: max }); const playable = async (url, h, depth = 0) => { try { const r = await probeGet(url, h, 1048576); if (!OKS.includes(r.status)) return false; const ct = (r.headers.get('content-type') || '').toLowerCase(); if (!(url.includes('.m3u8') || ct.includes('mpegurl') || ct.includes('m3u8'))) return true; if (depth > 4) return false; const line = (await r.text()).split('\n').map(x => x.trim()).find(x => x && x[0] !== '#'); if (!line) return false; const base = url.slice(0, url.lastIndexOf('/') + 1), origin = new URL(url).origin; const t = line.startsWith('http') ? line : line.startsWith('/') ? origin + line : base + line; if (t.includes('m3u8')) return playable(t, h, depth + 1); return OKS.includes((await probeGet(t, h, 1024)).status); } catch (_) { return false } }; window.Vyla = { sources() { boot(); return VylaLocal.sources().map(c => ({ key: c.key, label: c.label })); }, async stream(provider, tmdbId, season, episode) { boot(); try { const d = await VylaLocal.stream(String(provider), String(tmdbId), opt(season), opt(episode)); if (!d) return { success: false }; const h = d.headers || {}; if (Array.isArray(d.allUrls) && d.allUrls.length) { const ok = await Promise.all(d.allUrls.map(async u => (u && u.url && await playable(u.url, h)) ? u : null)); d.allUrls = ok.filter(Boolean); if (d.allUrls.length) { if (!d.url || !d.allUrls.some(u => u.url === d.url)) d.url = d.allUrls[0].url } else d.url = null; } else if (d.url && !(await playable(d.url, h))) d.url = null; return d.url ? { success: true, provider, stream: d } : { success: false }; } catch (_) { return { success: false } } }, async subtitles(id, season, episode) { boot(); try { return (await VylaLocal.subtitles(String(id), opt(season), opt(episode))) || [] } catch (_) { return [] } }, async timestamps(id, season, episode, durationSec) { let u = `https://api.theintrodb.org/v3/media?tmdb_id=${id}`; if (season) u += `&season=${season}`; if (episode) u += `&episode=${episode}`; if (durationSec) u += `&duration_ms=${Math.round(durationSec * 1000)}`; try { const r = await fetch(u, { timeout: 8000 }); if (!r.ok) return []; const d = await r.json(), out = []; Object.keys(SEG).forEach(t => (Array.isArray(d[t]) ? d[t] : []).forEach(x => out.push({ type: t, label: SEG[t], start: x.start_ms != null ? x.start_ms / 1000 : 0, end: x.end_ms != null ? x.end_ms / 1000 : null }))); return out; } catch (_) { return [] } } }; })();