import React from 'react';
import { Trans } from '@lingui/macro';
import Grid from '@mui/material/Grid';
import Icon from '@mui/icons-material/ScreenShare';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import Button from '@mui/material/Button';
import CircularProgress from '@mui/material/CircularProgress';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import RefreshIcon from '@mui/icons-material/Refresh';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
import Chip from '@mui/material/Chip';
import * as S from '../../Sources/WebRTCRoom';
/** Extrae el puerto de una dirección del tipo ":8555" o "host:8555" → "8555" */
function extractPort(address, fallback) {
if (!address) return fallback;
const m = address.match(/:(\d+)$/);
return m ? m[1] : fallback;
}
/** Construye la URL base WHIP a partir del config del Core */
function buildWhipBaseUrl(coreConfig) {
const whip = coreConfig?.whip;
if (!whip?.enable) return '';
const port = extractPort(whip.address, '8555');
const hosts = coreConfig?.host?.name;
const host = Array.isArray(hosts) && hosts.length > 0 ? hosts[0] : window.location.hostname;
return `http://${host}:${port}/whip`;
}
const initSettings = (initialSettings, config) => {
return S.func.initSettings(initialSettings, config);
};
function Source(props) {
const config = { channelid: 'external', ...(props.config || {}) };
const settings = initSettings(props.settings, config);
const streamKey = settings.streamKey || config.channelid || 'external';
const [mode, setMode] = React.useState(settings.mode || 'whip');
const [copied, setCopied] = React.useState(false);
// Core WHIP config
const [coreConfig, setCoreConfig] = React.useState(null);
const [configLoad, setConfigLoad] = React.useState(false);
const [configErr, setConfigErr] = React.useState('');
// Active streams polling
const [active, setActive] = React.useState(false);
const [localKey, setLocalKey] = React.useState(streamKey);
const origin = window.location.origin;
const host = settings.relayHost || window.location.host;
const roomUrl = `${origin}/webrtc-room/?room=${encodeURIComponent(localKey)}&host=${encodeURIComponent(host)}`;
const whipBase = coreConfig ? buildWhipBaseUrl(coreConfig) : '';
const token = coreConfig?.whip?.token || '';
const whipEnabled = coreConfig?.whip?.enable === true;
const whipUrl = whipBase
? `${whipBase}/${localKey}${token ? '?token=' + token : ''}`
: '';
const makeInputs = () => {
const s = { ...settings, streamKey: localKey, mode };
return S.func.createInputs(s, config);
};
const handleChange = (m, key) => {
const k = key !== undefined ? key : localKey;
props.onChange(S.id, { ...settings, streamKey: k, mode: m }, makeInputs(), false);
};
// Carga la config del Core (GET /api/v3/config)
const fetchCoreConfig = React.useCallback(async () => {
setConfigLoad(true);
setConfigErr('');
try {
const res = await fetch('/api/v3/config');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
setCoreConfig(data);
} catch (err) {
setConfigErr(err.message);
} finally {
setConfigLoad(false);
}
}, []);
// Poll GET /api/v3/whip para ver si el stream está activo
const pollWhipStreams = React.useCallback(async () => {
try {
const res = await fetch('/api/v3/whip');
if (!res.ok) return;
const streams = await res.json();
setActive(Array.isArray(streams) && streams.some((s) => s.name === localKey));
} catch (_) {}
}, [localKey]);
React.useEffect(() => {
fetchCoreConfig();
}, [fetchCoreConfig]);
// Poll cada 5s cuando modo = whip
React.useEffect(() => {
if (mode !== 'whip') return;
pollWhipStreams();
const id = setInterval(pollWhipStreams, 5000);
return () => clearInterval(id);
}, [mode, pollWhipStreams]);
React.useEffect(() => {
handleChange(mode);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleMode = (_e, newMode) => {
if (!newMode) return;
setMode(newMode);
handleChange(newMode);
};
const handleKeyChange = (e) => {
const k = e.target.value;
setLocalKey(k);
handleChange(mode, k);
};
const handleCopy = (text) => () => {
if (text && navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
setCopied(text);
setTimeout(() => setCopied(false), 2000);
});
}
};
return (
{/* ── Mode toggle ── */}
WHIP / OBSSala WebRTC (navegador)
{/* ── WHIP mode ── */}
{mode === 'whip' && (
<>
{configLoad && (
Cargando config WHIP del Core…
)}
{configErr && (
⚠️ {configErr}
} onClick={fetchCoreConfig} style={{ marginTop: 4 }}>
Reintentar
)}
{!configLoad && coreConfig && !whipEnabled && (
⚠️ El servidor WHIP del Core está deshabilitado. Actívalo en Ajustes → WHIP Server.
)}
{!configLoad && coreConfig && whipEnabled && (
<>
{/* Stream Key */}
Stream Key}
value={localKey}
onChange={handleKeyChange}
helperText={OBS enviará a esta clave. Usa el ID del canal o escribe una personalizada.}
/>
{/* URL de publicación para OBS */}
URL para OBS → Configuración → Emisión → Servicio: Custom (WHIP)
Clave de stream en OBS: dejar vacía (ya va en la URL)}
/>
}
style={{ whiteSpace: 'nowrap', minWidth: 90, height: 56 }}
>
{copied === whipUrl ? ¡Copiado! : Copiar}
{/* Estado en tiempo real */}
{active ? (
}
label={Transmitiendo}
size="small"
style={{ backgroundColor: 'rgba(39,174,96,0.15)', color: '#27ae60', border: '1px solid #27ae60' }}
/>
) : (
}
label={Esperando publicador…}
size="small"
style={{ backgroundColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.5)' }}
/>
)}
{/* Token warning */}
{token && (
🔑 El servidor requiere token. Ya está incluido en la URL anterior.
)}
>
)}
>
)}
{/* ── Room mode ── */}
{mode === 'room' && (
URL de la sala (compartir con el presentador)}
value={roomUrl}
InputProps={{ readOnly: true, style: { fontFamily: 'monospace', fontSize: '0.78rem' } }}
helperText={Room ID: {localKey}}
/>
}
style={{ whiteSpace: 'nowrap', minWidth: 90, height: 56 }}
>
{copied === roomUrl ? ¡Copiado! : Copiar}
)}
);
}
Source.defaultProps = {
knownDevices: [],
settings: {},
config: null,
skills: null,
onChange: function (type, settings, inputs, ready) {},
onRefresh: function () {},
};
function SourceIcon(props) {
return ;
}
const id = 'webrtcroom';
const type = 'webrtcroom';
const name = WebRTC Room;
const capabilities = ['audio', 'video'];
export { id, type, name, capabilities, SourceIcon as icon, Source as component };