import React, { useEffect, useRef, useState } from 'react' import { useStudioSession } from '../hooks/useStudioSession' import { connect, createLocalTracks, Room, LocalTrack } from 'livekit-client' export const StudioConnector: React.FC = () => { const { state, session, error, connect, disconnect } = useStudioSession() const [room, setRoom] = useState(null) const [localTracks, setLocalTracks] = useState(null) const videoRef = useRef(null) const [connectingError, setConnectingError] = useState(null) // Determine LiveKit server URL (from Vite env or fallback) const LIVEKIT_URL = (import.meta.env.VITE_LIVEKIT_URL as string) || (window as any).VITE_LIVEKIT_URL || 'wss://livekit-server.bfzqqk.easypanel.host' useEffect(() => { // When the hook reports connected state and we have a token, establish the livekit Room const doConnect = async () => { setConnectingError(null) if (!session?.token) return // If we already have a room, skip if (room) return try { // Request local media permissions and create tracks const tracks = await createLocalTracks({ audio: true, video: true }) setLocalTracks(tracks) // Connect to LiveKit room using token (session.token) and LIVEKIT_URL const r = await connect(LIVEKIT_URL, session.token, { reconnect: true }) // Publish local tracks for (const t of tracks) { try { await r.localParticipant.publishTrack(t) } catch (err) { console.warn('publishTrack failed', err) } } // Attach the first video track to our preview element const videoTrack = tracks.find((t) => t.kind === 'video') as LocalTrack | undefined if (videoTrack && videoRef.current) { try { const el = videoTrack.attach() // attach returns HTMLMediaElement, ensure it's a video element // Replace container's children with this element if (videoRef.current.parentElement) { const parent = videoRef.current.parentElement parent.replaceChild(el, videoRef.current) videoRef.current = el as HTMLVideoElement } } catch (err) { console.warn('attach track failed', err) } } // Listen for room events (optional) r.on('disconnected', () => { setRoom(null) }) r.on('reconnecting', () => { // Could set state to reconnecting }) setRoom(r) } catch (e: any) { console.error('LiveKit connect error', e) setConnectingError(String(e?.message ?? e)) } } if (state === 'connected' && session?.token) { void doConnect() } return () => { // nothing here: cleanup handled separately } // eslint-disable-next-line react-hooks/exhaustive-deps }, [state, session?.token]) // Cleanup on unmount or disconnect useEffect(() => { return () => { // Stop local tracks and disconnect room try { if (localTracks) { for (const t of localTracks) { try { t.stop(); t.detach(); } catch (e) { } } } if (room) { try { room.disconnect(); } catch (e) { } } } catch (e) {} } }, [localTracks, room]) const onCreateAndEnter = async () => { try { await connect({ createIfMissing: true, createPayload: { title: 'E2E Transmisión' } }) } catch (e) { console.error('connect failed', e) } } const onEnterExisting = async () => { if (!session?.id) { await onCreateAndEnter() return } try { await connect({ sessionId: session.id }) } catch (e) { console.error('connect failed', e) } } const onDisconnect = async () => { try { if (room) { try { room.disconnect() } catch (e) { } setRoom(null) } if (localTracks) { for (const t of localTracks) { try { t.stop(); t.detach(); } catch (e) { } } setLocalTracks(null) } await disconnect() } catch (e) { console.warn('disconnect error', e) } } return (
Estado: {state}
{error &&
Error: {error}
} {connectingError &&
Conexión LiveKit: {connectingError}
}

Session: {session?.id ?? 'n/a'}

Token: {session?.token ? `${session.token.substring(0,20)}...` : 'n/a'}

{/* placeholder video element - will be replaced by attach() result when track attaches */}
) } export default StudioConnector