blanchon commited on
Commit
f372eeb
·
1 Parent(s): 9e3a88b
.gitignore CHANGED
@@ -6,7 +6,7 @@ __pycache__/
6
  .Python
7
  build/
8
  develop-eggs/
9
- dist/
10
  downloads/
11
  eggs/
12
  .eggs/
 
6
  .Python
7
  build/
8
  develop-eggs/
9
+ # dist/
10
  downloads/
11
  eggs/
12
  .eggs/
client/.gitignore CHANGED
@@ -2,8 +2,8 @@
2
  node_modules
3
 
4
  # output
5
- out
6
- dist
7
  *.tgz
8
 
9
  # code coverage
 
2
  node_modules
3
 
4
  # output
5
+ # out
6
+ # dist
7
  *.tgz
8
 
9
  # code coverage
client/dist/index.js ADDED
@@ -0,0 +1,1621 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+
29
+ // ../../../../node_modules/eventemitter3/index.js
30
+ var require_eventemitter3 = __commonJS((exports, module) => {
31
+ var has = Object.prototype.hasOwnProperty;
32
+ var prefix = "~";
33
+ function Events() {}
34
+ if (Object.create) {
35
+ Events.prototype = Object.create(null);
36
+ if (!new Events().__proto__)
37
+ prefix = false;
38
+ }
39
+ function EE(fn, context, once) {
40
+ this.fn = fn;
41
+ this.context = context;
42
+ this.once = once || false;
43
+ }
44
+ function addListener(emitter, event, fn, context, once) {
45
+ if (typeof fn !== "function") {
46
+ throw new TypeError("The listener must be a function");
47
+ }
48
+ var listener = new EE(fn, context || emitter, once), evt = prefix ? prefix + event : event;
49
+ if (!emitter._events[evt])
50
+ emitter._events[evt] = listener, emitter._eventsCount++;
51
+ else if (!emitter._events[evt].fn)
52
+ emitter._events[evt].push(listener);
53
+ else
54
+ emitter._events[evt] = [emitter._events[evt], listener];
55
+ return emitter;
56
+ }
57
+ function clearEvent(emitter, evt) {
58
+ if (--emitter._eventsCount === 0)
59
+ emitter._events = new Events;
60
+ else
61
+ delete emitter._events[evt];
62
+ }
63
+ function EventEmitter() {
64
+ this._events = new Events;
65
+ this._eventsCount = 0;
66
+ }
67
+ EventEmitter.prototype.eventNames = function eventNames() {
68
+ var names = [], events, name;
69
+ if (this._eventsCount === 0)
70
+ return names;
71
+ for (name in events = this._events) {
72
+ if (has.call(events, name))
73
+ names.push(prefix ? name.slice(1) : name);
74
+ }
75
+ if (Object.getOwnPropertySymbols) {
76
+ return names.concat(Object.getOwnPropertySymbols(events));
77
+ }
78
+ return names;
79
+ };
80
+ EventEmitter.prototype.listeners = function listeners(event) {
81
+ var evt = prefix ? prefix + event : event, handlers = this._events[evt];
82
+ if (!handlers)
83
+ return [];
84
+ if (handlers.fn)
85
+ return [handlers.fn];
86
+ for (var i = 0, l = handlers.length, ee = new Array(l);i < l; i++) {
87
+ ee[i] = handlers[i].fn;
88
+ }
89
+ return ee;
90
+ };
91
+ EventEmitter.prototype.listenerCount = function listenerCount(event) {
92
+ var evt = prefix ? prefix + event : event, listeners = this._events[evt];
93
+ if (!listeners)
94
+ return 0;
95
+ if (listeners.fn)
96
+ return 1;
97
+ return listeners.length;
98
+ };
99
+ EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
100
+ var evt = prefix ? prefix + event : event;
101
+ if (!this._events[evt])
102
+ return false;
103
+ var listeners = this._events[evt], len = arguments.length, args, i;
104
+ if (listeners.fn) {
105
+ if (listeners.once)
106
+ this.removeListener(event, listeners.fn, undefined, true);
107
+ switch (len) {
108
+ case 1:
109
+ return listeners.fn.call(listeners.context), true;
110
+ case 2:
111
+ return listeners.fn.call(listeners.context, a1), true;
112
+ case 3:
113
+ return listeners.fn.call(listeners.context, a1, a2), true;
114
+ case 4:
115
+ return listeners.fn.call(listeners.context, a1, a2, a3), true;
116
+ case 5:
117
+ return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
118
+ case 6:
119
+ return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
120
+ }
121
+ for (i = 1, args = new Array(len - 1);i < len; i++) {
122
+ args[i - 1] = arguments[i];
123
+ }
124
+ listeners.fn.apply(listeners.context, args);
125
+ } else {
126
+ var length = listeners.length, j;
127
+ for (i = 0;i < length; i++) {
128
+ if (listeners[i].once)
129
+ this.removeListener(event, listeners[i].fn, undefined, true);
130
+ switch (len) {
131
+ case 1:
132
+ listeners[i].fn.call(listeners[i].context);
133
+ break;
134
+ case 2:
135
+ listeners[i].fn.call(listeners[i].context, a1);
136
+ break;
137
+ case 3:
138
+ listeners[i].fn.call(listeners[i].context, a1, a2);
139
+ break;
140
+ case 4:
141
+ listeners[i].fn.call(listeners[i].context, a1, a2, a3);
142
+ break;
143
+ default:
144
+ if (!args)
145
+ for (j = 1, args = new Array(len - 1);j < len; j++) {
146
+ args[j - 1] = arguments[j];
147
+ }
148
+ listeners[i].fn.apply(listeners[i].context, args);
149
+ }
150
+ }
151
+ }
152
+ return true;
153
+ };
154
+ EventEmitter.prototype.on = function on(event, fn, context) {
155
+ return addListener(this, event, fn, context, false);
156
+ };
157
+ EventEmitter.prototype.once = function once(event, fn, context) {
158
+ return addListener(this, event, fn, context, true);
159
+ };
160
+ EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
161
+ var evt = prefix ? prefix + event : event;
162
+ if (!this._events[evt])
163
+ return this;
164
+ if (!fn) {
165
+ clearEvent(this, evt);
166
+ return this;
167
+ }
168
+ var listeners = this._events[evt];
169
+ if (listeners.fn) {
170
+ if (listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context)) {
171
+ clearEvent(this, evt);
172
+ }
173
+ } else {
174
+ for (var i = 0, events = [], length = listeners.length;i < length; i++) {
175
+ if (listeners[i].fn !== fn || once && !listeners[i].once || context && listeners[i].context !== context) {
176
+ events.push(listeners[i]);
177
+ }
178
+ }
179
+ if (events.length)
180
+ this._events[evt] = events.length === 1 ? events[0] : events;
181
+ else
182
+ clearEvent(this, evt);
183
+ }
184
+ return this;
185
+ };
186
+ EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
187
+ var evt;
188
+ if (event) {
189
+ evt = prefix ? prefix + event : event;
190
+ if (this._events[evt])
191
+ clearEvent(this, evt);
192
+ } else {
193
+ this._events = new Events;
194
+ this._eventsCount = 0;
195
+ }
196
+ return this;
197
+ };
198
+ EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
199
+ EventEmitter.prototype.addListener = EventEmitter.prototype.on;
200
+ EventEmitter.prefixed = prefix;
201
+ EventEmitter.EventEmitter = EventEmitter;
202
+ if (typeof module !== "undefined") {
203
+ module.exports = EventEmitter;
204
+ }
205
+ });
206
+
207
+ // src/robotics/index.ts
208
+ var exports_robotics = {};
209
+ __export(exports_robotics, {
210
+ createProducerClient: () => createProducerClient,
211
+ createConsumerClient: () => createConsumerClient,
212
+ createClient: () => createClient,
213
+ RoboticsProducer: () => RoboticsProducer,
214
+ RoboticsConsumer: () => RoboticsConsumer,
215
+ RoboticsClientCore: () => RoboticsClientCore
216
+ });
217
+
218
+ // ../../../../node_modules/eventemitter3/index.mjs
219
+ var import__ = __toESM(require_eventemitter3(), 1);
220
+
221
+ // src/robotics/core.ts
222
+ class RoboticsClientCore extends import__.default {
223
+ baseUrl;
224
+ apiBase;
225
+ websocket = null;
226
+ workspaceId = null;
227
+ roomId = null;
228
+ role = null;
229
+ participantId = null;
230
+ connected = false;
231
+ options;
232
+ onErrorCallback = null;
233
+ onConnectedCallback = null;
234
+ onDisconnectedCallback = null;
235
+ constructor(baseUrl = "http://localhost:8000", options = {}) {
236
+ super();
237
+ this.baseUrl = baseUrl.replace(/\/$/, "");
238
+ this.apiBase = `${this.baseUrl}/robotics`;
239
+ this.options = {
240
+ timeout: 5000,
241
+ reconnect_attempts: 3,
242
+ heartbeat_interval: 30000,
243
+ ...options
244
+ };
245
+ }
246
+ async listRooms(workspaceId) {
247
+ const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms`);
248
+ return response.rooms;
249
+ }
250
+ async createRoom(workspaceId, roomId) {
251
+ const finalWorkspaceId = workspaceId || this.generateWorkspaceId();
252
+ const payload = roomId ? { room_id: roomId, workspace_id: finalWorkspaceId } : { workspace_id: finalWorkspaceId };
253
+ const response = await this.fetchApi(`/workspaces/${finalWorkspaceId}/rooms`, {
254
+ method: "POST",
255
+ headers: { "Content-Type": "application/json" },
256
+ body: JSON.stringify(payload)
257
+ });
258
+ return { workspaceId: response.workspace_id, roomId: response.room_id };
259
+ }
260
+ async deleteRoom(workspaceId, roomId) {
261
+ try {
262
+ const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}`, {
263
+ method: "DELETE"
264
+ });
265
+ return response.success;
266
+ } catch (error) {
267
+ if (error instanceof Error && error.message.includes("404")) {
268
+ return false;
269
+ }
270
+ throw error;
271
+ }
272
+ }
273
+ async getRoomState(workspaceId, roomId) {
274
+ const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}/state`);
275
+ return response.state;
276
+ }
277
+ async getRoomInfo(workspaceId, roomId) {
278
+ const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}`);
279
+ return response.room;
280
+ }
281
+ async connectToRoom(workspaceId, roomId, role, participantId) {
282
+ if (this.connected) {
283
+ await this.disconnect();
284
+ }
285
+ this.workspaceId = workspaceId;
286
+ this.roomId = roomId;
287
+ this.role = role;
288
+ this.participantId = participantId || `${role}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
289
+ const wsUrl = this.baseUrl.replace(/^http/, "ws").replace(/^https/, "wss");
290
+ const wsEndpoint = `${wsUrl}/robotics/workspaces/${workspaceId}/rooms/${roomId}/ws`;
291
+ try {
292
+ this.websocket = new WebSocket(wsEndpoint);
293
+ return new Promise((resolve, reject) => {
294
+ const timeout = setTimeout(() => {
295
+ reject(new Error("Connection timeout"));
296
+ }, this.options.timeout || 5000);
297
+ this.websocket.onopen = () => {
298
+ clearTimeout(timeout);
299
+ this.sendJoinMessage();
300
+ };
301
+ this.websocket.onmessage = (event) => {
302
+ try {
303
+ const message = JSON.parse(event.data);
304
+ this.handleMessage(message);
305
+ if (message.type === "joined") {
306
+ this.connected = true;
307
+ this.onConnectedCallback?.();
308
+ this.emit("connected");
309
+ resolve(true);
310
+ } else if (message.type === "error") {
311
+ this.handleError(message.message);
312
+ resolve(false);
313
+ }
314
+ } catch (error) {
315
+ console.error("Failed to parse WebSocket message:", error);
316
+ }
317
+ };
318
+ this.websocket.onerror = (error) => {
319
+ clearTimeout(timeout);
320
+ console.error("WebSocket error:", error);
321
+ this.handleError("WebSocket connection error");
322
+ reject(error);
323
+ };
324
+ this.websocket.onclose = () => {
325
+ clearTimeout(timeout);
326
+ this.connected = false;
327
+ this.onDisconnectedCallback?.();
328
+ this.emit("disconnected");
329
+ };
330
+ });
331
+ } catch (error) {
332
+ console.error("Failed to connect to room:", error);
333
+ return false;
334
+ }
335
+ }
336
+ async disconnect() {
337
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
338
+ this.websocket.close();
339
+ }
340
+ this.websocket = null;
341
+ this.connected = false;
342
+ this.workspaceId = null;
343
+ this.roomId = null;
344
+ this.role = null;
345
+ this.participantId = null;
346
+ this.onDisconnectedCallback?.();
347
+ this.emit("disconnected");
348
+ }
349
+ sendJoinMessage() {
350
+ if (!this.websocket || !this.participantId || !this.role)
351
+ return;
352
+ const joinMessage = {
353
+ participant_id: this.participantId,
354
+ role: this.role
355
+ };
356
+ this.websocket.send(JSON.stringify(joinMessage));
357
+ }
358
+ handleMessage(message) {
359
+ switch (message.type) {
360
+ case "joined":
361
+ console.log(`Successfully joined room ${message.room_id} as ${message.role}`);
362
+ break;
363
+ case "heartbeat_ack":
364
+ console.debug("Heartbeat acknowledged");
365
+ break;
366
+ case "error":
367
+ this.handleError(message.message);
368
+ break;
369
+ default:
370
+ this.handleRoleSpecificMessage(message);
371
+ }
372
+ }
373
+ handleRoleSpecificMessage(message) {
374
+ this.emit("message", message);
375
+ }
376
+ handleError(errorMessage) {
377
+ console.error("Client error:", errorMessage);
378
+ this.onErrorCallback?.(errorMessage);
379
+ this.emit("error", errorMessage);
380
+ }
381
+ async sendHeartbeat() {
382
+ if (!this.connected || !this.websocket)
383
+ return;
384
+ const message = { type: "heartbeat" };
385
+ this.websocket.send(JSON.stringify(message));
386
+ }
387
+ isConnected() {
388
+ return this.connected;
389
+ }
390
+ getConnectionInfo() {
391
+ return {
392
+ connected: this.connected,
393
+ workspace_id: this.workspaceId,
394
+ room_id: this.roomId,
395
+ role: this.role,
396
+ participant_id: this.participantId,
397
+ base_url: this.baseUrl
398
+ };
399
+ }
400
+ onError(callback) {
401
+ this.onErrorCallback = callback;
402
+ }
403
+ onConnected(callback) {
404
+ this.onConnectedCallback = callback;
405
+ }
406
+ onDisconnected(callback) {
407
+ this.onDisconnectedCallback = callback;
408
+ }
409
+ async fetchApi(endpoint, options = {}) {
410
+ const url = `${this.apiBase}${endpoint}`;
411
+ const response = await fetch(url, {
412
+ ...options,
413
+ signal: AbortSignal.timeout(this.options.timeout || 5000)
414
+ });
415
+ if (!response.ok) {
416
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
417
+ }
418
+ return response.json();
419
+ }
420
+ generateWorkspaceId() {
421
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
422
+ const r = Math.random() * 16 | 0;
423
+ const v = c === "x" ? r : r & 3 | 8;
424
+ return v.toString(16);
425
+ });
426
+ }
427
+ }
428
+ // src/robotics/producer.ts
429
+ class RoboticsProducer extends RoboticsClientCore {
430
+ constructor(baseUrl = "http://localhost:8000", options = {}) {
431
+ super(baseUrl, options);
432
+ }
433
+ async connect(workspaceId, roomId, participantId) {
434
+ return this.connectToRoom(workspaceId, roomId, "producer", participantId);
435
+ }
436
+ async sendJointUpdate(joints) {
437
+ if (!this.connected || !this.websocket) {
438
+ throw new Error("Must be connected to send joint updates");
439
+ }
440
+ const message = {
441
+ type: "joint_update",
442
+ data: joints,
443
+ timestamp: new Date().toISOString()
444
+ };
445
+ this.websocket.send(JSON.stringify(message));
446
+ }
447
+ async sendStateSync(state) {
448
+ if (!this.connected || !this.websocket) {
449
+ throw new Error("Must be connected to send state sync");
450
+ }
451
+ const joints = Object.entries(state).map(([name, value]) => ({
452
+ name,
453
+ value
454
+ }));
455
+ await this.sendJointUpdate(joints);
456
+ }
457
+ async sendEmergencyStop(reason = "Emergency stop") {
458
+ if (!this.connected || !this.websocket) {
459
+ throw new Error("Must be connected to send emergency stop");
460
+ }
461
+ const message = {
462
+ type: "emergency_stop",
463
+ reason,
464
+ timestamp: new Date().toISOString()
465
+ };
466
+ this.websocket.send(JSON.stringify(message));
467
+ }
468
+ handleRoleSpecificMessage(message) {
469
+ switch (message.type) {
470
+ case "emergency_stop":
471
+ console.warn(`\uD83D\uDEA8 Emergency stop: ${message.reason || "Unknown reason"}`);
472
+ this.handleError(`Emergency stop: ${message.reason || "Unknown reason"}`);
473
+ break;
474
+ case "error":
475
+ console.error(`Server error: ${message.message}`);
476
+ this.handleError(message.message);
477
+ break;
478
+ default:
479
+ console.warn(`Unknown message type for producer: ${message.type}`);
480
+ }
481
+ }
482
+ static async createAndConnect(baseUrl = "http://localhost:8000", workspaceId, roomId, participantId) {
483
+ const producer = new RoboticsProducer(baseUrl);
484
+ const roomData = await producer.createRoom(workspaceId, roomId);
485
+ const connected = await producer.connect(roomData.workspaceId, roomData.roomId, participantId);
486
+ if (!connected) {
487
+ throw new Error("Failed to connect as producer");
488
+ }
489
+ return producer;
490
+ }
491
+ get currentRoomId() {
492
+ return this.roomId;
493
+ }
494
+ }
495
+ // src/robotics/consumer.ts
496
+ class RoboticsConsumer extends RoboticsClientCore {
497
+ onStateSyncCallback = null;
498
+ onJointUpdateCallback = null;
499
+ constructor(baseUrl = "http://localhost:8000", options = {}) {
500
+ super(baseUrl, options);
501
+ }
502
+ async connect(workspaceId, roomId, participantId) {
503
+ return this.connectToRoom(workspaceId, roomId, "consumer", participantId);
504
+ }
505
+ async getStateSyncAsync() {
506
+ if (!this.workspaceId || !this.roomId) {
507
+ throw new Error("Must be connected to a room");
508
+ }
509
+ const state = await this.getRoomState(this.workspaceId, this.roomId);
510
+ return state.joints;
511
+ }
512
+ onStateSync(callback) {
513
+ this.onStateSyncCallback = callback;
514
+ }
515
+ onJointUpdate(callback) {
516
+ this.onJointUpdateCallback = callback;
517
+ }
518
+ handleRoleSpecificMessage(message) {
519
+ switch (message.type) {
520
+ case "state_sync":
521
+ this.handleStateSync(message);
522
+ break;
523
+ case "joint_update":
524
+ this.handleJointUpdate(message);
525
+ break;
526
+ case "emergency_stop":
527
+ console.warn(`\uD83D\uDEA8 Emergency stop: ${message.reason || "Unknown reason"}`);
528
+ this.handleError(`Emergency stop: ${message.reason || "Unknown reason"}`);
529
+ break;
530
+ case "error":
531
+ console.error(`Server error: ${message.message}`);
532
+ this.handleError(message.message);
533
+ break;
534
+ default:
535
+ console.warn(`Unknown message type for consumer: ${message.type}`);
536
+ }
537
+ }
538
+ handleStateSync(message) {
539
+ if (this.onStateSyncCallback) {
540
+ this.onStateSyncCallback(message.data);
541
+ }
542
+ this.emit("stateSync", message.data);
543
+ }
544
+ handleJointUpdate(message) {
545
+ if (this.onJointUpdateCallback) {
546
+ this.onJointUpdateCallback(message.data);
547
+ }
548
+ this.emit("jointUpdate", message.data);
549
+ }
550
+ static async createAndConnect(workspaceId, roomId, baseUrl = "http://localhost:8000", participantId) {
551
+ const consumer = new RoboticsConsumer(baseUrl);
552
+ const connected = await consumer.connect(workspaceId, roomId, participantId);
553
+ if (!connected) {
554
+ throw new Error("Failed to connect as consumer");
555
+ }
556
+ return consumer;
557
+ }
558
+ }
559
+ // src/robotics/factory.ts
560
+ function createClient(role, baseUrl = "http://localhost:8000", options = {}) {
561
+ if (role === "producer") {
562
+ return new RoboticsProducer(baseUrl, options);
563
+ }
564
+ if (role === "consumer") {
565
+ return new RoboticsConsumer(baseUrl, options);
566
+ }
567
+ throw new Error(`Invalid role: ${role}. Must be 'producer' or 'consumer'`);
568
+ }
569
+ async function createProducerClient(baseUrl = "http://localhost:8000", workspaceId, roomId, participantId, options = {}) {
570
+ const producer = new RoboticsProducer(baseUrl, options);
571
+ const roomData = await producer.createRoom(workspaceId, roomId);
572
+ const connected = await producer.connect(roomData.workspaceId, roomData.roomId, participantId);
573
+ if (!connected) {
574
+ throw new Error("Failed to connect as producer");
575
+ }
576
+ return producer;
577
+ }
578
+ async function createConsumerClient(workspaceId, roomId, baseUrl = "http://localhost:8000", participantId, options = {}) {
579
+ const consumer = new RoboticsConsumer(baseUrl, options);
580
+ const connected = await consumer.connect(workspaceId, roomId, participantId);
581
+ if (!connected) {
582
+ throw new Error("Failed to connect as consumer");
583
+ }
584
+ return consumer;
585
+ }
586
+ // src/video/index.ts
587
+ var exports_video = {};
588
+ __export(exports_video, {
589
+ createProducerClient: () => createProducerClient2,
590
+ createConsumerClient: () => createConsumerClient2,
591
+ createClient: () => createClient2,
592
+ VideoProducer: () => VideoProducer,
593
+ VideoConsumer: () => VideoConsumer,
594
+ VideoClientCore: () => VideoClientCore
595
+ });
596
+
597
+ // src/video/core.ts
598
+ class VideoClientCore extends import__.default {
599
+ baseUrl;
600
+ apiBase;
601
+ websocket = null;
602
+ peerConnection = null;
603
+ localStream = null;
604
+ remoteStream = null;
605
+ workspaceId = null;
606
+ roomId = null;
607
+ role = null;
608
+ participantId = null;
609
+ connected = false;
610
+ options;
611
+ webrtcConfig;
612
+ onErrorCallback = null;
613
+ onConnectedCallback = null;
614
+ onDisconnectedCallback = null;
615
+ constructor(baseUrl = "http://localhost:8000", options = {}) {
616
+ super();
617
+ this.baseUrl = baseUrl.replace(/\/$/, "");
618
+ this.apiBase = `${this.baseUrl}/video`;
619
+ this.options = {
620
+ timeout: 5000,
621
+ reconnect_attempts: 3,
622
+ heartbeat_interval: 30000,
623
+ ...options
624
+ };
625
+ this.webrtcConfig = {
626
+ iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
627
+ constraints: {
628
+ video: {
629
+ width: { ideal: 640 },
630
+ height: { ideal: 480 },
631
+ frameRate: { ideal: 30 }
632
+ },
633
+ audio: false
634
+ },
635
+ bitrate: 1e6,
636
+ framerate: 30,
637
+ resolution: { width: 640, height: 480 },
638
+ codecPreferences: ["VP8", "H264"],
639
+ ...this.options.webrtc_config
640
+ };
641
+ }
642
+ async listRooms(workspaceId) {
643
+ const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms`);
644
+ return response.rooms;
645
+ }
646
+ async createRoom(workspaceId, roomId, config, recoveryConfig) {
647
+ const finalWorkspaceId = workspaceId || this.generateWorkspaceId();
648
+ const payload = {
649
+ room_id: roomId,
650
+ workspace_id: finalWorkspaceId,
651
+ config,
652
+ recovery_config: recoveryConfig
653
+ };
654
+ const response = await this.fetchApi(`/workspaces/${finalWorkspaceId}/rooms`, {
655
+ method: "POST",
656
+ headers: { "Content-Type": "application/json" },
657
+ body: JSON.stringify(payload)
658
+ });
659
+ return { workspaceId: response.workspace_id, roomId: response.room_id };
660
+ }
661
+ async deleteRoom(workspaceId, roomId) {
662
+ try {
663
+ const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}`, {
664
+ method: "DELETE"
665
+ });
666
+ return response.success;
667
+ } catch (error) {
668
+ if (error instanceof Error && error.message.includes("404")) {
669
+ return false;
670
+ }
671
+ throw error;
672
+ }
673
+ }
674
+ async getRoomState(workspaceId, roomId) {
675
+ const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}/state`);
676
+ return response.state;
677
+ }
678
+ async getRoomInfo(workspaceId, roomId) {
679
+ const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}`);
680
+ return response.room;
681
+ }
682
+ async sendWebRTCSignal(workspaceId, roomId, clientId, message) {
683
+ const request = { client_id: clientId, message };
684
+ return this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}/webrtc/signal`, {
685
+ method: "POST",
686
+ headers: { "Content-Type": "application/json" },
687
+ body: JSON.stringify(request)
688
+ });
689
+ }
690
+ async connectToRoom(workspaceId, roomId, role, participantId) {
691
+ if (this.connected) {
692
+ await this.disconnect();
693
+ }
694
+ this.workspaceId = workspaceId;
695
+ this.roomId = roomId;
696
+ this.role = role;
697
+ this.participantId = participantId || `${role}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
698
+ const wsUrl = this.baseUrl.replace(/^http/, "ws").replace(/^https/, "wss");
699
+ const wsEndpoint = `${wsUrl}/video/workspaces/${workspaceId}/rooms/${roomId}/ws`;
700
+ try {
701
+ this.websocket = new WebSocket(wsEndpoint);
702
+ return new Promise((resolve, reject) => {
703
+ const timeout = setTimeout(() => {
704
+ reject(new Error("Connection timeout"));
705
+ }, this.options.timeout || 5000);
706
+ this.websocket.onopen = () => {
707
+ clearTimeout(timeout);
708
+ this.sendJoinMessage();
709
+ };
710
+ this.websocket.onmessage = (event) => {
711
+ try {
712
+ const message = JSON.parse(event.data);
713
+ this.handleMessage(message);
714
+ if (message.type === "joined") {
715
+ this.connected = true;
716
+ this.onConnectedCallback?.();
717
+ this.emit("connected");
718
+ resolve(true);
719
+ } else if (message.type === "error") {
720
+ this.handleError(message.message);
721
+ resolve(false);
722
+ }
723
+ } catch (error) {
724
+ console.error("Failed to parse WebSocket message:", error);
725
+ }
726
+ };
727
+ this.websocket.onerror = (error) => {
728
+ clearTimeout(timeout);
729
+ console.error("WebSocket error:", error);
730
+ this.handleError("WebSocket connection error");
731
+ reject(error);
732
+ };
733
+ this.websocket.onclose = () => {
734
+ clearTimeout(timeout);
735
+ this.connected = false;
736
+ this.onDisconnectedCallback?.();
737
+ this.emit("disconnected");
738
+ };
739
+ });
740
+ } catch (error) {
741
+ console.error("Failed to connect to room:", error);
742
+ return false;
743
+ }
744
+ }
745
+ async disconnect() {
746
+ if (this.peerConnection) {
747
+ this.peerConnection.close();
748
+ this.peerConnection = null;
749
+ }
750
+ if (this.localStream) {
751
+ this.localStream.getTracks().forEach((track) => track.stop());
752
+ this.localStream = null;
753
+ }
754
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
755
+ this.websocket.close();
756
+ }
757
+ this.websocket = null;
758
+ this.remoteStream = null;
759
+ this.connected = false;
760
+ this.workspaceId = null;
761
+ this.roomId = null;
762
+ this.role = null;
763
+ this.participantId = null;
764
+ this.onDisconnectedCallback?.();
765
+ this.emit("disconnected");
766
+ }
767
+ createPeerConnection() {
768
+ const config = {
769
+ iceServers: this.webrtcConfig.iceServers || [
770
+ { urls: "stun:stun.l.google.com:19302" }
771
+ ]
772
+ };
773
+ this.peerConnection = new RTCPeerConnection(config);
774
+ this.peerConnection.onconnectionstatechange = () => {
775
+ const state = this.peerConnection?.connectionState;
776
+ console.info(`\uD83D\uDD0C WebRTC connection state: ${state}`);
777
+ };
778
+ this.peerConnection.oniceconnectionstatechange = () => {
779
+ const state = this.peerConnection?.iceConnectionState;
780
+ console.info(`\uD83E\uDDCA ICE connection state: ${state}`);
781
+ };
782
+ this.peerConnection.onicecandidate = (event) => {
783
+ if (event.candidate && this.workspaceId && this.roomId && this.participantId) {
784
+ this.sendWebRTCSignal(this.workspaceId, this.roomId, this.participantId, {
785
+ type: "ice",
786
+ candidate: event.candidate.toJSON()
787
+ });
788
+ }
789
+ };
790
+ this.peerConnection.ontrack = (event) => {
791
+ console.info("\uD83D\uDCFA Received remote track:", event.track.kind);
792
+ this.remoteStream = event.streams[0] || null;
793
+ this.emit("remoteStream", this.remoteStream);
794
+ };
795
+ return this.peerConnection;
796
+ }
797
+ async createOffer() {
798
+ if (!this.peerConnection) {
799
+ throw new Error("Peer connection not created");
800
+ }
801
+ const offer = await this.peerConnection.createOffer();
802
+ await this.peerConnection.setLocalDescription(offer);
803
+ return offer;
804
+ }
805
+ async createAnswer(offer) {
806
+ if (!this.peerConnection) {
807
+ throw new Error("Peer connection not created");
808
+ }
809
+ await this.peerConnection.setRemoteDescription(offer);
810
+ const answer = await this.peerConnection.createAnswer();
811
+ await this.peerConnection.setLocalDescription(answer);
812
+ return answer;
813
+ }
814
+ async setRemoteDescription(description) {
815
+ if (!this.peerConnection) {
816
+ throw new Error("Peer connection not created");
817
+ }
818
+ await this.peerConnection.setRemoteDescription(description);
819
+ }
820
+ async addIceCandidate(candidate) {
821
+ if (!this.peerConnection) {
822
+ throw new Error("Peer connection not created");
823
+ }
824
+ await this.peerConnection.addIceCandidate(candidate);
825
+ }
826
+ async startProducing(constraints) {
827
+ const mediaConstraints = constraints || this.webrtcConfig.constraints;
828
+ try {
829
+ this.localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
830
+ return this.localStream;
831
+ } catch (error) {
832
+ throw new Error(`Failed to start video production: ${error}`);
833
+ }
834
+ }
835
+ async startScreenShare() {
836
+ try {
837
+ this.localStream = await navigator.mediaDevices.getDisplayMedia({
838
+ video: {
839
+ width: this.webrtcConfig.resolution?.width || 1920,
840
+ height: this.webrtcConfig.resolution?.height || 1080,
841
+ frameRate: this.webrtcConfig.framerate || 30
842
+ },
843
+ audio: false
844
+ });
845
+ return this.localStream;
846
+ } catch (error) {
847
+ throw new Error(`Failed to start screen share: ${error}`);
848
+ }
849
+ }
850
+ stopProducing() {
851
+ if (this.localStream) {
852
+ this.localStream.getTracks().forEach((track) => track.stop());
853
+ this.localStream = null;
854
+ }
855
+ }
856
+ getLocalStream() {
857
+ return this.localStream;
858
+ }
859
+ getRemoteStream() {
860
+ return this.remoteStream;
861
+ }
862
+ getPeerConnection() {
863
+ return this.peerConnection;
864
+ }
865
+ async getStats() {
866
+ if (!this.peerConnection) {
867
+ return null;
868
+ }
869
+ const stats = await this.peerConnection.getStats();
870
+ return this.extractVideoStats(stats);
871
+ }
872
+ sendJoinMessage() {
873
+ if (!this.websocket || !this.participantId || !this.role)
874
+ return;
875
+ const joinMessage = {
876
+ participant_id: this.participantId,
877
+ role: this.role
878
+ };
879
+ this.websocket.send(JSON.stringify(joinMessage));
880
+ }
881
+ handleMessage(message) {
882
+ switch (message.type) {
883
+ case "joined":
884
+ console.log(`Successfully joined room ${message.room_id} as ${message.role}`);
885
+ break;
886
+ case "heartbeat_ack":
887
+ console.debug("Heartbeat acknowledged");
888
+ break;
889
+ case "error":
890
+ this.handleError(message.message);
891
+ break;
892
+ default:
893
+ this.handleRoleSpecificMessage(message);
894
+ }
895
+ }
896
+ handleRoleSpecificMessage(message) {
897
+ this.emit("message", message);
898
+ }
899
+ handleError(errorMessage) {
900
+ console.error("Video client error:", errorMessage);
901
+ this.onErrorCallback?.(errorMessage);
902
+ this.emit("error", errorMessage);
903
+ }
904
+ async sendHeartbeat() {
905
+ if (!this.connected || !this.websocket)
906
+ return;
907
+ const message = { type: "heartbeat" };
908
+ this.websocket.send(JSON.stringify(message));
909
+ }
910
+ isConnected() {
911
+ return this.connected;
912
+ }
913
+ getConnectionInfo() {
914
+ return {
915
+ connected: this.connected,
916
+ workspace_id: this.workspaceId,
917
+ room_id: this.roomId,
918
+ role: this.role,
919
+ participant_id: this.participantId,
920
+ base_url: this.baseUrl
921
+ };
922
+ }
923
+ onError(callback) {
924
+ this.onErrorCallback = callback;
925
+ }
926
+ onConnected(callback) {
927
+ this.onConnectedCallback = callback;
928
+ }
929
+ onDisconnected(callback) {
930
+ this.onDisconnectedCallback = callback;
931
+ }
932
+ async fetchApi(endpoint, options = {}) {
933
+ const url = `${this.apiBase}${endpoint}`;
934
+ const response = await fetch(url, {
935
+ ...options,
936
+ signal: AbortSignal.timeout(this.options.timeout || 5000)
937
+ });
938
+ if (!response.ok) {
939
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
940
+ }
941
+ return response.json();
942
+ }
943
+ extractVideoStats(stats) {
944
+ let inboundVideoStats = null;
945
+ let outboundVideoStats = null;
946
+ stats.forEach((report) => {
947
+ if (report.type === "inbound-rtp" && "kind" in report && report.kind === "video") {
948
+ inboundVideoStats = report;
949
+ } else if (report.type === "outbound-rtp" && "kind" in report && report.kind === "video") {
950
+ outboundVideoStats = report;
951
+ }
952
+ });
953
+ if (inboundVideoStats) {
954
+ return {
955
+ videoBitsPerSecond: inboundVideoStats.bytesReceived || 0,
956
+ framesPerSecond: inboundVideoStats.framesPerSecond || 0,
957
+ frameWidth: inboundVideoStats.frameWidth || 0,
958
+ frameHeight: inboundVideoStats.frameHeight || 0,
959
+ packetsLost: inboundVideoStats.packetsLost || 0,
960
+ totalPackets: inboundVideoStats.packetsReceived || inboundVideoStats.framesDecoded || 0
961
+ };
962
+ }
963
+ if (outboundVideoStats) {
964
+ return {
965
+ videoBitsPerSecond: outboundVideoStats.bytesSent || 0,
966
+ framesPerSecond: outboundVideoStats.framesPerSecond || 0,
967
+ frameWidth: outboundVideoStats.frameWidth || 0,
968
+ frameHeight: outboundVideoStats.frameHeight || 0,
969
+ packetsLost: outboundVideoStats.packetsLost || 0,
970
+ totalPackets: outboundVideoStats.packetsSent || outboundVideoStats.framesSent || 0
971
+ };
972
+ }
973
+ return null;
974
+ }
975
+ generateWorkspaceId() {
976
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
977
+ const r = Math.random() * 16 | 0;
978
+ const v = c === "x" ? r : r & 3 | 8;
979
+ return v.toString(16);
980
+ });
981
+ }
982
+ }
983
+ // src/video/producer.ts
984
+ class VideoProducer extends VideoClientCore {
985
+ consumerConnections = new Map;
986
+ constructor(baseUrl = "http://localhost:8000", options = {}) {
987
+ super(baseUrl, options);
988
+ }
989
+ async connect(workspaceId, roomId, participantId) {
990
+ const success = await this.connectToRoom(workspaceId, roomId, "producer", participantId);
991
+ if (success) {
992
+ this.on("consumer_joined", (consumerId) => {
993
+ console.info(`\uD83C\uDFAF Consumer ${consumerId} joined, initiating WebRTC...`);
994
+ this.initiateWebRTCWithConsumer(consumerId);
995
+ });
996
+ setTimeout(() => this.connectToExistingConsumers(), 1000);
997
+ }
998
+ return success;
999
+ }
1000
+ async connectToExistingConsumers() {
1001
+ if (!this.workspaceId || !this.roomId)
1002
+ return;
1003
+ try {
1004
+ const roomInfo = await this.getRoomInfo(this.workspaceId, this.roomId);
1005
+ for (const consumerId of roomInfo.participants.consumers) {
1006
+ if (!this.consumerConnections.has(consumerId)) {
1007
+ console.info(`\uD83D\uDD04 Connecting to existing consumer ${consumerId}`);
1008
+ await this.initiateWebRTCWithConsumer(consumerId);
1009
+ }
1010
+ }
1011
+ } catch (error) {
1012
+ console.error("Failed to connect to existing consumers:", error);
1013
+ }
1014
+ }
1015
+ createPeerConnectionForConsumer(consumerId) {
1016
+ const config = {
1017
+ iceServers: this.webrtcConfig.iceServers || [
1018
+ { urls: "stun:stun.l.google.com:19302" }
1019
+ ]
1020
+ };
1021
+ const peerConnection = new RTCPeerConnection(config);
1022
+ if (this.localStream) {
1023
+ this.localStream.getTracks().forEach((track) => {
1024
+ peerConnection.addTrack(track, this.localStream);
1025
+ });
1026
+ }
1027
+ peerConnection.onconnectionstatechange = () => {
1028
+ const state = peerConnection.connectionState;
1029
+ console.info(`\uD83D\uDD0C WebRTC connection state for ${consumerId}: ${state}`);
1030
+ if (state === "failed" || state === "disconnected") {
1031
+ console.warn(`Connection to ${consumerId} failed, attempting restart...`);
1032
+ setTimeout(() => this.restartConnectionToConsumer(consumerId), 2000);
1033
+ }
1034
+ };
1035
+ peerConnection.oniceconnectionstatechange = () => {
1036
+ const state = peerConnection.iceConnectionState;
1037
+ console.info(`\uD83E\uDDCA ICE connection state for ${consumerId}: ${state}`);
1038
+ };
1039
+ peerConnection.onicecandidate = (event) => {
1040
+ if (event.candidate && this.workspaceId && this.roomId && this.participantId) {
1041
+ this.sendWebRTCSignal(this.workspaceId, this.roomId, this.participantId, {
1042
+ type: "ice",
1043
+ candidate: event.candidate.toJSON(),
1044
+ target_consumer: consumerId
1045
+ });
1046
+ }
1047
+ };
1048
+ this.consumerConnections.set(consumerId, peerConnection);
1049
+ return peerConnection;
1050
+ }
1051
+ async restartConnectionToConsumer(consumerId) {
1052
+ console.info(`\uD83D\uDD04 Restarting connection to consumer ${consumerId}`);
1053
+ await this.initiateWebRTCWithConsumer(consumerId);
1054
+ }
1055
+ handleConsumerLeft(consumerId) {
1056
+ const peerConnection = this.consumerConnections.get(consumerId);
1057
+ if (peerConnection) {
1058
+ peerConnection.close();
1059
+ this.consumerConnections.delete(consumerId);
1060
+ console.info(`\uD83E\uDDF9 Cleaned up peer connection for consumer ${consumerId}`);
1061
+ }
1062
+ }
1063
+ async restartConnectionsWithNewStream(stream) {
1064
+ console.info("\uD83D\uDD04 Restarting connections with new stream...", { streamId: stream.id });
1065
+ for (const [consumerId, peerConnection] of this.consumerConnections) {
1066
+ peerConnection.close();
1067
+ console.info(`\uD83E\uDDF9 Closed existing connection to consumer ${consumerId}`);
1068
+ }
1069
+ this.consumerConnections.clear();
1070
+ try {
1071
+ if (this.workspaceId && this.roomId) {
1072
+ const roomInfo = await this.getRoomInfo(this.workspaceId, this.roomId);
1073
+ for (const consumerId of roomInfo.participants.consumers) {
1074
+ console.info(`\uD83D\uDD04 Creating new connection to consumer ${consumerId}...`);
1075
+ await this.initiateWebRTCWithConsumer(consumerId);
1076
+ }
1077
+ }
1078
+ } catch (error) {
1079
+ console.error("Failed to restart connections:", error);
1080
+ }
1081
+ }
1082
+ async startCamera(constraints) {
1083
+ if (!this.connected) {
1084
+ throw new Error("Must be connected to start camera");
1085
+ }
1086
+ const stream = await this.startProducing(constraints);
1087
+ this.localStream = stream;
1088
+ await this.restartConnectionsWithNewStream(stream);
1089
+ this.notifyStreamStarted(stream);
1090
+ return stream;
1091
+ }
1092
+ async startScreenShare() {
1093
+ if (!this.connected) {
1094
+ throw new Error("Must be connected to start screen share");
1095
+ }
1096
+ const stream = await super.startScreenShare();
1097
+ this.localStream = stream;
1098
+ await this.restartConnectionsWithNewStream(stream);
1099
+ this.notifyStreamStarted(stream);
1100
+ return stream;
1101
+ }
1102
+ async stopStreaming() {
1103
+ if (!this.connected || !this.websocket) {
1104
+ throw new Error("Must be connected to stop streaming");
1105
+ }
1106
+ for (const [consumerId, peerConnection] of this.consumerConnections) {
1107
+ peerConnection.close();
1108
+ console.info(`\uD83E\uDDF9 Closed connection to consumer ${consumerId}`);
1109
+ }
1110
+ this.consumerConnections.clear();
1111
+ this.stopProducing();
1112
+ this.notifyStreamStopped();
1113
+ }
1114
+ async updateVideoConfig(config) {
1115
+ if (!this.connected || !this.websocket) {
1116
+ throw new Error("Must be connected to update video config");
1117
+ }
1118
+ const message = {
1119
+ type: "video_config_update",
1120
+ config,
1121
+ timestamp: new Date().toISOString()
1122
+ };
1123
+ this.websocket.send(JSON.stringify(message));
1124
+ }
1125
+ async sendEmergencyStop(reason = "Emergency stop") {
1126
+ if (!this.connected || !this.websocket) {
1127
+ throw new Error("Must be connected to send emergency stop");
1128
+ }
1129
+ const message = {
1130
+ type: "emergency_stop",
1131
+ reason,
1132
+ timestamp: new Date().toISOString()
1133
+ };
1134
+ this.websocket.send(JSON.stringify(message));
1135
+ }
1136
+ async initiateWebRTCWithConsumer(consumerId) {
1137
+ if (!this.workspaceId || !this.roomId || !this.participantId) {
1138
+ console.warn("WebRTC not ready, skipping negotiation with consumer");
1139
+ return;
1140
+ }
1141
+ if (this.consumerConnections.has(consumerId)) {
1142
+ const existingConn = this.consumerConnections.get(consumerId);
1143
+ existingConn?.close();
1144
+ this.consumerConnections.delete(consumerId);
1145
+ }
1146
+ try {
1147
+ console.info(`\uD83D\uDD04 Creating WebRTC offer for consumer ${consumerId}...`);
1148
+ const peerConnection = this.createPeerConnectionForConsumer(consumerId);
1149
+ const offer = await peerConnection.createOffer();
1150
+ await peerConnection.setLocalDescription(offer);
1151
+ console.info(`\uD83D\uDCE4 Sending WebRTC offer to consumer ${consumerId}...`);
1152
+ await this.sendWebRTCSignal(this.workspaceId, this.roomId, this.participantId, {
1153
+ type: "offer",
1154
+ sdp: offer.sdp,
1155
+ target_consumer: consumerId
1156
+ });
1157
+ console.info(`\u2705 WebRTC offer sent to consumer ${consumerId}`);
1158
+ } catch (error) {
1159
+ console.error(`Failed to initiate WebRTC with consumer ${consumerId}:`, error);
1160
+ }
1161
+ }
1162
+ async handleWebRTCAnswer(message) {
1163
+ try {
1164
+ const consumerId = message.from_consumer;
1165
+ console.info(`\uD83D\uDCE5 Received WebRTC answer from consumer ${consumerId}`);
1166
+ const peerConnection = this.consumerConnections.get(consumerId);
1167
+ if (!peerConnection) {
1168
+ console.warn(`No peer connection found for consumer ${consumerId}`);
1169
+ return;
1170
+ }
1171
+ const answer = new RTCSessionDescription({
1172
+ type: "answer",
1173
+ sdp: message.answer.sdp
1174
+ });
1175
+ await peerConnection.setRemoteDescription(answer);
1176
+ console.info(`\u2705 WebRTC negotiation completed with consumer ${consumerId}`);
1177
+ } catch (error) {
1178
+ console.error(`Failed to handle WebRTC answer from ${message.from_consumer}:`, error);
1179
+ this.handleError(`Failed to handle WebRTC answer: ${error}`);
1180
+ }
1181
+ }
1182
+ async handleWebRTCIce(message) {
1183
+ try {
1184
+ const consumerId = message.from_consumer;
1185
+ if (!consumerId) {
1186
+ console.warn("No consumer ID in ICE message");
1187
+ return;
1188
+ }
1189
+ const peerConnection = this.consumerConnections.get(consumerId);
1190
+ if (!peerConnection) {
1191
+ console.warn(`No peer connection found for consumer ${consumerId}`);
1192
+ return;
1193
+ }
1194
+ console.info(`\uD83D\uDCE5 Received WebRTC ICE from consumer ${consumerId}`);
1195
+ const candidate = new RTCIceCandidate(message.candidate);
1196
+ await peerConnection.addIceCandidate(candidate);
1197
+ console.info(`\u2705 WebRTC ICE handled with consumer ${consumerId}`);
1198
+ } catch (error) {
1199
+ console.error(`Failed to handle WebRTC ICE from ${message.from_consumer}:`, error);
1200
+ this.handleError(`Failed to handle WebRTC ICE: ${error}`);
1201
+ }
1202
+ }
1203
+ handleRoleSpecificMessage(message) {
1204
+ switch (message.type) {
1205
+ case "participant_joined":
1206
+ if (message.role === "consumer" && message.participant_id !== this.participantId) {
1207
+ console.info(`\uD83C\uDFAF Consumer ${message.participant_id} joined room`);
1208
+ this.emit("consumer_joined", message.participant_id);
1209
+ }
1210
+ break;
1211
+ case "participant_left":
1212
+ if (message.role === "consumer") {
1213
+ console.info(`\uD83D\uDC4B Consumer ${message.participant_id} left room`);
1214
+ this.handleConsumerLeft(message.participant_id);
1215
+ }
1216
+ break;
1217
+ case "webrtc_answer":
1218
+ this.handleWebRTCAnswer(message);
1219
+ break;
1220
+ case "webrtc_ice":
1221
+ this.handleWebRTCIce(message);
1222
+ break;
1223
+ case "status_update":
1224
+ this.handleStatusUpdate(message);
1225
+ break;
1226
+ case "stream_stats":
1227
+ this.handleStreamStats(message);
1228
+ break;
1229
+ case "emergency_stop":
1230
+ console.warn(`\uD83D\uDEA8 Emergency stop: ${message.reason || "Unknown reason"}`);
1231
+ this.handleError(`Emergency stop: ${message.reason || "Unknown reason"}`);
1232
+ break;
1233
+ case "error":
1234
+ console.error(`Server error: ${message.message}`);
1235
+ this.handleError(message.message);
1236
+ break;
1237
+ default:
1238
+ console.warn(`Unknown message type for producer: ${message.type}`);
1239
+ }
1240
+ }
1241
+ handleStatusUpdate(message) {
1242
+ console.info(`\uD83D\uDCCA Status update: ${message.status}`, message.data);
1243
+ this.emit("statusUpdate", message.status, message.data);
1244
+ }
1245
+ handleStreamStats(message) {
1246
+ console.debug(`\uD83D\uDCC8 Stream stats:`, message.stats);
1247
+ this.emit("streamStats", message.stats);
1248
+ }
1249
+ async notifyStreamStarted(stream) {
1250
+ if (!this.websocket)
1251
+ return;
1252
+ const message = {
1253
+ type: "stream_started",
1254
+ config: {
1255
+ resolution: this.webrtcConfig.resolution,
1256
+ framerate: this.webrtcConfig.framerate,
1257
+ bitrate: this.webrtcConfig.bitrate
1258
+ },
1259
+ participant_id: this.participantId,
1260
+ timestamp: new Date().toISOString()
1261
+ };
1262
+ this.websocket.send(JSON.stringify(message));
1263
+ this.emit("streamStarted", stream);
1264
+ }
1265
+ async notifyStreamStopped() {
1266
+ if (!this.websocket)
1267
+ return;
1268
+ const message = {
1269
+ type: "stream_stopped",
1270
+ participant_id: this.participantId,
1271
+ timestamp: new Date().toISOString()
1272
+ };
1273
+ this.websocket.send(JSON.stringify(message));
1274
+ this.emit("streamStopped");
1275
+ }
1276
+ static async createAndConnect(baseUrl = "http://localhost:8000", workspaceId, roomId, participantId) {
1277
+ const producer = new VideoProducer(baseUrl);
1278
+ const roomData = await producer.createRoom(workspaceId, roomId);
1279
+ const connected = await producer.connect(roomData.workspaceId, roomData.roomId, participantId);
1280
+ if (!connected) {
1281
+ throw new Error("Failed to connect as video producer");
1282
+ }
1283
+ return producer;
1284
+ }
1285
+ get currentRoomId() {
1286
+ return this.roomId;
1287
+ }
1288
+ }
1289
+ // src/video/consumer.ts
1290
+ class VideoConsumer extends VideoClientCore {
1291
+ onFrameUpdateCallback = null;
1292
+ onVideoConfigUpdateCallback = null;
1293
+ onStreamStartedCallback = null;
1294
+ onStreamStoppedCallback = null;
1295
+ onRecoveryTriggeredCallback = null;
1296
+ onStatusUpdateCallback = null;
1297
+ onStreamStatsCallback = null;
1298
+ iceCandidateQueue = [];
1299
+ hasRemoteDescription = false;
1300
+ constructor(baseUrl = "http://localhost:8000", options = {}) {
1301
+ super(baseUrl, options);
1302
+ }
1303
+ async connect(workspaceId, roomId, participantId) {
1304
+ const connected = await this.connectToRoom(workspaceId, roomId, "consumer", participantId);
1305
+ if (connected) {
1306
+ console.info("\uD83D\uDD27 Creating peer connection for consumer...");
1307
+ await this.startReceiving();
1308
+ }
1309
+ return connected;
1310
+ }
1311
+ async startReceiving() {
1312
+ if (!this.connected) {
1313
+ throw new Error("Must be connected to start receiving");
1314
+ }
1315
+ this.hasRemoteDescription = false;
1316
+ this.iceCandidateQueue = [];
1317
+ this.createPeerConnection();
1318
+ if (this.peerConnection) {
1319
+ this.peerConnection.ontrack = (event) => {
1320
+ console.info("\uD83D\uDCFA Received remote track:", event.track.kind);
1321
+ this.remoteStream = event.streams[0] || null;
1322
+ this.emit("remoteStream", this.remoteStream);
1323
+ this.emit("streamReceived", this.remoteStream);
1324
+ };
1325
+ }
1326
+ }
1327
+ async stopReceiving() {
1328
+ if (this.peerConnection) {
1329
+ this.peerConnection.close();
1330
+ this.peerConnection = null;
1331
+ }
1332
+ this.remoteStream = null;
1333
+ this.emit("streamStopped");
1334
+ }
1335
+ async handleWebRTCOffer(message) {
1336
+ try {
1337
+ console.info(`\uD83D\uDCE5 Received WebRTC offer from producer ${message.from_producer}`);
1338
+ if (!this.peerConnection) {
1339
+ console.warn("No peer connection available to handle offer");
1340
+ return;
1341
+ }
1342
+ this.hasRemoteDescription = false;
1343
+ this.iceCandidateQueue = [];
1344
+ await this.setRemoteDescription(message.offer);
1345
+ this.hasRemoteDescription = true;
1346
+ await this.processQueuedIceCandidates();
1347
+ const answer = await this.createAnswer(message.offer);
1348
+ console.info(`\uD83D\uDCE4 Sending WebRTC answer to producer ${message.from_producer}`);
1349
+ if (this.workspaceId && this.roomId && this.participantId) {
1350
+ await this.sendWebRTCSignal(this.workspaceId, this.roomId, this.participantId, {
1351
+ type: "answer",
1352
+ sdp: answer.sdp,
1353
+ target_producer: message.from_producer
1354
+ });
1355
+ }
1356
+ console.info("\u2705 WebRTC negotiation completed from consumer side");
1357
+ } catch (error) {
1358
+ console.error("Failed to handle WebRTC offer:", error);
1359
+ this.handleError(`Failed to handle WebRTC offer: ${error}`);
1360
+ }
1361
+ }
1362
+ async handleWebRTCIce(message) {
1363
+ if (!this.peerConnection) {
1364
+ console.warn("No peer connection available to handle ICE");
1365
+ return;
1366
+ }
1367
+ try {
1368
+ console.info(`\uD83D\uDCE5 Received WebRTC ICE from producer ${message.from_producer}`);
1369
+ const candidate = new RTCIceCandidate(message.candidate);
1370
+ if (!this.hasRemoteDescription) {
1371
+ console.info(`\uD83D\uDD04 Queuing ICE candidate from ${message.from_producer} (no remote description yet)`);
1372
+ this.iceCandidateQueue.push({
1373
+ candidate,
1374
+ fromProducer: message.from_producer || "unknown"
1375
+ });
1376
+ return;
1377
+ }
1378
+ await this.peerConnection.addIceCandidate(candidate);
1379
+ console.info(`\u2705 WebRTC ICE handled from producer ${message.from_producer}`);
1380
+ } catch (error) {
1381
+ console.error(`Failed to handle WebRTC ICE from ${message.from_producer}:`, error);
1382
+ this.handleError(`Failed to handle WebRTC ICE: ${error}`);
1383
+ }
1384
+ }
1385
+ async processQueuedIceCandidates() {
1386
+ if (this.iceCandidateQueue.length === 0) {
1387
+ return;
1388
+ }
1389
+ console.info(`\uD83D\uDD04 Processing ${this.iceCandidateQueue.length} queued ICE candidates`);
1390
+ for (const { candidate, fromProducer } of this.iceCandidateQueue) {
1391
+ try {
1392
+ if (this.peerConnection) {
1393
+ await this.peerConnection.addIceCandidate(candidate);
1394
+ console.info(`\u2705 Processed queued ICE candidate from ${fromProducer}`);
1395
+ }
1396
+ } catch (error) {
1397
+ console.error(`Failed to process queued ICE candidate from ${fromProducer}:`, error);
1398
+ }
1399
+ }
1400
+ this.iceCandidateQueue = [];
1401
+ }
1402
+ createPeerConnection() {
1403
+ const config = {
1404
+ iceServers: this.webrtcConfig.iceServers || [
1405
+ { urls: "stun:stun.l.google.com:19302" }
1406
+ ]
1407
+ };
1408
+ this.peerConnection = new RTCPeerConnection(config);
1409
+ this.peerConnection.onconnectionstatechange = () => {
1410
+ const state = this.peerConnection?.connectionState;
1411
+ console.info(`\uD83D\uDD0C WebRTC connection state: ${state}`);
1412
+ };
1413
+ this.peerConnection.oniceconnectionstatechange = () => {
1414
+ const state = this.peerConnection?.iceConnectionState;
1415
+ console.info(`\uD83E\uDDCA ICE connection state: ${state}`);
1416
+ };
1417
+ this.peerConnection.onicecandidate = (event) => {
1418
+ if (event.candidate && this.workspaceId && this.roomId && this.participantId) {
1419
+ this.sendIceCandidateToProducer(event.candidate);
1420
+ }
1421
+ };
1422
+ this.peerConnection.ontrack = (event) => {
1423
+ console.info("\uD83D\uDCFA Received remote track:", event.track.kind);
1424
+ this.remoteStream = event.streams[0] || null;
1425
+ this.emit("remoteStream", this.remoteStream);
1426
+ this.emit("streamReceived", this.remoteStream);
1427
+ };
1428
+ return this.peerConnection;
1429
+ }
1430
+ async sendIceCandidateToProducer(candidate) {
1431
+ if (!this.workspaceId || !this.roomId || !this.participantId)
1432
+ return;
1433
+ try {
1434
+ const roomInfo = await this.getRoomInfo(this.workspaceId, this.roomId);
1435
+ if (roomInfo.participants.producer) {
1436
+ await this.sendWebRTCSignal(this.workspaceId, this.roomId, this.participantId, {
1437
+ type: "ice",
1438
+ candidate: candidate.toJSON(),
1439
+ target_producer: roomInfo.participants.producer
1440
+ });
1441
+ }
1442
+ } catch (error) {
1443
+ console.error("Failed to send ICE candidate to producer:", error);
1444
+ }
1445
+ }
1446
+ async handleStreamStarted(message) {
1447
+ if (this.onStreamStartedCallback) {
1448
+ this.onStreamStartedCallback(message.config, message.participant_id);
1449
+ }
1450
+ this.emit("streamStarted", message.config, message.participant_id);
1451
+ console.info(`\uD83D\uDE80 Stream started by producer ${message.participant_id}, ready to receive video`);
1452
+ }
1453
+ onFrameUpdate(callback) {
1454
+ this.onFrameUpdateCallback = callback;
1455
+ }
1456
+ onVideoConfigUpdate(callback) {
1457
+ this.onVideoConfigUpdateCallback = callback;
1458
+ }
1459
+ onStreamStarted(callback) {
1460
+ this.onStreamStartedCallback = callback;
1461
+ }
1462
+ onStreamStopped(callback) {
1463
+ this.onStreamStoppedCallback = callback;
1464
+ }
1465
+ onRecoveryTriggered(callback) {
1466
+ this.onRecoveryTriggeredCallback = callback;
1467
+ }
1468
+ onStatusUpdate(callback) {
1469
+ this.onStatusUpdateCallback = callback;
1470
+ }
1471
+ onStreamStats(callback) {
1472
+ this.onStreamStatsCallback = callback;
1473
+ }
1474
+ handleRoleSpecificMessage(message) {
1475
+ switch (message.type) {
1476
+ case "frame_update":
1477
+ this.handleFrameUpdate(message);
1478
+ break;
1479
+ case "video_config_update":
1480
+ this.handleVideoConfigUpdate(message);
1481
+ break;
1482
+ case "stream_started":
1483
+ this.handleStreamStarted(message);
1484
+ break;
1485
+ case "stream_stopped":
1486
+ this.handleStreamStopped(message);
1487
+ break;
1488
+ case "recovery_triggered":
1489
+ this.handleRecoveryTriggered(message);
1490
+ break;
1491
+ case "status_update":
1492
+ this.handleStatusUpdate(message);
1493
+ break;
1494
+ case "stream_stats":
1495
+ this.handleStreamStats(message);
1496
+ break;
1497
+ case "participant_joined":
1498
+ console.info(`\uD83D\uDCE5 Participant joined: ${message.participant_id} as ${message.role}`);
1499
+ break;
1500
+ case "participant_left":
1501
+ console.info(`\uD83D\uDCE4 Participant left: ${message.participant_id} (${message.role})`);
1502
+ break;
1503
+ case "webrtc_offer":
1504
+ this.handleWebRTCOffer(message);
1505
+ break;
1506
+ case "webrtc_answer":
1507
+ console.info("\uD83D\uDCE8 Received WebRTC answer (consumer should not receive this)");
1508
+ break;
1509
+ case "webrtc_ice":
1510
+ this.handleWebRTCIce(message);
1511
+ break;
1512
+ case "emergency_stop":
1513
+ console.warn(`\uD83D\uDEA8 Emergency stop: ${message.reason || "Unknown reason"}`);
1514
+ this.handleError(`Emergency stop: ${message.reason || "Unknown reason"}`);
1515
+ break;
1516
+ case "error":
1517
+ console.error(`Server error: ${message.message}`);
1518
+ this.handleError(message.message);
1519
+ break;
1520
+ default:
1521
+ console.warn(`Unknown message type for consumer: ${message.type}`);
1522
+ }
1523
+ }
1524
+ handleFrameUpdate(message) {
1525
+ if (this.onFrameUpdateCallback) {
1526
+ const frameData = {
1527
+ data: message.data,
1528
+ metadata: message.metadata
1529
+ };
1530
+ this.onFrameUpdateCallback(frameData);
1531
+ }
1532
+ this.emit("frameUpdate", message.data);
1533
+ }
1534
+ handleVideoConfigUpdate(message) {
1535
+ if (this.onVideoConfigUpdateCallback) {
1536
+ this.onVideoConfigUpdateCallback(message.config);
1537
+ }
1538
+ this.emit("videoConfigUpdate", message.config);
1539
+ }
1540
+ handleStreamStopped(message) {
1541
+ if (this.onStreamStoppedCallback) {
1542
+ this.onStreamStoppedCallback(message.participant_id, message.reason);
1543
+ }
1544
+ this.emit("streamStopped", message.participant_id, message.reason);
1545
+ }
1546
+ handleRecoveryTriggered(message) {
1547
+ if (this.onRecoveryTriggeredCallback) {
1548
+ this.onRecoveryTriggeredCallback(message.policy, message.reason);
1549
+ }
1550
+ this.emit("recoveryTriggered", message.policy, message.reason);
1551
+ }
1552
+ handleStatusUpdate(message) {
1553
+ if (this.onStatusUpdateCallback) {
1554
+ this.onStatusUpdateCallback(message.status, message.data);
1555
+ }
1556
+ this.emit("statusUpdate", message.status, message.data);
1557
+ }
1558
+ handleStreamStats(message) {
1559
+ if (this.onStreamStatsCallback) {
1560
+ this.onStreamStatsCallback(message.stats);
1561
+ }
1562
+ this.emit("streamStats", message.stats);
1563
+ }
1564
+ static async createAndConnect(workspaceId, roomId, baseUrl = "http://localhost:8000", participantId) {
1565
+ const consumer = new VideoConsumer(baseUrl);
1566
+ const connected = await consumer.connect(workspaceId, roomId, participantId);
1567
+ if (!connected) {
1568
+ throw new Error("Failed to connect as video consumer");
1569
+ }
1570
+ return consumer;
1571
+ }
1572
+ attachToVideoElement(videoElement) {
1573
+ if (this.remoteStream) {
1574
+ videoElement.srcObject = this.remoteStream;
1575
+ }
1576
+ this.on("remoteStream", (stream) => {
1577
+ videoElement.srcObject = stream;
1578
+ });
1579
+ }
1580
+ async getVideoStats() {
1581
+ const stats = await this.getStats();
1582
+ return stats;
1583
+ }
1584
+ }
1585
+ // src/video/factory.ts
1586
+ function createClient2(role, baseUrl = "http://localhost:8000", options = {}) {
1587
+ if (role === "producer") {
1588
+ return new VideoProducer(baseUrl, options);
1589
+ }
1590
+ if (role === "consumer") {
1591
+ return new VideoConsumer(baseUrl, options);
1592
+ }
1593
+ throw new Error(`Invalid role: ${role}. Must be 'producer' or 'consumer'`);
1594
+ }
1595
+ async function createProducerClient2(baseUrl = "http://localhost:8000", workspaceId, roomId, participantId, options = {}) {
1596
+ const producer = new VideoProducer(baseUrl, options);
1597
+ const roomData = await producer.createRoom(workspaceId, roomId);
1598
+ const connected = await producer.connect(roomData.workspaceId, roomData.roomId, participantId);
1599
+ if (!connected) {
1600
+ throw new Error("Failed to connect as video producer");
1601
+ }
1602
+ return producer;
1603
+ }
1604
+ async function createConsumerClient2(workspaceId, roomId, baseUrl = "http://localhost:8000", participantId, options = {}) {
1605
+ const consumer = new VideoConsumer(baseUrl, options);
1606
+ const connected = await consumer.connect(workspaceId, roomId, participantId);
1607
+ if (!connected) {
1608
+ throw new Error("Failed to connect as video consumer");
1609
+ }
1610
+ return consumer;
1611
+ }
1612
+ // src/index.ts
1613
+ var VERSION = "1.0.0";
1614
+ export {
1615
+ exports_video as video,
1616
+ exports_robotics as robotics,
1617
+ VERSION
1618
+ };
1619
+
1620
+ //# debugId=4A2D211384B624B064756E2164756E21
1621
+ //# sourceMappingURL=index.js.map
client/dist/index.js.map ADDED
The diff for this file is too large to render. See raw diff