ttp-agent-sdk 2.48.14 → 2.48.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/agent-widget.dev.js +312 -110
- package/dist/agent-widget.esm.js +1 -1
- package/dist/agent-widget.js +1 -1
- package/dist/audio-processor.js +10 -4
- package/dist/examples/pet-store.html +494 -0
- package/dist/examples/seabreeze-hotel.html +403 -0
- package/dist/index.html +111 -1
- package/examples/pet-store.html +494 -0
- package/examples/seabreeze-hotel.html +403 -0
- package/package.json +2 -1
package/dist/agent-widget.dev.js
CHANGED
|
@@ -9170,7 +9170,7 @@ if (false) // removed by dead control flow
|
|
|
9170
9170
|
/***/ ((module) => {
|
|
9171
9171
|
|
|
9172
9172
|
"use strict";
|
|
9173
|
-
module.exports = "/**\n * AudioProcessor - AudioWorklet for real-time audio processing\n * \n * This AudioWorklet processes audio data in real-time and sends it to the main thread\n * for transmission to the WebSocket server.\n */\n\nclass AudioProcessor extends AudioWorkletProcessor {\n constructor(options) {\n super();\n \n // Configuration\n this.config = options.processorOptions || {};\n // Use AudioContext sampleRate (available as global 'sampleRate' in AudioWorkletProcessor)\n // Fall back to config if sampleRate not available, default to 24 kHz to match typical server/TTS output\n this.sampleRate = typeof sampleRate !== 'undefined' ? sampleRate : (this.config.sampleRate || this.config.outputSampleRate || 24000);\n this.bufferSize = 128; // Process 128 samples at a time (256 bytes = 8ms at 16kHz)\n this.buffer = new Float32Array(this.bufferSize);\n this.bufferIndex = 0;\n \n // VAD (Voice Activity Detection) parameters\n this.silenceThreshold = 0.02; // Base RMS threshold for voice detection (scaled by micSensitivity below)\n this.VOICE_FRAMES_REQUIRED = 2; // Require 2 consecutive frames above threshold before activating\n this.minVoiceDuration = 100; // ms - minimum speech duration\n this.pauseThreshold = 3000; // ms - longer pause before processing\n\n // Mic sensitivity: user/agent-adjustable multiplier on the VAD energy gate.\n // 1.0 = default; 2.0 = twice as sensitive (half the RMS floor); 0.5 = half as\n // sensitive (double the floor — only close/loud speech triggers, e.g. TV in the room).\n this.micSensitivity = (typeof this.config.micSensitivity === 'number' && this.config.micSensitivity > 0)\n ? this.config.micSensitivity : 1.0;\n // While agent audio plays, the gate rises by this factor (stricter barge-in: background\n // TV/chatter must not interrupt playback). The SDK main thread tracks playback state.\n this.playbackActive = false;\n this.PLAYBACK_GATE_FACTOR = 1.6;\n // Hysteresis: once voice is active the floor drops, so a borderline signal isn't\n // chopped on every frame (quiet speech tails stay under the trigger level).\n this.HYSTERESIS_FACTOR = 0.75;\n \n // VAD state\n this.isVoiceActive = false;\n this.voiceStartTime = 0;\n this.lastVoiceTime = 0;\n this.consecutiveSilenceFrames = 0;\n this.silenceFramesThreshold = 5; // More frames needed for silence detection\n this.voiceFrameCount = 0; // Count consecutive frames above threshold\n \n // Audio quality tracking\n this.frameCount = 0;\n this.lastLogTime = 0;\n \n // Continuous recording mode\n this.continuousMode = true; // Always send audio when voice is detected\n this.forceContinuous = true; // Force continuous for toggle button behavior\n this.isCurrentlyStreaming = false; // Track if we're currently sending audio\n \n // Batching buffer\n this.sendBuffer = null;\n this.sendBufferBytes = 0;\n\n // Start true so the first process() call cannot terminate the worklet before\n // the main thread posts setForceContinuous (WebKit/iOS often renders audio before port messages are handled).\n this.isProcessing = true;\n \n // Handle messages from main thread\n this.port.onmessage = (event) => {\n const { type, data } = event.data;\n \n switch (type) {\n case 'start':\n this.isProcessing = true;\n this.isCurrentlyStreaming = true;\n break;\n \n case 'stop':\n this.isProcessing = false;\n this.isCurrentlyStreaming = false;\n this.isVoiceActive = false;\n this.forceContinuous = false;\n this.voiceFrameCount = 0; // Reset voice frame count\n // Flush any remaining data\n this.flushBuffer();\n break;\n \n case 'setForceContinuous':\n this.forceContinuous = data.enabled;\n this.isProcessing = true;\n // iOS: bypass client VAD - always send. Desktop: keep VAD (saves bandwidth, reduces noise).\n this.isCurrentlyStreaming = data.enabled && (data.bypassVad === true);\n break;\n\n case 'setMicSensitivity': {\n const v = Number(data && data.value);\n if (isFinite(v) && v > 0) {\n this.micSensitivity = Math.max(0.25, Math.min(4.0, v));\n }\n break;\n }\n\n case 'setPlaybackActive':\n this.playbackActive = !!(data && data.active);\n break;\n \n case 'flush':\n this.flushBuffer();\n break;\n \n case 'config':\n Object.assign(this.config, data);\n break;\n }\n };\n }\n \n /**\n * Process audio data\n */\n process(inputs, outputs, parameters) {\n // CRITICAL: If processing is stopped, terminate the processor\n if (!this.isProcessing) {\n // Return false to terminate the AudioWorklet processor\n // This stops all VAD processing immediately\n return false;\n }\n \n const input = inputs[0];\n const output = outputs[0];\n \n // Copy input to output (pass-through)\n if (input.length > 0 && output.length > 0) {\n output[0].set(input[0]);\n }\n \n // Process audio for PCM recording and VAD\n if (input.length > 0 && input[0].length > 0) {\n this.processAudioData(input[0]);\n }\n \n // Keep the processor alive\n return true;\n }\n \n processAudioData(audioData) {\n // CRITICAL: Early return if processing is stopped\n // This prevents VAD calculations and logging when stopped\n if (!this.isProcessing) {\n return;\n }\n \n this.frameCount++;\n \n // Process audio in consistent 128-sample chunks (256 bytes)\n for (let i = 0; i < audioData.length; i += this.bufferSize) {\n const chunkSize = Math.min(this.bufferSize, audioData.length - i);\n \n // Copy chunk to buffer\n for (let j = 0; j < chunkSize; j++) {\n this.buffer[j] = audioData[i + j];\n }\n \n // Pad with zeros if needed\n for (let j = chunkSize; j < this.bufferSize; j++) {\n this.buffer[j] = 0;\n }\n \n // Calculate RMS for VAD on this chunk\n let sum = 0;\n for (let j = 0; j < this.bufferSize; j++) {\n sum += this.buffer[j] * this.buffer[j];\n }\n const rms = Math.sqrt(sum / this.bufferSize);\n \n // Calculate additional features for better VAD\n let variation = 0;\n let highFreqCount = 0;\n for (let j = 1; j < this.bufferSize; j++) {\n const diff = Math.abs(this.buffer[j] - this.buffer[j-1]);\n variation += diff;\n if (diff > 0.1) highFreqCount++;\n }\n variation = variation / this.bufferSize;\n const highFreqRatio = highFreqCount / this.bufferSize;\n \n const currentTime = Date.now();\n\n // Effective VAD floor: base threshold scaled by the mic-sensitivity multiplier,\n // raised during agent playback (stricter barge-in), lowered while voice is\n // already active (hysteresis, so speech tails aren't clipped).\n let effectiveThreshold = this.silenceThreshold / this.micSensitivity;\n if (this.playbackActive) effectiveThreshold *= this.PLAYBACK_GATE_FACTOR;\n if (this.isVoiceActive) effectiveThreshold *= this.HYSTERESIS_FACTOR;\n\n // VAD with reduced sensitivity - require consecutive frames above threshold\n let hasVoice = rms > effectiveThreshold;\n // Calculate time since last voice detection\n const timeSinceLastVoice = currentTime - this.lastVoiceTime;\n\n // Voice detection logic - require consecutive frames above threshold\n if (hasVoice) {\n this.consecutiveSilenceFrames = 0;\n this.voiceFrameCount++; // Increment consecutive voice frame count\n\n // Only activate streaming after required consecutive frames\n if (this.voiceFrameCount >= this.VOICE_FRAMES_REQUIRED) {\n // Start voice if needed\n if (!this.isVoiceActive) {\n this.isVoiceActive = true;\n this.voiceStartTime = currentTime;\n this.isCurrentlyStreaming = true;\n // Log voice detection (every 50 frames = ~400ms to avoid spam)\n if (this.frameCount % 50 === 0) {\n console.log(`🎤 VAD: VOICE DETECTED (RMS: ${rms.toFixed(4)}, frames: ${this.voiceFrameCount})`);\n }\n }\n }\n\n this.lastVoiceTime = currentTime;\n } else {\n // Silence detected - reset voice frame count\n this.voiceFrameCount = 0;\n this.consecutiveSilenceFrames++;\n \n // In continuous mode, we still use VAD but require longer silence before stopping\n // In non-continuous mode, stop quickly\n const silenceThreshold = this.forceContinuous ? 3000 : 200; // 3s for continuous, 200ms otherwise\n \n // FIXED: Stop condition - also check isCurrentlyStreaming (not just isVoiceActive)\n // This handles case where setForceContinuous set streaming=true but no voice was detected yet\n if (!hasVoice && (this.isVoiceActive || this.isCurrentlyStreaming) && timeSinceLastVoice >= silenceThreshold) {\n this.isVoiceActive = false;\n this.isCurrentlyStreaming = false;\n this.voiceStartTime = 0;\n this.lastVoiceTime = 0;\n this.consecutiveSilenceFrames = 0;\n this.voiceFrameCount = 0; // Reset voice frame count\n // Log silence detection\n console.log(`🔇 VAD: SILENCE DETECTED (${timeSinceLastVoice}ms silence, RMS: ${rms.toFixed(4)})`);\n }\n }\n\n // Send PCM **only if streaming and processing** - hard gate\n // This ensures we only send audio when voice is detected, even in continuous mode\n if (this.isCurrentlyStreaming && this.isProcessing) {\n // Log occasionally when sending (every 200 frames = ~1.6 seconds to avoid spam)\n if (this.frameCount % 200 === 0) {\n console.log(`📤 VAD: Sending audio (isVoiceActive: ${this.isVoiceActive}, RMS: ${rms.toFixed(4)})`);\n }\n this.sendPCMAudioData(this.buffer);\n } else {\n // Log occasionally when blocking (every 200 frames)\n if (this.frameCount % 200 === 0 && this.isProcessing) {\n console.log(`🚫 VAD: Blocking audio (isCurrentlyStreaming: ${this.isCurrentlyStreaming}, RMS: ${rms.toFixed(4)})`);\n }\n }\n }\n }\n \n sendPCMAudioData(float32Data) {\n // Convert Float32Array (-1.0 to 1.0) to Int16Array (-32768 to 32767)\n const pcmData = new Int16Array(float32Data.length);\n \n for (let i = 0; i < float32Data.length; i++) {\n // Clamp and convert to 16-bit PCM\n const sample = Math.max(-1.0, Math.min(1.0, float32Data[i]));\n pcmData[i] = Math.round(sample * 32767);\n }\n \n // Initialize send buffer if not exists\n if (!this.sendBuffer) {\n this.sendBuffer = [];\n this.sendBufferBytes = 0;\n }\n \n // Accumulate chunks in buffer\n this.sendBuffer.push(pcmData);\n this.sendBufferBytes += pcmData.byteLength;\n \n // Send in ~4 KB batches (≈128 ms of audio at 16kHz)\n // Use sliding window approach to maintain continuous flow\n while (this.sendBufferBytes >= 4096) {\n // Calculate how many chunks we need for ~4KB\n let chunksToSend = 0;\n let bytesToSend = 0;\n \n for (let i = 0; i < this.sendBuffer.length; i++) {\n const chunkBytes = this.sendBuffer[i].byteLength;\n if (bytesToSend + chunkBytes <= 4096) {\n chunksToSend++;\n bytesToSend += chunkBytes;\n } else {\n break;\n }\n }\n\n // Create merged buffer from selected chunks\n const chunksForBatch = this.sendBuffer.slice(0, chunksToSend);\n const totalSamples = chunksForBatch.reduce((a, b) => a + b.length, 0);\n const merged = new Int16Array(totalSamples);\n let offset = 0;\n \n for (const chunk of chunksForBatch) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n \n // Send batched PCM data to main thread\n this.port.postMessage({\n type: 'pcm_audio_data',\n data: merged, // Send the Int16Array directly, not the buffer\n sampleRate: this.sampleRate,\n channelCount: 1,\n frameCount: this.frameCount,\n batchSize: chunksToSend,\n totalBytes: merged.byteLength\n });\n \n // Remove sent chunks from buffer (sliding window)\n this.sendBuffer = this.sendBuffer.slice(chunksToSend);\n this.sendBufferBytes -= bytesToSend;\n }\n }\n \n // Flush any remaining buffered data\n flushBuffer() {\n if (this.sendBuffer && this.sendBuffer.length > 0) {\n // Merge remaining chunks\n const totalSamples = this.sendBuffer.reduce((a, b) => a + b.length, 0);\n const merged = new Int16Array(totalSamples);\n let offset = 0;\n \n for (const chunk of this.sendBuffer) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n \n // Send remaining data\n this.port.postMessage({\n type: 'pcm_audio_data',\n data: merged, // Send the Int16Array directly, not the buffer\n sampleRate: this.sampleRate,\n channelCount: 1,\n frameCount: this.frameCount,\n batchSize: this.sendBuffer.length,\n totalBytes: merged.byteLength,\n isFlush: true\n });\n \n // Reset buffer\n this.sendBuffer = [];\n this.sendBufferBytes = 0;\n }\n }\n}\n\n// Register the processor\nregisterProcessor('audio-processor', AudioProcessor);\n";
|
|
9173
|
+
module.exports = "/**\n * AudioProcessor - AudioWorklet for real-time audio processing\n * \n * This AudioWorklet processes audio data in real-time and sends it to the main thread\n * for transmission to the WebSocket server.\n */\n\nclass AudioProcessor extends AudioWorkletProcessor {\n constructor(options) {\n super();\n \n // Configuration\n this.config = options.processorOptions || {};\n // Use AudioContext sampleRate (available as global 'sampleRate' in AudioWorkletProcessor)\n // Fall back to config if sampleRate not available, default to 24 kHz to match typical server/TTS output\n this.sampleRate = typeof sampleRate !== 'undefined' ? sampleRate : (this.config.sampleRate || this.config.outputSampleRate || 24000);\n this.bufferSize = 128; // Process 128 samples at a time (256 bytes = 8ms at 16kHz)\n this.buffer = new Float32Array(this.bufferSize);\n this.bufferIndex = 0;\n \n // VAD (Voice Activity Detection) parameters\n this.silenceThreshold = 0.02; // Base RMS threshold for voice detection (scaled by micSensitivity below)\n this.VOICE_FRAMES_REQUIRED = 2; // Require 2 consecutive frames above threshold before activating\n this.minVoiceDuration = 100; // ms - minimum speech duration\n this.pauseThreshold = 3000; // ms - longer pause before processing\n\n // Mic sensitivity: user/agent-adjustable multiplier on the VAD energy gate.\n // 1.0 = default; 2.0 = twice as sensitive (half the RMS floor); 0.5 = half as\n // sensitive (double the floor — only close/loud speech triggers, e.g. TV in the room).\n this.micSensitivity = (typeof this.config.micSensitivity === 'number' && this.config.micSensitivity > 0)\n ? this.config.micSensitivity : 1.0;\n // While agent audio plays, the gate rises by this factor (stricter barge-in: background\n // TV/chatter must not interrupt playback). The SDK main thread tracks playback state.\n this.playbackActive = false;\n this.PLAYBACK_GATE_FACTOR = 1.6;\n // Hysteresis: once voice is active the floor drops, so a borderline signal isn't\n // chopped on every frame (quiet speech tails stay under the trigger level).\n this.HYSTERESIS_FACTOR = 0.75;\n \n // VAD state\n this.isVoiceActive = false;\n this.voiceStartTime = 0;\n this.lastVoiceTime = 0;\n this.consecutiveSilenceFrames = 0;\n this.silenceFramesThreshold = 5; // More frames needed for silence detection\n this.voiceFrameCount = 0; // Count consecutive frames above threshold\n \n // Audio quality tracking\n this.frameCount = 0;\n this.lastLogTime = 0;\n \n // Continuous recording mode\n this.continuousMode = true; // Always send audio when voice is detected\n this.forceContinuous = true; // Force continuous for toggle button behavior\n this.isCurrentlyStreaming = false; // Track if we're currently sending audio\n \n // Batching buffer\n this.sendBuffer = null;\n this.sendBufferBytes = 0;\n\n // Start true so the first process() call cannot terminate the worklet before\n // the main thread posts setForceContinuous (WebKit/iOS often renders audio before port messages are handled).\n this.isProcessing = true;\n \n // Handle messages from main thread\n this.port.onmessage = (event) => {\n const { type, data } = event.data;\n \n switch (type) {\n case 'start':\n this.isProcessing = true;\n this.isCurrentlyStreaming = true;\n break;\n \n case 'stop':\n this.isProcessing = false;\n this.isCurrentlyStreaming = false;\n this.isVoiceActive = false;\n this.forceContinuous = false;\n this.voiceFrameCount = 0; // Reset voice frame count\n // Flush any remaining data\n this.flushBuffer();\n break;\n \n case 'setForceContinuous':\n this.forceContinuous = data.enabled;\n this.isProcessing = true;\n // iOS: bypass client VAD - always send. Desktop: keep VAD (saves bandwidth, reduces noise).\n this.isCurrentlyStreaming = data.enabled && (data.bypassVad === true);\n break;\n\n case 'setMicSensitivity': {\n const v = Number(data && data.value);\n if (isFinite(v) && v > 0) {\n this.micSensitivity = Math.max(0.25, Math.min(4.0, v));\n }\n break;\n }\n\n case 'setPlaybackActive':\n this.playbackActive = !!(data && data.active);\n break;\n \n case 'flush':\n this.flushBuffer();\n break;\n \n case 'config':\n Object.assign(this.config, data);\n break;\n }\n };\n }\n \n /**\n * Process audio data\n */\n process(inputs, outputs, parameters) {\n // CRITICAL: If processing is stopped, terminate the processor\n if (!this.isProcessing) {\n // Return false to terminate the AudioWorklet processor\n // This stops all VAD processing immediately\n return false;\n }\n \n const input = inputs[0];\n const output = outputs[0];\n \n // Copy input to output (pass-through)\n if (input.length > 0 && output.length > 0) {\n output[0].set(input[0]);\n }\n \n // Process audio for PCM recording and VAD\n if (input.length > 0 && input[0].length > 0) {\n this.processAudioData(input[0]);\n }\n \n // Keep the processor alive\n return true;\n }\n \n processAudioData(audioData) {\n // CRITICAL: Early return if processing is stopped\n // This prevents VAD calculations and logging when stopped\n if (!this.isProcessing) {\n return;\n }\n \n this.frameCount++;\n \n // Process audio in consistent 128-sample chunks (256 bytes)\n for (let i = 0; i < audioData.length; i += this.bufferSize) {\n const chunkSize = Math.min(this.bufferSize, audioData.length - i);\n \n // Copy chunk to buffer\n for (let j = 0; j < chunkSize; j++) {\n this.buffer[j] = audioData[i + j];\n }\n \n // Pad with zeros if needed\n for (let j = chunkSize; j < this.bufferSize; j++) {\n this.buffer[j] = 0;\n }\n \n // Calculate RMS for VAD on this chunk\n let sum = 0;\n for (let j = 0; j < this.bufferSize; j++) {\n sum += this.buffer[j] * this.buffer[j];\n }\n const rms = Math.sqrt(sum / this.bufferSize);\n \n // Calculate additional features for better VAD\n let variation = 0;\n let highFreqCount = 0;\n for (let j = 1; j < this.bufferSize; j++) {\n const diff = Math.abs(this.buffer[j] - this.buffer[j-1]);\n variation += diff;\n if (diff > 0.1) highFreqCount++;\n }\n variation = variation / this.bufferSize;\n const highFreqRatio = highFreqCount / this.bufferSize;\n \n const currentTime = Date.now();\n\n // Effective VAD floor: base threshold scaled by the mic-sensitivity multiplier —\n // QUADRATIC below 1.0 so the strict end actually bites (0.5 → 0.08 floor, not\n // 0.04: a linear ±6dB band is imperceptible, and browser AGC flattens level\n // differences before we see them). Raised during agent playback (stricter\n // barge-in), lowered while voice is already active (hysteresis, so speech\n // tails aren't clipped).\n const sens = this.micSensitivity;\n let effectiveThreshold = sens >= 1\n ? this.silenceThreshold / sens\n : this.silenceThreshold / (sens * sens);\n if (this.playbackActive) effectiveThreshold *= this.PLAYBACK_GATE_FACTOR;\n if (this.isVoiceActive) effectiveThreshold *= this.HYSTERESIS_FACTOR;\n\n // VAD with reduced sensitivity - require consecutive frames above threshold\n let hasVoice = rms > effectiveThreshold;\n // Calculate time since last voice detection\n const timeSinceLastVoice = currentTime - this.lastVoiceTime;\n\n // Voice detection logic - require consecutive frames above threshold\n if (hasVoice) {\n this.consecutiveSilenceFrames = 0;\n this.voiceFrameCount++; // Increment consecutive voice frame count\n\n // Only activate streaming after required consecutive frames\n if (this.voiceFrameCount >= this.VOICE_FRAMES_REQUIRED) {\n // Start voice if needed\n if (!this.isVoiceActive) {\n this.isVoiceActive = true;\n this.voiceStartTime = currentTime;\n this.isCurrentlyStreaming = true;\n // Log voice detection (every 50 frames = ~400ms to avoid spam)\n if (this.frameCount % 50 === 0) {\n console.log(`🎤 VAD: VOICE DETECTED (RMS: ${rms.toFixed(4)}, frames: ${this.voiceFrameCount})`);\n }\n }\n }\n\n this.lastVoiceTime = currentTime;\n } else {\n // Silence detected - reset voice frame count\n this.voiceFrameCount = 0;\n this.consecutiveSilenceFrames++;\n \n // In continuous mode, we still use VAD but require longer silence before stopping\n // In non-continuous mode, stop quickly\n const silenceThreshold = this.forceContinuous ? 3000 : 200; // 3s for continuous, 200ms otherwise\n \n // FIXED: Stop condition - also check isCurrentlyStreaming (not just isVoiceActive)\n // This handles case where setForceContinuous set streaming=true but no voice was detected yet\n if (!hasVoice && (this.isVoiceActive || this.isCurrentlyStreaming) && timeSinceLastVoice >= silenceThreshold) {\n this.isVoiceActive = false;\n this.isCurrentlyStreaming = false;\n this.voiceStartTime = 0;\n this.lastVoiceTime = 0;\n this.consecutiveSilenceFrames = 0;\n this.voiceFrameCount = 0; // Reset voice frame count\n // Log silence detection\n console.log(`🔇 VAD: SILENCE DETECTED (${timeSinceLastVoice}ms silence, RMS: ${rms.toFixed(4)})`);\n }\n }\n\n // Send PCM **only if streaming and processing** - hard gate\n // This ensures we only send audio when voice is detected, even in continuous mode\n if (this.isCurrentlyStreaming && this.isProcessing) {\n // Log occasionally when sending (every 200 frames = ~1.6 seconds to avoid spam)\n if (this.frameCount % 200 === 0) {\n console.log(`📤 VAD: Sending audio (isVoiceActive: ${this.isVoiceActive}, RMS: ${rms.toFixed(4)})`);\n }\n this.sendPCMAudioData(this.buffer);\n } else {\n // Log occasionally when blocking (every 200 frames)\n if (this.frameCount % 200 === 0 && this.isProcessing) {\n console.log(`🚫 VAD: Blocking audio (isCurrentlyStreaming: ${this.isCurrentlyStreaming}, RMS: ${rms.toFixed(4)})`);\n }\n }\n }\n }\n \n sendPCMAudioData(float32Data) {\n // Convert Float32Array (-1.0 to 1.0) to Int16Array (-32768 to 32767)\n const pcmData = new Int16Array(float32Data.length);\n \n for (let i = 0; i < float32Data.length; i++) {\n // Clamp and convert to 16-bit PCM\n const sample = Math.max(-1.0, Math.min(1.0, float32Data[i]));\n pcmData[i] = Math.round(sample * 32767);\n }\n \n // Initialize send buffer if not exists\n if (!this.sendBuffer) {\n this.sendBuffer = [];\n this.sendBufferBytes = 0;\n }\n \n // Accumulate chunks in buffer\n this.sendBuffer.push(pcmData);\n this.sendBufferBytes += pcmData.byteLength;\n \n // Send in ~4 KB batches (≈128 ms of audio at 16kHz)\n // Use sliding window approach to maintain continuous flow\n while (this.sendBufferBytes >= 4096) {\n // Calculate how many chunks we need for ~4KB\n let chunksToSend = 0;\n let bytesToSend = 0;\n \n for (let i = 0; i < this.sendBuffer.length; i++) {\n const chunkBytes = this.sendBuffer[i].byteLength;\n if (bytesToSend + chunkBytes <= 4096) {\n chunksToSend++;\n bytesToSend += chunkBytes;\n } else {\n break;\n }\n }\n\n // Create merged buffer from selected chunks\n const chunksForBatch = this.sendBuffer.slice(0, chunksToSend);\n const totalSamples = chunksForBatch.reduce((a, b) => a + b.length, 0);\n const merged = new Int16Array(totalSamples);\n let offset = 0;\n \n for (const chunk of chunksForBatch) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n \n // Send batched PCM data to main thread\n this.port.postMessage({\n type: 'pcm_audio_data',\n data: merged, // Send the Int16Array directly, not the buffer\n sampleRate: this.sampleRate,\n channelCount: 1,\n frameCount: this.frameCount,\n batchSize: chunksToSend,\n totalBytes: merged.byteLength\n });\n \n // Remove sent chunks from buffer (sliding window)\n this.sendBuffer = this.sendBuffer.slice(chunksToSend);\n this.sendBufferBytes -= bytesToSend;\n }\n }\n \n // Flush any remaining buffered data\n flushBuffer() {\n if (this.sendBuffer && this.sendBuffer.length > 0) {\n // Merge remaining chunks\n const totalSamples = this.sendBuffer.reduce((a, b) => a + b.length, 0);\n const merged = new Int16Array(totalSamples);\n let offset = 0;\n \n for (const chunk of this.sendBuffer) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n \n // Send remaining data\n this.port.postMessage({\n type: 'pcm_audio_data',\n data: merged, // Send the Int16Array directly, not the buffer\n sampleRate: this.sampleRate,\n channelCount: 1,\n frameCount: this.frameCount,\n batchSize: this.sendBuffer.length,\n totalBytes: merged.byteLength,\n isFlush: true\n });\n \n // Reset buffer\n this.sendBuffer = [];\n this.sendBufferBytes = 0;\n }\n }\n}\n\n// Register the processor\nregisterProcessor('audio-processor', AudioProcessor);\n";
|
|
9174
9174
|
|
|
9175
9175
|
/***/ }),
|
|
9176
9176
|
|
|
@@ -10073,6 +10073,15 @@ var AudioRecorder = /*#__PURE__*/function (_EventEmitter) {
|
|
|
10073
10073
|
case 8:
|
|
10074
10074
|
this.mediaStream = _context4.v;
|
|
10075
10075
|
case 9:
|
|
10076
|
+
// Fresh track: it always starts with the getUserMedia defaults (AGC on), so a
|
|
10077
|
+
// persisted low sensitivity must re-apply its AGC-off constraint on it.
|
|
10078
|
+
if (typeof this.config.micSensitivity === 'number' && this.config.micSensitivity < 0.999) {
|
|
10079
|
+
this._agcApplied = undefined;
|
|
10080
|
+
this._applyAgcForSensitivity(this.config.micSensitivity);
|
|
10081
|
+
}
|
|
10082
|
+
|
|
10083
|
+
// CRITICAL: Check connection status AFTER getting permission but BEFORE creating AudioWorkletNode
|
|
10084
|
+
// Server might have rejected during the async permission request
|
|
10076
10085
|
if (!(this.config.checkConnection && typeof this.config.checkConnection === 'function')) {
|
|
10077
10086
|
_context4.n = 10;
|
|
10078
10087
|
break;
|
|
@@ -10354,6 +10363,39 @@ var AudioRecorder = /*#__PURE__*/function (_EventEmitter) {
|
|
|
10354
10363
|
}
|
|
10355
10364
|
});
|
|
10356
10365
|
}
|
|
10366
|
+
this._applyAgcForSensitivity(clamped);
|
|
10367
|
+
}
|
|
10368
|
+
|
|
10369
|
+
/**
|
|
10370
|
+
* AGC fights the sensitivity gate: it re-amplifies quiet background noise (a TV
|
|
10371
|
+
* across the room) toward speech level before the worklet's VAD ever measures it,
|
|
10372
|
+
* so a lower floor alone changes nothing. Below sensitivity 1.0 we disable auto
|
|
10373
|
+
* gain control on the live track (Chrome supports applyConstraints without a
|
|
10374
|
+
* stream restart); at/above 1.0 the platform default (AGC on) is restored.
|
|
10375
|
+
*/
|
|
10376
|
+
}, {
|
|
10377
|
+
key: "_applyAgcForSensitivity",
|
|
10378
|
+
value: function _applyAgcForSensitivity(sensitivity) {
|
|
10379
|
+
var _this$mediaStream,
|
|
10380
|
+
_this$mediaStream$get,
|
|
10381
|
+
_track$getCapabilitie,
|
|
10382
|
+
_this3 = this;
|
|
10383
|
+
var wantAgc = !(typeof sensitivity === 'number' && sensitivity < 0.999);
|
|
10384
|
+
if (this._agcApplied === wantAgc) return;
|
|
10385
|
+
var track = (_this$mediaStream = this.mediaStream) === null || _this$mediaStream === void 0 || (_this$mediaStream$get = _this$mediaStream.getAudioTracks) === null || _this$mediaStream$get === void 0 ? void 0 : _this$mediaStream$get.call(_this$mediaStream)[0];
|
|
10386
|
+
if (!track || track.readyState !== 'live' || typeof track.applyConstraints !== 'function') return;
|
|
10387
|
+
var caps = (_track$getCapabilitie = track.getCapabilities) === null || _track$getCapabilitie === void 0 ? void 0 : _track$getCapabilitie.call(track);
|
|
10388
|
+
if (caps !== null && caps !== void 0 && caps.autoGainControl && !caps.autoGainControl.includes(wantAgc)) return;
|
|
10389
|
+
// Mark before the async call so rapid slider moves don't stack constraint calls.
|
|
10390
|
+
this._agcApplied = wantAgc;
|
|
10391
|
+
track.applyConstraints({
|
|
10392
|
+
autoGainControl: wantAgc
|
|
10393
|
+
}).then(function () {
|
|
10394
|
+
return console.log("\uD83C\uDF99\uFE0F AudioRecorder: autoGainControl ".concat(wantAgc ? 'ON (default)' : 'OFF (low mic sensitivity)'));
|
|
10395
|
+
}).catch(function (e) {
|
|
10396
|
+
_this3._agcApplied = undefined;
|
|
10397
|
+
console.warn('⚠️ AudioRecorder: applyConstraints(autoGainControl) failed:', e);
|
|
10398
|
+
});
|
|
10357
10399
|
}
|
|
10358
10400
|
|
|
10359
10401
|
/**
|
|
@@ -12087,7 +12129,7 @@ var TextChatSDK = /*#__PURE__*/function (_EventEmitter) {
|
|
|
12087
12129
|
|
|
12088
12130
|
// SDK build time for debugging
|
|
12089
12131
|
if (true) {
|
|
12090
|
-
helloMessage.lastBuildTime = "2026-08-
|
|
12132
|
+
helloMessage.lastBuildTime = "2026-08-27T12:25:20.756Z";
|
|
12091
12133
|
}
|
|
12092
12134
|
try {
|
|
12093
12135
|
this.ws.send(JSON.stringify(helloMessage));
|
|
@@ -21397,8 +21439,8 @@ var VoiceSDK = _v2_VoiceSDK_js__WEBPACK_IMPORTED_MODULE_0__["default"];
|
|
|
21397
21439
|
|
|
21398
21440
|
|
|
21399
21441
|
// Version - injected at build time from package.json via webpack DefinePlugin
|
|
21400
|
-
var VERSION = "2.48.
|
|
21401
|
-
var BUILD_TIME = "2026-08-
|
|
21442
|
+
var VERSION = "2.48.18";
|
|
21443
|
+
var BUILD_TIME = "2026-08-27T12:25:20.756Z";
|
|
21402
21444
|
console.log("%c TTP Agent SDK v".concat(VERSION, " (").concat(BUILD_TIME, ") "), 'background: #4f46e5; color: white; font-size: 12px; font-weight: bold; padding: 2px 6px; border-radius: 4px;');
|
|
21403
21445
|
|
|
21404
21446
|
// Named exports
|
|
@@ -25573,6 +25615,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
25573
25615
|
/* harmony import */ var _codecs_PCMCodec_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./codecs/PCMCodec.js */ "./src/v2/codecs/PCMCodec.js");
|
|
25574
25616
|
/* harmony import */ var _codecs_PCMUCodec_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./codecs/PCMUCodec.js */ "./src/v2/codecs/PCMUCodec.js");
|
|
25575
25617
|
/* harmony import */ var _codecs_PCMACodec_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./codecs/PCMACodec.js */ "./src/v2/codecs/PCMACodec.js");
|
|
25618
|
+
/* harmony import */ var _hopPlatform_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./hopPlatform.js */ "./src/v2/hopPlatform.js");
|
|
25576
25619
|
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
25577
25620
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
25578
25621
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
@@ -25604,6 +25647,7 @@ function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf
|
|
|
25604
25647
|
|
|
25605
25648
|
|
|
25606
25649
|
|
|
25650
|
+
|
|
25607
25651
|
// iOS FIX: Shared AudioContext for playback that persists across AudioPlayer instances.
|
|
25608
25652
|
// iOS WebKit doesn't release audio hardware synchronously when AudioContext.close() is called,
|
|
25609
25653
|
// so creating a new AudioContext immediately after closing the old one can fail silently.
|
|
@@ -25703,6 +25747,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
25703
25747
|
_this._mediaStreamDest = null;
|
|
25704
25748
|
_this._htmlAudioRouteActive = false;
|
|
25705
25749
|
_this._htmlAudioPlayOk = false;
|
|
25750
|
+
_this._htmlAudioPlayWatchdog = null;
|
|
25706
25751
|
_this._loggedHopResample = false;
|
|
25707
25752
|
// Greeting (and other bursts) fire many playChunk()s without await.
|
|
25708
25753
|
// Serialize context creation so they cannot open two AudioContexts
|
|
@@ -27122,37 +27167,40 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27122
27167
|
}, {
|
|
27123
27168
|
key: "wantsHtmlAudioPlayback",
|
|
27124
27169
|
value: function wantsHtmlAudioPlayback() {
|
|
27125
|
-
// Debug/QA override: ?ttp_hop=0|1
|
|
27126
|
-
//
|
|
27127
|
-
|
|
27128
|
-
|
|
27129
|
-
|
|
27130
|
-
|
|
27131
|
-
|
|
27132
|
-
}
|
|
27133
|
-
} catch (e) {/* non-browser env */}
|
|
27170
|
+
// Debug/QA override: ?ttp_hop=0|1, its sessionStorage mirror (survives
|
|
27171
|
+
// same-origin full page loads that drop the query string — Borusan's
|
|
27172
|
+
// navigate_to_form), or localStorage 'ttp_hop'. Wins over config.
|
|
27173
|
+
var override = (0,_hopPlatform_js__WEBPACK_IMPORTED_MODULE_4__.readTtpHopOverride)(typeof window !== 'undefined' ? window : null);
|
|
27174
|
+
if (override !== null) {
|
|
27175
|
+
return override;
|
|
27176
|
+
}
|
|
27134
27177
|
if (typeof this.config.htmlAudioPlayback === 'boolean') {
|
|
27135
27178
|
return this.config.htmlAudioPlayback;
|
|
27136
27179
|
}
|
|
27137
|
-
// Default:
|
|
27138
|
-
//
|
|
27139
|
-
//
|
|
27140
|
-
// desktop
|
|
27141
|
-
|
|
27180
|
+
// Default: HTML hop (AudioContext → MediaStreamDestination → <audio>) gives
|
|
27181
|
+
// Safari/Chrome a far-end AEC reference on Apple mobile + macOS where Web
|
|
27182
|
+
// Audio → destination is not in the echo-cancellation loop. On Windows and
|
|
27183
|
+
// Linux desktop, Chrome-wide AEC already covers speaker echo; the hop only
|
|
27184
|
+
// adds adaptive live-stream speed wobble — keep it off there (same as Linux).
|
|
27185
|
+
var hopOn = this._needsHtmlAudioHopForAec();
|
|
27142
27186
|
if (!this._hopDefaultLogged) {
|
|
27143
27187
|
this._hopDefaultLogged = true;
|
|
27144
|
-
|
|
27188
|
+
var reason = hopOn ? 'on (mobile / macOS: AEC far-end route)' : 'off (desktop Windows/Linux: stable direct Web Audio playback)';
|
|
27189
|
+
console.log("\uD83D\uDD0A AudioPlayer: htmlAudioPlayback default \u2192 ".concat(reason));
|
|
27145
27190
|
}
|
|
27146
27191
|
return hopOn;
|
|
27147
27192
|
}
|
|
27193
|
+
|
|
27194
|
+
/** Hop default on only where Web Audio destination lacks AEC far-end reference.
|
|
27195
|
+
* Policy lives in hopPlatform.js (pure, unit-tested); this only supplies the
|
|
27196
|
+
* environment strings. */
|
|
27148
27197
|
}, {
|
|
27149
|
-
key: "
|
|
27150
|
-
value: function
|
|
27198
|
+
key: "_needsHtmlAudioHopForAec",
|
|
27199
|
+
value: function _needsHtmlAudioHopForAec() {
|
|
27151
27200
|
try {
|
|
27152
27201
|
var ua = navigator.userAgent || '';
|
|
27153
|
-
if (/Android|iPhone|iPad|iPod/i.test(ua)) return false;
|
|
27154
27202
|
var platform = navigator.userAgentData && navigator.userAgentData.platform || navigator.platform || '';
|
|
27155
|
-
return
|
|
27203
|
+
return (0,_hopPlatform_js__WEBPACK_IMPORTED_MODULE_4__.needsHtmlAudioHopForAec)(ua, platform);
|
|
27156
27204
|
} catch (e) {
|
|
27157
27205
|
return false;
|
|
27158
27206
|
}
|
|
@@ -27247,6 +27295,10 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27247
27295
|
}
|
|
27248
27296
|
if (!this.audioContext || typeof this.audioContext.createMediaStreamDestination !== 'function') {
|
|
27249
27297
|
console.warn('⚠️ AudioPlayer: MediaStreamDestination unavailable — using AudioContext.destination');
|
|
27298
|
+
this.emit('htmlAudioRoute', {
|
|
27299
|
+
state: 'fallback',
|
|
27300
|
+
reason: 'no_media_stream_destination'
|
|
27301
|
+
});
|
|
27250
27302
|
return false;
|
|
27251
27303
|
}
|
|
27252
27304
|
try {
|
|
@@ -27264,31 +27316,72 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27264
27316
|
this._htmlAudioEl = el;
|
|
27265
27317
|
this._htmlAudioRouteActive = true;
|
|
27266
27318
|
this._ensureHtmlAudioPlaying();
|
|
27319
|
+
this._armHtmlAudioPlayWatchdog(el);
|
|
27267
27320
|
console.log('🔊 AudioPlayer: Playback last-hop is HTMLAudioElement (AEC far-end route)');
|
|
27268
27321
|
return true;
|
|
27269
27322
|
} catch (e) {
|
|
27270
27323
|
console.warn('⚠️ AudioPlayer: HTML audio route failed, falling back to destination:', e);
|
|
27271
27324
|
this._teardownHtmlAudioRoute();
|
|
27325
|
+
this.emit('htmlAudioRoute', {
|
|
27326
|
+
state: 'fallback',
|
|
27327
|
+
reason: 'route_init_failed'
|
|
27328
|
+
});
|
|
27272
27329
|
return false;
|
|
27273
27330
|
}
|
|
27274
27331
|
}
|
|
27332
|
+
|
|
27333
|
+
/**
|
|
27334
|
+
* play() on a MediaStream-backed element can stay pending forever on some
|
|
27335
|
+
* WebKit builds — neither resolving nor rejecting. If the element is still
|
|
27336
|
+
* paused shortly after route creation, report it: the hop never engaged and
|
|
27337
|
+
* AEC has no far-end reference, but audio still reaches the user via the
|
|
27338
|
+
* (silent-failure) direct path only if a fallback happened — so this state
|
|
27339
|
+
* means "route exists, playback through it unconfirmed".
|
|
27340
|
+
*/
|
|
27341
|
+
}, {
|
|
27342
|
+
key: "_armHtmlAudioPlayWatchdog",
|
|
27343
|
+
value: function _armHtmlAudioPlayWatchdog(el) {
|
|
27344
|
+
var _this7 = this;
|
|
27345
|
+
this._clearHtmlAudioPlayWatchdog();
|
|
27346
|
+
this._htmlAudioPlayWatchdog = setTimeout(function () {
|
|
27347
|
+
_this7._htmlAudioPlayWatchdog = null;
|
|
27348
|
+
if (_this7._htmlAudioRouteActive && _this7._htmlAudioEl === el && !_this7._htmlAudioPlayOk && el.paused) {
|
|
27349
|
+
console.warn('⚠️ AudioPlayer: HTMLAudioElement.play() still pending after 2.5s — hop engagement unconfirmed');
|
|
27350
|
+
_this7.emit('htmlAudioRoute', {
|
|
27351
|
+
state: 'play_pending'
|
|
27352
|
+
});
|
|
27353
|
+
}
|
|
27354
|
+
}, 2500);
|
|
27355
|
+
}
|
|
27356
|
+
}, {
|
|
27357
|
+
key: "_clearHtmlAudioPlayWatchdog",
|
|
27358
|
+
value: function _clearHtmlAudioPlayWatchdog() {
|
|
27359
|
+
if (this._htmlAudioPlayWatchdog) {
|
|
27360
|
+
clearTimeout(this._htmlAudioPlayWatchdog);
|
|
27361
|
+
this._htmlAudioPlayWatchdog = null;
|
|
27362
|
+
}
|
|
27363
|
+
}
|
|
27275
27364
|
}, {
|
|
27276
27365
|
key: "_ensureHtmlAudioPlaying",
|
|
27277
27366
|
value: function _ensureHtmlAudioPlaying() {
|
|
27278
|
-
var
|
|
27367
|
+
var _this8 = this;
|
|
27279
27368
|
var el = this._htmlAudioEl;
|
|
27280
27369
|
if (!el) return;
|
|
27281
27370
|
if (!el.paused && this._htmlAudioPlayOk) return;
|
|
27282
27371
|
var playResult = el.play();
|
|
27283
27372
|
if (playResult && typeof playResult.then === 'function') {
|
|
27284
27373
|
playResult.then(function () {
|
|
27285
|
-
|
|
27374
|
+
_this8._htmlAudioPlayOk = true;
|
|
27375
|
+
_this8._clearHtmlAudioPlayWatchdog();
|
|
27376
|
+
_this8.emit('htmlAudioRoute', {
|
|
27377
|
+
state: 'established'
|
|
27378
|
+
});
|
|
27286
27379
|
}).catch(function (err) {
|
|
27287
|
-
if (
|
|
27380
|
+
if (_this8._htmlAudioPlayOk || !el.paused) {
|
|
27288
27381
|
return;
|
|
27289
27382
|
}
|
|
27290
27383
|
console.warn('⚠️ AudioPlayer: HTMLAudioElement.play() rejected — falling back to destination:', err);
|
|
27291
|
-
|
|
27384
|
+
_this8._fallbackToDestination('play_rejected');
|
|
27292
27385
|
});
|
|
27293
27386
|
}
|
|
27294
27387
|
}
|
|
@@ -27302,10 +27395,15 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27302
27395
|
this._teardownHtmlAudioRoute();
|
|
27303
27396
|
this.gainNode.connect(this.audioContext.destination);
|
|
27304
27397
|
console.warn("\u26A0\uFE0F AudioPlayer: Fell back to AudioContext.destination (".concat(reason, ")"));
|
|
27398
|
+
this.emit('htmlAudioRoute', {
|
|
27399
|
+
state: 'fallback',
|
|
27400
|
+
reason: reason
|
|
27401
|
+
});
|
|
27305
27402
|
}
|
|
27306
27403
|
}, {
|
|
27307
27404
|
key: "_teardownHtmlAudioRoute",
|
|
27308
27405
|
value: function _teardownHtmlAudioRoute() {
|
|
27406
|
+
this._clearHtmlAudioPlayWatchdog();
|
|
27309
27407
|
if (this._htmlAudioEl) {
|
|
27310
27408
|
try {
|
|
27311
27409
|
this._htmlAudioEl.pause();
|
|
@@ -27329,7 +27427,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27329
27427
|
key: "initializeAudioContext",
|
|
27330
27428
|
value: (function () {
|
|
27331
27429
|
var _initializeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
|
|
27332
|
-
var
|
|
27430
|
+
var _this9 = this;
|
|
27333
27431
|
return _regenerator().w(function (_context8) {
|
|
27334
27432
|
while (1) switch (_context8.n) {
|
|
27335
27433
|
case 0:
|
|
@@ -27340,7 +27438,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27340
27438
|
return _context8.a(2, this._audioContextInitPromise);
|
|
27341
27439
|
case 1:
|
|
27342
27440
|
this._audioContextInitPromise = this._initializeAudioContextImpl().finally(function () {
|
|
27343
|
-
|
|
27441
|
+
_this9._audioContextInitPromise = null;
|
|
27344
27442
|
});
|
|
27345
27443
|
return _context8.a(2, this._audioContextInitPromise);
|
|
27346
27444
|
}
|
|
@@ -27355,7 +27453,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27355
27453
|
key: "_initializeAudioContextImpl",
|
|
27356
27454
|
value: function () {
|
|
27357
27455
|
var _initializeAudioContextImpl2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee8() {
|
|
27358
|
-
var
|
|
27456
|
+
var _this0 = this;
|
|
27359
27457
|
var ttsSampleRate, useHardwareRate, desiredSampleRate, contextFits, canReuseShared, setupAfterResume, ctxOpts, _t4;
|
|
27360
27458
|
return _regenerator().w(function (_context9) {
|
|
27361
27459
|
while (1) switch (_context9.p = _context9.n) {
|
|
@@ -27376,7 +27474,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27376
27474
|
_sharedPlayerSampleRate = null;
|
|
27377
27475
|
}
|
|
27378
27476
|
contextFits = function contextFits(rate) {
|
|
27379
|
-
if (useHardwareRate) return
|
|
27477
|
+
if (useHardwareRate) return _this0._isHardwarePlaybackRate(rate);
|
|
27380
27478
|
return Math.abs(rate - desiredSampleRate) <= 100;
|
|
27381
27479
|
}; // Check if current instance AudioContext exists and matches
|
|
27382
27480
|
if (!this.audioContext) {
|
|
@@ -27413,19 +27511,19 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27413
27511
|
console.log("\u267B\uFE0F AudioPlayer: Reusing shared AudioContext at ".concat(_sharedPlayerContext.sampleRate, "Hz (iOS-safe)"));
|
|
27414
27512
|
this.audioContext = _sharedPlayerContext;
|
|
27415
27513
|
setupAfterResume = function setupAfterResume() {
|
|
27416
|
-
|
|
27417
|
-
if (
|
|
27514
|
+
_this0.setupAudioContextStateMonitoring();
|
|
27515
|
+
if (_this0.gainNode) {
|
|
27418
27516
|
try {
|
|
27419
|
-
|
|
27517
|
+
_this0.gainNode.disconnect();
|
|
27420
27518
|
} catch (e) {
|
|
27421
27519
|
console.warn('⚠️ AudioPlayer: Error disconnecting old GainNode:', e);
|
|
27422
27520
|
}
|
|
27423
27521
|
}
|
|
27424
|
-
|
|
27425
|
-
|
|
27426
|
-
|
|
27427
|
-
if (!
|
|
27428
|
-
|
|
27522
|
+
_this0.gainNode = _this0.audioContext.createGain();
|
|
27523
|
+
_this0.gainNode.gain.value = 1.0;
|
|
27524
|
+
_this0._connectGainToOutput();
|
|
27525
|
+
if (!_this0._audioContextPrimed) {
|
|
27526
|
+
_this0._primeAudioContext();
|
|
27429
27527
|
}
|
|
27430
27528
|
};
|
|
27431
27529
|
if (!(this.audioContext.state === 'suspended')) {
|
|
@@ -27617,7 +27715,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27617
27715
|
* Handles mic permission grants, tab switching, browser suspension, etc.
|
|
27618
27716
|
*/
|
|
27619
27717
|
function setupAudioContextStateMonitoring() {
|
|
27620
|
-
var
|
|
27718
|
+
var _this1 = this;
|
|
27621
27719
|
if (!this.audioContext) {
|
|
27622
27720
|
return;
|
|
27623
27721
|
}
|
|
@@ -27630,38 +27728,38 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27630
27728
|
// Create handler that references this.audioContext dynamically
|
|
27631
27729
|
this._audioContextStateChangeHandler = function () {
|
|
27632
27730
|
// Null check required because audioContext may be cleaned up while handler is queued
|
|
27633
|
-
if (!
|
|
27731
|
+
if (!_this1.audioContext) {
|
|
27634
27732
|
console.warn('⚠️ AudioPlayer: State change handler fired but AudioContext is null');
|
|
27635
27733
|
return;
|
|
27636
27734
|
}
|
|
27637
|
-
console.log("\uD83C\uDFB5 AudioPlayer: AudioContext state changed to: ".concat(
|
|
27638
|
-
if (
|
|
27639
|
-
|
|
27735
|
+
console.log("\uD83C\uDFB5 AudioPlayer: AudioContext state changed to: ".concat(_this1.audioContext.state));
|
|
27736
|
+
if (_this1.audioContext.state === 'running') {
|
|
27737
|
+
_this1._ensureHtmlAudioPlaying();
|
|
27640
27738
|
}
|
|
27641
|
-
if (
|
|
27739
|
+
if (_this1.audioContext.state === 'suspended' && _this1.isPlaying) {
|
|
27642
27740
|
// AudioContext was suspended during playback (tab switch, mic permission, etc.)
|
|
27643
27741
|
console.warn('⚠️ AudioPlayer: AudioContext suspended during playback');
|
|
27644
27742
|
// Note: Playback will pause automatically, but we should handle queue processing
|
|
27645
27743
|
// The state change will be handled when we try to process next frame
|
|
27646
|
-
} else if (
|
|
27744
|
+
} else if (_this1.audioContext.state === 'running' && !_this1.isPlaying && (_this1.audioQueue.length > 0 || _this1.pcmChunkQueue.length > 0 || _this1.preparedBuffer.length > 0)) {
|
|
27647
27745
|
// AudioContext resumed and we have queued frames
|
|
27648
27746
|
// This handles: mic permission grant, tab switching back, browser resume, etc.
|
|
27649
27747
|
console.log('✅ AudioPlayer: AudioContext resumed - resuming queue processing');
|
|
27650
27748
|
|
|
27651
27749
|
// Resume queue processing if we have frames
|
|
27652
|
-
if (
|
|
27750
|
+
if (_this1.audioQueue.length > 0 && !_this1.isProcessingQueue) {
|
|
27653
27751
|
setTimeout(function () {
|
|
27654
|
-
return
|
|
27752
|
+
return _this1.processQueue();
|
|
27655
27753
|
}, 50);
|
|
27656
27754
|
}
|
|
27657
|
-
if (
|
|
27755
|
+
if (_this1.pcmChunkQueue.length > 0 && !_this1.isProcessingPcmQueue) {
|
|
27658
27756
|
setTimeout(function () {
|
|
27659
|
-
return
|
|
27757
|
+
return _this1.processPcmQueue();
|
|
27660
27758
|
}, 50);
|
|
27661
27759
|
}
|
|
27662
|
-
if (
|
|
27760
|
+
if (_this1.preparedBuffer.length > 0 && !_this1.isSchedulingFrames) {
|
|
27663
27761
|
setTimeout(function () {
|
|
27664
|
-
return
|
|
27762
|
+
return _this1.scheduleFrames();
|
|
27665
27763
|
}, 50);
|
|
27666
27764
|
}
|
|
27667
27765
|
}
|
|
@@ -27680,7 +27778,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27680
27778
|
key: "processQueue",
|
|
27681
27779
|
value: (function () {
|
|
27682
27780
|
var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee0() {
|
|
27683
|
-
var
|
|
27781
|
+
var _this10 = this;
|
|
27684
27782
|
var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source, _t6;
|
|
27685
27783
|
return _regenerator().w(function (_context1) {
|
|
27686
27784
|
while (1) switch (_context1.p = _context1.n) {
|
|
@@ -27740,22 +27838,22 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27740
27838
|
// Handle end
|
|
27741
27839
|
|
|
27742
27840
|
source.onended = function () {
|
|
27743
|
-
|
|
27744
|
-
|
|
27841
|
+
_this10.currentSource = null;
|
|
27842
|
+
_this10.isProcessingQueue = false;
|
|
27745
27843
|
|
|
27746
27844
|
// Process next chunk
|
|
27747
27845
|
|
|
27748
|
-
if (
|
|
27846
|
+
if (_this10.audioQueue.length > 0) {
|
|
27749
27847
|
setTimeout(function () {
|
|
27750
|
-
return
|
|
27848
|
+
return _this10.processQueue();
|
|
27751
27849
|
}, 50);
|
|
27752
27850
|
} else {
|
|
27753
27851
|
// No more chunks - stop after delay
|
|
27754
27852
|
|
|
27755
27853
|
setTimeout(function () {
|
|
27756
|
-
if (
|
|
27757
|
-
|
|
27758
|
-
|
|
27854
|
+
if (_this10.audioQueue.length === 0 && !_this10.currentSource) {
|
|
27855
|
+
_this10.isPlaying = false;
|
|
27856
|
+
_this10.emit('playbackStopped');
|
|
27759
27857
|
}
|
|
27760
27858
|
}, 100);
|
|
27761
27859
|
}
|
|
@@ -27778,7 +27876,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27778
27876
|
if (this.audioQueue.length > 0) {
|
|
27779
27877
|
this.isProcessingQueue = false;
|
|
27780
27878
|
setTimeout(function () {
|
|
27781
|
-
return
|
|
27879
|
+
return _this10.processQueue();
|
|
27782
27880
|
}, 100);
|
|
27783
27881
|
} else {
|
|
27784
27882
|
this.isPlaying = false;
|
|
@@ -28001,7 +28099,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28001
28099
|
}, {
|
|
28002
28100
|
key: "markNewSentence",
|
|
28003
28101
|
value: function markNewSentence(text, synced, segmentId) {
|
|
28004
|
-
var
|
|
28102
|
+
var _this11 = this;
|
|
28005
28103
|
var wasStopped = this._isStopped;
|
|
28006
28104
|
var isCurrentlyPlaying = this.isPlaying || this.scheduledSources.size > 0;
|
|
28007
28105
|
|
|
@@ -28060,34 +28158,34 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28060
28158
|
var sentenceText = text; // Capture for timeout callback
|
|
28061
28159
|
this._emptySentenceTimeout = setTimeout(function () {
|
|
28062
28160
|
// Check if this sentence still has no chunks after timeout
|
|
28063
|
-
if (
|
|
28161
|
+
if (_this11.pendingSentenceText === sentenceText && _this11.scheduledBuffers === 0 && _this11.preparedBuffer.length === 0 && _this11.pcmChunkQueue.length === 0 && !_this11._isStopped) {
|
|
28064
28162
|
console.warn("\u26A0\uFE0F AudioPlayer: Empty sentence detected after 5s timeout - no chunks received for: \"".concat(sentenceText.substring(0, 40), "...\""));
|
|
28065
28163
|
// If this empty sentence carried a segment id, report it done (nothing was heard) and
|
|
28066
28164
|
// adopt it as current so the coarse stop below matches the backend's last-sent id.
|
|
28067
|
-
if (
|
|
28068
|
-
var emptySegId =
|
|
28069
|
-
|
|
28070
|
-
|
|
28071
|
-
|
|
28165
|
+
if (_this11.pendingSegmentId != null) {
|
|
28166
|
+
var emptySegId = _this11.pendingSegmentId;
|
|
28167
|
+
_this11.currentSegmentId = emptySegId;
|
|
28168
|
+
_this11.pendingSegmentId = null;
|
|
28169
|
+
_this11.emit('segmentDone', {
|
|
28072
28170
|
segmentId: emptySegId,
|
|
28073
28171
|
status: 'finished',
|
|
28074
28172
|
playedMs: 0
|
|
28075
28173
|
});
|
|
28076
28174
|
}
|
|
28077
28175
|
// Clear pending sentence to unblock next sentence
|
|
28078
|
-
if (
|
|
28079
|
-
|
|
28176
|
+
if (_this11.pendingSentenceText === sentenceText) {
|
|
28177
|
+
_this11.pendingSentenceText = null;
|
|
28080
28178
|
}
|
|
28081
28179
|
// Emit playbackStopped to allow next sentence to start
|
|
28082
28180
|
// Only if we're not currently playing (to avoid interrupting real playback)
|
|
28083
|
-
if (!
|
|
28181
|
+
if (!_this11.isPlaying && _this11.scheduledSources.size === 0) {
|
|
28084
28182
|
console.log('🛑 AudioPlayer: Emitting playbackStopped for empty sentence timeout');
|
|
28085
|
-
|
|
28086
|
-
segmentId:
|
|
28183
|
+
_this11.emit('playbackStopped', {
|
|
28184
|
+
segmentId: _this11.currentSegmentId
|
|
28087
28185
|
});
|
|
28088
28186
|
}
|
|
28089
28187
|
}
|
|
28090
|
-
|
|
28188
|
+
_this11._emptySentenceTimeout = null;
|
|
28091
28189
|
}, 5000); // 5 second timeout - adjust based on expected chunk arrival rate
|
|
28092
28190
|
}
|
|
28093
28191
|
|
|
@@ -28097,14 +28195,14 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28097
28195
|
}, {
|
|
28098
28196
|
key: "startTranscriptChecker",
|
|
28099
28197
|
value: function startTranscriptChecker() {
|
|
28100
|
-
var
|
|
28198
|
+
var _this12 = this;
|
|
28101
28199
|
if (this.isCheckingTranscripts) return;
|
|
28102
28200
|
this.isCheckingTranscripts = true;
|
|
28103
28201
|
console.log('📝 AudioPlayer: Transcript checker started');
|
|
28104
28202
|
var _checkLoop = function checkLoop() {
|
|
28105
|
-
if (!
|
|
28106
|
-
var currentTime =
|
|
28107
|
-
var _iterator2 = _createForOfIteratorHelper(
|
|
28203
|
+
if (!_this12.isCheckingTranscripts || !_this12.audioContext) return;
|
|
28204
|
+
var currentTime = _this12.audioContext.currentTime;
|
|
28205
|
+
var _iterator2 = _createForOfIteratorHelper(_this12.sentenceTimings),
|
|
28108
28206
|
_step2;
|
|
28109
28207
|
try {
|
|
28110
28208
|
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
@@ -28118,13 +28216,13 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28118
28216
|
eventData.synced = _timing.synced;
|
|
28119
28217
|
}
|
|
28120
28218
|
console.log("\uD83D\uDCDD AudioPlayer: Display transcript at ".concat(currentTime.toFixed(3), "s: \"").concat(_timing.text.substring(0, 40), "...\" (synced: ").concat(_timing.synced ? _timing.synced.length : 0, ")"));
|
|
28121
|
-
|
|
28219
|
+
_this12.emit('transcriptDisplay', eventData);
|
|
28122
28220
|
}
|
|
28123
28221
|
// Per-segment natural finish: this segment's audio window has fully elapsed.
|
|
28124
28222
|
if (!_timing.doneReported && _timing.endTime != null && currentTime >= _timing.endTime) {
|
|
28125
28223
|
_timing.doneReported = true;
|
|
28126
28224
|
var _playedMs = Math.round((_timing.endTime - _timing.startTime) * 1000);
|
|
28127
|
-
|
|
28225
|
+
_this12.emit('segmentDone', {
|
|
28128
28226
|
segmentId: _timing.segmentId,
|
|
28129
28227
|
status: 'finished',
|
|
28130
28228
|
playedMs: _playedMs
|
|
@@ -28136,12 +28234,12 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28136
28234
|
} finally {
|
|
28137
28235
|
_iterator2.f();
|
|
28138
28236
|
}
|
|
28139
|
-
if (
|
|
28237
|
+
if (_this12.isPlaying || _this12.scheduledBuffers > 0) {
|
|
28140
28238
|
requestAnimationFrame(_checkLoop);
|
|
28141
28239
|
} else {
|
|
28142
28240
|
// Playback drained naturally — flush any segment whose finish tick we may have missed
|
|
28143
28241
|
// (the last buffer's onended can flip isPlaying=false before this loop's next tick).
|
|
28144
|
-
var _iterator3 = _createForOfIteratorHelper(
|
|
28242
|
+
var _iterator3 = _createForOfIteratorHelper(_this12.sentenceTimings),
|
|
28145
28243
|
_step3;
|
|
28146
28244
|
try {
|
|
28147
28245
|
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
@@ -28150,7 +28248,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28150
28248
|
timing.doneReported = true;
|
|
28151
28249
|
var end = timing.endTime != null ? timing.endTime : timing.startTime;
|
|
28152
28250
|
var playedMs = Math.round((end - timing.startTime) * 1000);
|
|
28153
|
-
|
|
28251
|
+
_this12.emit('segmentDone', {
|
|
28154
28252
|
segmentId: timing.segmentId,
|
|
28155
28253
|
status: 'finished',
|
|
28156
28254
|
playedMs: playedMs
|
|
@@ -28162,7 +28260,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28162
28260
|
} finally {
|
|
28163
28261
|
_iterator3.f();
|
|
28164
28262
|
}
|
|
28165
|
-
|
|
28263
|
+
_this12.isCheckingTranscripts = false;
|
|
28166
28264
|
console.log('📝 AudioPlayer: Transcript checker stopped');
|
|
28167
28265
|
}
|
|
28168
28266
|
};
|
|
@@ -28326,8 +28424,9 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
28326
28424
|
/* harmony import */ var _core_ClientToolsRegistry_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../core/ClientToolsRegistry.js */ "./src/core/ClientToolsRegistry.js");
|
|
28327
28425
|
/* harmony import */ var _core_ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../core/ClientScriptManager.js */ "./src/core/ClientScriptManager.js");
|
|
28328
28426
|
/* harmony import */ var _core_helloFlavor_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../core/helloFlavor.js */ "./src/core/helloFlavor.js");
|
|
28329
|
-
/* harmony import */ var
|
|
28330
|
-
/* harmony import */ var
|
|
28427
|
+
/* harmony import */ var _hopPlatform_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./hopPlatform.js */ "./src/v2/hopPlatform.js");
|
|
28428
|
+
/* harmony import */ var _utils_screenshot_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../utils/screenshot.js */ "./src/utils/screenshot.js");
|
|
28429
|
+
/* harmony import */ var _utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../utils/visual-tools.js */ "./src/utils/visual-tools.js");
|
|
28331
28430
|
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
28332
28431
|
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
28333
28432
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
@@ -28363,6 +28462,7 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
|
|
|
28363
28462
|
|
|
28364
28463
|
|
|
28365
28464
|
|
|
28465
|
+
|
|
28366
28466
|
/**
|
|
28367
28467
|
|
|
28368
28468
|
* VoiceSDK v2 - Multi-codec speech-to-speech SDK
|
|
@@ -28460,23 +28560,20 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28460
28560
|
// Audio constraints for getUserMedia (optional)
|
|
28461
28561
|
// If not provided, defaults will be used: echoCancellation: true, noiseSuppression: true, autoGainControl: true
|
|
28462
28562
|
audioConstraints: config.audioConstraints || null,
|
|
28463
|
-
// Last-hop TTS playback via hidden <audio> (AEC far-end).
|
|
28464
|
-
//
|
|
28465
|
-
// QA/debug override wins over config: ?ttp_hop=0
|
|
28466
|
-
//
|
|
28563
|
+
// Last-hop TTS playback via hidden <audio> (AEC far-end). Platform default
|
|
28564
|
+
// (hop on mobile/macOS only) applies when unset; set explicitly to force.
|
|
28565
|
+
// QA/debug override wins over config: ?ttp_hop=0|1 — mirrored to
|
|
28566
|
+
// sessionStorage 'ttp_hop_pref' so it survives same-origin full page loads
|
|
28567
|
+
// (Borusan navigate_to_form) — then localStorage 'ttp_hop'.
|
|
28467
28568
|
htmlAudioPlayback: function () {
|
|
28468
28569
|
try {
|
|
28469
|
-
var
|
|
28470
|
-
|
|
28471
|
-
|
|
28472
|
-
|
|
28473
|
-
return flag === '1';
|
|
28474
|
-
}
|
|
28475
|
-
if (flag !== null && flag !== undefined) {
|
|
28476
|
-
console.log("\uD83D\uDD27 VoiceSDK v2: ttp_hop ignored (value '".concat(flag, "', expected '0' or '1')"));
|
|
28570
|
+
var override = (0,_hopPlatform_js__WEBPACK_IMPORTED_MODULE_7__.readTtpHopOverride)(typeof window !== 'undefined' ? window : null);
|
|
28571
|
+
if (override !== null) {
|
|
28572
|
+
console.log("\uD83D\uDD27 VoiceSDK v2: ttp_hop override \u2192 htmlAudioPlayback=".concat(override));
|
|
28573
|
+
return override;
|
|
28477
28574
|
}
|
|
28478
28575
|
} catch (e) {
|
|
28479
|
-
console.log('🔧 VoiceSDK v2: ttp_hop check threw
|
|
28576
|
+
console.log('🔧 VoiceSDK v2: ttp_hop check threw:', e && e.message);
|
|
28480
28577
|
}
|
|
28481
28578
|
// Pass through undefined so AudioPlayer applies the platform default
|
|
28482
28579
|
return config.htmlAudioPlayback;
|
|
@@ -28623,7 +28720,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28623
28720
|
key: "_registerBuiltInTools",
|
|
28624
28721
|
value: function _registerBuiltInTools() {
|
|
28625
28722
|
try {
|
|
28626
|
-
(0,
|
|
28723
|
+
(0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_9__.registerVisualTools)(this.clientToolsRegistry);
|
|
28627
28724
|
} catch (error) {
|
|
28628
28725
|
console.error('❌ VoiceSDK: Error registering built-in tools:', error);
|
|
28629
28726
|
console.error(' Error details:', error.message, error.stack);
|
|
@@ -28949,6 +29046,12 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28949
29046
|
key: "setupAudioPlayerEvents",
|
|
28950
29047
|
value: function setupAudioPlayerEvents() {
|
|
28951
29048
|
var _this3 = this;
|
|
29049
|
+
// The initial client_audio_info snapshot (at recordingStarted) predates
|
|
29050
|
+
// HTML-hop establishment, so its htmlAudioRouteActive/htmlAudioPlayOk are
|
|
29051
|
+
// always false. Re-report once the route outcome is known.
|
|
29052
|
+
this.audioPlayer.on('htmlAudioRoute', function (info) {
|
|
29053
|
+
_this3._sendClientAudioInfo(info);
|
|
29054
|
+
});
|
|
28952
29055
|
this.audioPlayer.on('playbackStarted', function (info) {
|
|
28953
29056
|
_this3.isPlaying = true;
|
|
28954
29057
|
|
|
@@ -29375,7 +29478,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29375
29478
|
// iOS in-app webviews lack "Safari" in the UA (real Safari always has it).
|
|
29376
29479
|
var webview = isAndroid && (/\bwv\b/.test(ua) || /Version\/[\d.]+.*Chrome/.test(ua)) || isIos && !/Safari/i.test(ua) || false;
|
|
29377
29480
|
var env = {
|
|
29378
|
-
sdkVersion: true ? "2.48.
|
|
29481
|
+
sdkVersion: true ? "2.48.18" : 0,
|
|
29379
29482
|
ua: ua,
|
|
29380
29483
|
platform: (uaData === null || uaData === void 0 ? void 0 : uaData.platform) || navigator.platform || '',
|
|
29381
29484
|
mobile: (_uaData$mobile = uaData === null || uaData === void 0 ? void 0 : uaData.mobile) !== null && _uaData$mobile !== void 0 ? _uaData$mobile : isAndroid || isIos,
|
|
@@ -29390,7 +29493,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29390
29493
|
} catch (e) {
|
|
29391
29494
|
console.warn('⚠️ VoiceSDK v2: Failed to build client env:', e);
|
|
29392
29495
|
return {
|
|
29393
|
-
sdkVersion: true ? "2.48.
|
|
29496
|
+
sdkVersion: true ? "2.48.18" : 0
|
|
29394
29497
|
};
|
|
29395
29498
|
}
|
|
29396
29499
|
}
|
|
@@ -29404,12 +29507,14 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29404
29507
|
*/
|
|
29405
29508
|
}, {
|
|
29406
29509
|
key: "_sendClientAudioInfo",
|
|
29407
|
-
value: function _sendClientAudioInfo() {
|
|
29510
|
+
value: function _sendClientAudioInfo(routeUpdate) {
|
|
29408
29511
|
try {
|
|
29409
29512
|
var _this$audioRecorder, _this$audioRecorder$g, _s$echoCancellation, _s$noiseSuppression, _s$autoGainControl, _s$voiceIsolation, _s$sampleRate, _s$channelCount, _this$audioRecorder2, _this$audioPlayer, _this$audioRecorder3, _this$audioPlayer2, _this$audioPlayer2$ge;
|
|
29410
29513
|
var track = (_this$audioRecorder = this.audioRecorder) === null || _this$audioRecorder === void 0 || (_this$audioRecorder = _this$audioRecorder.mediaStream) === null || _this$audioRecorder === void 0 || (_this$audioRecorder$g = _this$audioRecorder.getAudioTracks) === null || _this$audioRecorder$g === void 0 ? void 0 : _this$audioRecorder$g.call(_this$audioRecorder)[0];
|
|
29411
|
-
|
|
29412
|
-
|
|
29514
|
+
// Route updates must go out even before the mic track exists — the hop
|
|
29515
|
+
// state is the whole point of the follow-up.
|
|
29516
|
+
if (!track && !routeUpdate) return;
|
|
29517
|
+
var s = track && track.getSettings ? track.getSettings() : {};
|
|
29413
29518
|
var audio = _objectSpread({
|
|
29414
29519
|
aec: (_s$echoCancellation = s.echoCancellation) !== null && _s$echoCancellation !== void 0 ? _s$echoCancellation : null,
|
|
29415
29520
|
ns: (_s$noiseSuppression = s.noiseSuppression) !== null && _s$noiseSuppression !== void 0 ? _s$noiseSuppression : null,
|
|
@@ -29417,11 +29522,15 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29417
29522
|
voiceIsolation: (_s$voiceIsolation = s.voiceIsolation) !== null && _s$voiceIsolation !== void 0 ? _s$voiceIsolation : null,
|
|
29418
29523
|
trackRate: (_s$sampleRate = s.sampleRate) !== null && _s$sampleRate !== void 0 ? _s$sampleRate : null,
|
|
29419
29524
|
channels: (_s$channelCount = s.channelCount) !== null && _s$channelCount !== void 0 ? _s$channelCount : null,
|
|
29420
|
-
mic: track.label || null,
|
|
29525
|
+
mic: track ? track.label || null : null,
|
|
29421
29526
|
captureCtxRate: ((_this$audioRecorder2 = this.audioRecorder) === null || _this$audioRecorder2 === void 0 || (_this$audioRecorder2 = _this$audioRecorder2.audioContext) === null || _this$audioRecorder2 === void 0 ? void 0 : _this$audioRecorder2.sampleRate) || null,
|
|
29422
29527
|
playbackCtxRate: ((_this$audioPlayer = this.audioPlayer) === null || _this$audioPlayer === void 0 || (_this$audioPlayer = _this$audioPlayer.audioContext) === null || _this$audioPlayer === void 0 ? void 0 : _this$audioPlayer.sampleRate) || null,
|
|
29423
29528
|
constraintsFallback: ((_this$audioRecorder3 = this.audioRecorder) === null || _this$audioRecorder3 === void 0 || (_this$audioRecorder3 = _this$audioRecorder3.mediaStream) === null || _this$audioRecorder3 === void 0 ? void 0 : _this$audioRecorder3._ttpConstraintsFallback) === true
|
|
29424
29529
|
}, ((_this$audioPlayer2 = this.audioPlayer) === null || _this$audioPlayer2 === void 0 || (_this$audioPlayer2$ge = _this$audioPlayer2.getHtmlAudioPlaybackInfo) === null || _this$audioPlayer2$ge === void 0 ? void 0 : _this$audioPlayer2$ge.call(_this$audioPlayer2)) || {});
|
|
29530
|
+
if (routeUpdate && routeUpdate.state) {
|
|
29531
|
+
audio.routeUpdate = routeUpdate.state; // 'established' | 'fallback' | 'play_pending'
|
|
29532
|
+
if (routeUpdate.reason) audio.routeFallbackReason = routeUpdate.reason;
|
|
29533
|
+
}
|
|
29425
29534
|
this.sendMessage({
|
|
29426
29535
|
t: 'client_audio_info',
|
|
29427
29536
|
audio: audio
|
|
@@ -29532,7 +29641,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29532
29641
|
|
|
29533
29642
|
// Include SDK build time for debugging
|
|
29534
29643
|
if (true) {
|
|
29535
|
-
helloMessage.lastBuildTime = "2026-08-
|
|
29644
|
+
helloMessage.lastBuildTime = "2026-08-27T12:25:20.756Z";
|
|
29536
29645
|
}
|
|
29537
29646
|
|
|
29538
29647
|
// Client environment (device/browser/webview) for backend logs + Langfuse metadata
|
|
@@ -29776,7 +29885,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29776
29885
|
case 'get_page_context':
|
|
29777
29886
|
// Handle get_page_context request from backend
|
|
29778
29887
|
console.log('📖 [VISUAL ASSISTANT] Backend requested page context');
|
|
29779
|
-
(0,
|
|
29888
|
+
(0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_9__.extractPageContext)().then(function (pageContext) {
|
|
29780
29889
|
// Use existing DOM scanner
|
|
29781
29890
|
_this6.sendMessage({
|
|
29782
29891
|
t: 'page_context',
|
|
@@ -29797,7 +29906,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29797
29906
|
console.log('📸 [VISUAL ASSISTANT] Backend requested screenshot');
|
|
29798
29907
|
try {
|
|
29799
29908
|
// Use existing screenshot capture function
|
|
29800
|
-
(0,
|
|
29909
|
+
(0,_utils_screenshot_js__WEBPACK_IMPORTED_MODULE_8__.captureScreenshot)().then(function (screenshot) {
|
|
29801
29910
|
_this6.sendMessage({
|
|
29802
29911
|
t: 'screenshot',
|
|
29803
29912
|
screenshot: {
|
|
@@ -32157,6 +32266,99 @@ var PCMUCodec = /*#__PURE__*/function () {
|
|
|
32157
32266
|
|
|
32158
32267
|
/***/ }),
|
|
32159
32268
|
|
|
32269
|
+
/***/ "./src/v2/hopPlatform.js":
|
|
32270
|
+
/*!*******************************!*\
|
|
32271
|
+
!*** ./src/v2/hopPlatform.js ***!
|
|
32272
|
+
\*******************************/
|
|
32273
|
+
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
32274
|
+
|
|
32275
|
+
"use strict";
|
|
32276
|
+
__webpack_require__.r(__webpack_exports__);
|
|
32277
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
32278
|
+
/* harmony export */ TTP_HOP_SESSION_KEY: () => (/* binding */ TTP_HOP_SESSION_KEY),
|
|
32279
|
+
/* harmony export */ isMacDesktopUa: () => (/* binding */ isMacDesktopUa),
|
|
32280
|
+
/* harmony export */ isMobileUa: () => (/* binding */ isMobileUa),
|
|
32281
|
+
/* harmony export */ needsHtmlAudioHopForAec: () => (/* binding */ needsHtmlAudioHopForAec),
|
|
32282
|
+
/* harmony export */ readTtpHopOverride: () => (/* binding */ readTtpHopOverride)
|
|
32283
|
+
/* harmony export */ });
|
|
32284
|
+
/**
|
|
32285
|
+
* HTML-audio-hop platform policy + the ttp_hop QA override.
|
|
32286
|
+
*
|
|
32287
|
+
* The "hop" is AudioContext → MediaStreamDestination → hidden <audio>, used so
|
|
32288
|
+
* the browser's echo canceller sees agent TTS as a far-end reference. Chrome
|
|
32289
|
+
* renders that live stream with adaptive jitter-buffer speed control, which
|
|
32290
|
+
* audibly wobbles on Windows/Linux desktop — so the hop defaults ON only where
|
|
32291
|
+
* Web Audio → destination genuinely lacks an AEC reference (mobile + macOS).
|
|
32292
|
+
*
|
|
32293
|
+
* Kept DOM-free (all inputs passed in) so the policy is unit-testable without
|
|
32294
|
+
* a browser: see test/hop-platform.test.mjs.
|
|
32295
|
+
*/
|
|
32296
|
+
|
|
32297
|
+
/** sessionStorage key mirroring an explicit ?ttp_hop= URL value (see readTtpHopOverride). */
|
|
32298
|
+
var TTP_HOP_SESSION_KEY = 'ttp_hop_pref';
|
|
32299
|
+
function isMobileUa(ua) {
|
|
32300
|
+
return /Android|iPhone|iPad|iPod/i.test(ua || '');
|
|
32301
|
+
}
|
|
32302
|
+
function isMacDesktopUa(ua, platform) {
|
|
32303
|
+
if (/iPhone|iPad|iPod/i.test(ua || '')) return false;
|
|
32304
|
+
return /mac/i.test(platform || '') || /Mac OS X/i.test(ua || '');
|
|
32305
|
+
}
|
|
32306
|
+
|
|
32307
|
+
/**
|
|
32308
|
+
* Hop default: on only where Web Audio destination lacks an AEC far-end
|
|
32309
|
+
* reference (mobile + macOS). Windows/Linux/ChromeOS desktop: off — Chrome-wide
|
|
32310
|
+
* AEC already covers speaker echo there, and the hop only adds speed wobble.
|
|
32311
|
+
* Unknown/empty environment: off (fail toward the stable direct path).
|
|
32312
|
+
*/
|
|
32313
|
+
function needsHtmlAudioHopForAec(ua, platform) {
|
|
32314
|
+
return isMobileUa(ua) || isMacDesktopUa(ua, platform);
|
|
32315
|
+
}
|
|
32316
|
+
|
|
32317
|
+
/**
|
|
32318
|
+
* QA/debug override for the hop route. Returns true (force hop on), false
|
|
32319
|
+
* (force off), or null (no override — apply config/platform default).
|
|
32320
|
+
*
|
|
32321
|
+
* Precedence: URL ?ttp_hop=0|1 → sessionStorage mirror → localStorage 'ttp_hop'.
|
|
32322
|
+
*
|
|
32323
|
+
* An explicit URL value is mirrored into sessionStorage (TTP_HOP_SESSION_KEY)
|
|
32324
|
+
* so it survives same-origin full page loads that drop the query string —
|
|
32325
|
+
* Borusan's navigate_to_form does window.location.href = '/path' (call
|
|
32326
|
+
* 84b5af7d), which used to silently re-apply the platform default mid-call.
|
|
32327
|
+
* The mirror dies with the tab; localStorage remains the sticky manual knob
|
|
32328
|
+
* and ranks below the mirror because the URL value is the more recent intent.
|
|
32329
|
+
*
|
|
32330
|
+
* Every storage touch is individually guarded: browsers can block one storage
|
|
32331
|
+
* area but not another, and a blocked sessionStorage must not cost us the
|
|
32332
|
+
* localStorage fallback.
|
|
32333
|
+
*/
|
|
32334
|
+
function readTtpHopOverride(win) {
|
|
32335
|
+
var w = win || (typeof window !== 'undefined' ? window : null);
|
|
32336
|
+
if (!w) return null;
|
|
32337
|
+
var flag = null;
|
|
32338
|
+
try {
|
|
32339
|
+
flag = new URLSearchParams(w.location.search).get('ttp_hop');
|
|
32340
|
+
} catch (e) {/* no location in this env */}
|
|
32341
|
+
if (flag === '0' || flag === '1') {
|
|
32342
|
+
try {
|
|
32343
|
+
w.sessionStorage.setItem(TTP_HOP_SESSION_KEY, flag);
|
|
32344
|
+
} catch (e) {/* storage blocked */}
|
|
32345
|
+
return flag === '1';
|
|
32346
|
+
}
|
|
32347
|
+
var mirrored = null;
|
|
32348
|
+
try {
|
|
32349
|
+
mirrored = w.sessionStorage.getItem(TTP_HOP_SESSION_KEY);
|
|
32350
|
+
} catch (e) {/* storage blocked */}
|
|
32351
|
+
if (mirrored === '0' || mirrored === '1') return mirrored === '1';
|
|
32352
|
+
var stored = null;
|
|
32353
|
+
try {
|
|
32354
|
+
stored = w.localStorage.getItem('ttp_hop');
|
|
32355
|
+
} catch (e) {/* storage blocked */}
|
|
32356
|
+
if (stored === '0' || stored === '1') return stored === '1';
|
|
32357
|
+
return null;
|
|
32358
|
+
}
|
|
32359
|
+
|
|
32360
|
+
/***/ }),
|
|
32361
|
+
|
|
32160
32362
|
/***/ "./src/v2/utils/AudioFormatConverter.js":
|
|
32161
32363
|
/*!**********************************************!*\
|
|
32162
32364
|
!*** ./src/v2/utils/AudioFormatConverter.js ***!
|
|
@@ -35761,7 +35963,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
|
|
|
35761
35963
|
return;
|
|
35762
35964
|
}
|
|
35763
35965
|
this._ensureAboutStyles();
|
|
35764
|
-
var version = true ? "2.48.
|
|
35966
|
+
var version = true ? "2.48.18" : 0;
|
|
35765
35967
|
var convId = this._getLastConversationId();
|
|
35766
35968
|
var t = function t(k, fb) {
|
|
35767
35969
|
try {
|