RobotHub-Frontend / src /lib /elements /video /videoConnection.svelte.ts
blanchon's picture
Update
3165745
/**
* 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);
}
};