blanchon's picture
Update
b83a268
// @bun
var __create = Object.create;
var __getProtoOf = Object.getPrototypeOf;
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __toESM = (mod, isNodeMode, target) => {
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: () => mod[key],
enumerable: true
});
return to;
};
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
});
};
// node_modules/eventemitter3/index.js
var require_eventemitter3 = __commonJS((exports, module) => {
var has = Object.prototype.hasOwnProperty;
var prefix = "~";
function Events() {}
if (Object.create) {
Events.prototype = Object.create(null);
if (!new Events().__proto__)
prefix = false;
}
function EE(fn, context, once) {
this.fn = fn;
this.context = context;
this.once = once || false;
}
function addListener(emitter, event, fn, context, once) {
if (typeof fn !== "function") {
throw new TypeError("The listener must be a function");
}
var listener = new EE(fn, context || emitter, once), evt = prefix ? prefix + event : event;
if (!emitter._events[evt])
emitter._events[evt] = listener, emitter._eventsCount++;
else if (!emitter._events[evt].fn)
emitter._events[evt].push(listener);
else
emitter._events[evt] = [emitter._events[evt], listener];
return emitter;
}
function clearEvent(emitter, evt) {
if (--emitter._eventsCount === 0)
emitter._events = new Events;
else
delete emitter._events[evt];
}
function EventEmitter() {
this._events = new Events;
this._eventsCount = 0;
}
EventEmitter.prototype.eventNames = function eventNames() {
var names = [], events, name;
if (this._eventsCount === 0)
return names;
for (name in events = this._events) {
if (has.call(events, name))
names.push(prefix ? name.slice(1) : name);
}
if (Object.getOwnPropertySymbols) {
return names.concat(Object.getOwnPropertySymbols(events));
}
return names;
};
EventEmitter.prototype.listeners = function listeners(event) {
var evt = prefix ? prefix + event : event, handlers = this._events[evt];
if (!handlers)
return [];
if (handlers.fn)
return [handlers.fn];
for (var i = 0, l = handlers.length, ee = new Array(l);i < l; i++) {
ee[i] = handlers[i].fn;
}
return ee;
};
EventEmitter.prototype.listenerCount = function listenerCount(event) {
var evt = prefix ? prefix + event : event, listeners = this._events[evt];
if (!listeners)
return 0;
if (listeners.fn)
return 1;
return listeners.length;
};
EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
var evt = prefix ? prefix + event : event;
if (!this._events[evt])
return false;
var listeners = this._events[evt], len = arguments.length, args, i;
if (listeners.fn) {
if (listeners.once)
this.removeListener(event, listeners.fn, undefined, true);
switch (len) {
case 1:
return listeners.fn.call(listeners.context), true;
case 2:
return listeners.fn.call(listeners.context, a1), true;
case 3:
return listeners.fn.call(listeners.context, a1, a2), true;
case 4:
return listeners.fn.call(listeners.context, a1, a2, a3), true;
case 5:
return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
case 6:
return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
}
for (i = 1, args = new Array(len - 1);i < len; i++) {
args[i - 1] = arguments[i];
}
listeners.fn.apply(listeners.context, args);
} else {
var length = listeners.length, j;
for (i = 0;i < length; i++) {
if (listeners[i].once)
this.removeListener(event, listeners[i].fn, undefined, true);
switch (len) {
case 1:
listeners[i].fn.call(listeners[i].context);
break;
case 2:
listeners[i].fn.call(listeners[i].context, a1);
break;
case 3:
listeners[i].fn.call(listeners[i].context, a1, a2);
break;
case 4:
listeners[i].fn.call(listeners[i].context, a1, a2, a3);
break;
default:
if (!args)
for (j = 1, args = new Array(len - 1);j < len; j++) {
args[j - 1] = arguments[j];
}
listeners[i].fn.apply(listeners[i].context, args);
}
}
}
return true;
};
EventEmitter.prototype.on = function on(event, fn, context) {
return addListener(this, event, fn, context, false);
};
EventEmitter.prototype.once = function once(event, fn, context) {
return addListener(this, event, fn, context, true);
};
EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
var evt = prefix ? prefix + event : event;
if (!this._events[evt])
return this;
if (!fn) {
clearEvent(this, evt);
return this;
}
var listeners = this._events[evt];
if (listeners.fn) {
if (listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context)) {
clearEvent(this, evt);
}
} else {
for (var i = 0, events = [], length = listeners.length;i < length; i++) {
if (listeners[i].fn !== fn || once && !listeners[i].once || context && listeners[i].context !== context) {
events.push(listeners[i]);
}
}
if (events.length)
this._events[evt] = events.length === 1 ? events[0] : events;
else
clearEvent(this, evt);
}
return this;
};
EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
var evt;
if (event) {
evt = prefix ? prefix + event : event;
if (this._events[evt])
clearEvent(this, evt);
} else {
this._events = new Events;
this._eventsCount = 0;
}
return this;
};
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
EventEmitter.prefixed = prefix;
EventEmitter.EventEmitter = EventEmitter;
if (typeof module !== "undefined") {
module.exports = EventEmitter;
}
});
// src/robotics/index.ts
var exports_robotics = {};
__export(exports_robotics, {
createProducerClient: () => createProducerClient,
createConsumerClient: () => createConsumerClient,
createClient: () => createClient,
RoboticsProducer: () => RoboticsProducer,
RoboticsConsumer: () => RoboticsConsumer,
RoboticsClientCore: () => RoboticsClientCore
});
// node_modules/eventemitter3/index.mjs
var import__ = __toESM(require_eventemitter3(), 1);
// src/robotics/core.ts
class RoboticsClientCore extends import__.default {
baseUrl;
apiBase;
websocket = null;
workspaceId = null;
roomId = null;
role = null;
participantId = null;
connected = false;
options;
onErrorCallback = null;
onConnectedCallback = null;
onDisconnectedCallback = null;
constructor(baseUrl, options = {}) {
super();
this.baseUrl = baseUrl.replace(/\/$/, "");
this.apiBase = `${this.baseUrl}/robotics`;
this.options = {
timeout: 5000,
reconnect_attempts: 3,
heartbeat_interval: 30000,
...options
};
}
async listRooms(workspaceId) {
const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms`);
return response.rooms;
}
async createRoom(workspaceId, roomId) {
const finalWorkspaceId = workspaceId || this.generateWorkspaceId();
const payload = roomId ? { room_id: roomId, workspace_id: finalWorkspaceId } : { workspace_id: finalWorkspaceId };
const response = await this.fetchApi(`/workspaces/${finalWorkspaceId}/rooms`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
return { workspaceId: response.workspace_id, roomId: response.room_id };
}
async deleteRoom(workspaceId, roomId) {
try {
const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}`, {
method: "DELETE"
});
return response.success;
} catch (error) {
if (error instanceof Error && error.message.includes("404")) {
return false;
}
throw error;
}
}
async getRoomState(workspaceId, roomId) {
const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}/state`);
return response.state;
}
async getRoomInfo(workspaceId, roomId) {
const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}`);
return response.room;
}
async connectToRoom(workspaceId, roomId, role, participantId) {
if (this.connected) {
await this.disconnect();
}
this.workspaceId = workspaceId;
this.roomId = roomId;
this.role = role;
this.participantId = participantId || `${role}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const wsUrl = this.baseUrl.replace(/^http/, "ws").replace(/^https/, "wss");
const wsEndpoint = `${wsUrl}/robotics/workspaces/${workspaceId}/rooms/${roomId}/ws`;
try {
this.websocket = new WebSocket(wsEndpoint);
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Connection timeout"));
}, this.options.timeout || 5000);
this.websocket.onopen = () => {
clearTimeout(timeout);
this.sendJoinMessage();
};
this.websocket.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.handleMessage(message);
if (message.type === "joined") {
this.connected = true;
this.onConnectedCallback?.();
this.emit("connected");
resolve(true);
} else if (message.type === "error") {
this.handleError(message.message);
resolve(false);
}
} catch (error) {
console.error("Failed to parse WebSocket message:", error);
}
};
this.websocket.onerror = (error) => {
clearTimeout(timeout);
console.error("WebSocket error:", error);
this.handleError("WebSocket connection error");
reject(error);
};
this.websocket.onclose = () => {
clearTimeout(timeout);
this.connected = false;
this.onDisconnectedCallback?.();
this.emit("disconnected");
};
});
} catch (error) {
console.error("Failed to connect to room:", error);
return false;
}
}
async disconnect() {
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
this.websocket.close();
}
this.websocket = null;
this.connected = false;
this.workspaceId = null;
this.roomId = null;
this.role = null;
this.participantId = null;
this.onDisconnectedCallback?.();
this.emit("disconnected");
}
sendJoinMessage() {
if (!this.websocket || !this.participantId || !this.role)
return;
const joinMessage = {
participant_id: this.participantId,
role: this.role
};
this.websocket.send(JSON.stringify(joinMessage));
}
handleMessage(message) {
switch (message.type) {
case "joined":
console.log(`Successfully joined room ${message.room_id} as ${message.role}`);
break;
case "heartbeat_ack":
console.debug("Heartbeat acknowledged");
break;
case "error":
this.handleError(message.message);
break;
default:
this.handleRoleSpecificMessage(message);
}
}
handleRoleSpecificMessage(message) {
this.emit("message", message);
}
handleError(errorMessage) {
console.error("Client error:", errorMessage);
this.onErrorCallback?.(errorMessage);
this.emit("error", errorMessage);
}
async sendHeartbeat() {
if (!this.connected || !this.websocket)
return;
const message = { type: "heartbeat" };
this.websocket.send(JSON.stringify(message));
}
isConnected() {
return this.connected;
}
getConnectionInfo() {
return {
connected: this.connected,
workspace_id: this.workspaceId,
room_id: this.roomId,
role: this.role,
participant_id: this.participantId,
base_url: this.baseUrl
};
}
onError(callback) {
this.onErrorCallback = callback;
}
onConnected(callback) {
this.onConnectedCallback = callback;
}
onDisconnected(callback) {
this.onDisconnectedCallback = callback;
}
async fetchApi(endpoint, options = {}) {
const url = `${this.apiBase}${endpoint}`;
const response = await fetch(url, {
...options,
signal: AbortSignal.timeout(this.options.timeout || 5000)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
generateWorkspaceId() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === "x" ? r : r & 3 | 8;
return v.toString(16);
});
}
}
// src/robotics/producer.ts
class RoboticsProducer extends RoboticsClientCore {
constructor(baseUrl, options = {}) {
super(baseUrl, options);
}
async connect(workspaceId, roomId, participantId) {
return this.connectToRoom(workspaceId, roomId, "producer", participantId);
}
async sendJointUpdate(joints) {
if (!this.connected || !this.websocket) {
throw new Error("Must be connected to send joint updates");
}
const message = {
type: "joint_update",
data: joints,
timestamp: new Date().toISOString()
};
this.websocket.send(JSON.stringify(message));
}
async sendStateSync(state) {
if (!this.connected || !this.websocket) {
throw new Error("Must be connected to send state sync");
}
const joints = Object.entries(state).map(([name, value]) => ({
name,
value
}));
await this.sendJointUpdate(joints);
}
async sendEmergencyStop(reason = "Emergency stop") {
if (!this.connected || !this.websocket) {
throw new Error("Must be connected to send emergency stop");
}
const message = {
type: "emergency_stop",
reason,
timestamp: new Date().toISOString()
};
this.websocket.send(JSON.stringify(message));
}
handleRoleSpecificMessage(message) {
switch (message.type) {
case "emergency_stop":
console.warn(`\uD83D\uDEA8 Emergency stop: ${message.reason || "Unknown reason"}`);
this.handleError(`Emergency stop: ${message.reason || "Unknown reason"}`);
break;
case "error":
console.error(`Server error: ${message.message}`);
this.handleError(message.message);
break;
default:
console.warn(`Unknown message type for producer: ${message.type}`);
}
}
static async createAndConnect(baseUrl, workspaceId, roomId, participantId) {
const producer = new RoboticsProducer(baseUrl);
const roomData = await producer.createRoom(workspaceId, roomId);
const connected = await producer.connect(roomData.workspaceId, roomData.roomId, participantId);
if (!connected) {
throw new Error("Failed to connect as producer");
}
return producer;
}
get currentRoomId() {
return this.roomId;
}
}
// src/robotics/consumer.ts
class RoboticsConsumer extends RoboticsClientCore {
onStateSyncCallback = null;
onJointUpdateCallback = null;
constructor(baseUrl, options = {}) {
super(baseUrl, options);
}
async connect(workspaceId, roomId, participantId) {
return this.connectToRoom(workspaceId, roomId, "consumer", participantId);
}
async getStateSyncAsync() {
if (!this.workspaceId || !this.roomId) {
throw new Error("Must be connected to a room");
}
const state = await this.getRoomState(this.workspaceId, this.roomId);
return state.joints;
}
onStateSync(callback) {
this.onStateSyncCallback = callback;
}
onJointUpdate(callback) {
this.onJointUpdateCallback = callback;
}
handleRoleSpecificMessage(message) {
switch (message.type) {
case "state_sync":
this.handleStateSync(message);
break;
case "joint_update":
this.handleJointUpdate(message);
break;
case "emergency_stop":
console.warn(`\uD83D\uDEA8 Emergency stop: ${message.reason || "Unknown reason"}`);
this.handleError(`Emergency stop: ${message.reason || "Unknown reason"}`);
break;
case "error":
console.error(`Server error: ${message.message}`);
this.handleError(message.message);
break;
default:
console.warn(`Unknown message type for consumer: ${message.type}`);
}
}
handleStateSync(message) {
if (this.onStateSyncCallback) {
this.onStateSyncCallback(message.data);
}
this.emit("stateSync", message.data);
}
handleJointUpdate(message) {
if (this.onJointUpdateCallback) {
this.onJointUpdateCallback(message.data);
}
this.emit("jointUpdate", message.data);
}
static async createAndConnect(workspaceId, roomId, baseUrl, participantId) {
const consumer = new RoboticsConsumer(baseUrl);
const connected = await consumer.connect(workspaceId, roomId, participantId);
if (!connected) {
throw new Error("Failed to connect as consumer");
}
return consumer;
}
}
// src/robotics/factory.ts
function createClient(role, baseUrl, options = {}) {
if (role === "producer") {
return new RoboticsProducer(baseUrl, options);
}
if (role === "consumer") {
return new RoboticsConsumer(baseUrl, options);
}
throw new Error(`Invalid role: ${role}. Must be 'producer' or 'consumer'`);
}
async function createProducerClient(baseUrl, workspaceId, roomId, participantId, options = {}) {
const producer = new RoboticsProducer(baseUrl, options);
const roomData = await producer.createRoom(workspaceId, roomId);
const connected = await producer.connect(roomData.workspaceId, roomData.roomId, participantId);
if (!connected) {
throw new Error("Failed to connect as producer");
}
return producer;
}
async function createConsumerClient(workspaceId, roomId, baseUrl, participantId, options = {}) {
const consumer = new RoboticsConsumer(baseUrl, options);
const connected = await consumer.connect(workspaceId, roomId, participantId);
if (!connected) {
throw new Error("Failed to connect as consumer");
}
return consumer;
}
// src/video/index.ts
var exports_video = {};
__export(exports_video, {
createProducerClient: () => createProducerClient2,
createConsumerClient: () => createConsumerClient2,
createClient: () => createClient2,
VideoProducer: () => VideoProducer,
VideoConsumer: () => VideoConsumer,
VideoClientCore: () => VideoClientCore
});
// src/video/core.ts
class VideoClientCore extends import__.default {
baseUrl;
apiBase;
websocket = null;
peerConnection = null;
localStream = null;
remoteStream = null;
workspaceId = null;
roomId = null;
role = null;
participantId = null;
connected = false;
options;
webrtcConfig;
onErrorCallback = null;
onConnectedCallback = null;
onDisconnectedCallback = null;
constructor(baseUrl, options = {}) {
super();
this.baseUrl = baseUrl.replace(/\/$/, "");
this.apiBase = `${this.baseUrl}/video`;
this.options = {
timeout: 5000,
reconnect_attempts: 3,
heartbeat_interval: 30000,
...options
};
this.webrtcConfig = {
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
constraints: {
video: {
width: { ideal: 640 },
height: { ideal: 480 },
frameRate: { ideal: 30 }
},
audio: false
},
bitrate: 1e6,
framerate: 30,
resolution: { width: 640, height: 480 },
codecPreferences: ["VP8", "H264"],
...this.options.webrtc_config
};
}
async listRooms(workspaceId) {
const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms`);
return response.rooms;
}
async createRoom(workspaceId, roomId, config, recoveryConfig) {
const finalWorkspaceId = workspaceId || this.generateWorkspaceId();
const payload = {
room_id: roomId,
workspace_id: finalWorkspaceId,
config,
recovery_config: recoveryConfig
};
const response = await this.fetchApi(`/workspaces/${finalWorkspaceId}/rooms`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
return { workspaceId: response.workspace_id, roomId: response.room_id };
}
async deleteRoom(workspaceId, roomId) {
try {
const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}`, {
method: "DELETE"
});
return response.success;
} catch (error) {
if (error instanceof Error && error.message.includes("404")) {
return false;
}
throw error;
}
}
async getRoomState(workspaceId, roomId) {
const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}/state`);
return response.state;
}
async getRoomInfo(workspaceId, roomId) {
const response = await this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}`);
return response.room;
}
async sendWebRTCSignal(workspaceId, roomId, clientId, message) {
const request = { client_id: clientId, message };
return this.fetchApi(`/workspaces/${workspaceId}/rooms/${roomId}/webrtc/signal`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request)
});
}
async connectToRoom(workspaceId, roomId, role, participantId) {
if (this.connected) {
await this.disconnect();
}
this.workspaceId = workspaceId;
this.roomId = roomId;
this.role = role;
this.participantId = participantId || `${role}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const wsUrl = this.baseUrl.replace(/^http/, "ws").replace(/^https/, "wss");
const wsEndpoint = `${wsUrl}/video/workspaces/${workspaceId}/rooms/${roomId}/ws`;
try {
this.websocket = new WebSocket(wsEndpoint);
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Connection timeout"));
}, this.options.timeout || 5000);
this.websocket.onopen = () => {
clearTimeout(timeout);
this.sendJoinMessage();
};
this.websocket.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.handleMessage(message);
if (message.type === "joined") {
this.connected = true;
this.onConnectedCallback?.();
this.emit("connected");
resolve(true);
} else if (message.type === "error") {
this.handleError(message.message);
resolve(false);
}
} catch (error) {
console.error("Failed to parse WebSocket message:", error);
}
};
this.websocket.onerror = (error) => {
clearTimeout(timeout);
console.error("WebSocket error:", error);
this.handleError("WebSocket connection error");
reject(error);
};
this.websocket.onclose = () => {
clearTimeout(timeout);
this.connected = false;
this.onDisconnectedCallback?.();
this.emit("disconnected");
};
});
} catch (error) {
console.error("Failed to connect to room:", error);
return false;
}
}
async disconnect() {
if (this.peerConnection) {
this.peerConnection.close();
this.peerConnection = null;
}
if (this.localStream) {
this.localStream.getTracks().forEach((track) => track.stop());
this.localStream = null;
}
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
this.websocket.close();
}
this.websocket = null;
this.remoteStream = null;
this.connected = false;
this.workspaceId = null;
this.roomId = null;
this.role = null;
this.participantId = null;
this.onDisconnectedCallback?.();
this.emit("disconnected");
}
createPeerConnection() {
const config = {
iceServers: this.webrtcConfig.iceServers || [
{ urls: "stun:stun.l.google.com:19302" }
]
};
this.peerConnection = new RTCPeerConnection(config);
this.peerConnection.onconnectionstatechange = () => {
const state = this.peerConnection?.connectionState;
console.info(`\uD83D\uDD0C WebRTC connection state: ${state}`);
};
this.peerConnection.oniceconnectionstatechange = () => {
const state = this.peerConnection?.iceConnectionState;
console.info(`\uD83E\uDDCA ICE connection state: ${state}`);
};
this.peerConnection.onicecandidate = (event) => {
if (event.candidate && this.workspaceId && this.roomId && this.participantId) {
this.sendWebRTCSignal(this.workspaceId, this.roomId, this.participantId, {
type: "ice",
candidate: event.candidate.toJSON()
});
}
};
this.peerConnection.ontrack = (event) => {
console.info("\uD83D\uDCFA Received remote track:", event.track.kind);
this.remoteStream = event.streams[0] || null;
this.emit("remoteStream", this.remoteStream);
};
return this.peerConnection;
}
async createOffer() {
if (!this.peerConnection) {
throw new Error("Peer connection not created");
}
const offer = await this.peerConnection.createOffer();
await this.peerConnection.setLocalDescription(offer);
return offer;
}
async createAnswer(offer) {
if (!this.peerConnection) {
throw new Error("Peer connection not created");
}
await this.peerConnection.setRemoteDescription(offer);
const answer = await this.peerConnection.createAnswer();
await this.peerConnection.setLocalDescription(answer);
return answer;
}
async setRemoteDescription(description) {
if (!this.peerConnection) {
throw new Error("Peer connection not created");
}
await this.peerConnection.setRemoteDescription(description);
}
async addIceCandidate(candidate) {
if (!this.peerConnection) {
throw new Error("Peer connection not created");
}
await this.peerConnection.addIceCandidate(candidate);
}
async startProducing(constraints) {
const mediaConstraints = constraints || this.webrtcConfig.constraints;
try {
this.localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
return this.localStream;
} catch (error) {
throw new Error(`Failed to start video production: ${error}`);
}
}
async startScreenShare() {
try {
this.localStream = await navigator.mediaDevices.getDisplayMedia({
video: {
width: this.webrtcConfig.resolution?.width || 1920,
height: this.webrtcConfig.resolution?.height || 1080,
frameRate: this.webrtcConfig.framerate || 30
},
audio: false
});
return this.localStream;
} catch (error) {
throw new Error(`Failed to start screen share: ${error}`);
}
}
stopProducing() {
if (this.localStream) {
this.localStream.getTracks().forEach((track) => track.stop());
this.localStream = null;
}
}
getLocalStream() {
return this.localStream;
}
getRemoteStream() {
return this.remoteStream;
}
getPeerConnection() {
return this.peerConnection;
}
async getStats() {
if (!this.peerConnection) {
return null;
}
const stats = await this.peerConnection.getStats();
return this.extractVideoStats(stats);
}
sendJoinMessage() {
if (!this.websocket || !this.participantId || !this.role)
return;
const joinMessage = {
participant_id: this.participantId,
role: this.role
};
this.websocket.send(JSON.stringify(joinMessage));
}
handleMessage(message) {
switch (message.type) {
case "joined":
console.log(`Successfully joined room ${message.room_id} as ${message.role}`);
break;
case "heartbeat_ack":
console.debug("Heartbeat acknowledged");
break;
case "error":
this.handleError(message.message);
break;
default:
this.handleRoleSpecificMessage(message);
}
}
handleRoleSpecificMessage(message) {
this.emit("message", message);
}
handleError(errorMessage) {
console.error("Video client error:", errorMessage);
this.onErrorCallback?.(errorMessage);
this.emit("error", errorMessage);
}
async sendHeartbeat() {
if (!this.connected || !this.websocket)
return;
const message = { type: "heartbeat" };
this.websocket.send(JSON.stringify(message));
}
isConnected() {
return this.connected;
}
getConnectionInfo() {
return {
connected: this.connected,
workspace_id: this.workspaceId,
room_id: this.roomId,
role: this.role,
participant_id: this.participantId,
base_url: this.baseUrl
};
}
onError(callback) {
this.onErrorCallback = callback;
}
onConnected(callback) {
this.onConnectedCallback = callback;
}
onDisconnected(callback) {
this.onDisconnectedCallback = callback;
}
async fetchApi(endpoint, options = {}) {
const url = `${this.apiBase}${endpoint}`;
const response = await fetch(url, {
...options,
signal: AbortSignal.timeout(this.options.timeout || 5000)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
extractVideoStats(stats) {
let inboundVideoStats = null;
let outboundVideoStats = null;
stats.forEach((report) => {
if (report.type === "inbound-rtp" && "kind" in report && report.kind === "video") {
inboundVideoStats = report;
} else if (report.type === "outbound-rtp" && "kind" in report && report.kind === "video") {
outboundVideoStats = report;
}
});
if (inboundVideoStats) {
return {
videoBitsPerSecond: inboundVideoStats.bytesReceived || 0,
framesPerSecond: inboundVideoStats.framesPerSecond || 0,
frameWidth: inboundVideoStats.frameWidth || 0,
frameHeight: inboundVideoStats.frameHeight || 0,
packetsLost: inboundVideoStats.packetsLost || 0,
totalPackets: inboundVideoStats.packetsReceived || inboundVideoStats.framesDecoded || 0
};
}
if (outboundVideoStats) {
return {
videoBitsPerSecond: outboundVideoStats.bytesSent || 0,
framesPerSecond: outboundVideoStats.framesPerSecond || 0,
frameWidth: outboundVideoStats.frameWidth || 0,
frameHeight: outboundVideoStats.frameHeight || 0,
packetsLost: outboundVideoStats.packetsLost || 0,
totalPackets: outboundVideoStats.packetsSent || outboundVideoStats.framesSent || 0
};
}
return null;
}
generateWorkspaceId() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === "x" ? r : r & 3 | 8;
return v.toString(16);
});
}
}
// src/video/producer.ts
class VideoProducer extends VideoClientCore {
consumerConnections = new Map;
constructor(baseUrl, options = {}) {
super(baseUrl, options);
}
async connect(workspaceId, roomId, participantId) {
const success = await this.connectToRoom(workspaceId, roomId, "producer", participantId);
if (success) {
this.on("consumer_joined", (consumerId) => {
console.info(`\uD83C\uDFAF Consumer ${consumerId} joined, initiating WebRTC...`);
this.initiateWebRTCWithConsumer(consumerId);
});
setTimeout(() => this.connectToExistingConsumers(), 1000);
}
return success;
}
async connectToExistingConsumers() {
if (!this.workspaceId || !this.roomId)
return;
try {
const roomInfo = await this.getRoomInfo(this.workspaceId, this.roomId);
for (const consumerId of roomInfo.participants.consumers) {
if (!this.consumerConnections.has(consumerId)) {
console.info(`\uD83D\uDD04 Connecting to existing consumer ${consumerId}`);
await this.initiateWebRTCWithConsumer(consumerId);
}
}
} catch (error) {
console.error("Failed to connect to existing consumers:", error);
}
}
createPeerConnectionForConsumer(consumerId) {
const config = {
iceServers: this.webrtcConfig.iceServers || [
{ urls: "stun:stun.l.google.com:19302" }
]
};
const peerConnection = new RTCPeerConnection(config);
if (this.localStream) {
this.localStream.getTracks().forEach((track) => {
peerConnection.addTrack(track, this.localStream);
});
}
peerConnection.onconnectionstatechange = () => {
const state = peerConnection.connectionState;
console.info(`\uD83D\uDD0C WebRTC connection state for ${consumerId}: ${state}`);
if (state === "failed" || state === "disconnected") {
console.warn(`Connection to ${consumerId} failed, attempting restart...`);
setTimeout(() => this.restartConnectionToConsumer(consumerId), 2000);
}
};
peerConnection.oniceconnectionstatechange = () => {
const state = peerConnection.iceConnectionState;
console.info(`\uD83E\uDDCA ICE connection state for ${consumerId}: ${state}`);
};
peerConnection.onicecandidate = (event) => {
if (event.candidate && this.workspaceId && this.roomId && this.participantId) {
this.sendWebRTCSignal(this.workspaceId, this.roomId, this.participantId, {
type: "ice",
candidate: event.candidate.toJSON(),
target_consumer: consumerId
});
}
};
this.consumerConnections.set(consumerId, peerConnection);
return peerConnection;
}
async restartConnectionToConsumer(consumerId) {
console.info(`\uD83D\uDD04 Restarting connection to consumer ${consumerId}`);
await this.initiateWebRTCWithConsumer(consumerId);
}
handleConsumerLeft(consumerId) {
const peerConnection = this.consumerConnections.get(consumerId);
if (peerConnection) {
peerConnection.close();
this.consumerConnections.delete(consumerId);
console.info(`\uD83E\uDDF9 Cleaned up peer connection for consumer ${consumerId}`);
}
}
async restartConnectionsWithNewStream(stream) {
console.info("\uD83D\uDD04 Restarting connections with new stream...", { streamId: stream.id });
for (const entry of Array.from(this.consumerConnections.entries())) {
const [consumerId, peerConnection] = entry;
peerConnection.close();
console.info(`\uD83E\uDDF9 Closed existing connection to consumer ${consumerId}`);
}
this.consumerConnections.clear();
try {
if (this.workspaceId && this.roomId) {
const roomInfo = await this.getRoomInfo(this.workspaceId, this.roomId);
for (const consumerId of roomInfo.participants.consumers) {
console.info(`\uD83D\uDD04 Creating new connection to consumer ${consumerId}...`);
await this.initiateWebRTCWithConsumer(consumerId);
}
}
} catch (error) {
console.error("Failed to restart connections:", error);
}
}
async startCamera(constraints) {
if (!this.connected) {
throw new Error("Must be connected to start camera");
}
const stream = await this.startProducing(constraints);
this.localStream = stream;
await this.restartConnectionsWithNewStream(stream);
this.notifyStreamStarted(stream);
return stream;
}
async startScreenShare() {
if (!this.connected) {
throw new Error("Must be connected to start screen share");
}
const stream = await super.startScreenShare();
this.localStream = stream;
await this.restartConnectionsWithNewStream(stream);
this.notifyStreamStarted(stream);
return stream;
}
async stopStreaming() {
if (!this.connected || !this.websocket) {
throw new Error("Must be connected to stop streaming");
}
for (const consumerId of this.consumerConnections.keys()) {
const peerConnection = this.consumerConnections.get(consumerId);
peerConnection?.close();
console.info(`\uD83E\uDDF9 Closed connection to consumer ${consumerId}`);
}
this.consumerConnections.clear();
this.stopProducing();
this.notifyStreamStopped();
}
async updateVideoConfig(config) {
if (!this.connected || !this.websocket) {
throw new Error("Must be connected to update video config");
}
const message = {
type: "video_config_update",
config,
timestamp: new Date().toISOString()
};
this.websocket.send(JSON.stringify(message));
}
async sendEmergencyStop(reason = "Emergency stop") {
if (!this.connected || !this.websocket) {
throw new Error("Must be connected to send emergency stop");
}
const message = {
type: "emergency_stop",
reason,
timestamp: new Date().toISOString()
};
this.websocket.send(JSON.stringify(message));
}
async initiateWebRTCWithConsumer(consumerId) {
if (!this.workspaceId || !this.roomId || !this.participantId) {
console.warn("WebRTC not ready, skipping negotiation with consumer");
return;
}
if (this.consumerConnections.has(consumerId)) {
const existingConn = this.consumerConnections.get(consumerId);
existingConn?.close();
this.consumerConnections.delete(consumerId);
}
try {
console.info(`\uD83D\uDD04 Creating WebRTC offer for consumer ${consumerId}...`);
const peerConnection = this.createPeerConnectionForConsumer(consumerId);
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
console.info(`\uD83D\uDCE4 Sending WebRTC offer to consumer ${consumerId}...`);
await this.sendWebRTCSignal(this.workspaceId, this.roomId, this.participantId, {
type: "offer",
sdp: offer.sdp,
target_consumer: consumerId
});
console.info(`\u2705 WebRTC offer sent to consumer ${consumerId}`);
} catch (error) {
console.error(`Failed to initiate WebRTC with consumer ${consumerId}:`, error);
}
}
async handleWebRTCAnswer(message) {
try {
const consumerId = message.from_consumer;
console.info(`\uD83D\uDCE5 Received WebRTC answer from consumer ${consumerId}`);
const peerConnection = this.consumerConnections.get(consumerId);
if (!peerConnection) {
console.warn(`No peer connection found for consumer ${consumerId}`);
return;
}
const answer = new RTCSessionDescription({
type: "answer",
sdp: message.answer.sdp
});
await peerConnection.setRemoteDescription(answer);
console.info(`\u2705 WebRTC negotiation completed with consumer ${consumerId}`);
} catch (error) {
console.error(`Failed to handle WebRTC answer from ${message.from_consumer}:`, error);
this.handleError(`Failed to handle WebRTC answer: ${error}`);
}
}
async handleWebRTCIce(message) {
try {
const consumerId = message.from_consumer;
if (!consumerId) {
console.warn("No consumer ID in ICE message");
return;
}
const peerConnection = this.consumerConnections.get(consumerId);
if (!peerConnection) {
console.warn(`No peer connection found for consumer ${consumerId}`);
return;
}
console.info(`\uD83D\uDCE5 Received WebRTC ICE from consumer ${consumerId}`);
const candidate = new RTCIceCandidate(message.candidate);
await peerConnection.addIceCandidate(candidate);
console.info(`\u2705 WebRTC ICE handled with consumer ${consumerId}`);
} catch (error) {
console.error(`Failed to handle WebRTC ICE from ${message.from_consumer}:`, error);
this.handleError(`Failed to handle WebRTC ICE: ${error}`);
}
}
handleRoleSpecificMessage(message) {
switch (message.type) {
case "participant_joined":
if (message.role === "consumer" && message.participant_id !== this.participantId) {
console.info(`\uD83C\uDFAF Consumer ${message.participant_id} joined room`);
this.emit("consumer_joined", message.participant_id);
}
break;
case "participant_left":
if (message.role === "consumer") {
console.info(`\uD83D\uDC4B Consumer ${message.participant_id} left room`);
this.handleConsumerLeft(message.participant_id);
}
break;
case "webrtc_answer":
this.handleWebRTCAnswer(message);
break;
case "webrtc_ice":
this.handleWebRTCIce(message);
break;
case "status_update":
this.handleStatusUpdate(message);
break;
case "stream_stats":
this.handleStreamStats(message);
break;
case "emergency_stop":
console.warn(`\uD83D\uDEA8 Emergency stop: ${message.reason || "Unknown reason"}`);
this.handleError(`Emergency stop: ${message.reason || "Unknown reason"}`);
break;
case "error":
console.error(`Server error: ${message.message}`);
this.handleError(message.message);
break;
default:
console.warn(`Unknown message type for producer: ${message.type}`);
}
}
handleStatusUpdate(message) {
console.info(`\uD83D\uDCCA Status update: ${message.status}`, message.data);
this.emit("statusUpdate", message.status, message.data);
}
handleStreamStats(message) {
console.debug(`\uD83D\uDCC8 Stream stats:`, message.stats);
this.emit("streamStats", message.stats);
}
async notifyStreamStarted(stream) {
if (!this.websocket)
return;
const message = {
type: "stream_started",
config: {
resolution: this.webrtcConfig.resolution,
framerate: this.webrtcConfig.framerate,
bitrate: this.webrtcConfig.bitrate
},
participant_id: this.participantId,
timestamp: new Date().toISOString()
};
this.websocket.send(JSON.stringify(message));
this.emit("streamStarted", stream);
}
async notifyStreamStopped() {
if (!this.websocket)
return;
const message = {
type: "stream_stopped",
participant_id: this.participantId,
timestamp: new Date().toISOString()
};
this.websocket.send(JSON.stringify(message));
this.emit("streamStopped");
}
static async createAndConnect(baseUrl, workspaceId, roomId, participantId) {
const producer = new VideoProducer(baseUrl);
const roomData = await producer.createRoom(workspaceId, roomId);
const connected = await producer.connect(roomData.workspaceId, roomData.roomId, participantId);
if (!connected) {
throw new Error("Failed to connect as video producer");
}
return producer;
}
get currentRoomId() {
return this.roomId;
}
}
// src/video/consumer.ts
class VideoConsumer extends VideoClientCore {
onFrameUpdateCallback = null;
onVideoConfigUpdateCallback = null;
onStreamStartedCallback = null;
onStreamStoppedCallback = null;
onRecoveryTriggeredCallback = null;
onStatusUpdateCallback = null;
onStreamStatsCallback = null;
iceCandidateQueue = [];
hasRemoteDescription = false;
constructor(baseUrl, options = {}) {
super(baseUrl, options);
}
async connect(workspaceId, roomId, participantId) {
const connected = await this.connectToRoom(workspaceId, roomId, "consumer", participantId);
if (connected) {
console.info("\uD83D\uDD27 Creating peer connection for consumer...");
await this.startReceiving();
}
return connected;
}
async startReceiving() {
if (!this.connected) {
throw new Error("Must be connected to start receiving");
}
this.hasRemoteDescription = false;
this.iceCandidateQueue = [];
this.createPeerConnection();
if (this.peerConnection) {
this.peerConnection.ontrack = (event) => {
console.info("\uD83D\uDCFA Received remote track:", event.track.kind);
this.remoteStream = event.streams[0] || null;
this.emit("remoteStream", this.remoteStream);
this.emit("streamReceived", this.remoteStream);
};
}
}
async stopReceiving() {
if (this.peerConnection) {
this.peerConnection.close();
this.peerConnection = null;
}
this.remoteStream = null;
this.emit("streamStopped");
}
async handleWebRTCOffer(message) {
try {
console.info(`\uD83D\uDCE5 Received WebRTC offer from producer ${message.from_producer}`);
if (!this.peerConnection) {
console.warn("No peer connection available to handle offer");
return;
}
this.hasRemoteDescription = false;
this.iceCandidateQueue = [];
await this.setRemoteDescription(message.offer);
this.hasRemoteDescription = true;
await this.processQueuedIceCandidates();
const answer = await this.createAnswer(message.offer);
console.info(`\uD83D\uDCE4 Sending WebRTC answer to producer ${message.from_producer}`);
if (this.workspaceId && this.roomId && this.participantId) {
await this.sendWebRTCSignal(this.workspaceId, this.roomId, this.participantId, {
type: "answer",
sdp: answer.sdp,
target_producer: message.from_producer
});
}
console.info("\u2705 WebRTC negotiation completed from consumer side");
} catch (error) {
console.error("Failed to handle WebRTC offer:", error);
this.handleError(`Failed to handle WebRTC offer: ${error}`);
}
}
async handleWebRTCIce(message) {
if (!this.peerConnection) {
console.warn("No peer connection available to handle ICE");
return;
}
try {
console.info(`\uD83D\uDCE5 Received WebRTC ICE from producer ${message.from_producer}`);
const candidate = new RTCIceCandidate(message.candidate);
if (!this.hasRemoteDescription) {
console.info(`\uD83D\uDD04 Queuing ICE candidate from ${message.from_producer} (no remote description yet)`);
this.iceCandidateQueue.push({
candidate,
fromProducer: message.from_producer || "unknown"
});
return;
}
await this.peerConnection.addIceCandidate(candidate);
console.info(`\u2705 WebRTC ICE handled from producer ${message.from_producer}`);
} catch (error) {
console.error(`Failed to handle WebRTC ICE from ${message.from_producer}:`, error);
this.handleError(`Failed to handle WebRTC ICE: ${error}`);
}
}
async processQueuedIceCandidates() {
if (this.iceCandidateQueue.length === 0) {
return;
}
console.info(`\uD83D\uDD04 Processing ${this.iceCandidateQueue.length} queued ICE candidates`);
for (const { candidate, fromProducer } of this.iceCandidateQueue) {
try {
if (this.peerConnection) {
await this.peerConnection.addIceCandidate(candidate);
console.info(`\u2705 Processed queued ICE candidate from ${fromProducer}`);
}
} catch (error) {
console.error(`Failed to process queued ICE candidate from ${fromProducer}:`, error);
}
}
this.iceCandidateQueue = [];
}
createPeerConnection() {
const config = {
iceServers: this.webrtcConfig.iceServers || [
{ urls: "stun:stun.l.google.com:19302" }
]
};
this.peerConnection = new RTCPeerConnection(config);
this.peerConnection.onconnectionstatechange = () => {
const state = this.peerConnection?.connectionState;
console.info(`\uD83D\uDD0C WebRTC connection state: ${state}`);
};
this.peerConnection.oniceconnectionstatechange = () => {
const state = this.peerConnection?.iceConnectionState;
console.info(`\uD83E\uDDCA ICE connection state: ${state}`);
};
this.peerConnection.onicecandidate = (event) => {
if (event.candidate && this.workspaceId && this.roomId && this.participantId) {
this.sendIceCandidateToProducer(event.candidate);
}
};
this.peerConnection.ontrack = (event) => {
console.info("\uD83D\uDCFA Received remote track:", event.track.kind);
this.remoteStream = event.streams[0] || null;
this.emit("remoteStream", this.remoteStream);
this.emit("streamReceived", this.remoteStream);
};
return this.peerConnection;
}
async sendIceCandidateToProducer(candidate) {
if (!this.workspaceId || !this.roomId || !this.participantId)
return;
try {
const roomInfo = await this.getRoomInfo(this.workspaceId, this.roomId);
if (roomInfo.participants.producer) {
await this.sendWebRTCSignal(this.workspaceId, this.roomId, this.participantId, {
type: "ice",
candidate: candidate.toJSON(),
target_producer: roomInfo.participants.producer
});
}
} catch (error) {
console.error("Failed to send ICE candidate to producer:", error);
}
}
async handleStreamStarted(message) {
if (this.onStreamStartedCallback) {
this.onStreamStartedCallback(message.config, message.participant_id);
}
this.emit("streamStarted", message.config, message.participant_id);
console.info(`\uD83D\uDE80 Stream started by producer ${message.participant_id}, ready to receive video`);
}
onFrameUpdate(callback) {
this.onFrameUpdateCallback = callback;
}
onVideoConfigUpdate(callback) {
this.onVideoConfigUpdateCallback = callback;
}
onStreamStarted(callback) {
this.onStreamStartedCallback = callback;
}
onStreamStopped(callback) {
this.onStreamStoppedCallback = callback;
}
onRecoveryTriggered(callback) {
this.onRecoveryTriggeredCallback = callback;
}
onStatusUpdate(callback) {
this.onStatusUpdateCallback = callback;
}
onStreamStats(callback) {
this.onStreamStatsCallback = callback;
}
handleRoleSpecificMessage(message) {
switch (message.type) {
case "frame_update":
this.handleFrameUpdate(message);
break;
case "video_config_update":
this.handleVideoConfigUpdate(message);
break;
case "stream_started":
this.handleStreamStarted(message);
break;
case "stream_stopped":
this.handleStreamStopped(message);
break;
case "recovery_triggered":
this.handleRecoveryTriggered(message);
break;
case "status_update":
this.handleStatusUpdate(message);
break;
case "stream_stats":
this.handleStreamStats(message);
break;
case "participant_joined":
console.info(`\uD83D\uDCE5 Participant joined: ${message.participant_id} as ${message.role}`);
break;
case "participant_left":
console.info(`\uD83D\uDCE4 Participant left: ${message.participant_id} (${message.role})`);
break;
case "webrtc_offer":
this.handleWebRTCOffer(message);
break;
case "webrtc_answer":
console.info("\uD83D\uDCE8 Received WebRTC answer (consumer should not receive this)");
break;
case "webrtc_ice":
this.handleWebRTCIce(message);
break;
case "emergency_stop":
console.warn(`\uD83D\uDEA8 Emergency stop: ${message.reason || "Unknown reason"}`);
this.handleError(`Emergency stop: ${message.reason || "Unknown reason"}`);
break;
case "error":
console.error(`Server error: ${message.message}`);
this.handleError(message.message);
break;
default:
console.warn(`Unknown message type for consumer: ${message.type}`);
}
}
handleFrameUpdate(message) {
if (this.onFrameUpdateCallback) {
const frameData = {
data: message.data,
metadata: message.metadata
};
this.onFrameUpdateCallback(frameData);
}
this.emit("frameUpdate", message.data);
}
handleVideoConfigUpdate(message) {
if (this.onVideoConfigUpdateCallback) {
this.onVideoConfigUpdateCallback(message.config);
}
this.emit("videoConfigUpdate", message.config);
}
handleStreamStopped(message) {
if (this.onStreamStoppedCallback) {
this.onStreamStoppedCallback(message.participant_id, message.reason);
}
this.emit("streamStopped", message.participant_id, message.reason);
}
handleRecoveryTriggered(message) {
if (this.onRecoveryTriggeredCallback) {
this.onRecoveryTriggeredCallback(message.policy, message.reason);
}
this.emit("recoveryTriggered", message.policy, message.reason);
}
handleStatusUpdate(message) {
if (this.onStatusUpdateCallback) {
this.onStatusUpdateCallback(message.status, message.data);
}
this.emit("statusUpdate", message.status, message.data);
}
handleStreamStats(message) {
if (this.onStreamStatsCallback) {
this.onStreamStatsCallback(message.stats);
}
this.emit("streamStats", message.stats);
}
static async createAndConnect(workspaceId, roomId, baseUrl, participantId) {
const consumer = new VideoConsumer(baseUrl);
const connected = await consumer.connect(workspaceId, roomId, participantId);
if (!connected) {
throw new Error("Failed to connect as video consumer");
}
return consumer;
}
attachToVideoElement(videoElement) {
if (this.remoteStream) {
videoElement.srcObject = this.remoteStream;
}
this.on("remoteStream", (stream) => {
videoElement.srcObject = stream;
});
}
async getVideoStats() {
const stats = await this.getStats();
return stats;
}
}
// src/video/factory.ts
function createClient2(role, baseUrl, options = {}) {
if (role === "producer") {
return new VideoProducer(baseUrl, options);
}
if (role === "consumer") {
return new VideoConsumer(baseUrl, options);
}
throw new Error(`Invalid role: ${role}. Must be 'producer' or 'consumer'`);
}
async function createProducerClient2(baseUrl, workspaceId, roomId, participantId, options = {}) {
const producer = new VideoProducer(baseUrl, options);
const roomData = await producer.createRoom(workspaceId, roomId);
const connected = await producer.connect(roomData.workspaceId, roomData.roomId, participantId);
if (!connected) {
throw new Error("Failed to connect as video producer");
}
return producer;
}
async function createConsumerClient2(workspaceId, roomId, baseUrl, participantId, options = {}) {
const consumer = new VideoConsumer(baseUrl, options);
const connected = await consumer.connect(workspaceId, roomId, participantId);
if (!connected) {
throw new Error("Failed to connect as video consumer");
}
return consumer;
}
// src/index.ts
var VERSION = "1.0.0";
export {
exports_video as video,
exports_robotics as robotics,
VERSION
};
//# debugId=836AB17011247B3F64756E2164756E21
//# sourceMappingURL=index.js.map