diff --git a/apps/api-daemon/src/capture/streamIngest.ts b/apps/api-daemon/src/capture/streamIngest.ts index 4db287e..3fcb75e 100644 --- a/apps/api-daemon/src/capture/streamIngest.ts +++ b/apps/api-daemon/src/capture/streamIngest.ts @@ -181,8 +181,12 @@ export function startStreamIngestWorker(): Worker { // count; the real ceiling is the server's CPU/bandwidth for running that // many yt-dlp+ffmpeg pairs at once, not this number. concurrency: env.maxConcurrentCaptures, - // Captures run for the lifetime of the stream (can be hours) — extend the - // default lock well beyond BullMQ's stalled-job assumptions for an MVP. - lockDuration: 24 * 60 * 60 * 1000, + // BullMQ renews this lock automatically while a worker is alive, so a + // low value doesn't cap how long a capture can run — it only controls + // how fast a *dead* worker's job is detected as stalled and recovered. + // (An earlier 24h value meant a killed process's job wouldn't be + // recovered for up to 24h; closeOrphanedSessions() in index.ts is what + // actually unblocks that channel's StreamSession in the meantime.) + lockDuration: 10 * 60 * 1000, }); } diff --git a/apps/api-daemon/src/index.ts b/apps/api-daemon/src/index.ts index de28a1f..1570139 100644 --- a/apps/api-daemon/src/index.ts +++ b/apps/api-daemon/src/index.ts @@ -30,7 +30,28 @@ async function forceLiveBypass(youtubeUrl: string) { }); } +/** + * A container restart (e.g. a redeploy) kills any in-progress capture + * without running its cleanup code, leaving that StreamSession's endedAt + * stuck at null forever. Besides showing "still recording" in the panel, + * startSession() treats any open session as a reason to skip starting a new + * one for that channel — so an orphaned session permanently blocks that + * channel from ever being recorded again until this runs. Since a fresh + * process start means no capture can actually still be in progress, every + * open session at boot is stale by definition and gets closed out. + */ +async function closeOrphanedSessions() { + const { count } = await prisma.streamSession.updateMany({ + where: { endedAt: null }, + data: { endedAt: new Date() }, + }); + if (count > 0) { + console.log(`[index] closed ${count} orphaned session(s) left open by a previous process`); + } +} + async function main() { + await closeOrphanedSessions(); startServer(); startStreamIngestWorker();