diff --git a/apps/master-api/src/iptv/service.ts b/apps/master-api/src/iptv/service.ts index a17d651..885c15a 100644 --- a/apps/master-api/src/iptv/service.ts +++ b/apps/master-api/src/iptv/service.ts @@ -433,27 +433,51 @@ export async function getIptvStreams( const parsed = parseIptvId(id); if (!parsed) return []; - // Direct client → XUI URLs. Prefer XUI /play/{token} from m3u playlist - // (classic /live/user/pass/id hangs on real live; works for created_live 24/7). + // Direct client → XUI. Prefer /play/{token} from m3u playlist. + // Stremio: notWebReady MUST be true for non-MP4 (m3u8/ts) so the local + // streaming server handles redirects — traffic stays client→XUI, not Master. + // Real live HLS often hangs on XUI /auth/*.m3u8; MPEG-TS is reliable. if (type === "tv" && parsed.kind === "live") { const sid = parsed.streamId; const resolved = await xt.client.resolveLivePlayUrls(sid); - const out: Array<{ name: string; title: string; url: string }> = []; + const out: Array<{ + name: string; + title: string; + url: string; + behaviorHints: Record; + }> = []; - if (resolved.hls) { - out.push({ name: "Live · HLS", title: "XUI play m3u8", url: resolved.hls }); + const hint = (filename: string) => ({ + notWebReady: true, + bingeGroup: `mciptv-live-${sid}`, + filename, + }); + + if (resolved.ts) { + out.push({ + name: "Live · MPEG-TS", + title: "XUI · direct ts", + url: resolved.ts, + behaviorHints: hint(`live-${sid}.ts`), + }); } - if (resolved.ts && resolved.ts !== resolved.hls) { - out.push({ name: "Live · MPEG-TS", title: "XUI play ts", url: resolved.ts }); + if (resolved.hls && resolved.hls !== resolved.ts) { + out.push({ + name: "Live · HLS", + title: "XUI · m3u8 (kan hangen)", + url: resolved.hls, + behaviorHints: hint(`live-${sid}.m3u8`), + }); } - // Fallback for panels without /play/ playlist (or created_live) if (out.length === 0) { for (const url of resolved.classic) { + const isTs = url.endsWith(".ts"); out.push({ - name: url.endsWith(".ts") ? "Live · MPEG-TS" : "Live · HLS", + name: isTs ? "Live · MPEG-TS" : "Live · HLS", title: "classic Xtream", url, + behaviorHints: hint(isTs ? `live-${sid}.ts` : `live-${sid}.m3u8`), }); } } @@ -465,11 +489,16 @@ export async function getIptvStreams( const streams = await xt.client.getVodStreams(); const s = streams.find((x) => x.stream_id === parsed.streamId); const ext = s?.container_extension || "mp4"; + // VOD/series also non-MP4 often — keep notWebReady for containers != mp4 return [ { name: `IPTV · ${ext.toUpperCase()}`, title: s?.name || "VOD", url: xt.client.vodStreamUrl(parsed.streamId, ext), + behaviorHints: { + notWebReady: ext.toLowerCase() !== "mp4", + filename: `vod-${parsed.streamId}.${ext}`, + }, }, ]; } @@ -489,6 +518,10 @@ export async function getIptvStreams( name: `IPTV · S${parsed.season}E${parsed.episode}`, title: ep?.title || `Episode ${parsed.episode}`, url: xt.client.seriesStreamUrl(epId, ext), + behaviorHints: { + notWebReady: ext.toLowerCase() !== "mp4", + filename: `series-${parsed.streamId}-s${parsed.season}e${parsed.episode}.${ext}`, + }, }, ]; } diff --git a/apps/master-api/src/stremio/routes.ts b/apps/master-api/src/stremio/routes.ts index 213385a..aa68297 100644 --- a/apps/master-api/src/stremio/routes.ts +++ b/apps/master-api/src/stremio/routes.ts @@ -41,7 +41,7 @@ async function buildManifest(tokenId: string | null, config: Config) { const types = new Set(["movie", "series", ...iptv.types]); return { id: "com.mediacluster.library", - version: "1.5.3", + version: "1.5.4", name: "Media Cluster", description: "Privé bibliotheek + Live TV / IPTV (Xtream) met EPG", catalogs: [...BASE_CATALOGS, ...iptv.catalogs],