ttp-agent-sdk 2.34.3 → 2.34.5
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/dist/agent-widget.dev.js
CHANGED
|
@@ -12744,6 +12744,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
|
|
|
12744
12744
|
onAudioStartPlaying: this.config.onAudioStartPlaying,
|
|
12745
12745
|
onAudioStoppedPlaying: this.config.onAudioStoppedPlaying,
|
|
12746
12746
|
onSubtitleDisplay: this.config.onSubtitleDisplay,
|
|
12747
|
+
onVoiceCallButtonClick: this.config.onVoiceCallButtonClick,
|
|
12747
12748
|
// Callback when call ends - show landing screen (only in unified mode)
|
|
12748
12749
|
// BUT: Don't return to landing if we're showing a domain error
|
|
12749
12750
|
onCallEnd: function onCallEnd() {
|
|
@@ -13185,6 +13186,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
|
|
|
13185
13186
|
onAudioStartPlaying: userConfig.onAudioStartPlaying,
|
|
13186
13187
|
onAudioStoppedPlaying: userConfig.onAudioStoppedPlaying,
|
|
13187
13188
|
onSubtitleDisplay: userConfig.onSubtitleDisplay,
|
|
13189
|
+
onVoiceCallButtonClick: userConfig.onVoiceCallButtonClick,
|
|
13188
13190
|
// Legacy support (for backward compatibility)
|
|
13189
13191
|
primaryColor: primaryColor
|
|
13190
13192
|
}, typeof userConfig.position === 'string' ? {
|
|
@@ -13669,6 +13671,17 @@ var TTPChatWidget = /*#__PURE__*/function () {
|
|
|
13669
13671
|
console.log('⚠️ Call already starting, ignoring duplicate click');
|
|
13670
13672
|
return _context.a(2);
|
|
13671
13673
|
case 1:
|
|
13674
|
+
// Call the callback if provided
|
|
13675
|
+
if (typeof _this6.config.onVoiceCallButtonClick === 'function') {
|
|
13676
|
+
try {
|
|
13677
|
+
_this6.config.onVoiceCallButtonClick({
|
|
13678
|
+
timestamp: Date.now(),
|
|
13679
|
+
widgetState: 'start'
|
|
13680
|
+
});
|
|
13681
|
+
} catch (error) {
|
|
13682
|
+
console.error('Error in onVoiceCallButtonClick callback:', error);
|
|
13683
|
+
}
|
|
13684
|
+
}
|
|
13672
13685
|
_this6.isStartingCall = true;
|
|
13673
13686
|
_context.p = 2;
|
|
13674
13687
|
// Show voice interface first (needed for UI state)
|
|
@@ -14533,6 +14546,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
|
|
|
14533
14546
|
mergedConfig.onAudioStartPlaying = newConfig.onAudioStartPlaying !== undefined ? newConfig.onAudioStartPlaying : this.config.onAudioStartPlaying;
|
|
14534
14547
|
mergedConfig.onAudioStoppedPlaying = newConfig.onAudioStoppedPlaying !== undefined ? newConfig.onAudioStoppedPlaying : this.config.onAudioStoppedPlaying;
|
|
14535
14548
|
mergedConfig.onSubtitleDisplay = newConfig.onSubtitleDisplay !== undefined ? newConfig.onSubtitleDisplay : this.config.onSubtitleDisplay;
|
|
14549
|
+
mergedConfig.onVoiceCallButtonClick = newConfig.onVoiceCallButtonClick !== undefined ? newConfig.onVoiceCallButtonClick : this.config.onVoiceCallButtonClick;
|
|
14536
14550
|
|
|
14537
14551
|
// Merge useShadowDOM if provided
|
|
14538
14552
|
if (newConfig.useShadowDOM !== undefined) {
|
|
@@ -14541,7 +14555,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
|
|
|
14541
14555
|
|
|
14542
14556
|
// Merge any other top-level properties
|
|
14543
14557
|
Object.keys(newConfig).forEach(function (key) {
|
|
14544
|
-
if (!['panel', 'button', 'header', 'footer', 'icon', 'messages', 'direction', 'voice', 'text', 'animation', 'behavior', 'accessibility', 'language', 'tooltips', 'landing', 'primaryColor', 'useShadowDOM', 'onConversationStart', 'onConversationEnd', 'onBargeIn', 'onAudioStartPlaying', 'onAudioStoppedPlaying', 'onSubtitleDisplay'].includes(key)) {
|
|
14558
|
+
if (!['panel', 'button', 'header', 'footer', 'icon', 'messages', 'direction', 'voice', 'text', 'animation', 'behavior', 'accessibility', 'language', 'tooltips', 'landing', 'primaryColor', 'useShadowDOM', 'onConversationStart', 'onConversationEnd', 'onBargeIn', 'onAudioStartPlaying', 'onAudioStoppedPlaying', 'onSubtitleDisplay', 'onVoiceCallButtonClick'].includes(key)) {
|
|
14545
14559
|
mergedConfig[key] = newConfig[key];
|
|
14546
14560
|
}
|
|
14547
14561
|
});
|
|
@@ -17119,7 +17133,19 @@ var VoiceInterface = /*#__PURE__*/function () {
|
|
|
17119
17133
|
var activeState = this.shadowRoot.getElementById('voiceActiveState');
|
|
17120
17134
|
if (activeState) activeState.style.display = 'none';
|
|
17121
17135
|
|
|
17122
|
-
// Call the callback if provided (for
|
|
17136
|
+
// Call the public callback if provided (for user tracking/analytics)
|
|
17137
|
+
if (typeof this.config.onVoiceCallButtonClick === 'function') {
|
|
17138
|
+
try {
|
|
17139
|
+
this.config.onVoiceCallButtonClick({
|
|
17140
|
+
timestamp: Date.now(),
|
|
17141
|
+
widgetState: 'end'
|
|
17142
|
+
});
|
|
17143
|
+
} catch (error) {
|
|
17144
|
+
console.error('Error in onVoiceCallButtonClick callback:', error);
|
|
17145
|
+
}
|
|
17146
|
+
}
|
|
17147
|
+
|
|
17148
|
+
// Call the internal callback if provided (for unified mode - shows landing screen)
|
|
17123
17149
|
// Otherwise show idle state (for voice-only mode or backward compatibility)
|
|
17124
17150
|
if (this.config.onCallEnd) {
|
|
17125
17151
|
this.config.onCallEnd();
|
|
@@ -23031,9 +23057,18 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23031
23057
|
|
|
23032
23058
|
// Queue for prepared AudioBuffers (ready to schedule)
|
|
23033
23059
|
_this.preparedBuffer = [];
|
|
23060
|
+
|
|
23061
|
+
// Maximum buffer sizes to prevent unbounded memory growth
|
|
23062
|
+
// If backend sends sentences faster than playback, oldest frames are dropped
|
|
23063
|
+
_this.MAX_PREPARED_BUFFER_SIZE = 200; // Max prepared frames (~10-12 seconds at 600ms per frame)
|
|
23064
|
+
_this.MAX_PCM_CHUNK_QUEUE_SIZE = 50; // Max raw PCM chunks
|
|
23065
|
+
|
|
23034
23066
|
_this.isProcessingPcmQueue = false;
|
|
23035
23067
|
_this.isSchedulingFrames = false;
|
|
23036
23068
|
|
|
23069
|
+
// Timeout to detect empty sentences (audio_start but no chunks)
|
|
23070
|
+
_this._emptySentenceTimeout = null;
|
|
23071
|
+
|
|
23037
23072
|
// Minimal scheduling delay to avoid scheduling audio in the past
|
|
23038
23073
|
// REMOVED: Lookahead buffering was causing quality degradation due to browser resampling/timing issues
|
|
23039
23074
|
// Now we only schedule with minimal delay (20ms) just enough to avoid gaps
|
|
@@ -23049,6 +23084,10 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23049
23084
|
// Cleared when markNewSentence() is called (signals new audio is starting)
|
|
23050
23085
|
_this._isStopped = false;
|
|
23051
23086
|
|
|
23087
|
+
// Track current sentence ID to reject chunks from previous sentences
|
|
23088
|
+
// Incremented each time markNewSentence() is called
|
|
23089
|
+
_this._currentSentenceId = 0;
|
|
23090
|
+
|
|
23052
23091
|
// Codec registry
|
|
23053
23092
|
|
|
23054
23093
|
_this.codecs = {
|
|
@@ -23185,7 +23224,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23185
23224
|
value: (function () {
|
|
23186
23225
|
var _playChunk = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(pcmData) {
|
|
23187
23226
|
var _this3 = this;
|
|
23188
|
-
var preparedFrame, _this$audioContext;
|
|
23227
|
+
var preparedFrame, dropped, _this$audioContext;
|
|
23189
23228
|
return _regenerator().w(function (_context) {
|
|
23190
23229
|
while (1) switch (_context.n) {
|
|
23191
23230
|
case 0:
|
|
@@ -23199,6 +23238,20 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23199
23238
|
// Pre-process frame immediately (convert to AudioBuffer)
|
|
23200
23239
|
preparedFrame = this.prepareChunk(pcmData);
|
|
23201
23240
|
if (preparedFrame) {
|
|
23241
|
+
// CRITICAL: Clear empty sentence timeout since chunks are arriving
|
|
23242
|
+
// This resets the timer for the current sentence
|
|
23243
|
+
if (this._emptySentenceTimeout) {
|
|
23244
|
+
clearTimeout(this._emptySentenceTimeout);
|
|
23245
|
+
this._emptySentenceTimeout = null;
|
|
23246
|
+
}
|
|
23247
|
+
|
|
23248
|
+
// CRITICAL: Prevent unbounded buffer growth
|
|
23249
|
+
// If backend sends sentences faster than playback, drop oldest frames
|
|
23250
|
+
if (this.preparedBuffer.length >= this.MAX_PREPARED_BUFFER_SIZE) {
|
|
23251
|
+
dropped = this.preparedBuffer.shift(); // Drop oldest frame
|
|
23252
|
+
console.warn("\u26A0\uFE0F AudioPlayer: preparedBuffer at max size (".concat(this.MAX_PREPARED_BUFFER_SIZE, "), dropped oldest frame"));
|
|
23253
|
+
}
|
|
23254
|
+
|
|
23202
23255
|
// Add prepared frame to buffer
|
|
23203
23256
|
this.preparedBuffer.push(preparedFrame);
|
|
23204
23257
|
|
|
@@ -23206,7 +23259,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23206
23259
|
|
|
23207
23260
|
// Use requestAnimationFrame to avoid blocking, but ensure scheduling happens
|
|
23208
23261
|
|
|
23209
|
-
if (!this.isSchedulingFrames) {
|
|
23262
|
+
if (!this.isSchedulingFrames && !this._isStopped) {
|
|
23210
23263
|
// Schedule immediately if not already scheduling
|
|
23211
23264
|
|
|
23212
23265
|
this.schedulePreparedFrames();
|
|
@@ -23218,7 +23271,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23218
23271
|
// Use a short timeout to ensure we check again after current scheduling completes
|
|
23219
23272
|
|
|
23220
23273
|
setTimeout(function () {
|
|
23221
|
-
if (_this3.preparedBuffer.length > 0 && !_this3.isSchedulingFrames) {
|
|
23274
|
+
if (_this3.preparedBuffer.length > 0 && !_this3.isSchedulingFrames && !_this3._isStopped) {
|
|
23222
23275
|
_this3.schedulePreparedFrames();
|
|
23223
23276
|
}
|
|
23224
23277
|
}, 5); // Very short delay to check after current scheduling completes
|
|
@@ -23357,7 +23410,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23357
23410
|
value: (function () {
|
|
23358
23411
|
var _schedulePreparedFrames = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
23359
23412
|
var _this4 = this;
|
|
23360
|
-
var queuedFrames, targetLookaheadFrames, _this$audioContext2, _this$audioContext3, _this$audioContext4, _this$audioContext5, scheduledCount, _loop, _t;
|
|
23413
|
+
var queuedFrames, targetLookaheadFrames, _this$audioContext2, _this$audioContext3, _this$audioContext4, _this$audioContext5, scheduledCount, _loop, _ret, _t;
|
|
23361
23414
|
return _regenerator().w(function (_context3) {
|
|
23362
23415
|
while (1) switch (_context3.p = _context3.n) {
|
|
23363
23416
|
case 0:
|
|
@@ -23409,14 +23462,21 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23409
23462
|
return _regenerator().w(function (_context2) {
|
|
23410
23463
|
while (1) switch (_context2.n) {
|
|
23411
23464
|
case 0:
|
|
23465
|
+
if (!_this4._isStopped) {
|
|
23466
|
+
_context2.n = 1;
|
|
23467
|
+
break;
|
|
23468
|
+
}
|
|
23469
|
+
console.log('🛑 AudioPlayer: Stopping frame scheduling - playback was stopped');
|
|
23470
|
+
return _context2.a(2, 0);
|
|
23471
|
+
case 1:
|
|
23412
23472
|
// Get next prepared frame
|
|
23413
23473
|
preparedFrame = _this4.preparedBuffer.shift();
|
|
23414
23474
|
if (preparedFrame) {
|
|
23415
|
-
_context2.n =
|
|
23475
|
+
_context2.n = 2;
|
|
23416
23476
|
break;
|
|
23417
23477
|
}
|
|
23418
|
-
return _context2.a(2,
|
|
23419
|
-
case
|
|
23478
|
+
return _context2.a(2, 0);
|
|
23479
|
+
case 2:
|
|
23420
23480
|
// Create source and schedule playback
|
|
23421
23481
|
source = _this4.audioContext.createBufferSource();
|
|
23422
23482
|
source.buffer = preparedFrame.buffer;
|
|
@@ -23493,29 +23553,46 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23493
23553
|
// Track when this buffer finishes (for cleanup only)
|
|
23494
23554
|
|
|
23495
23555
|
source.onended = function () {
|
|
23496
|
-
//
|
|
23556
|
+
// CRITICAL: Check if playback was stopped before processing cleanup
|
|
23557
|
+
// This prevents race conditions where onended fires after stopImmediate() was called
|
|
23558
|
+
if (_this4._isStopped) {
|
|
23559
|
+
// Playback was stopped, ignore this callback
|
|
23560
|
+
return;
|
|
23561
|
+
}
|
|
23562
|
+
|
|
23563
|
+
// CRITICAL: Only process if source is still in scheduledSources set
|
|
23564
|
+
// This prevents race conditions where stopImmediate() cleared the set but callback fires later
|
|
23565
|
+
if (!_this4.scheduledSources.has(source)) {
|
|
23566
|
+
// Source was already removed (probably by stopImmediate), ignore this callback
|
|
23567
|
+
return;
|
|
23568
|
+
}
|
|
23497
23569
|
|
|
23570
|
+
// Remove from tracked sources
|
|
23498
23571
|
_this4.scheduledSources.delete(source);
|
|
23499
|
-
|
|
23572
|
+
|
|
23573
|
+
// Only decrement if we're still playing and buffer count is positive
|
|
23574
|
+
if (!_this4._isStopped && _this4.scheduledBuffers > 0) {
|
|
23575
|
+
_this4.scheduledBuffers--;
|
|
23576
|
+
}
|
|
23500
23577
|
|
|
23501
23578
|
// If no more scheduled buffers and no prepared frames, playback is complete
|
|
23502
23579
|
|
|
23503
|
-
if (_this4.scheduledBuffers === 0 && _this4.preparedBuffer.length === 0 && _this4.pcmChunkQueue.length === 0) {
|
|
23580
|
+
if (_this4.scheduledBuffers === 0 && _this4.preparedBuffer.length === 0 && _this4.pcmChunkQueue.length === 0 && !_this4._isStopped) {
|
|
23504
23581
|
_this4.isPlaying = false;
|
|
23505
23582
|
_this4.isSchedulingFrames = false;
|
|
23506
23583
|
console.log('🛑 AudioPlayer: Emitting playbackStopped event (all buffers finished)');
|
|
23507
23584
|
_this4.emit('playbackStopped');
|
|
23508
|
-
} else if (_this4.preparedBuffer.length > 0) {
|
|
23585
|
+
} else if (_this4.preparedBuffer.length > 0 && !_this4._isStopped) {
|
|
23509
23586
|
// More frames available, schedule them immediately
|
|
23510
23587
|
|
|
23511
23588
|
// Use setTimeout to avoid blocking, but schedule quickly
|
|
23512
23589
|
|
|
23513
23590
|
setTimeout(function () {
|
|
23514
|
-
if (_this4.preparedBuffer.length > 0 && !_this4.isSchedulingFrames) {
|
|
23591
|
+
if (_this4.preparedBuffer.length > 0 && !_this4.isSchedulingFrames && !_this4._isStopped) {
|
|
23515
23592
|
_this4.schedulePreparedFrames();
|
|
23516
23593
|
}
|
|
23517
23594
|
}, 0);
|
|
23518
|
-
} else if (_this4.scheduledBuffers > 0) {
|
|
23595
|
+
} else if (_this4.scheduledBuffers > 0 && !_this4._isStopped) {
|
|
23519
23596
|
// No more prepared frames but still have scheduled buffers playing
|
|
23520
23597
|
|
|
23521
23598
|
// Set up a check to schedule new frames when they arrive
|
|
@@ -23523,9 +23600,13 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23523
23600
|
// Keep checking periodically until we have no more scheduled buffers
|
|
23524
23601
|
|
|
23525
23602
|
var _checkForMoreFrames = function checkForMoreFrames() {
|
|
23603
|
+
// CRITICAL: Check if stopped before scheduling
|
|
23604
|
+
if (_this4._isStopped) {
|
|
23605
|
+
return;
|
|
23606
|
+
}
|
|
23526
23607
|
if (_this4.preparedBuffer.length > 0 && !_this4.isSchedulingFrames && _this4.scheduledBuffers > 0) {
|
|
23527
23608
|
_this4.schedulePreparedFrames();
|
|
23528
|
-
} else if (_this4.scheduledBuffers > 0) {
|
|
23609
|
+
} else if (_this4.scheduledBuffers > 0 && !_this4._isStopped) {
|
|
23529
23610
|
// Keep checking - frames might arrive soon
|
|
23530
23611
|
|
|
23531
23612
|
setTimeout(_checkForMoreFrames, 10);
|
|
@@ -23539,7 +23620,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23539
23620
|
console.log('🎵 AudioPlayer: Emitting playbackStarted event');
|
|
23540
23621
|
_this4.emit('playbackStarted');
|
|
23541
23622
|
}
|
|
23542
|
-
case
|
|
23623
|
+
case 3:
|
|
23543
23624
|
return _context2.a(2);
|
|
23544
23625
|
}
|
|
23545
23626
|
}, _loop);
|
|
@@ -23551,7 +23632,8 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23551
23632
|
}
|
|
23552
23633
|
return _context3.d(_regeneratorValues(_loop()), 6);
|
|
23553
23634
|
case 6:
|
|
23554
|
-
|
|
23635
|
+
_ret = _context3.v;
|
|
23636
|
+
if (!(_ret === 0)) {
|
|
23555
23637
|
_context3.n = 7;
|
|
23556
23638
|
break;
|
|
23557
23639
|
}
|
|
@@ -23568,11 +23650,11 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23568
23650
|
|
|
23569
23651
|
// Use requestAnimationFrame for smooth scheduling without blocking
|
|
23570
23652
|
|
|
23571
|
-
if (this.preparedBuffer.length > 0) {
|
|
23653
|
+
if (this.preparedBuffer.length > 0 && !this._isStopped) {
|
|
23572
23654
|
// More frames arrived, schedule them immediately
|
|
23573
23655
|
|
|
23574
23656
|
requestAnimationFrame(function () {
|
|
23575
|
-
if (_this4.preparedBuffer.length > 0 && !_this4.isSchedulingFrames) {
|
|
23657
|
+
if (_this4.preparedBuffer.length > 0 && !_this4.isSchedulingFrames && !_this4._isStopped) {
|
|
23576
23658
|
_this4.schedulePreparedFrames();
|
|
23577
23659
|
}
|
|
23578
23660
|
});
|
|
@@ -23580,19 +23662,19 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23580
23662
|
|
|
23581
23663
|
// Always set up a periodic check if we have scheduled buffers playing
|
|
23582
23664
|
// This ensures continuous playback even if frames arrive slowly
|
|
23583
|
-
if (this.scheduledBuffers > 0) {
|
|
23665
|
+
if (this.scheduledBuffers > 0 && !this._isStopped) {
|
|
23584
23666
|
// Set up a periodic check to schedule new frames as they arrive
|
|
23585
23667
|
// Use a shorter interval to catch new frames quickly
|
|
23586
23668
|
setTimeout(function () {
|
|
23587
|
-
if (_this4.preparedBuffer.length > 0 && !_this4.isSchedulingFrames && _this4.scheduledBuffers > 0) {
|
|
23669
|
+
if (_this4.preparedBuffer.length > 0 && !_this4.isSchedulingFrames && _this4.scheduledBuffers > 0 && !_this4._isStopped) {
|
|
23588
23670
|
_this4.schedulePreparedFrames();
|
|
23589
|
-
} else if (_this4.scheduledBuffers > 0) {
|
|
23671
|
+
} else if (_this4.scheduledBuffers > 0 && !_this4._isStopped) {
|
|
23590
23672
|
// Keep checking even if no frames yet - they might arrive soon
|
|
23591
23673
|
|
|
23592
23674
|
// Recursively check until we have no more scheduled buffers
|
|
23593
23675
|
|
|
23594
23676
|
setTimeout(function () {
|
|
23595
|
-
if (_this4.preparedBuffer.length > 0 && !_this4.isSchedulingFrames && _this4.scheduledBuffers > 0) {
|
|
23677
|
+
if (_this4.preparedBuffer.length > 0 && !_this4.isSchedulingFrames && _this4.scheduledBuffers > 0 && !_this4._isStopped) {
|
|
23596
23678
|
_this4.schedulePreparedFrames();
|
|
23597
23679
|
}
|
|
23598
23680
|
}, 10);
|
|
@@ -23659,16 +23741,23 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23659
23741
|
return this.waitForAudioContextReady();
|
|
23660
23742
|
case 4:
|
|
23661
23743
|
if (!(this.pcmChunkQueue.length > 0)) {
|
|
23662
|
-
_context4.n =
|
|
23744
|
+
_context4.n = 7;
|
|
23663
23745
|
break;
|
|
23664
23746
|
}
|
|
23747
|
+
if (!this._isStopped) {
|
|
23748
|
+
_context4.n = 5;
|
|
23749
|
+
break;
|
|
23750
|
+
}
|
|
23751
|
+
console.log('🛑 AudioPlayer: Stopping PCM queue processing - playback was stopped');
|
|
23752
|
+
return _context4.a(3, 7);
|
|
23753
|
+
case 5:
|
|
23665
23754
|
pcmData = this.pcmChunkQueue.shift();
|
|
23666
23755
|
if (pcmData) {
|
|
23667
|
-
_context4.n =
|
|
23756
|
+
_context4.n = 6;
|
|
23668
23757
|
break;
|
|
23669
23758
|
}
|
|
23670
23759
|
return _context4.a(3, 4);
|
|
23671
|
-
case
|
|
23760
|
+
case 6:
|
|
23672
23761
|
// Ensure even byte count for 16-bit PCM
|
|
23673
23762
|
processedData = pcmData;
|
|
23674
23763
|
if (pcmData.byteLength % 2 !== 0) {
|
|
@@ -23762,23 +23851,23 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
23762
23851
|
}
|
|
23763
23852
|
_context4.n = 4;
|
|
23764
23853
|
break;
|
|
23765
|
-
case
|
|
23854
|
+
case 7:
|
|
23766
23855
|
// end while loop
|
|
23767
23856
|
|
|
23768
23857
|
// All chunks scheduled, reset processing flag
|
|
23769
23858
|
this.isProcessingPcmQueue = false;
|
|
23770
|
-
_context4.n =
|
|
23859
|
+
_context4.n = 9;
|
|
23771
23860
|
break;
|
|
23772
|
-
case
|
|
23773
|
-
_context4.p =
|
|
23861
|
+
case 8:
|
|
23862
|
+
_context4.p = 8;
|
|
23774
23863
|
_t2 = _context4.v;
|
|
23775
23864
|
console.error('❌ AudioPlayer v2: Error playing chunk:', _t2);
|
|
23776
23865
|
this.emit('playbackError', _t2);
|
|
23777
23866
|
this.isProcessingPcmQueue = false;
|
|
23778
|
-
case
|
|
23867
|
+
case 9:
|
|
23779
23868
|
return _context4.a(2);
|
|
23780
23869
|
}
|
|
23781
|
-
}, _callee3, this, [[3,
|
|
23870
|
+
}, _callee3, this, [[3, 8]]);
|
|
23782
23871
|
}));
|
|
23783
23872
|
function processPcmQueue() {
|
|
23784
23873
|
return _processPcmQueue.apply(this, arguments);
|
|
@@ -24638,6 +24727,15 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24638
24727
|
console.log(' scheduledSources.size:', this.scheduledSources.size);
|
|
24639
24728
|
console.log(' scheduledBuffers:', this.scheduledBuffers);
|
|
24640
24729
|
|
|
24730
|
+
// CRITICAL: Set stopped flag FIRST to prevent new audio from being queued/scheduled
|
|
24731
|
+
// This prevents race conditions where audio chunks arrive after stop but before sources are stopped
|
|
24732
|
+
// The flag will be cleared when markNewSentence() is called (signals new audio is starting)
|
|
24733
|
+
this._isStopped = true;
|
|
24734
|
+
|
|
24735
|
+
// CRITICAL: Stop scheduling immediately to prevent frames from being scheduled
|
|
24736
|
+
// This ensures schedulePreparedFrames() loop will exit on next _isStopped check
|
|
24737
|
+
this.isSchedulingFrames = false;
|
|
24738
|
+
|
|
24641
24739
|
// Stop current source (legacy queue-based system)
|
|
24642
24740
|
|
|
24643
24741
|
if (this.currentSource) {
|
|
@@ -24659,27 +24757,31 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24659
24757
|
if (this.scheduledSources.size > 0) {
|
|
24660
24758
|
console.log(" Stopping ".concat(this.scheduledSources.size, " scheduled sources..."));
|
|
24661
24759
|
var stoppedCount = 0;
|
|
24662
|
-
var _iterator = _createForOfIteratorHelper(this.scheduledSources),
|
|
24663
|
-
_step;
|
|
24664
|
-
try {
|
|
24665
|
-
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
24666
|
-
var source = _step.value;
|
|
24667
|
-
try {
|
|
24668
|
-
source.stop();
|
|
24669
|
-
stoppedCount++;
|
|
24670
|
-
} catch (e) {
|
|
24671
|
-
// Ignore if already stopped or not started yet
|
|
24672
24760
|
|
|
24673
|
-
|
|
24674
|
-
|
|
24761
|
+
// Store sources to stop and count before clearing the set
|
|
24762
|
+
var sourcesToStop = Array.from(this.scheduledSources);
|
|
24763
|
+
var sourcesCount = sourcesToStop.length;
|
|
24764
|
+
|
|
24765
|
+
// Clear the set BEFORE stopping sources to prevent onended callbacks from modifying it
|
|
24766
|
+
// This ensures onended callbacks will see empty set and return early
|
|
24767
|
+
this.scheduledSources.clear();
|
|
24768
|
+
|
|
24769
|
+
// CRITICAL: Reset scheduledBuffers to 0 BEFORE stopping sources
|
|
24770
|
+
// This prevents onended callbacks from decrementing it below 0
|
|
24771
|
+
// Any onended callbacks that fire will see scheduledSources is empty and return early
|
|
24772
|
+
this.scheduledBuffers = 0;
|
|
24773
|
+
for (var _i = 0, _sourcesToStop = sourcesToStop; _i < _sourcesToStop.length; _i++) {
|
|
24774
|
+
var source = _sourcesToStop[_i];
|
|
24775
|
+
try {
|
|
24776
|
+
source.stop();
|
|
24777
|
+
stoppedCount++;
|
|
24778
|
+
} catch (e) {
|
|
24779
|
+
// Ignore if already stopped or not started yet
|
|
24780
|
+
|
|
24781
|
+
console.log(' Source already stopped or not started:', e.message);
|
|
24675
24782
|
}
|
|
24676
|
-
} catch (err) {
|
|
24677
|
-
_iterator.e(err);
|
|
24678
|
-
} finally {
|
|
24679
|
-
_iterator.f();
|
|
24680
24783
|
}
|
|
24681
|
-
console.log(" Stopped ".concat(stoppedCount, " sources"));
|
|
24682
|
-
this.scheduledSources.clear();
|
|
24784
|
+
console.log(" Stopped ".concat(stoppedCount, " sources (cleared ").concat(sourcesCount, " from scheduledSources)"));
|
|
24683
24785
|
}
|
|
24684
24786
|
|
|
24685
24787
|
// Clear state
|
|
@@ -24695,15 +24797,15 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24695
24797
|
this.isProcessingPcmQueue = false;
|
|
24696
24798
|
this.isSchedulingFrames = false;
|
|
24697
24799
|
|
|
24698
|
-
//
|
|
24699
|
-
|
|
24700
|
-
|
|
24701
|
-
|
|
24800
|
+
// Clear empty sentence timeout (barge-in means sentence was interrupted, not empty)
|
|
24801
|
+
if (this._emptySentenceTimeout) {
|
|
24802
|
+
clearTimeout(this._emptySentenceTimeout);
|
|
24803
|
+
this._emptySentenceTimeout = null;
|
|
24804
|
+
}
|
|
24702
24805
|
|
|
24703
24806
|
// Reset scheduling properties
|
|
24704
|
-
|
|
24807
|
+
// Note: scheduledBuffers was already reset to 0 above when clearing scheduledSources
|
|
24705
24808
|
this.nextStartTime = 0;
|
|
24706
|
-
this.scheduledBuffers = 0;
|
|
24707
24809
|
|
|
24708
24810
|
// Clear transcript state
|
|
24709
24811
|
this.clearTranscriptState();
|
|
@@ -24723,14 +24825,77 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24723
24825
|
}, {
|
|
24724
24826
|
key: "markNewSentence",
|
|
24725
24827
|
value: function markNewSentence(text) {
|
|
24726
|
-
|
|
24828
|
+
var _this9 = this;
|
|
24829
|
+
var wasStopped = this._isStopped;
|
|
24830
|
+
var isCurrentlyPlaying = this.isPlaying || this.scheduledSources.size > 0;
|
|
24831
|
+
|
|
24832
|
+
// CRITICAL: Clear stopped flag when new audio starts (after barge-in)
|
|
24727
24833
|
// This allows new audio chunks to be queued after barge-in
|
|
24728
24834
|
if (this._isStopped) {
|
|
24729
|
-
console.log('🛑 AudioPlayer: Clearing stopped flag - new audio starting');
|
|
24835
|
+
console.log('🛑 AudioPlayer: Clearing stopped flag - new audio starting after barge-in');
|
|
24730
24836
|
this._isStopped = false;
|
|
24837
|
+
|
|
24838
|
+
// CRITICAL: Reset scheduling state when starting a new sentence after a stop
|
|
24839
|
+
// This ensures the new sentence starts immediately without delay
|
|
24840
|
+
// Reset nextStartTime so first chunk schedules immediately (not in the past)
|
|
24841
|
+
this.nextStartTime = 0;
|
|
24842
|
+
|
|
24843
|
+
// CRITICAL: Reset scheduledBuffers to 0, but ensure it's not negative
|
|
24844
|
+
// This accounts for any onended callbacks that might fire from stopped sources
|
|
24845
|
+
// If scheduledBuffers is negative, it means onended callbacks fired after stopImmediate()
|
|
24846
|
+
// In that case, we should reset to 0 to start fresh
|
|
24847
|
+
if (this.scheduledBuffers < 0) {
|
|
24848
|
+
console.log("\uD83D\uDD04 AudioPlayer: scheduledBuffers was negative (".concat(this.scheduledBuffers, "), resetting to 0"));
|
|
24849
|
+
}
|
|
24850
|
+
this.scheduledBuffers = 0;
|
|
24851
|
+
|
|
24852
|
+
// CRITICAL: ALWAYS clear preparedBuffer after barge-in - any remaining chunks are from previous sentence
|
|
24853
|
+
// This prevents old audio from playing when new sentence starts after interruption
|
|
24854
|
+
if (this.preparedBuffer.length > 0) {
|
|
24855
|
+
console.log("\uD83D\uDD04 AudioPlayer: Clearing ".concat(this.preparedBuffer.length, " prepared frames from previous sentence (after barge-in)"));
|
|
24856
|
+
}
|
|
24857
|
+
this.preparedBuffer = [];
|
|
24858
|
+
|
|
24859
|
+
// CRITICAL: Also clear pcmChunkQueue to prevent raw chunks from previous sentence being processed
|
|
24860
|
+
if (this.pcmChunkQueue.length > 0) {
|
|
24861
|
+
console.log("\uD83D\uDD04 AudioPlayer: Clearing ".concat(this.pcmChunkQueue.length, " raw PCM chunks from previous sentence (after barge-in)"));
|
|
24862
|
+
}
|
|
24863
|
+
this.pcmChunkQueue = [];
|
|
24864
|
+
this.isProcessingPcmQueue = false;
|
|
24865
|
+
console.log('🔄 AudioPlayer: Reset scheduling state for new sentence (after barge-in)');
|
|
24866
|
+
} else if (isCurrentlyPlaying) {
|
|
24867
|
+
// New sentence received while audio is currently playing (no barge-in)
|
|
24868
|
+
// Don't clear buffers or reset state - let current sentence finish, then new one will play
|
|
24869
|
+
console.log("\uD83D\uDCDD AudioPlayer: New sentence queued while audio playing - will start after current sentence finishes");
|
|
24731
24870
|
}
|
|
24871
|
+
|
|
24872
|
+
// Always update pending sentence text (for transcript display)
|
|
24732
24873
|
this.pendingSentenceText = text;
|
|
24733
|
-
console.log("\uD83D\uDCDD AudioPlayer: New sentence marked: \"".concat(text.substring(0, 40), "...\""));
|
|
24874
|
+
console.log("\uD83D\uDCDD AudioPlayer: New sentence marked: \"".concat(text.substring(0, 40), "...\" (wasStopped: ").concat(wasStopped, ", isPlaying: ").concat(isCurrentlyPlaying, ")"));
|
|
24875
|
+
|
|
24876
|
+
// CRITICAL: Set timeout to detect empty sentences (audio_start but no chunks)
|
|
24877
|
+
// This prevents queue blocking if backend sends audio_start but no audio chunks follow
|
|
24878
|
+
if (this._emptySentenceTimeout) {
|
|
24879
|
+
clearTimeout(this._emptySentenceTimeout);
|
|
24880
|
+
}
|
|
24881
|
+
var sentenceText = text; // Capture for timeout callback
|
|
24882
|
+
this._emptySentenceTimeout = setTimeout(function () {
|
|
24883
|
+
// Check if this sentence still has no chunks after timeout
|
|
24884
|
+
if (_this9.pendingSentenceText === sentenceText && _this9.scheduledBuffers === 0 && _this9.preparedBuffer.length === 0 && _this9.pcmChunkQueue.length === 0 && !_this9._isStopped) {
|
|
24885
|
+
console.warn("\u26A0\uFE0F AudioPlayer: Empty sentence detected after 5s timeout - no chunks received for: \"".concat(sentenceText.substring(0, 40), "...\""));
|
|
24886
|
+
// Clear pending sentence to unblock next sentence
|
|
24887
|
+
if (_this9.pendingSentenceText === sentenceText) {
|
|
24888
|
+
_this9.pendingSentenceText = null;
|
|
24889
|
+
}
|
|
24890
|
+
// Emit playbackStopped to allow next sentence to start
|
|
24891
|
+
// Only if we're not currently playing (to avoid interrupting real playback)
|
|
24892
|
+
if (!_this9.isPlaying && _this9.scheduledSources.size === 0) {
|
|
24893
|
+
console.log('🛑 AudioPlayer: Emitting playbackStopped for empty sentence timeout');
|
|
24894
|
+
_this9.emit('playbackStopped');
|
|
24895
|
+
}
|
|
24896
|
+
}
|
|
24897
|
+
_this9._emptySentenceTimeout = null;
|
|
24898
|
+
}, 5000); // 5 second timeout - adjust based on expected chunk arrival rate
|
|
24734
24899
|
}
|
|
24735
24900
|
|
|
24736
24901
|
/**
|
|
@@ -24739,35 +24904,35 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
24739
24904
|
}, {
|
|
24740
24905
|
key: "startTranscriptChecker",
|
|
24741
24906
|
value: function startTranscriptChecker() {
|
|
24742
|
-
var
|
|
24907
|
+
var _this0 = this;
|
|
24743
24908
|
if (this.isCheckingTranscripts) return;
|
|
24744
24909
|
this.isCheckingTranscripts = true;
|
|
24745
24910
|
console.log('📝 AudioPlayer: Transcript checker started');
|
|
24746
24911
|
var _checkLoop = function checkLoop() {
|
|
24747
|
-
if (!
|
|
24748
|
-
var currentTime =
|
|
24749
|
-
var
|
|
24750
|
-
|
|
24912
|
+
if (!_this0.isCheckingTranscripts || !_this0.audioContext) return;
|
|
24913
|
+
var currentTime = _this0.audioContext.currentTime;
|
|
24914
|
+
var _iterator = _createForOfIteratorHelper(_this0.sentenceTimings),
|
|
24915
|
+
_step;
|
|
24751
24916
|
try {
|
|
24752
|
-
for (
|
|
24753
|
-
var timing =
|
|
24917
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
24918
|
+
var timing = _step.value;
|
|
24754
24919
|
if (!timing.displayed && currentTime >= timing.startTime) {
|
|
24755
24920
|
timing.displayed = true;
|
|
24756
24921
|
console.log("\uD83D\uDCDD AudioPlayer: Display transcript at ".concat(currentTime.toFixed(3), "s: \"").concat(timing.text.substring(0, 40), "...\""));
|
|
24757
|
-
|
|
24922
|
+
_this0.emit('transcriptDisplay', {
|
|
24758
24923
|
text: timing.text
|
|
24759
24924
|
});
|
|
24760
24925
|
}
|
|
24761
24926
|
}
|
|
24762
24927
|
} catch (err) {
|
|
24763
|
-
|
|
24928
|
+
_iterator.e(err);
|
|
24764
24929
|
} finally {
|
|
24765
|
-
|
|
24930
|
+
_iterator.f();
|
|
24766
24931
|
}
|
|
24767
|
-
if (
|
|
24932
|
+
if (_this0.isPlaying || _this0.scheduledBuffers > 0) {
|
|
24768
24933
|
requestAnimationFrame(_checkLoop);
|
|
24769
24934
|
} else {
|
|
24770
|
-
|
|
24935
|
+
_this0.isCheckingTranscripts = false;
|
|
24771
24936
|
console.log('📝 AudioPlayer: Transcript checker stopped');
|
|
24772
24937
|
}
|
|
24773
24938
|
};
|
|
@@ -26129,15 +26294,16 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
26129
26294
|
// Store the text in AudioPlayer for synced display when audio actually starts playing
|
|
26130
26295
|
console.log('📝 VoiceSDK v2: Received audio_start with text:', message.text);
|
|
26131
26296
|
|
|
26132
|
-
//
|
|
26133
|
-
//
|
|
26297
|
+
// NOTE: We do NOT stop current audio here - that only happens on user barge-in (stop_playing)
|
|
26298
|
+
// If audio is already playing, the new sentence will queue and wait for current one to finish
|
|
26299
|
+
// This allows sentences to play sequentially without interruption
|
|
26134
26300
|
if (this.audioPlayer && (this.audioPlayer.isPlaying || ((_this$audioPlayer$sch = this.audioPlayer.scheduledSources) === null || _this$audioPlayer$sch === void 0 ? void 0 : _this$audioPlayer$sch.size) > 0)) {
|
|
26135
|
-
console.log('
|
|
26136
|
-
this.audioPlayer.stopImmediate();
|
|
26301
|
+
console.log('📝 VoiceSDK v2: New sentence received while audio playing - will queue and wait for current sentence to finish');
|
|
26137
26302
|
}
|
|
26138
26303
|
if (message.text && this.audioPlayer) {
|
|
26139
26304
|
// Use AudioPlayer's transcript sync mechanism
|
|
26140
26305
|
// AudioPlayer will emit transcriptDisplay when audio actually starts playing (synced with audioContext.currentTime)
|
|
26306
|
+
// If audio is currently playing, markNewSentence will queue this sentence
|
|
26141
26307
|
this.audioPlayer.markNewSentence(message.text);
|
|
26142
26308
|
}
|
|
26143
26309
|
// Also emit as message for other listeners
|