simli-client
Advanced tools
@@ -15,2 +15,3 @@ interface SimliClientConfig { | ||
| videoReceivedTimeout: number | 15000; | ||
| enableSFU: boolean | true; | ||
| model: "fasttalk" | "artalk" | ""; | ||
@@ -70,2 +71,3 @@ } | ||
| private SimliURL; | ||
| private enableSFU; | ||
| isAvatarSpeaking: boolean; | ||
@@ -72,0 +74,0 @@ enableConsoleLogs: boolean; |
+32
-17
@@ -66,2 +66,3 @@ "use strict"; | ||
| this.SimliURL = ""; | ||
| this.enableSFU = true; | ||
| this.isAvatarSpeaking = false; | ||
@@ -105,3 +106,3 @@ this.enableConsoleLogs = false; | ||
| var _a; | ||
| (_a = this.events.get(event)) === null || _a === void 0 ? void 0 : _a.forEach(callback => { | ||
| (_a = this.events.get(event)) === null || _a === void 0 ? void 0 : _a.forEach((callback) => { | ||
| callback(...args); | ||
@@ -112,3 +113,4 @@ }); | ||
| var _a, _b, _c, _d; | ||
| if ((!config.apiKey || config.apiKey === "") && (!config.session_token || config.session_token === "")) { | ||
| if ((!config.apiKey || config.apiKey === "") && | ||
| (!config.session_token || config.session_token === "")) { | ||
| console.error("SIMLI: apiKey or session_token is required in config"); | ||
@@ -125,5 +127,8 @@ throw new Error("apiKey or session_token is required in config"); | ||
| this.session_token = config.session_token; | ||
| this.MAX_RETRY_ATTEMPTS = (_b = config.maxRetryAttempts) !== null && _b !== void 0 ? _b : this.MAX_RETRY_ATTEMPTS; | ||
| this.MAX_RETRY_ATTEMPTS = | ||
| (_b = config.maxRetryAttempts) !== null && _b !== void 0 ? _b : this.MAX_RETRY_ATTEMPTS; | ||
| this.RETRY_DELAY = (_c = config.retryDelay_ms) !== null && _c !== void 0 ? _c : this.RETRY_DELAY; | ||
| this.VIDEO_TIMEOUT = (_d = config.videoReceivedTimeout) !== null && _d !== void 0 ? _d : this.VIDEO_TIMEOUT; | ||
| if (config.enableSFU) | ||
| this.enableSFU = config.enableSFU; | ||
| if (config.model !== "") { | ||
@@ -177,3 +182,3 @@ this.model = config.model; | ||
| if (attempt < this.MAX_RETRY_ATTEMPTS) { | ||
| await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY)); | ||
| await new Promise((resolve) => setTimeout(resolve, this.RETRY_DELAY)); | ||
| return this.getIceServers(apiKey, SimliURL, attempt + 1); | ||
@@ -293,3 +298,3 @@ } | ||
| maxIdleTime: this.maxIdleTime, | ||
| model: this.model | ||
| model: this.model, | ||
| }; | ||
@@ -304,3 +309,3 @@ // Get All POST request related data at the same time | ||
| } | ||
| const url = `ws${this.SimliURL}/StartWebRTCSession`; | ||
| const url = `ws${this.SimliURL}/StartWebRTCSession?enableSFU=${this.enableSFU}`; | ||
| const ws = new WebSocket(url); | ||
@@ -327,4 +332,8 @@ this.webSocket = ws; | ||
| this.setupConnectionStateHandler(); | ||
| (_a = this.pc) === null || _a === void 0 ? void 0 : _a.addTransceiver("audio", { direction: "recvonly" }); | ||
| (_b = this.pc) === null || _b === void 0 ? void 0 : _b.addTransceiver("video", { direction: "recvonly" }); | ||
| (_a = this.pc) === null || _a === void 0 ? void 0 : _a.addTransceiver("audio", { | ||
| direction: "recvonly", | ||
| }); | ||
| (_b = this.pc) === null || _b === void 0 ? void 0 : _b.addTransceiver("video", { | ||
| direction: "recvonly", | ||
| }); | ||
| await this.negotiate(); | ||
@@ -352,3 +361,3 @@ } | ||
| await this.cleanup(); | ||
| await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY)); | ||
| await new Promise((resolve) => setTimeout(resolve, this.RETRY_DELAY)); | ||
| this.retryAttempt += 1; | ||
@@ -391,3 +400,4 @@ return this.start(iceServers, this.retryAttempt); | ||
| var _a, _b; | ||
| if (this.webSocket && this.webSocket.readyState === this.webSocket.OPEN) { | ||
| if (this.webSocket && | ||
| this.webSocket.readyState === this.webSocket.OPEN) { | ||
| const message = "ping " + Date.now(); | ||
@@ -443,3 +453,4 @@ this.pingSendTimes.set(message, Date.now()); | ||
| try { | ||
| if (this.webSocket && this.webSocket.readyState === this.webSocket.OPEN) { | ||
| if (this.webSocket && | ||
| this.webSocket.readyState === this.webSocket.OPEN) { | ||
| (_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.send(sessionToken); | ||
@@ -541,3 +552,3 @@ } | ||
| .then(() => this.start()) | ||
| .catch(error => { | ||
| .catch((error) => { | ||
| if (this.enableConsoleLogs) | ||
@@ -614,3 +625,5 @@ console.error("SIMLI: Reconnection failed:", error); | ||
| audioContext.audioWorklet | ||
| .addModule(URL.createObjectURL(new Blob([AudioProcessor], { type: "application/javascript" }))) | ||
| .addModule(URL.createObjectURL(new Blob([AudioProcessor], { | ||
| type: "application/javascript", | ||
| }))) | ||
| .then(() => { | ||
@@ -629,3 +642,3 @@ this.audioWorklet = new AudioWorkletNode(audioContext, "audio-processor"); | ||
| }) | ||
| .catch(error => { | ||
| .catch((error) => { | ||
| if (this.enableConsoleLogs) | ||
@@ -653,3 +666,4 @@ console.error("SIMLI: Failed to initialize AudioWorklet:", error); | ||
| const timeBetweenSends = currentTime - this.lastSendTime; | ||
| if (timeBetweenSends > 100) { // Log only if significant delay | ||
| if (timeBetweenSends > 100) { | ||
| // Log only if significant delay | ||
| if (this.enableConsoleLogs) | ||
@@ -690,3 +704,4 @@ console.log("SIMLI: Time between sends:", timeBetweenSends); | ||
| const timeBetweenSends = currentTime - this.lastSendTime; | ||
| if (timeBetweenSends > 100) { // Log only if significant delay | ||
| if (timeBetweenSends > 100) { | ||
| // Log only if significant delay | ||
| if (this.enableConsoleLogs) | ||
@@ -739,3 +754,3 @@ console.log("SIMLI: Time between sends:", timeBetweenSends); | ||
| while (!this.localDescription) { | ||
| await new Promise(resolve => setTimeout(resolve, 50)); | ||
| await new Promise((resolve) => setTimeout(resolve, 50)); | ||
| } | ||
@@ -742,0 +757,0 @@ await ws.send(JSON.stringify(this.localDescription)); |
+316
-160
@@ -29,3 +29,3 @@ // src/index.ts | ||
| registerProcessor('audio-processor', AudioProcessor); | ||
| ` | ||
| `; | ||
@@ -50,2 +50,3 @@ // Custom event handler types | ||
| videoReceivedTimeout: number | 15000; | ||
| enableSFU: boolean | true; | ||
| model: "fasttalk" | "artalk" | ""; | ||
@@ -61,7 +62,7 @@ } | ||
| maxIdleTime: number; | ||
| model: "fasttalk" | "artalk" | ||
| model: "fasttalk" | "artalk"; | ||
| } | ||
| interface SimliSessionToken { | ||
| session_token: string | ||
| session_token: string; | ||
| } | ||
@@ -108,2 +109,3 @@ interface SimliClientEvents { | ||
| private SimliURL: string = ""; | ||
| private enableSFU: boolean = true; | ||
| public isAvatarSpeaking: boolean = false; | ||
@@ -139,3 +141,3 @@ public enableConsoleLogs: boolean = false; | ||
| ): void { | ||
| this.events.get(event)?.forEach(callback => { | ||
| this.events.get(event)?.forEach((callback) => { | ||
| callback(...args); | ||
@@ -146,4 +148,9 @@ }); | ||
| public Initialize(config: SimliClientConfig) { | ||
| if ((!config.apiKey || config.apiKey === "") && (!config.session_token || config.session_token === "")) { | ||
| console.error("SIMLI: apiKey or session_token is required in config"); | ||
| if ( | ||
| (!config.apiKey || config.apiKey === "") && | ||
| (!config.session_token || config.session_token === "") | ||
| ) { | ||
| console.error( | ||
| "SIMLI: apiKey or session_token is required in config" | ||
| ); | ||
| throw new Error("apiKey or session_token is required in config"); | ||
@@ -159,5 +166,8 @@ } | ||
| this.session_token = config.session_token; | ||
| this.MAX_RETRY_ATTEMPTS = config.maxRetryAttempts ?? this.MAX_RETRY_ATTEMPTS; | ||
| this.MAX_RETRY_ATTEMPTS = | ||
| config.maxRetryAttempts ?? this.MAX_RETRY_ATTEMPTS; | ||
| this.RETRY_DELAY = config.retryDelay_ms ?? this.RETRY_DELAY; | ||
| this.VIDEO_TIMEOUT = config.videoReceivedTimeout ?? this.VIDEO_TIMEOUT; | ||
| if (config.enableSFU) | ||
| this.enableSFU = config.enableSFU; | ||
| if (config.model !== "") { | ||
@@ -168,4 +178,3 @@ this.model = config.model; | ||
| this.SimliURL = "s://api.simli.ai"; | ||
| } | ||
| else { | ||
| } else { | ||
| this.SimliURL = config.SimliURL; | ||
@@ -177,6 +186,10 @@ } | ||
| if (!(this.videoRef instanceof HTMLVideoElement)) { | ||
| console.error("SIMLI: videoRef is required in config as HTMLVideoElement"); | ||
| console.error( | ||
| "SIMLI: videoRef is required in config as HTMLVideoElement" | ||
| ); | ||
| } | ||
| if (!(this.audioRef instanceof HTMLAudioElement)) { | ||
| console.error("SIMLI: audioRef is required in config as HTMLAudioElement"); | ||
| console.error( | ||
| "SIMLI: audioRef is required in config as HTMLAudioElement" | ||
| ); | ||
| } | ||
@@ -191,3 +204,7 @@ console.log("SIMLI: simli-client@1.2.15 initialized"); | ||
| public async getIceServers(apiKey: string, SimliURL: string, attempt = 1): Promise<RTCIceServer[]> { | ||
| public async getIceServers( | ||
| apiKey: string, | ||
| SimliURL: string, | ||
| attempt = 1 | ||
| ): Promise<RTCIceServer[]> { | ||
| try { | ||
@@ -202,3 +219,9 @@ const url = `http${SimliURL}/getIceServers`; | ||
| new Promise((_, reject) => | ||
| setTimeout(() => reject(new Error("SIMLI: ICE server request timeout")), 5000) | ||
| setTimeout( | ||
| () => | ||
| reject( | ||
| new Error("SIMLI: ICE server request timeout") | ||
| ), | ||
| 5000 | ||
| ) | ||
| ), | ||
@@ -208,3 +231,5 @@ ]); | ||
| if (!response.ok) { | ||
| throw new Error(`SIMLI: HTTP error! status: ${response.status}`); | ||
| throw new Error( | ||
| `SIMLI: HTTP error! status: ${response.status}` | ||
| ); | ||
| } | ||
@@ -218,10 +243,17 @@ | ||
| } catch (error) { | ||
| if (this.enableConsoleLogs) console.warn(`SIMLI: ICE servers fetch attempt ${attempt} failed:`, error); | ||
| if (this.enableConsoleLogs) | ||
| console.warn( | ||
| `SIMLI: ICE servers fetch attempt ${attempt} failed:`, | ||
| error | ||
| ); | ||
| if (attempt < this.MAX_RETRY_ATTEMPTS) { | ||
| await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY)); | ||
| await new Promise((resolve) => | ||
| setTimeout(resolve, this.RETRY_DELAY) | ||
| ); | ||
| return this.getIceServers(apiKey, SimliURL, attempt + 1); | ||
| } | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Using fallback STUN server"); | ||
| if (this.enableConsoleLogs) | ||
| console.log("SIMLI: Using fallback STUN server"); | ||
| return [{ urls: ["stun:stun.l.google.com:19302"] }]; | ||
@@ -231,6 +263,8 @@ } | ||
| private async createPeerConnection(iceServers: RTCIceServer[] = [], videoReceivedPromise: () => void) { | ||
| private async createPeerConnection( | ||
| iceServers: RTCIceServer[] = [], | ||
| videoReceivedPromise: () => void | ||
| ) { | ||
| if (this.pc) { | ||
| this.pc.close() | ||
| this.pc.close(); | ||
| } | ||
@@ -241,3 +275,4 @@ const config = { | ||
| }; | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Server running: ", config.iceServers); | ||
| if (this.enableConsoleLogs) | ||
| console.log("SIMLI: Server running: ", config.iceServers); | ||
@@ -255,13 +290,20 @@ this.pc = new window.RTCPeerConnection(config); | ||
| this.pc.addEventListener("icegatheringstatechange", () => { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: ICE gathering state changed: ", this.pc?.iceGatheringState); | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| "SIMLI: ICE gathering state changed: ", | ||
| this.pc?.iceGatheringState | ||
| ); | ||
| }); | ||
| this.pc.addEventListener("iceconnectionstatechange", () => { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: ICE connection state changed: ", this.pc?.iceConnectionState); | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| "SIMLI: ICE connection state changed: ", | ||
| this.pc?.iceConnectionState | ||
| ); | ||
| if (this.pc?.iceConnectionState === "failed") { | ||
| if (this.retryAttempt < this.MAX_RETRY_ATTEMPTS) { | ||
| this.retryAttempt += 1; | ||
| this.start(this.inputIceServers, this.retryAttempt) | ||
| } | ||
| else { | ||
| this.start(this.inputIceServers, this.retryAttempt); | ||
| } else { | ||
| this.handleConnectionFailure("ICE connection failed"); | ||
@@ -273,12 +315,17 @@ } | ||
| this.pc.addEventListener("signalingstatechange", () => { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Signaling state changed: ", this.pc?.signalingState); | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| "SIMLI: Signaling state changed: ", | ||
| this.pc?.signalingState | ||
| ); | ||
| }); | ||
| this.pc.addEventListener("track", (evt) => { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Track event: ", evt.track.kind); | ||
| if (this.enableConsoleLogs) | ||
| console.log("SIMLI: Track event: ", evt.track.kind); | ||
| if (evt.track.kind === "video" && this.videoRef) { | ||
| this.videoRef.srcObject = evt.streams[0]; | ||
| this.videoRef.requestVideoFrameCallback(() => { | ||
| videoReceivedResolve() | ||
| }) | ||
| videoReceivedResolve(); | ||
| }); | ||
| } else if (evt.track.kind === "audio" && this.audioRef) { | ||
@@ -303,3 +350,7 @@ this.audioRef.srcObject = evt.streams[0]; | ||
| this.pc.addEventListener("connectionstatechange", () => { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Connection state changed to:", this.pc?.connectionState); | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| "SIMLI: Connection state changed to:", | ||
| this.pc?.connectionState | ||
| ); | ||
@@ -325,6 +376,7 @@ switch (this.pc?.connectionState) { | ||
| async start( | ||
| iceServers: RTCIceServer[] = [], retryAttempt = 1 | ||
| iceServers: RTCIceServer[] = [], | ||
| retryAttempt = 1 | ||
| ): Promise<void> { | ||
| try { | ||
| await this.cleanup() | ||
| await this.cleanup(); | ||
| this.clearTimeouts(); | ||
@@ -334,6 +386,5 @@ // Set overall connection timeout | ||
| this.handleConnectionTimeout(); | ||
| }, this.CONNECTION_TIMEOUT_MS) | ||
| }, this.CONNECTION_TIMEOUT_MS); | ||
| this.inputIceServers = iceServers | ||
| this.inputIceServers = iceServers; | ||
| if (iceServers.length === 0) { | ||
@@ -348,15 +399,13 @@ const metadata = { | ||
| maxIdleTime: this.maxIdleTime, | ||
| model: this.model | ||
| model: this.model, | ||
| }; | ||
| // Get All POST request related data at the same time | ||
| const sessionRunData = await Promise.all( | ||
| [ | ||
| this.getIceServers(this.apiKey, this.SimliURL), | ||
| this.createSessionToken(this.SimliURL, metadata), | ||
| ] | ||
| ) | ||
| iceServers = sessionRunData[0] | ||
| this.session_token = sessionRunData[1].session_token | ||
| const sessionRunData = await Promise.all([ | ||
| this.getIceServers(this.apiKey, this.SimliURL), | ||
| this.createSessionToken(this.SimliURL, metadata), | ||
| ]); | ||
| iceServers = sessionRunData[0]; | ||
| this.session_token = sessionRunData[1].session_token; | ||
| } | ||
| const url = `ws${this.SimliURL}/StartWebRTCSession`; | ||
| const url = `ws${this.SimliURL}/StartWebRTCSession?enableSFU=${this.enableSFU}`; | ||
| const ws = new WebSocket(url); | ||
@@ -366,3 +415,3 @@ this.webSocket = ws; | ||
| if (!this.webSocket) { | ||
| return | ||
| return; | ||
| } | ||
@@ -375,40 +424,69 @@ this.setupWebSocketListeners(this.webSocket, resolve); | ||
| new Promise((_, reject) => | ||
| setTimeout(() => reject(new Error("SIMLI: WebSocket connection timeout")), 5000) | ||
| setTimeout( | ||
| () => | ||
| reject( | ||
| new Error("SIMLI: WebSocket connection timeout") | ||
| ), | ||
| 5000 | ||
| ) | ||
| ), | ||
| ]); | ||
| const videoReceivedPromise = new Promise<void>(async (resolve, reject) => { | ||
| try { | ||
| await this.createPeerConnection(iceServers, resolve); | ||
| const parameters = { ordered: true }; | ||
| this.dc = this.pc!.createDataChannel("chat", parameters); | ||
| const videoReceivedPromise = new Promise<void>( | ||
| async (resolve, reject) => { | ||
| try { | ||
| await this.createPeerConnection(iceServers, resolve); | ||
| const parameters = { ordered: true }; | ||
| this.dc = this.pc!.createDataChannel( | ||
| "chat", | ||
| parameters | ||
| ); | ||
| this.setupDataChannelListeners(); | ||
| this.setupConnectionStateHandler(); | ||
| this.pc?.addTransceiver("audio", { direction: "recvonly" }); | ||
| this.pc?.addTransceiver("video", { direction: "recvonly" }); | ||
| await this.negotiate(); | ||
| this.setupDataChannelListeners(); | ||
| this.setupConnectionStateHandler(); | ||
| this.pc?.addTransceiver("audio", { | ||
| direction: "recvonly", | ||
| }); | ||
| this.pc?.addTransceiver("video", { | ||
| direction: "recvonly", | ||
| }); | ||
| await this.negotiate(); | ||
| } catch (error) { | ||
| reject(); | ||
| } | ||
| } | ||
| catch (error) { | ||
| reject() | ||
| } | ||
| }) | ||
| ); | ||
| await Promise.race([ | ||
| videoReceivedPromise, | ||
| new Promise((_, reject) => | ||
| setTimeout(() => reject(new Error("SIMLI: Video connection timeout")), this.VIDEO_TIMEOUT) | ||
| setTimeout( | ||
| () => | ||
| reject( | ||
| new Error("SIMLI: Video connection timeout") | ||
| ), | ||
| this.VIDEO_TIMEOUT | ||
| ) | ||
| ), | ||
| ]); | ||
| console.log("CONNECTED") | ||
| console.log("CONNECTED"); | ||
| // Clear timeout if connection successful | ||
| this.clearTimeouts(); | ||
| } catch (error) { | ||
| if (this.enableConsoleLogs) console.error(`SIMLI: Connection attempt ${retryAttempt} failed:`, error); | ||
| if (this.enableConsoleLogs) | ||
| console.error( | ||
| `SIMLI: Connection attempt ${retryAttempt} failed:`, | ||
| error | ||
| ); | ||
| this.clearTimeouts(); | ||
| if (this.retryAttempt < this.MAX_RETRY_ATTEMPTS) { | ||
| if (this.enableConsoleLogs) console.log(`SIMLI: Retrying connection... Attempt ${retryAttempt + 1}`); | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| `SIMLI: Retrying connection... Attempt ${retryAttempt + 1 | ||
| }` | ||
| ); | ||
| await this.cleanup(); | ||
| await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY)); | ||
| await new Promise((resolve) => | ||
| setTimeout(resolve, this.RETRY_DELAY) | ||
| ); | ||
| this.retryAttempt += 1; | ||
@@ -418,3 +496,6 @@ return this.start(iceServers, this.retryAttempt); | ||
| this.emit("failed", `Failed to connect after ${this.MAX_RETRY_ATTEMPTS} attempts`); | ||
| this.emit( | ||
| "failed", | ||
| `Failed to connect after ${this.MAX_RETRY_ATTEMPTS} attempts` | ||
| ); | ||
| throw error; | ||
@@ -428,3 +509,4 @@ } | ||
| this.dc.addEventListener("close", () => { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Data channel closed"); | ||
| if (this.enableConsoleLogs) | ||
| console.log("SIMLI: Data channel closed"); | ||
| this.emit("disconnected"); | ||
@@ -435,3 +517,4 @@ this.stopDataChannelInterval(); | ||
| this.dc.addEventListener("error", (error) => { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: Data channel error:", error); | ||
| if (this.enableConsoleLogs) | ||
| console.error("SIMLI: Data channel error:", error); | ||
| this.emit("disconnected"); | ||
@@ -457,3 +540,6 @@ this.handleConnectionFailure("Data channel error"); | ||
| private sendPingMessage() { | ||
| if (this.webSocket && this.webSocket.readyState === this.webSocket.OPEN) { | ||
| if ( | ||
| this.webSocket && | ||
| this.webSocket.readyState === this.webSocket.OPEN | ||
| ) { | ||
| const message = "ping " + Date.now(); | ||
@@ -464,3 +550,4 @@ this.pingSendTimes.set(message, Date.now()); | ||
| } catch (error) { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: Failed to send message:", error); | ||
| if (this.enableConsoleLogs) | ||
| console.error("SIMLI: Failed to send message:", error); | ||
| this.stopDataChannelInterval(); | ||
@@ -470,8 +557,10 @@ this.handleConnectionFailure("Failed to send ping message"); | ||
| } else { | ||
| if (this.enableConsoleLogs) console.warn( | ||
| "SIMLI: WebSocket is not open. Current state:", | ||
| this.webSocket?.readyState | ||
| ); | ||
| if (this.enableConsoleLogs) | ||
| console.warn( | ||
| "SIMLI: WebSocket is not open. Current state:", | ||
| this.webSocket?.readyState | ||
| ); | ||
| if (this.errorReason !== null) { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: Error Reason: ", this.errorReason); | ||
| if (this.enableConsoleLogs) | ||
| console.error("SIMLI: Error Reason: ", this.errorReason); | ||
| } | ||
@@ -482,16 +571,18 @@ this.stopDataChannelInterval(); | ||
| public async createSessionToken(SimliURL: string, metadata: SimliSessionRequest): Promise<SimliSessionToken> { | ||
| if (this.session_token && this.session_token !== "") { return { session_token: this.session_token } } | ||
| public async createSessionToken( | ||
| SimliURL: string, | ||
| metadata: SimliSessionRequest | ||
| ): Promise<SimliSessionToken> { | ||
| if (this.session_token && this.session_token !== "") { | ||
| return { session_token: this.session_token }; | ||
| } | ||
| try { | ||
| const url = `http${SimliURL}/startAudioToVideoSession`; | ||
| const response = await fetch( | ||
| url, | ||
| { | ||
| method: "POST", | ||
| body: JSON.stringify(metadata), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| } | ||
| ); | ||
| const response = await fetch(url, { | ||
| method: "POST", | ||
| body: JSON.stringify(metadata), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }); | ||
@@ -506,3 +597,5 @@ if (!response.ok) { | ||
| } catch (error) { | ||
| this.handleConnectionFailure(`Session initialization failed: ${error}`); | ||
| this.handleConnectionFailure( | ||
| `Session initialization failed: ${error}` | ||
| ); | ||
| throw error; | ||
@@ -513,9 +606,16 @@ } | ||
| try { | ||
| if (this.webSocket && this.webSocket.readyState === this.webSocket.OPEN) { | ||
| if ( | ||
| this.webSocket && | ||
| this.webSocket.readyState === this.webSocket.OPEN | ||
| ) { | ||
| this.webSocket?.send(sessionToken); | ||
| } else { | ||
| throw new Error("WebSocket not open when trying to send session token"); | ||
| throw new Error( | ||
| "WebSocket not open when trying to send session token" | ||
| ); | ||
| } | ||
| } catch (error) { | ||
| this.handleConnectionFailure(`Session initialization failed: ${error}`); | ||
| this.handleConnectionFailure( | ||
| `Session initialization failed: ${error}` | ||
| ); | ||
| throw error; | ||
@@ -541,13 +641,15 @@ } | ||
| // Wait for answer with timeout | ||
| let timeoutId: NodeJS.Timeout; | ||
| this.answer = null | ||
| this.answer = null; | ||
| await Promise.race([ | ||
| new Promise<void>((resolve, reject) => { | ||
| timeoutId = setTimeout(() => reject(new Error("Answer timeout A")), 10000); | ||
| timeoutId = setTimeout( | ||
| () => reject(new Error("Answer timeout A")), | ||
| 10000 | ||
| ); | ||
| const checkAnswer = async () => { | ||
| if (!this.pc) { | ||
| reject() | ||
| return | ||
| reject(); | ||
| return; | ||
| } | ||
@@ -557,3 +659,5 @@ if (this.answer) { | ||
| resolve(); | ||
| await this.pc!.setRemoteDescription(new RTCSessionDescription(this.answer)); | ||
| await this.pc!.setRemoteDescription( | ||
| new RTCSessionDescription(this.answer) | ||
| ); | ||
| } else { | ||
@@ -566,6 +670,8 @@ setTimeout(checkAnswer, 100); | ||
| new Promise((_, reject) => | ||
| setTimeout(() => reject(new Error("SIMLI: Answer timeout B")), 10000) | ||
| setTimeout( | ||
| () => reject(new Error("SIMLI: Answer timeout B")), | ||
| 10000 | ||
| ) | ||
| ), | ||
| ]); | ||
| } catch (error) { | ||
@@ -610,3 +716,4 @@ this.handleConnectionFailure(`SIMLI: Negotiation failed: ${error}`); | ||
| this.errorReason = reason; | ||
| if (this.enableConsoleLogs) console.error("SIMLI: connection failure:", reason); | ||
| if (this.enableConsoleLogs) | ||
| console.error("SIMLI: connection failure:", reason); | ||
| this.emit("failed", reason); | ||
@@ -622,7 +729,11 @@ this.cleanup(); | ||
| if (this.sessionInitialized) { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Connection lost, attempting to reconnect..."); | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| "SIMLI: Connection lost, attempting to reconnect..." | ||
| ); | ||
| this.cleanup() | ||
| .then(() => this.start()) | ||
| .catch(error => { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: Reconnection failed:", error); | ||
| .catch((error) => { | ||
| if (this.enableConsoleLogs) | ||
| console.error("SIMLI: Reconnection failed:", error); | ||
| this.emit("failed", "Reconnection failed"); | ||
@@ -634,6 +745,4 @@ }); | ||
| private async cleanup() { | ||
| if (this.videoRef) | ||
| this.videoRef.srcObject = null | ||
| if (this.audioRef) | ||
| this.audioRef.srcObject = null | ||
| if (this.videoRef) this.videoRef.srcObject = null; | ||
| if (this.audioRef) this.audioRef.srcObject = null; | ||
@@ -680,7 +789,6 @@ if (this.webSocket) { | ||
| this.stopDataChannelInterval(); | ||
| this.answer = null | ||
| this.answer = null; | ||
| if (this.config) { | ||
| this.Initialize(this.config); | ||
| } | ||
| } | ||
@@ -704,3 +812,7 @@ | ||
| } catch (error) { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: Failed to initialize audio stream:", error); | ||
| if (this.enableConsoleLogs) | ||
| console.error( | ||
| "SIMLI: Failed to initialize audio stream:", | ||
| error | ||
| ); | ||
| this.emit("failed", "Audio initialization failed"); | ||
@@ -717,3 +829,5 @@ } | ||
| URL.createObjectURL( | ||
| new Blob([AudioProcessor], { type: "application/javascript" }) | ||
| new Blob([AudioProcessor], { | ||
| type: "application/javascript", | ||
| }) | ||
| ) | ||
@@ -735,8 +849,14 @@ ) | ||
| if (event.data.type === "audioData") { | ||
| this.sendAudioData(new Uint8Array(event.data.data.buffer)); | ||
| this.sendAudioData( | ||
| new Uint8Array(event.data.data.buffer) | ||
| ); | ||
| } | ||
| }; | ||
| }) | ||
| .catch(error => { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: Failed to initialize AudioWorklet:", error); | ||
| .catch((error) => { | ||
| if (this.enableConsoleLogs) | ||
| console.error( | ||
| "SIMLI: Failed to initialize AudioWorklet:", | ||
| error | ||
| ); | ||
| this.emit("failed", "AudioWorklet initialization failed"); | ||
@@ -748,3 +868,6 @@ }); | ||
| if (!this.sessionInitialized) { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Session not initialized. Ignoring audio data."); | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| "SIMLI: Session not initialized. Ignoring audio data." | ||
| ); | ||
| return; | ||
@@ -754,8 +877,9 @@ } | ||
| if (this.webSocket?.readyState !== WebSocket.OPEN) { | ||
| if (this.enableConsoleLogs) console.error( | ||
| "SIMLI: WebSocket is not open. Current state:", | ||
| this.webSocket?.readyState, | ||
| "Error Reason:", | ||
| this.errorReason | ||
| ); | ||
| if (this.enableConsoleLogs) | ||
| console.error( | ||
| "SIMLI: WebSocket is not open. Current state:", | ||
| this.webSocket?.readyState, | ||
| "Error Reason:", | ||
| this.errorReason | ||
| ); | ||
| return; | ||
@@ -769,4 +893,9 @@ } | ||
| const timeBetweenSends = currentTime - this.lastSendTime; | ||
| if (timeBetweenSends > 100) { // Log only if significant delay | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Time between sends:", timeBetweenSends); | ||
| if (timeBetweenSends > 100) { | ||
| // Log only if significant delay | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| "SIMLI: Time between sends:", | ||
| timeBetweenSends | ||
| ); | ||
| } | ||
@@ -776,3 +905,4 @@ } | ||
| } catch (error) { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: Failed to send audio data:", error); | ||
| if (this.enableConsoleLogs) | ||
| console.error("SIMLI: Failed to send audio data:", error); | ||
| this.handleConnectionFailure("Failed to send audio data"); | ||
@@ -784,3 +914,6 @@ } | ||
| if (!this.sessionInitialized) { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Session not initialized. Ignoring audio data."); | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| "SIMLI: Session not initialized. Ignoring audio data." | ||
| ); | ||
| return; | ||
@@ -790,8 +923,9 @@ } | ||
| if (this.webSocket?.readyState !== WebSocket.OPEN) { | ||
| if (this.enableConsoleLogs) console.error( | ||
| "SIMLI: WebSocket is not open. Current state:", | ||
| this.webSocket?.readyState, | ||
| "Error Reason:", | ||
| this.errorReason | ||
| ); | ||
| if (this.enableConsoleLogs) | ||
| console.error( | ||
| "SIMLI: WebSocket is not open. Current state:", | ||
| this.webSocket?.readyState, | ||
| "Error Reason:", | ||
| this.errorReason | ||
| ); | ||
| return; | ||
@@ -812,4 +946,9 @@ } | ||
| const timeBetweenSends = currentTime - this.lastSendTime; | ||
| if (timeBetweenSends > 100) { // Log only if significant delay | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Time between sends:", timeBetweenSends); | ||
| if (timeBetweenSends > 100) { | ||
| // Log only if significant delay | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| "SIMLI: Time between sends:", | ||
| timeBetweenSends | ||
| ); | ||
| } | ||
@@ -819,3 +958,4 @@ } | ||
| } catch (error) { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: Failed to send audio data:", error); | ||
| if (this.enableConsoleLogs) | ||
| console.error("SIMLI: Failed to send audio data:", error); | ||
| this.handleConnectionFailure("Failed to send audio data"); | ||
@@ -826,5 +966,5 @@ } | ||
| close() { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Closing SimliClient connection"); | ||
| if (this.webSocket) | ||
| this.webSocket.send("DONE") | ||
| if (this.enableConsoleLogs) | ||
| console.log("SIMLI: Closing SimliClient connection"); | ||
| if (this.webSocket) this.webSocket.send("DONE"); | ||
| this.emit("disconnected"); | ||
@@ -835,3 +975,4 @@ | ||
| } catch (error) { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: Error during cleanup:", error); | ||
| if (this.enableConsoleLogs) | ||
| console.error("SIMLI: Error during cleanup:", error); | ||
| } | ||
@@ -845,6 +986,8 @@ } | ||
| } catch (error) { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: Failed to clear buffer:", error); | ||
| if (this.enableConsoleLogs) | ||
| console.error("SIMLI: Failed to clear buffer:", error); | ||
| } | ||
| } else { | ||
| if (this.enableConsoleLogs) console.warn("SIMLI: Cannot clear buffer: WebSocket not open"); | ||
| if (this.enableConsoleLogs) | ||
| console.warn("SIMLI: Cannot clear buffer: WebSocket not open"); | ||
| } | ||
@@ -877,8 +1020,10 @@ }; | ||
| private setupWebSocketListeners(ws: WebSocket, wsConnectResolve: () => void) { | ||
| private setupWebSocketListeners( | ||
| ws: WebSocket, | ||
| wsConnectResolve: () => void | ||
| ) { | ||
| ws.addEventListener("open", async () => { | ||
| wsConnectResolve(); | ||
| while (!this.localDescription) { | ||
| await new Promise(resolve => setTimeout(resolve, 50)); | ||
| await new Promise((resolve) => setTimeout(resolve, 50)); | ||
| } | ||
@@ -897,5 +1042,8 @@ await ws.send(JSON.stringify(this.localDescription)); | ||
| if (!this.session_token || this.session_token === "") { | ||
| await this.sendSessionToken((await this.createSessionToken(this.SimliURL, metadata)).session_token) | ||
| } | ||
| else { | ||
| await this.sendSessionToken( | ||
| ( | ||
| await this.createSessionToken(this.SimliURL, metadata) | ||
| ).session_token | ||
| ); | ||
| } else { | ||
| await this.sendSessionToken(this.session_token); | ||
@@ -906,6 +1054,5 @@ } | ||
| ws.addEventListener("message", async (evt) => { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Received message: ", evt.data); | ||
| if (this.enableConsoleLogs) | ||
| console.log("SIMLI: Received message: ", evt.data); | ||
| try { | ||
@@ -916,10 +1063,16 @@ if (evt.data === "START") { | ||
| this.emit("connected"); | ||
| console.log("START") | ||
| console.log(new Date().getTime()) | ||
| console.log("START"); | ||
| console.log(new Date().getTime()); | ||
| } else if (evt.data === "STOP") { | ||
| this.close(); | ||
| } else if (evt.data.startsWith("pong")) { | ||
| const pingTime = this.pingSendTimes.get(evt.data.replace("pong", "ping")); | ||
| const pingTime = this.pingSendTimes.get( | ||
| evt.data.replace("pong", "ping") | ||
| ); | ||
| if (pingTime) { | ||
| if (this.enableConsoleLogs) console.log("SIMLI: Simli Latency: ", Date.now() - pingTime); | ||
| if (this.enableConsoleLogs) | ||
| console.log( | ||
| "SIMLI: Simli Latency: ", | ||
| Date.now() - pingTime | ||
| ); | ||
| } | ||
@@ -941,10 +1094,14 @@ } else if (evt.data === "ACK") { | ||
| } catch (e) { | ||
| if (this.enableConsoleLogs) console.warn("SIMLI: Error processing WebSocket message:", e); | ||
| if (this.enableConsoleLogs) | ||
| console.warn( | ||
| "SIMLI: Error processing WebSocket message:", | ||
| e | ||
| ); | ||
| } | ||
| }); | ||
| ws.addEventListener("error", (error) => { | ||
| if (this.enableConsoleLogs) console.error("SIMLI: WebSocket error:", error); | ||
| if (this.enableConsoleLogs) | ||
| console.error("SIMLI: WebSocket error:", error); | ||
| this.emit("disconnected"); | ||
| this.handleConnectionFailure("WebSocket error"); | ||
| }); | ||
@@ -960,2 +1117,1 @@ | ||
| export { SimliClient, SimliClientConfig, SimliClientEvents }; | ||
+36
-36
| { | ||
| "name": "simli-client", | ||
| "version": "1.2.15", | ||
| "description": "Simli WebRTC Client", | ||
| "main": "dist/index.js", | ||
| "types": "dist/index.d.ts", | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "test": "jest" | ||
| }, | ||
| "keywords": [ | ||
| "simli", | ||
| "webrtc", | ||
| "faces", | ||
| "ai" | ||
| ], | ||
| "author": "Simli", | ||
| "license": "MIT", | ||
| "files": [ | ||
| "/dist", | ||
| "/lib" | ||
| ], | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/simliai/simli-client.git" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/simliai/simli-client/issues" | ||
| }, | ||
| "homepage": "https://github.com/simliai/simli-client#readme", | ||
| "devDependencies": { | ||
| "@types/node": "^20.14.11", | ||
| "@types/react": "^18.3.3", | ||
| "terser-webpack-plugin": "^5.3.11", | ||
| "ts-loader": "^9.5.2", | ||
| "typescript": "^5.7.3" | ||
| } | ||
| "name": "simli-client", | ||
| "version": "1.2.16", | ||
| "description": "Simli WebRTC Client", | ||
| "main": "dist/index.js", | ||
| "types": "dist/index.d.ts", | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "test": "jest" | ||
| }, | ||
| "keywords": [ | ||
| "simli", | ||
| "webrtc", | ||
| "faces", | ||
| "ai" | ||
| ], | ||
| "author": "Simli", | ||
| "license": "MIT", | ||
| "files": [ | ||
| "/dist", | ||
| "/lib" | ||
| ], | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/simliai/simli-client.git" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/simliai/simli-client/issues" | ||
| }, | ||
| "homepage": "https://github.com/simliai/simli-client#readme", | ||
| "devDependencies": { | ||
| "@types/node": "^20.14.11", | ||
| "@types/react": "^18.3.3", | ||
| "terser-webpack-plugin": "^5.3.11", | ||
| "ts-loader": "^9.5.2", | ||
| "typescript": "^5.7.3" | ||
| } | ||
| } |
78133
5.63%1906
10.69%