File size: 6,321 Bytes
6ce4ca6
 
 
 
 
3165745
 
6ce4ca6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/**
 * 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);
  }
};