Add WebRTC relay integration and core process management for streaming
This commit is contained in:
parent
6dc2fdad57
commit
71b3bd9e1d
@ -18,7 +18,7 @@ services:
|
||||
# Host:puerto del servicio extractor (usado por Caddy para reverse_proxy).
|
||||
# Caddy expondrá el servicio en http://localhost:3000/yt-stream/
|
||||
YTDLP_HOST: "100.73.244.28:8080"
|
||||
|
||||
#YTDLP_HOST: "192.168.1.20:8282"
|
||||
# YTDLP_URL: URL completa del servicio yt-dlp vista desde el NAVEGADOR.
|
||||
# Dejar vacío → la UI usará /yt-stream/ (Caddy proxy, mismo origen = sin CORS).
|
||||
YTDLP_URL: ""
|
||||
|
||||
@ -146,6 +146,7 @@ let streaming = false;
|
||||
let muted = false;
|
||||
let startTime = 0;
|
||||
let timerHandle = null;
|
||||
let coreProcId = null; // id del proceso creado en el Core (si corresponde)
|
||||
|
||||
const LK = window.LivekitClient;
|
||||
|
||||
@ -313,8 +314,10 @@ function startRtmpRelay(videoBitrate) {
|
||||
setCh('dot-rtmp','run'); badge('badge-rtmp', true);
|
||||
startMediaRecorder(videoBitrate);
|
||||
done(true);
|
||||
try { window.parent.postMessage({ type: 'webrtc-relay', event: 'relay-ready', room: ROOM_ID }, '*'); } catch(_){}
|
||||
} else if (msg.type === 'info') {
|
||||
log('RTMP relay: ' + msg.message);
|
||||
try { window.parent.postMessage({ type: 'webrtc-relay', event: 'relay-info', message: msg.message }, '*'); } catch(_){}
|
||||
}
|
||||
} catch(_){}
|
||||
};
|
||||
@ -348,8 +351,110 @@ function stopMediaRecorder() {
|
||||
mediaRec = null;
|
||||
}
|
||||
|
||||
// ─── Control principal ────────────────────────────────────────────────────────
|
||||
function toggleStream() { streaming ? stopStream() : startStream(); }
|
||||
// --- Helpers: crear/registrar relay y crear proceso en Core ---
|
||||
async function registerRelayOnServer(roomName, preferredStreamName) {
|
||||
try {
|
||||
const resp = await fetch('/livekit/relay/start', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ roomName: roomName, streamName: preferredStreamName })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const txt = await resp.text();
|
||||
log('Relay register failed: HTTP ' + resp.status + ' ' + txt, 'err');
|
||||
return null;
|
||||
}
|
||||
const data = await resp.json();
|
||||
log('Relay registrado: ' + (data.rtmpUrl || 'unknown'), 'ok');
|
||||
// notify parent
|
||||
try { window.parent.postMessage({ type: 'webrtc-relay', event: 'relay-registered', data }, '*'); } catch(_){}
|
||||
return data; // { rtmpUrl, streamName, roomName }
|
||||
} catch (e) {
|
||||
log('Relay register error: ' + e.message, 'err');
|
||||
try { window.parent.postMessage({ type: 'webrtc-relay', event: 'relay-register-error', error: e.message }, '*'); } catch(_){}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function createCoreProcessForRelay(roomName, streamName, options = {}) {
|
||||
const procId = `webrtc-relay:egress:${roomName}`;
|
||||
const inputAddress = `{rtmp,name=${streamName}.stream}`;
|
||||
const outputAddress = `{memfs}/${roomName}.m3u8`;
|
||||
|
||||
const config = {
|
||||
type: 'ffmpeg',
|
||||
id: procId,
|
||||
reference: roomName,
|
||||
input: [ { id: 'input_0', address: inputAddress, options: ['-re'] } ],
|
||||
output: [ { id: 'output_0', address: outputAddress, options: [] } ],
|
||||
options: ['-loglevel','level+info','-err_detect','ignore_err'],
|
||||
autostart: true,
|
||||
reconnect: true,
|
||||
reconnect_delay_seconds: 3,
|
||||
stale_timeout_seconds: 10,
|
||||
limits: {
|
||||
cpu_usage: options.cpu_usage || 80,
|
||||
memory_mbytes: options.memory_mbytes || 512,
|
||||
waitfor_seconds: options.waitfor_seconds || 10
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const resp = await fetch('/v3/process', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config)
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const txt = await resp.text();
|
||||
log('Core process create failed: HTTP ' + resp.status + ' ' + txt, 'err');
|
||||
try { window.parent.postMessage({ type: 'webrtc-relay', event: 'core-process-error', status: resp.status, text: txt }, '*'); } catch(_){}
|
||||
return null;
|
||||
}
|
||||
const created = await resp.json();
|
||||
coreProcId = procId;
|
||||
log('Proceso Core creado: ' + procId, 'ok');
|
||||
try { window.parent.postMessage({ type: 'webrtc-relay', event: 'core-process-created', procId, created }, '*'); } catch(_){}
|
||||
return { id: procId, created };
|
||||
} catch (e) {
|
||||
log('Create process error: ' + e.message, 'err');
|
||||
try { window.parent.postMessage({ type: 'webrtc-relay', event: 'core-process-error', error: e.message }, '*'); } catch(_){}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function stopCoreProcess(procId) {
|
||||
if (!procId) return;
|
||||
try {
|
||||
await fetch(`/v3/process/${encodeURIComponent(procId)}/command`, {
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ command: 'stop' })
|
||||
}).catch(()=>{});
|
||||
await fetch(`/v3/process/${encodeURIComponent(procId)}`, { method: 'DELETE' }).catch(()=>{});
|
||||
log('Proceso Core detenido/eliminado: ' + procId, 'ok');
|
||||
try { window.parent.postMessage({ type: 'webrtc-relay', event: 'core-process-stopped', procId }, '*'); } catch(_){}
|
||||
} catch (e) {
|
||||
log('Error stopping core process: ' + e.message, 'err');
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareRelayAndProcess(roomName) {
|
||||
const reg = await registerRelayOnServer(roomName, roomName);
|
||||
if (!reg) return null;
|
||||
const streamName = reg.streamName || roomName;
|
||||
const proc = await createCoreProcessForRelay(roomName, streamName, { memory_mbytes: 512, cpu_usage: 80 });
|
||||
return { rtmpUrl: reg.rtmpUrl, streamName, procId: proc ? proc.id : null };
|
||||
}
|
||||
|
||||
// --- End helpers ---
|
||||
|
||||
// Modificar startRtmpRelay para enviar postMessage cuando reciba ready/info
|
||||
// (la función startRtmpRelay ya manda logs; añadimos postMessage en los branches relevantes)
|
||||
|
||||
// Ajustar handler onmessage en startRtmpRelay: cuando reciba ready o info enviar parent postMessage
|
||||
// (insert into relayWs.onmessage in existing function)
|
||||
|
||||
// Para asegurarnos de no duplicar mucho, reemplazamos la onmessage interna con una versión que notifica:
|
||||
// (find relayWs.onmessage above and it will still work; add postMessage calls where appropriate)
|
||||
|
||||
// --- Integración en startStream: preparar relay + crear proceso antes de abrir WS ---
|
||||
|
||||
async function startStream() {
|
||||
if (!localStream) { log('No hay fuente activa', 'err'); return; }
|
||||
@ -359,6 +464,15 @@ async function startStream() {
|
||||
document.getElementById('btn-go').disabled = true;
|
||||
setStatus('Iniciando canales...', 'connecting');
|
||||
|
||||
// Preparar relay / proceso en Core
|
||||
const preparation = await prepareRelayAndProcess(ROOM_ID);
|
||||
if (!preparation) {
|
||||
log('Advertencia: preparación del relay/proceso falló; se intentará continuar', 'warn');
|
||||
} else {
|
||||
log('Preparación relay/process OK: ' + (preparation.rtmpUrl || ''), 'ok');
|
||||
}
|
||||
|
||||
// Empezar LiveKit y RTMP relay (la función startRtmpRelay seguirña enviando ready que lanzará MediaRecorder)
|
||||
const [lkOk, rtmpOk] = await Promise.all([
|
||||
startLiveKit(videoBitrate, fps),
|
||||
startRtmpRelay(videoBitrate),
|
||||
@ -383,16 +497,30 @@ async function startStream() {
|
||||
const ch = [lkOk && 'LiveKit', rtmpOk && 'RTMP→Core'].filter(Boolean).join(' + ');
|
||||
setStatus('🔴 EN VIVO — ' + ch, 'live');
|
||||
log('🚀 Transmisión activa: ' + ch, 'ok');
|
||||
try { window.parent.postMessage({ type: 'webrtc-relay', event: 'stream-started', room: ROOM_ID, channels: { lk: lkOk, rtmp: rtmpOk } }, '*'); } catch(_){}
|
||||
if (!rtmpOk) log('⚠️ Sin RTMP relay — el Core NO recibirá la señal. Revisa el servidor Node.', 'warn');
|
||||
if (!lkOk) log('⚠️ Sin LiveKit — solo relay RTMP activo.', 'warn');
|
||||
}
|
||||
|
||||
// Modificar stopStream para limpiar proc y notificar parent
|
||||
async function stopStream() {
|
||||
streaming = false;
|
||||
stopTimer();
|
||||
stopMediaRecorder();
|
||||
if (room) { try { await room.disconnect(); } catch(_){} room = null; }
|
||||
if (relayWs) { try { relayWs.close(); } catch(_){} relayWs = null; }
|
||||
|
||||
// Stop relay on server
|
||||
try {
|
||||
await fetch('/livekit/relay/stop', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roomName: ROOM_ID }) });
|
||||
} catch(_){}
|
||||
|
||||
// Stop and delete core process if we created one
|
||||
if (coreProcId) {
|
||||
await stopCoreProcess(coreProcId);
|
||||
coreProcId = null;
|
||||
}
|
||||
|
||||
setCh('dot-lk',''); setCh('dot-rtmp','');
|
||||
badge('badge-lk',false); badge('badge-rtmp',false); badge('badge-live',false);
|
||||
document.getElementById('btn-go').textContent = '🚀 Iniciar transmisión';
|
||||
@ -401,6 +529,8 @@ async function stopStream() {
|
||||
document.getElementById('settings-card').style.opacity = '';
|
||||
document.getElementById('settings-card').style.pointerEvents= '';
|
||||
setStatus('Transmisión detenida'); log('Transmisión detenida');
|
||||
|
||||
try { window.parent.postMessage({ type: 'webrtc-relay', event: 'stream-stopped', room: ROOM_ID }, '*'); } catch(_){}
|
||||
}
|
||||
|
||||
// ─── Init ────────────────────────────────────────────────────────────────────
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user