Spaces:
Running
Running
| /** | |
| * 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<videoTypes.RoomInfo[]>([]); | |
| 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<videoTypes.RoomInfo[]> { | |
| 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<string | null> { | |
| 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<boolean> { | |
| 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<void> { | |
| 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<void> { | |
| 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<void> { | |
| 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); | |
| } | |
| }; | |