/** * Video Connection System using Svelte 5 Runes * Clean and simple video producer/consumer management */ import { video } from 'lerobot-arena-client'; import type { video as videoTypes } from 'lerobot-arena-client'; import { settings } from '$lib/runes/settings.svelte'; // Simple connection state using runes export class VideoConnectionState { // Producer state producer = $state({ connected: false, client: null as videoTypes.VideoProducer | null, roomId: null as string | null, stream: null as MediaStream | null, }); // Consumer state consumer = $state({ connected: false, client: null as videoTypes.VideoConsumer | null, roomId: null as string | null, stream: null as MediaStream | null, }); // Room listing state rooms = $state([]); roomsLoading = $state(false); // Derived state get hasProducer() { return this.producer.connected; } get hasConsumer() { return this.consumer.connected; } get isStreaming() { return this.hasProducer && this.producer.stream !== null; } get canConnectConsumer() { return this.hasProducer && this.producer.roomId !== null; } } // Create global instance export const videoConnection = new VideoConnectionState(); // External action functions export const videoActions = { // Room management async listRooms(workspaceId: string): Promise { videoConnection.roomsLoading = true; try { const client = new video.VideoClientCore(settings.transportServerUrl); const rooms = await client.listRooms(workspaceId); videoConnection.rooms = rooms; return rooms; } catch (error) { console.error('Failed to list rooms:', error); videoConnection.rooms = []; return []; } finally { videoConnection.roomsLoading = false; } }, async createRoom(workspaceId: string, roomId?: string): Promise { try { const client = new video.VideoClientCore(settings.transportServerUrl); const result = await client.createRoom(workspaceId, roomId); if (result) { // Refresh room list await this.listRooms(workspaceId); return result.roomId; } return null; } catch (error) { console.error('Failed to create room:', error); return null; } }, async deleteRoom(workspaceId: string, roomId: string): Promise { try { const client = new video.VideoClientCore(settings.transportServerUrl); await client.deleteRoom(workspaceId, roomId); // Refresh room list await this.listRooms(workspaceId); return true; } catch (error) { console.error('Failed to delete room:', error); return false; } }, // Producer actions (simplified - only remote/local camera) async connectProducer(workspaceId: string): Promise<{ success: boolean; error?: string; roomId?: string }> { try { const producer = new video.VideoProducer(settings.transportServerUrl); // Create or join room const roomData = await producer.createRoom(workspaceId); const connected = await producer.connect(roomData.workspaceId, roomData.roomId); if (!connected) { throw new Error('Failed to connect producer'); } // Start camera stream const stream = await producer.startCamera({ video: { width: 1280, height: 720 }, audio: true }); // Update state videoConnection.producer.connected = true; videoConnection.producer.client = producer; videoConnection.producer.roomId = roomData.roomId; videoConnection.producer.stream = stream; // Refresh room list await this.listRooms(workspaceId); return { success: true, roomId: roomData.roomId }; } catch (error) { console.error('Failed to connect producer:', error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } }, async disconnectProducer(): Promise { if (videoConnection.producer.client) { videoConnection.producer.client.disconnect(); } if (videoConnection.producer.stream) { videoConnection.producer.stream.getTracks().forEach(track => track.stop()); } // Reset state videoConnection.producer.connected = false; videoConnection.producer.client = null; videoConnection.producer.roomId = null; videoConnection.producer.stream = null; }, // Consumer actions (simplified - only remote consumer) async connectConsumer(workspaceId: string, roomId: string): Promise<{ success: boolean; error?: string }> { try { const consumer = new video.VideoConsumer(settings.transportServerUrl); const connected = await consumer.connect(workspaceId, roomId); if (!connected) { throw new Error('Failed to connect consumer'); } // Start receiving video await consumer.startReceiving(); // Set up stream receiving consumer.on('streamReceived', (stream: MediaStream) => { videoConnection.consumer.stream = stream; }); // Update state videoConnection.consumer.connected = true; videoConnection.consumer.client = consumer; videoConnection.consumer.roomId = roomId; return { success: true }; } catch (error) { console.error('Failed to connect consumer:', error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } }, async disconnectConsumer(): Promise { if (videoConnection.consumer.client) { videoConnection.consumer.client.disconnect(); } // Reset state videoConnection.consumer.connected = false; videoConnection.consumer.client = null; videoConnection.consumer.roomId = null; videoConnection.consumer.stream = null; }, // Utility functions async refreshRooms(workspaceId: string): Promise { await this.listRooms(workspaceId); }, getAvailableRooms(): videoTypes.RoomInfo[] { return videoConnection.rooms.filter(room => room.participants.producer !== null); }, getRoomById(roomId: string): videoTypes.RoomInfo | undefined { return videoConnection.rooms.find(room => room.id === roomId); } };