// @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