feat: interaktif panel kontrolleri (Faz 3)
Yayın durdurma, kanal duraklat/devam (tekli+toplu), şimdi kontrol et, manuel URL ile zorla kayıt başlatma, canlı player+log önizleme, oturum süre sayacı, başarısız/takılı render için tekrar dene ve iptal, poll aralığı + ham segment TTL'yi panelden canlı değiştirme, cookie bayatlık uyarısı, Telegram bildirim aç/kapa, ve kullanım/maliyet özeti (/stats). api-daemon'ın yeni state-değiştiren endpoint'leri (stop/force-start/ render/cancel) INTERNAL_API_TOKEN ile korunuyor — bu daemon'ın portu Coolify'de public'e açık olduğu için korumasız bırakmak güvenlik açığı olurdu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { spawn, type ChildProcess } from "node:child_process";
|
||||
import { mkdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { Worker, type Job } from "bullmq";
|
||||
@@ -17,6 +17,24 @@ const SEGMENT_LIST_POLL_MS = 5_000;
|
||||
*/
|
||||
export const lastCaptureDebug = new Map<string, string[]>();
|
||||
|
||||
/**
|
||||
* Live yt-dlp processes, keyed by sessionId — lets a panel "stop" action
|
||||
* reach a specific in-progress capture. Only yt-dlp is tracked/killed:
|
||||
* ffmpeg is downstream of it in the pipe, so ending yt-dlp closes its stdin
|
||||
* with EOF and ffmpeg finishes the same way it does on a natural stream end
|
||||
* (flushes the last partial segment, exits, existing finalize code runs
|
||||
* unchanged).
|
||||
*/
|
||||
const activeCaptures = new Map<string, ChildProcess>();
|
||||
|
||||
/** Returns false if the session has no tracked live process (already ended, or this daemon didn't start it — e.g. after a restart). */
|
||||
export function stopCapture(sessionId: string): boolean {
|
||||
const ytdlp = activeCaptures.get(sessionId);
|
||||
if (!ytdlp) return false;
|
||||
ytdlp.kill("SIGTERM");
|
||||
return true;
|
||||
}
|
||||
|
||||
function appendCaptureDebug(sessionId: string, line: string) {
|
||||
const lines = lastCaptureDebug.get(sessionId) ?? [];
|
||||
lines.push(line);
|
||||
@@ -95,6 +113,7 @@ async function runCapture(job: Job<StreamIngestJob>): Promise<void> {
|
||||
],
|
||||
{ stdio: ["ignore", "pipe", "pipe"] },
|
||||
);
|
||||
activeCaptures.set(sessionId, ytdlp);
|
||||
|
||||
const ffmpeg = spawn(
|
||||
"ffmpeg",
|
||||
@@ -153,6 +172,7 @@ async function runCapture(job: Job<StreamIngestJob>): Promise<void> {
|
||||
});
|
||||
|
||||
clearInterval(pollTimer);
|
||||
activeCaptures.delete(sessionId);
|
||||
|
||||
// final sweep in case segments closed between the last poll and process exit
|
||||
const raw = await readFile(segmentListPath, "utf8").catch(() => "");
|
||||
|
||||
Reference in New Issue
Block a user