Mark IPTV live streams notWebReady and prefer MPEG-TS for XUI playback.
Stremio requires notWebReady for non-MP4 so the local streaming server follows XUI redirects; HLS auth often hangs while TS works.
This commit is contained in:
parent
2df8a6a83c
commit
002472e235
2 changed files with 43 additions and 10 deletions
|
|
@ -433,27 +433,51 @@ export async function getIptvStreams(
|
||||||
const parsed = parseIptvId(id);
|
const parsed = parseIptvId(id);
|
||||||
if (!parsed) return [];
|
if (!parsed) return [];
|
||||||
|
|
||||||
// Direct client → XUI URLs. Prefer XUI /play/{token} from m3u playlist
|
// Direct client → XUI. Prefer /play/{token} from m3u playlist.
|
||||||
// (classic /live/user/pass/id hangs on real live; works for created_live 24/7).
|
// 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") {
|
if (type === "tv" && parsed.kind === "live") {
|
||||||
const sid = parsed.streamId;
|
const sid = parsed.streamId;
|
||||||
const resolved = await xt.client.resolveLivePlayUrls(sid);
|
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<string, unknown>;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
if (resolved.hls) {
|
const hint = (filename: string) => ({
|
||||||
out.push({ name: "Live · HLS", title: "XUI play m3u8", url: resolved.hls });
|
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) {
|
if (resolved.hls && resolved.hls !== resolved.ts) {
|
||||||
out.push({ name: "Live · MPEG-TS", title: "XUI play ts", url: 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) {
|
if (out.length === 0) {
|
||||||
for (const url of resolved.classic) {
|
for (const url of resolved.classic) {
|
||||||
|
const isTs = url.endsWith(".ts");
|
||||||
out.push({
|
out.push({
|
||||||
name: url.endsWith(".ts") ? "Live · MPEG-TS" : "Live · HLS",
|
name: isTs ? "Live · MPEG-TS" : "Live · HLS",
|
||||||
title: "classic Xtream",
|
title: "classic Xtream",
|
||||||
url,
|
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 streams = await xt.client.getVodStreams();
|
||||||
const s = streams.find((x) => x.stream_id === parsed.streamId);
|
const s = streams.find((x) => x.stream_id === parsed.streamId);
|
||||||
const ext = s?.container_extension || "mp4";
|
const ext = s?.container_extension || "mp4";
|
||||||
|
// VOD/series also non-MP4 often — keep notWebReady for containers != mp4
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: `IPTV · ${ext.toUpperCase()}`,
|
name: `IPTV · ${ext.toUpperCase()}`,
|
||||||
title: s?.name || "VOD",
|
title: s?.name || "VOD",
|
||||||
url: xt.client.vodStreamUrl(parsed.streamId, ext),
|
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}`,
|
name: `IPTV · S${parsed.season}E${parsed.episode}`,
|
||||||
title: ep?.title || `Episode ${parsed.episode}`,
|
title: ep?.title || `Episode ${parsed.episode}`,
|
||||||
url: xt.client.seriesStreamUrl(epId, ext),
|
url: xt.client.seriesStreamUrl(epId, ext),
|
||||||
|
behaviorHints: {
|
||||||
|
notWebReady: ext.toLowerCase() !== "mp4",
|
||||||
|
filename: `series-${parsed.streamId}-s${parsed.season}e${parsed.episode}.${ext}`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ async function buildManifest(tokenId: string | null, config: Config) {
|
||||||
const types = new Set(["movie", "series", ...iptv.types]);
|
const types = new Set(["movie", "series", ...iptv.types]);
|
||||||
return {
|
return {
|
||||||
id: "com.mediacluster.library",
|
id: "com.mediacluster.library",
|
||||||
version: "1.5.3",
|
version: "1.5.4",
|
||||||
name: "Media Cluster",
|
name: "Media Cluster",
|
||||||
description: "Privé bibliotheek + Live TV / IPTV (Xtream) met EPG",
|
description: "Privé bibliotheek + Live TV / IPTV (Xtream) met EPG",
|
||||||
catalogs: [...BASE_CATALOGS, ...iptv.catalogs],
|
catalogs: [...BASE_CATALOGS, ...iptv.catalogs],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue