/** * Video Connection System using Svelte 5 Runes * Clean and simple video producer/consumer management */ import { video } from "@robothub/transport-server-client"; import type { video as videoTypes } from "@robothub/transport-server-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); } };