Stop Stremio binge prefetch creating phantom next-episode streams.

Remove bingeGroup, skip session creation on HEAD probes, and hide stale sessions in admin.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jos Vooges | STH 2026-08-25 02:35:29 +02:00
parent b1d008e8aa
commit 0608020556
2 changed files with 35 additions and 5 deletions

View file

@ -164,8 +164,25 @@ export async function registerAdminRoutes(app: FastifyInstance, config: Config)
});
app.get("/api/v1/admin/streams", { preHandler: requireAdmin }, async () => {
const now = new Date();
// Drop expired rows so prefetch leftovers do not clutter the UI.
await prisma.playbackSession.updateMany({
where: {
status: "ACTIVE",
revoked: false,
absoluteExpiresAt: { lt: now },
},
data: { status: "EXPIRED" },
});
const sessions = await prisma.playbackSession.findMany({
where: { status: "ACTIVE", revoked: false },
where: {
status: "ACTIVE",
revoked: false,
absoluteExpiresAt: { gt: now },
// Only show recently touched sessions (prefetch often never streams bytes).
lastActivity: { gt: new Date(now.getTime() - 15 * 60 * 1000) },
},
include: {
node: { select: { id: true, name: true } },
mediaFile: {

View file

@ -131,7 +131,8 @@ export async function registerStremioRoutes(app: FastifyInstance, config: Config
return { streams };
});
// Lazy session: create on play so the token is fresh; redirect to node stream URL.
// Lazy session: create on GET play so the token is fresh; redirect to node stream URL.
// HEAD is treated as a probe — do not create a session (Stremio binge/prefetch uses HEAD/GET).
app.route({
method: ["GET", "HEAD"],
url: "/stremio/:token/play/:mediaFileId",
@ -142,9 +143,23 @@ export async function registerStremioRoutes(app: FastifyInstance, config: Config
return reply.status(401).send({ error: "Invalid addon token" });
}
if (request.method === "HEAD") {
const mediaFile = await prisma.mediaFile.findUnique({
where: { id: mediaFileId },
include: { node: true },
});
if (
!mediaFile?.available ||
mediaFile.node.revoked ||
mediaFile.node.status !== "ONLINE"
) {
return reply.status(404).send();
}
return reply.status(200).send();
}
try {
const session = await playback.createSession(mediaFileId, userId);
// 307 keeps method; Stremio/external players follow to the node.
return reply.redirect(307, session.streamUrl);
} catch (err) {
const message = err instanceof Error ? err.message : "Playback unavailable";
@ -362,7 +377,6 @@ async function getStreams(
url: playback.resolveStreamUrl(addonToken, file.id),
behaviorHints: {
notWebReady: true,
bingeGroup: `mc-movie-${movie.id}`,
},
});
}
@ -409,7 +423,6 @@ async function getStreams(
url: playback.resolveStreamUrl(addonToken, file.id),
behaviorHints: {
notWebReady: true,
bingeGroup: `mc-series-${series.id}-S${seasonNumber}`,
},
});
}