ttp-agent-sdk 2.48.16 → 2.48.20
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 +848 -193
- package/dist/agent-widget.esm.js +1 -1
- package/dist/agent-widget.js +1 -1
- package/dist/index.html +111 -1
- package/package.json +3 -1
package/dist/agent-widget.dev.js
CHANGED
|
@@ -12129,7 +12129,7 @@ var TextChatSDK = /*#__PURE__*/function (_EventEmitter) {
|
|
|
12129
12129
|
|
|
12130
12130
|
// SDK build time for debugging
|
|
12131
12131
|
if (true) {
|
|
12132
|
-
helloMessage.lastBuildTime = "2026-08-
|
|
12132
|
+
helloMessage.lastBuildTime = "2026-08-27T16:45:39.666Z";
|
|
12133
12133
|
}
|
|
12134
12134
|
try {
|
|
12135
12135
|
this.ws.send(JSON.stringify(helloMessage));
|
|
@@ -21439,8 +21439,8 @@ var VoiceSDK = _v2_VoiceSDK_js__WEBPACK_IMPORTED_MODULE_0__["default"];
|
|
|
21439
21439
|
|
|
21440
21440
|
|
|
21441
21441
|
// Version - injected at build time from package.json via webpack DefinePlugin
|
|
21442
|
-
var VERSION = "2.48.
|
|
21443
|
-
var BUILD_TIME = "2026-08-
|
|
21442
|
+
var VERSION = "2.48.20";
|
|
21443
|
+
var BUILD_TIME = "2026-08-27T16:45:39.666Z";
|
|
21444
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;');
|
|
21445
21445
|
|
|
21446
21446
|
// Named exports
|
|
@@ -25615,6 +25615,8 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
25615
25615
|
/* harmony import */ var _codecs_PCMCodec_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./codecs/PCMCodec.js */ "./src/v2/codecs/PCMCodec.js");
|
|
25616
25616
|
/* harmony import */ var _codecs_PCMUCodec_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./codecs/PCMUCodec.js */ "./src/v2/codecs/PCMUCodec.js");
|
|
25617
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");
|
|
25619
|
+
/* harmony import */ var _loopbackAec_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./loopbackAec.js */ "./src/v2/loopbackAec.js");
|
|
25618
25620
|
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); }
|
|
25619
25621
|
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; }
|
|
25620
25622
|
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; }
|
|
@@ -25646,6 +25648,8 @@ function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf
|
|
|
25646
25648
|
|
|
25647
25649
|
|
|
25648
25650
|
|
|
25651
|
+
|
|
25652
|
+
|
|
25649
25653
|
// iOS FIX: Shared AudioContext for playback that persists across AudioPlayer instances.
|
|
25650
25654
|
// iOS WebKit doesn't release audio hardware synchronously when AudioContext.close() is called,
|
|
25651
25655
|
// so creating a new AudioContext immediately after closing the old one can fail silently.
|
|
@@ -25745,7 +25749,20 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
25745
25749
|
_this._mediaStreamDest = null;
|
|
25746
25750
|
_this._htmlAudioRouteActive = false;
|
|
25747
25751
|
_this._htmlAudioPlayOk = false;
|
|
25752
|
+
_this._htmlAudioPlayWatchdog = null;
|
|
25748
25753
|
_this._loggedHopResample = false;
|
|
25754
|
+
// Loopback AEC route (opt-in): the same MediaStream, but pushed through a
|
|
25755
|
+
// pair of local RTCPeerConnections first, because Chrome's echo canceller
|
|
25756
|
+
// only cancels peer-connection audio — a hidden <audio> is invisible to it.
|
|
25757
|
+
// See loopbackAec.js. Off unless asked for; the hop above is unchanged.
|
|
25758
|
+
_this._loopbackDest = null;
|
|
25759
|
+
_this._loopbackEl = null;
|
|
25760
|
+
_this._loopbackClose = null;
|
|
25761
|
+
_this._loopbackReady = false;
|
|
25762
|
+
_this._loopbackPending = false;
|
|
25763
|
+
_this._loopbackFailed = false;
|
|
25764
|
+
_this._loopbackPlayOk = false;
|
|
25765
|
+
_this._loopbackWanted = undefined; // memoized by wantsLoopbackAec()
|
|
25749
25766
|
// Greeting (and other bursts) fire many playChunk()s without await.
|
|
25750
25767
|
// Serialize context creation so they cannot open two AudioContexts
|
|
25751
25768
|
// at different rates in the middle of a sentence.
|
|
@@ -27019,6 +27036,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27019
27036
|
this.gainNode = null;
|
|
27020
27037
|
}
|
|
27021
27038
|
this._teardownHtmlAudioRoute();
|
|
27039
|
+
this._teardownLoopbackRoute();
|
|
27022
27040
|
if (this.audioContext === _sharedPlayerContext) {
|
|
27023
27041
|
_sharedPlayerContext = null;
|
|
27024
27042
|
_sharedPlayerSampleRate = null;
|
|
@@ -27164,37 +27182,40 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27164
27182
|
}, {
|
|
27165
27183
|
key: "wantsHtmlAudioPlayback",
|
|
27166
27184
|
value: function wantsHtmlAudioPlayback() {
|
|
27167
|
-
// Debug/QA override: ?ttp_hop=0|1
|
|
27168
|
-
//
|
|
27169
|
-
|
|
27170
|
-
|
|
27171
|
-
|
|
27172
|
-
|
|
27173
|
-
|
|
27174
|
-
}
|
|
27175
|
-
} catch (e) {/* non-browser env */}
|
|
27185
|
+
// Debug/QA override: ?ttp_hop=0|1, its sessionStorage mirror (survives
|
|
27186
|
+
// same-origin full page loads that drop the query string — Borusan's
|
|
27187
|
+
// navigate_to_form), or localStorage 'ttp_hop'. Wins over config.
|
|
27188
|
+
var override = (0,_hopPlatform_js__WEBPACK_IMPORTED_MODULE_4__.readTtpHopOverride)(typeof window !== 'undefined' ? window : null);
|
|
27189
|
+
if (override !== null) {
|
|
27190
|
+
return override;
|
|
27191
|
+
}
|
|
27176
27192
|
if (typeof this.config.htmlAudioPlayback === 'boolean') {
|
|
27177
27193
|
return this.config.htmlAudioPlayback;
|
|
27178
27194
|
}
|
|
27179
|
-
// Default:
|
|
27180
|
-
//
|
|
27181
|
-
//
|
|
27182
|
-
// desktop
|
|
27183
|
-
|
|
27195
|
+
// Default: HTML hop (AudioContext → MediaStreamDestination → <audio>) gives
|
|
27196
|
+
// Safari/Chrome a far-end AEC reference on Apple mobile + macOS where Web
|
|
27197
|
+
// Audio → destination is not in the echo-cancellation loop. On Windows and
|
|
27198
|
+
// Linux desktop, Chrome-wide AEC already covers speaker echo; the hop only
|
|
27199
|
+
// adds adaptive live-stream speed wobble — keep it off there (same as Linux).
|
|
27200
|
+
var hopOn = this._needsHtmlAudioHopForAec();
|
|
27184
27201
|
if (!this._hopDefaultLogged) {
|
|
27185
27202
|
this._hopDefaultLogged = true;
|
|
27186
|
-
|
|
27203
|
+
var reason = hopOn ? 'on (mobile / macOS: AEC far-end route)' : 'off (desktop Windows/Linux: stable direct Web Audio playback)';
|
|
27204
|
+
console.log("\uD83D\uDD0A AudioPlayer: htmlAudioPlayback default \u2192 ".concat(reason));
|
|
27187
27205
|
}
|
|
27188
27206
|
return hopOn;
|
|
27189
27207
|
}
|
|
27208
|
+
|
|
27209
|
+
/** Hop default on only where Web Audio destination lacks AEC far-end reference.
|
|
27210
|
+
* Policy lives in hopPlatform.js (pure, unit-tested); this only supplies the
|
|
27211
|
+
* environment strings. */
|
|
27190
27212
|
}, {
|
|
27191
|
-
key: "
|
|
27192
|
-
value: function
|
|
27213
|
+
key: "_needsHtmlAudioHopForAec",
|
|
27214
|
+
value: function _needsHtmlAudioHopForAec() {
|
|
27193
27215
|
try {
|
|
27194
27216
|
var ua = navigator.userAgent || '';
|
|
27195
|
-
if (/Android|iPhone|iPad|iPod/i.test(ua)) return false;
|
|
27196
27217
|
var platform = navigator.userAgentData && navigator.userAgentData.platform || navigator.platform || '';
|
|
27197
|
-
return
|
|
27218
|
+
return (0,_hopPlatform_js__WEBPACK_IMPORTED_MODULE_4__.needsHtmlAudioHopForAec)(ua, platform);
|
|
27198
27219
|
} catch (e) {
|
|
27199
27220
|
return false;
|
|
27200
27221
|
}
|
|
@@ -27215,7 +27236,10 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27215
27236
|
}, {
|
|
27216
27237
|
key: "_htmlHopNeedsHardwareRate",
|
|
27217
27238
|
value: function _htmlHopNeedsHardwareRate() {
|
|
27218
|
-
|
|
27239
|
+
// The loopback ends in the same live-MediaStream <audio> the hop does, so
|
|
27240
|
+
// it inherits the same device-rate requirement: a 24 kHz context makes
|
|
27241
|
+
// Chrome clock that stream wrong and play speech ~2x.
|
|
27242
|
+
return this.wantsHtmlAudioPlayback() || this.wantsLoopbackAec();
|
|
27219
27243
|
}
|
|
27220
27244
|
}, {
|
|
27221
27245
|
key: "_isHardwarePlaybackRate",
|
|
@@ -27256,7 +27280,10 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27256
27280
|
return {
|
|
27257
27281
|
htmlAudioPlayback: this.wantsHtmlAudioPlayback(),
|
|
27258
27282
|
htmlAudioRouteActive: this._htmlAudioRouteActive,
|
|
27259
|
-
htmlAudioPlayOk: this._htmlAudioPlayOk
|
|
27283
|
+
htmlAudioPlayOk: this._htmlAudioPlayOk,
|
|
27284
|
+
loopbackAec: this.wantsLoopbackAec(),
|
|
27285
|
+
loopbackRouteActive: this._loopbackReady,
|
|
27286
|
+
loopbackPlayOk: this._loopbackPlayOk
|
|
27260
27287
|
};
|
|
27261
27288
|
}
|
|
27262
27289
|
|
|
@@ -27273,6 +27300,18 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27273
27300
|
try {
|
|
27274
27301
|
this.gainNode.disconnect();
|
|
27275
27302
|
} catch (_) {/* ignore */}
|
|
27303
|
+
|
|
27304
|
+
// Loopback first when asked for. It returns null until the peer connections
|
|
27305
|
+
// are up (negotiation is async), and we deliberately fall through to a
|
|
27306
|
+
// normal audible route in the meantime rather than holding audio: the
|
|
27307
|
+
// establishment is re-entrant and calls back here when it completes.
|
|
27308
|
+
if (this.wantsLoopbackAec()) {
|
|
27309
|
+
var loopbackDest = this._ensureLoopbackRoute();
|
|
27310
|
+
if (loopbackDest) {
|
|
27311
|
+
this.gainNode.connect(loopbackDest);
|
|
27312
|
+
return;
|
|
27313
|
+
}
|
|
27314
|
+
}
|
|
27276
27315
|
if (this.wantsHtmlAudioPlayback() && this._ensureHtmlAudioRoute()) {
|
|
27277
27316
|
this.gainNode.connect(this._mediaStreamDest);
|
|
27278
27317
|
return;
|
|
@@ -27289,48 +27328,84 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27289
27328
|
}
|
|
27290
27329
|
if (!this.audioContext || typeof this.audioContext.createMediaStreamDestination !== 'function') {
|
|
27291
27330
|
console.warn('⚠️ AudioPlayer: MediaStreamDestination unavailable — using AudioContext.destination');
|
|
27331
|
+
this.emit('htmlAudioRoute', {
|
|
27332
|
+
state: 'fallback',
|
|
27333
|
+
reason: 'no_media_stream_destination'
|
|
27334
|
+
});
|
|
27292
27335
|
return false;
|
|
27293
27336
|
}
|
|
27294
27337
|
try {
|
|
27295
27338
|
this._mediaStreamDest = this.audioContext.createMediaStreamDestination();
|
|
27296
|
-
var el =
|
|
27297
|
-
el.setAttribute('playsinline', '');
|
|
27298
|
-
el.setAttribute('webkit-playsinline', '');
|
|
27299
|
-
el.autoplay = true;
|
|
27300
|
-
el.controls = false;
|
|
27301
|
-
el.preload = 'auto';
|
|
27302
|
-
el.playbackRate = 1;
|
|
27303
|
-
el.style.display = 'none';
|
|
27304
|
-
el.srcObject = this._mediaStreamDest.stream;
|
|
27305
|
-
document.body.appendChild(el);
|
|
27339
|
+
var el = this._createHiddenAudioEl(this._mediaStreamDest.stream);
|
|
27306
27340
|
this._htmlAudioEl = el;
|
|
27307
27341
|
this._htmlAudioRouteActive = true;
|
|
27308
27342
|
this._ensureHtmlAudioPlaying();
|
|
27343
|
+
this._armHtmlAudioPlayWatchdog(el);
|
|
27309
27344
|
console.log('🔊 AudioPlayer: Playback last-hop is HTMLAudioElement (AEC far-end route)');
|
|
27310
27345
|
return true;
|
|
27311
27346
|
} catch (e) {
|
|
27312
27347
|
console.warn('⚠️ AudioPlayer: HTML audio route failed, falling back to destination:', e);
|
|
27313
27348
|
this._teardownHtmlAudioRoute();
|
|
27349
|
+
this.emit('htmlAudioRoute', {
|
|
27350
|
+
state: 'fallback',
|
|
27351
|
+
reason: 'route_init_failed'
|
|
27352
|
+
});
|
|
27314
27353
|
return false;
|
|
27315
27354
|
}
|
|
27316
27355
|
}
|
|
27356
|
+
|
|
27357
|
+
/**
|
|
27358
|
+
* play() on a MediaStream-backed element can stay pending forever on some
|
|
27359
|
+
* WebKit builds — neither resolving nor rejecting. If the element is still
|
|
27360
|
+
* paused shortly after route creation, report it: the hop never engaged and
|
|
27361
|
+
* AEC has no far-end reference, but audio still reaches the user via the
|
|
27362
|
+
* (silent-failure) direct path only if a fallback happened — so this state
|
|
27363
|
+
* means "route exists, playback through it unconfirmed".
|
|
27364
|
+
*/
|
|
27365
|
+
}, {
|
|
27366
|
+
key: "_armHtmlAudioPlayWatchdog",
|
|
27367
|
+
value: function _armHtmlAudioPlayWatchdog(el) {
|
|
27368
|
+
var _this7 = this;
|
|
27369
|
+
this._clearHtmlAudioPlayWatchdog();
|
|
27370
|
+
this._htmlAudioPlayWatchdog = setTimeout(function () {
|
|
27371
|
+
_this7._htmlAudioPlayWatchdog = null;
|
|
27372
|
+
if (_this7._htmlAudioRouteActive && _this7._htmlAudioEl === el && !_this7._htmlAudioPlayOk && el.paused) {
|
|
27373
|
+
console.warn('⚠️ AudioPlayer: HTMLAudioElement.play() still pending after 2.5s — hop engagement unconfirmed');
|
|
27374
|
+
_this7.emit('htmlAudioRoute', {
|
|
27375
|
+
state: 'play_pending'
|
|
27376
|
+
});
|
|
27377
|
+
}
|
|
27378
|
+
}, 2500);
|
|
27379
|
+
}
|
|
27380
|
+
}, {
|
|
27381
|
+
key: "_clearHtmlAudioPlayWatchdog",
|
|
27382
|
+
value: function _clearHtmlAudioPlayWatchdog() {
|
|
27383
|
+
if (this._htmlAudioPlayWatchdog) {
|
|
27384
|
+
clearTimeout(this._htmlAudioPlayWatchdog);
|
|
27385
|
+
this._htmlAudioPlayWatchdog = null;
|
|
27386
|
+
}
|
|
27387
|
+
}
|
|
27317
27388
|
}, {
|
|
27318
27389
|
key: "_ensureHtmlAudioPlaying",
|
|
27319
27390
|
value: function _ensureHtmlAudioPlaying() {
|
|
27320
|
-
var
|
|
27391
|
+
var _this8 = this;
|
|
27321
27392
|
var el = this._htmlAudioEl;
|
|
27322
27393
|
if (!el) return;
|
|
27323
27394
|
if (!el.paused && this._htmlAudioPlayOk) return;
|
|
27324
27395
|
var playResult = el.play();
|
|
27325
27396
|
if (playResult && typeof playResult.then === 'function') {
|
|
27326
27397
|
playResult.then(function () {
|
|
27327
|
-
|
|
27398
|
+
_this8._htmlAudioPlayOk = true;
|
|
27399
|
+
_this8._clearHtmlAudioPlayWatchdog();
|
|
27400
|
+
_this8.emit('htmlAudioRoute', {
|
|
27401
|
+
state: 'established'
|
|
27402
|
+
});
|
|
27328
27403
|
}).catch(function (err) {
|
|
27329
|
-
if (
|
|
27404
|
+
if (_this8._htmlAudioPlayOk || !el.paused) {
|
|
27330
27405
|
return;
|
|
27331
27406
|
}
|
|
27332
27407
|
console.warn('⚠️ AudioPlayer: HTMLAudioElement.play() rejected — falling back to destination:', err);
|
|
27333
|
-
|
|
27408
|
+
_this8._fallbackToDestination('play_rejected');
|
|
27334
27409
|
});
|
|
27335
27410
|
}
|
|
27336
27411
|
}
|
|
@@ -27342,12 +27417,18 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27342
27417
|
this.gainNode.disconnect();
|
|
27343
27418
|
} catch (_) {/* ignore */}
|
|
27344
27419
|
this._teardownHtmlAudioRoute();
|
|
27420
|
+
this._teardownLoopbackRoute();
|
|
27345
27421
|
this.gainNode.connect(this.audioContext.destination);
|
|
27346
27422
|
console.warn("\u26A0\uFE0F AudioPlayer: Fell back to AudioContext.destination (".concat(reason, ")"));
|
|
27423
|
+
this.emit('htmlAudioRoute', {
|
|
27424
|
+
state: 'fallback',
|
|
27425
|
+
reason: reason
|
|
27426
|
+
});
|
|
27347
27427
|
}
|
|
27348
27428
|
}, {
|
|
27349
27429
|
key: "_teardownHtmlAudioRoute",
|
|
27350
27430
|
value: function _teardownHtmlAudioRoute() {
|
|
27431
|
+
this._clearHtmlAudioPlayWatchdog();
|
|
27351
27432
|
if (this._htmlAudioEl) {
|
|
27352
27433
|
try {
|
|
27353
27434
|
this._htmlAudioEl.pause();
|
|
@@ -27364,29 +27445,216 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27364
27445
|
this._htmlAudioRouteActive = false;
|
|
27365
27446
|
}
|
|
27366
27447
|
|
|
27448
|
+
/**
|
|
27449
|
+
* Shared factory for the hidden playback element. Both last-hop routes need
|
|
27450
|
+
* exactly this: autoplay, inline on iOS, invisible, rate pinned to 1 so a
|
|
27451
|
+
* page-level playbackRate can't stretch TTS under us.
|
|
27452
|
+
*/
|
|
27453
|
+
}, {
|
|
27454
|
+
key: "_createHiddenAudioEl",
|
|
27455
|
+
value: function _createHiddenAudioEl(stream) {
|
|
27456
|
+
var el = document.createElement('audio');
|
|
27457
|
+
el.setAttribute('playsinline', '');
|
|
27458
|
+
el.setAttribute('webkit-playsinline', '');
|
|
27459
|
+
el.autoplay = true;
|
|
27460
|
+
el.controls = false;
|
|
27461
|
+
el.preload = 'auto';
|
|
27462
|
+
el.playbackRate = 1;
|
|
27463
|
+
el.style.display = 'none';
|
|
27464
|
+
el.srcObject = stream;
|
|
27465
|
+
document.body.appendChild(el);
|
|
27466
|
+
return el;
|
|
27467
|
+
}
|
|
27468
|
+
|
|
27469
|
+
/**
|
|
27470
|
+
* Opt-in loopback route. Default OFF: this is an unproven replacement for the
|
|
27471
|
+
* hop running on live customer sites, so it ships dark and is turned on per
|
|
27472
|
+
* machine (?ttp_loopback=1 / localStorage) or per integration (config).
|
|
27473
|
+
*/
|
|
27474
|
+
}, {
|
|
27475
|
+
key: "wantsLoopbackAec",
|
|
27476
|
+
value: function wantsLoopbackAec() {
|
|
27477
|
+
// Memoized per player: _htmlHopNeedsHardwareRate() consults this from
|
|
27478
|
+
// _honestRateSamples, i.e. once per audio chunk, and reading it live would
|
|
27479
|
+
// put a synchronous storage hit on the hot path for every machine where
|
|
27480
|
+
// the hop is off (there the || cannot short-circuit). Changing the flag
|
|
27481
|
+
// requires a reload anyway — that is how it is documented and tested.
|
|
27482
|
+
if (this._loopbackWanted === undefined) {
|
|
27483
|
+
var override = (0,_loopbackAec_js__WEBPACK_IMPORTED_MODULE_5__.readTtpLoopbackOverride)(typeof window !== 'undefined' ? window : null);
|
|
27484
|
+
this._loopbackWanted = override !== null ? override : typeof this.config.loopbackAec === 'boolean' ? this.config.loopbackAec : false;
|
|
27485
|
+
if (this._loopbackWanted) {
|
|
27486
|
+
console.log('🔊 AudioPlayer: loopback AEC requested — TTS will play via a local peer connection');
|
|
27487
|
+
}
|
|
27488
|
+
}
|
|
27489
|
+
return this._loopbackWanted;
|
|
27490
|
+
}
|
|
27491
|
+
|
|
27492
|
+
/**
|
|
27493
|
+
* Returns the MediaStreamDestination to play into once the loopback is up,
|
|
27494
|
+
* or null while it is not. Never blocks: the caller routes audio somewhere
|
|
27495
|
+
* audible in the meantime and we re-route from _establishLoopback.
|
|
27496
|
+
*/
|
|
27497
|
+
}, {
|
|
27498
|
+
key: "_ensureLoopbackRoute",
|
|
27499
|
+
value: function _ensureLoopbackRoute() {
|
|
27500
|
+
var _this9 = this;
|
|
27501
|
+
if (this._loopbackReady && this._loopbackDest) {
|
|
27502
|
+
this._ensureLoopbackPlaying();
|
|
27503
|
+
return this._loopbackDest;
|
|
27504
|
+
}
|
|
27505
|
+
if (this._loopbackFailed || this._loopbackPending) return null;
|
|
27506
|
+
if (!this.audioContext || typeof this.audioContext.createMediaStreamDestination !== 'function') {
|
|
27507
|
+
this._failLoopback('no_media_stream_destination');
|
|
27508
|
+
return null;
|
|
27509
|
+
}
|
|
27510
|
+
if (!(0,_loopbackAec_js__WEBPACK_IMPORTED_MODULE_5__.isLoopbackAecSupported)(typeof window !== 'undefined' ? window : null)) {
|
|
27511
|
+
this._failLoopback('no_peer_connection');
|
|
27512
|
+
return null;
|
|
27513
|
+
}
|
|
27514
|
+
this._loopbackPending = true;
|
|
27515
|
+
this._establishLoopback().catch(function (e) {
|
|
27516
|
+
console.warn('⚠️ AudioPlayer: loopback AEC route unavailable, keeping existing playback route:', e && e.message);
|
|
27517
|
+
_this9._failLoopback('negotiation_failed');
|
|
27518
|
+
});
|
|
27519
|
+
return null;
|
|
27520
|
+
}
|
|
27521
|
+
}, {
|
|
27522
|
+
key: "_establishLoopback",
|
|
27523
|
+
value: function () {
|
|
27524
|
+
var _establishLoopback2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
|
|
27525
|
+
var gen, dest, _yield$createAecLoopb, remoteStream, close;
|
|
27526
|
+
return _regenerator().w(function (_context8) {
|
|
27527
|
+
while (1) switch (_context8.n) {
|
|
27528
|
+
case 0:
|
|
27529
|
+
// Generation token: a teardown (call end, context rebuild) while we are
|
|
27530
|
+
// negotiating must not resurrect a route into a dead AudioContext.
|
|
27531
|
+
gen = this._loopbackGen = (this._loopbackGen || 0) + 1;
|
|
27532
|
+
dest = this.audioContext.createMediaStreamDestination();
|
|
27533
|
+
_context8.n = 1;
|
|
27534
|
+
return (0,_loopbackAec_js__WEBPACK_IMPORTED_MODULE_5__.createAecLoopback)({
|
|
27535
|
+
stream: dest.stream
|
|
27536
|
+
});
|
|
27537
|
+
case 1:
|
|
27538
|
+
_yield$createAecLoopb = _context8.v;
|
|
27539
|
+
remoteStream = _yield$createAecLoopb.remoteStream;
|
|
27540
|
+
close = _yield$createAecLoopb.close;
|
|
27541
|
+
if (!(gen !== this._loopbackGen || !this.audioContext)) {
|
|
27542
|
+
_context8.n = 2;
|
|
27543
|
+
break;
|
|
27544
|
+
}
|
|
27545
|
+
try {
|
|
27546
|
+
close();
|
|
27547
|
+
} catch (_) {/* ignore */}
|
|
27548
|
+
return _context8.a(2);
|
|
27549
|
+
case 2:
|
|
27550
|
+
// Drop the transitional hop element if one was built while we negotiated,
|
|
27551
|
+
// or it keeps playing a destination nothing is connected to any more.
|
|
27552
|
+
this._teardownHtmlAudioRoute();
|
|
27553
|
+
this._loopbackDest = dest;
|
|
27554
|
+
this._loopbackClose = close;
|
|
27555
|
+
this._loopbackEl = this._createHiddenAudioEl(remoteStream);
|
|
27556
|
+
this._loopbackReady = true;
|
|
27557
|
+
this._loopbackPending = false;
|
|
27558
|
+
this._ensureLoopbackPlaying();
|
|
27559
|
+
this._connectGainToOutput();
|
|
27560
|
+
console.log('🔊 AudioPlayer: Playback last-hop is a local WebRTC loopback (Chrome-cancellable AEC far-end)');
|
|
27561
|
+
this.emit('htmlAudioRoute', {
|
|
27562
|
+
state: 'loopback_established'
|
|
27563
|
+
});
|
|
27564
|
+
case 3:
|
|
27565
|
+
return _context8.a(2);
|
|
27566
|
+
}
|
|
27567
|
+
}, _callee7, this);
|
|
27568
|
+
}));
|
|
27569
|
+
function _establishLoopback() {
|
|
27570
|
+
return _establishLoopback2.apply(this, arguments);
|
|
27571
|
+
}
|
|
27572
|
+
return _establishLoopback;
|
|
27573
|
+
}()
|
|
27574
|
+
}, {
|
|
27575
|
+
key: "_ensureLoopbackPlaying",
|
|
27576
|
+
value: function _ensureLoopbackPlaying() {
|
|
27577
|
+
var _this0 = this;
|
|
27578
|
+
var el = this._loopbackEl;
|
|
27579
|
+
if (!el) return;
|
|
27580
|
+
if (!el.paused && this._loopbackPlayOk) return;
|
|
27581
|
+
var playResult = el.play();
|
|
27582
|
+
if (playResult && typeof playResult.then === 'function') {
|
|
27583
|
+
playResult.then(function () {
|
|
27584
|
+
_this0._loopbackPlayOk = true;
|
|
27585
|
+
}).catch(function (err) {
|
|
27586
|
+
if (_this0._loopbackPlayOk || !el.paused) return;
|
|
27587
|
+
console.warn('⚠️ AudioPlayer: loopback element play() rejected — reverting to the previous route:', err);
|
|
27588
|
+
_this0._failLoopback('play_rejected');
|
|
27589
|
+
_this0._connectGainToOutput();
|
|
27590
|
+
});
|
|
27591
|
+
}
|
|
27592
|
+
}
|
|
27593
|
+
|
|
27594
|
+
/** One-way latch: once the loopback is off for this player it stays off,
|
|
27595
|
+
* rather than retrying every reconnect and stuttering the route. */
|
|
27596
|
+
}, {
|
|
27597
|
+
key: "_failLoopback",
|
|
27598
|
+
value: function _failLoopback(reason) {
|
|
27599
|
+
this._loopbackFailed = true;
|
|
27600
|
+
this._loopbackPending = false;
|
|
27601
|
+
this._teardownLoopbackRoute();
|
|
27602
|
+
this.emit('htmlAudioRoute', {
|
|
27603
|
+
state: 'loopback_fallback',
|
|
27604
|
+
reason: reason
|
|
27605
|
+
});
|
|
27606
|
+
}
|
|
27607
|
+
}, {
|
|
27608
|
+
key: "_teardownLoopbackRoute",
|
|
27609
|
+
value: function _teardownLoopbackRoute() {
|
|
27610
|
+
this._loopbackGen = (this._loopbackGen || 0) + 1;
|
|
27611
|
+
if (this._loopbackEl) {
|
|
27612
|
+
try {
|
|
27613
|
+
this._loopbackEl.pause();
|
|
27614
|
+
} catch (_) {/* ignore */}
|
|
27615
|
+
try {
|
|
27616
|
+
this._loopbackEl.srcObject = null;
|
|
27617
|
+
} catch (_) {/* ignore */}
|
|
27618
|
+
if (this._loopbackEl.parentNode) {
|
|
27619
|
+
this._loopbackEl.parentNode.removeChild(this._loopbackEl);
|
|
27620
|
+
}
|
|
27621
|
+
this._loopbackEl = null;
|
|
27622
|
+
}
|
|
27623
|
+
if (this._loopbackClose) {
|
|
27624
|
+
try {
|
|
27625
|
+
this._loopbackClose();
|
|
27626
|
+
} catch (_) {/* ignore */}
|
|
27627
|
+
this._loopbackClose = null;
|
|
27628
|
+
}
|
|
27629
|
+
this._loopbackDest = null;
|
|
27630
|
+
this._loopbackReady = false;
|
|
27631
|
+
this._loopbackPending = false;
|
|
27632
|
+
this._loopbackPlayOk = false;
|
|
27633
|
+
}
|
|
27634
|
+
|
|
27367
27635
|
/**
|
|
27368
27636
|
* Initialize audio context with correct sample rate
|
|
27369
27637
|
*/
|
|
27370
27638
|
}, {
|
|
27371
27639
|
key: "initializeAudioContext",
|
|
27372
27640
|
value: (function () {
|
|
27373
|
-
var _initializeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function
|
|
27374
|
-
var
|
|
27375
|
-
return _regenerator().w(function (
|
|
27376
|
-
while (1) switch (
|
|
27641
|
+
var _initializeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee8() {
|
|
27642
|
+
var _this1 = this;
|
|
27643
|
+
return _regenerator().w(function (_context9) {
|
|
27644
|
+
while (1) switch (_context9.n) {
|
|
27377
27645
|
case 0:
|
|
27378
27646
|
if (!this._audioContextInitPromise) {
|
|
27379
|
-
|
|
27647
|
+
_context9.n = 1;
|
|
27380
27648
|
break;
|
|
27381
27649
|
}
|
|
27382
|
-
return
|
|
27650
|
+
return _context9.a(2, this._audioContextInitPromise);
|
|
27383
27651
|
case 1:
|
|
27384
27652
|
this._audioContextInitPromise = this._initializeAudioContextImpl().finally(function () {
|
|
27385
|
-
|
|
27653
|
+
_this1._audioContextInitPromise = null;
|
|
27386
27654
|
});
|
|
27387
|
-
return
|
|
27655
|
+
return _context9.a(2, this._audioContextInitPromise);
|
|
27388
27656
|
}
|
|
27389
|
-
},
|
|
27657
|
+
}, _callee8, this);
|
|
27390
27658
|
}));
|
|
27391
27659
|
function initializeAudioContext() {
|
|
27392
27660
|
return _initializeAudioContext.apply(this, arguments);
|
|
@@ -27396,11 +27664,11 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27396
27664
|
}, {
|
|
27397
27665
|
key: "_initializeAudioContextImpl",
|
|
27398
27666
|
value: function () {
|
|
27399
|
-
var _initializeAudioContextImpl2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function
|
|
27400
|
-
var
|
|
27667
|
+
var _initializeAudioContextImpl2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee9() {
|
|
27668
|
+
var _this10 = this;
|
|
27401
27669
|
var ttsSampleRate, useHardwareRate, desiredSampleRate, contextFits, canReuseShared, setupAfterResume, ctxOpts, _t4;
|
|
27402
|
-
return _regenerator().w(function (
|
|
27403
|
-
while (1) switch (
|
|
27670
|
+
return _regenerator().w(function (_context0) {
|
|
27671
|
+
while (1) switch (_context0.p = _context0.n) {
|
|
27404
27672
|
case 0:
|
|
27405
27673
|
ttsSampleRate = this._ttsSampleRate();
|
|
27406
27674
|
useHardwareRate = this._htmlHopNeedsHardwareRate(); // Destination path: match TTS (24 kHz). HTML last-hop: request the standard
|
|
@@ -27418,27 +27686,27 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27418
27686
|
_sharedPlayerSampleRate = null;
|
|
27419
27687
|
}
|
|
27420
27688
|
contextFits = function contextFits(rate) {
|
|
27421
|
-
if (useHardwareRate) return
|
|
27689
|
+
if (useHardwareRate) return _this10._isHardwarePlaybackRate(rate);
|
|
27422
27690
|
return Math.abs(rate - desiredSampleRate) <= 100;
|
|
27423
27691
|
}; // Check if current instance AudioContext exists and matches
|
|
27424
27692
|
if (!this.audioContext) {
|
|
27425
|
-
|
|
27693
|
+
_context0.n = 3;
|
|
27426
27694
|
break;
|
|
27427
27695
|
}
|
|
27428
27696
|
if (!(this.audioContext.state === 'closed')) {
|
|
27429
|
-
|
|
27697
|
+
_context0.n = 1;
|
|
27430
27698
|
break;
|
|
27431
27699
|
}
|
|
27432
27700
|
this._invalidateClosedAudioContext();
|
|
27433
|
-
|
|
27701
|
+
_context0.n = 3;
|
|
27434
27702
|
break;
|
|
27435
27703
|
case 1:
|
|
27436
27704
|
if (!contextFits(this.audioContext.sampleRate)) {
|
|
27437
|
-
|
|
27705
|
+
_context0.n = 2;
|
|
27438
27706
|
break;
|
|
27439
27707
|
}
|
|
27440
27708
|
this._ensureHtmlAudioPlaying();
|
|
27441
|
-
return
|
|
27709
|
+
return _context0.a(2);
|
|
27442
27710
|
case 2:
|
|
27443
27711
|
console.warn("\u26A0\uFE0F AudioPlayer: AudioContext sample rate (".concat(this.audioContext.sampleRate, "Hz) doesn't match playback hop, recreating..."));
|
|
27444
27712
|
this.stopImmediate();
|
|
@@ -27449,44 +27717,44 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27449
27717
|
// causing newly created AudioContexts to fail silently.
|
|
27450
27718
|
canReuseShared = _sharedPlayerContext && _sharedPlayerContext.state !== 'closed' && contextFits(_sharedPlayerContext.sampleRate);
|
|
27451
27719
|
if (!canReuseShared) {
|
|
27452
|
-
|
|
27720
|
+
_context0.n = 8;
|
|
27453
27721
|
break;
|
|
27454
27722
|
}
|
|
27455
27723
|
console.log("\u267B\uFE0F AudioPlayer: Reusing shared AudioContext at ".concat(_sharedPlayerContext.sampleRate, "Hz (iOS-safe)"));
|
|
27456
27724
|
this.audioContext = _sharedPlayerContext;
|
|
27457
27725
|
setupAfterResume = function setupAfterResume() {
|
|
27458
|
-
|
|
27459
|
-
if (
|
|
27726
|
+
_this10.setupAudioContextStateMonitoring();
|
|
27727
|
+
if (_this10.gainNode) {
|
|
27460
27728
|
try {
|
|
27461
|
-
|
|
27729
|
+
_this10.gainNode.disconnect();
|
|
27462
27730
|
} catch (e) {
|
|
27463
27731
|
console.warn('⚠️ AudioPlayer: Error disconnecting old GainNode:', e);
|
|
27464
27732
|
}
|
|
27465
27733
|
}
|
|
27466
|
-
|
|
27467
|
-
|
|
27468
|
-
|
|
27469
|
-
if (!
|
|
27470
|
-
|
|
27734
|
+
_this10.gainNode = _this10.audioContext.createGain();
|
|
27735
|
+
_this10.gainNode.gain.value = 1.0;
|
|
27736
|
+
_this10._connectGainToOutput();
|
|
27737
|
+
if (!_this10._audioContextPrimed) {
|
|
27738
|
+
_this10._primeAudioContext();
|
|
27471
27739
|
}
|
|
27472
27740
|
};
|
|
27473
27741
|
if (!(this.audioContext.state === 'suspended')) {
|
|
27474
|
-
|
|
27742
|
+
_context0.n = 7;
|
|
27475
27743
|
break;
|
|
27476
27744
|
}
|
|
27477
|
-
|
|
27478
|
-
|
|
27745
|
+
_context0.p = 4;
|
|
27746
|
+
_context0.n = 5;
|
|
27479
27747
|
return this.audioContext.resume();
|
|
27480
27748
|
case 5:
|
|
27481
|
-
|
|
27749
|
+
_context0.n = 7;
|
|
27482
27750
|
break;
|
|
27483
27751
|
case 6:
|
|
27484
|
-
|
|
27485
|
-
_t4 =
|
|
27752
|
+
_context0.p = 6;
|
|
27753
|
+
_t4 = _context0.v;
|
|
27486
27754
|
console.warn('⚠️ AudioPlayer: Error resuming shared context:', _t4);
|
|
27487
27755
|
case 7:
|
|
27488
27756
|
setupAfterResume();
|
|
27489
|
-
return
|
|
27757
|
+
return _context0.a(2);
|
|
27490
27758
|
case 8:
|
|
27491
27759
|
console.log("\uD83C\uDFB5 AudioPlayer: Creating AudioContext (tts=".concat(ttsSampleRate, "Hz, htmlHop=").concat(useHardwareRate, ", requested=").concat(desiredSampleRate, "Hz)"));
|
|
27492
27760
|
|
|
@@ -27539,9 +27807,9 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27539
27807
|
this._primeAudioContext();
|
|
27540
27808
|
}
|
|
27541
27809
|
case 9:
|
|
27542
|
-
return
|
|
27810
|
+
return _context0.a(2);
|
|
27543
27811
|
}
|
|
27544
|
-
},
|
|
27812
|
+
}, _callee9, this, [[4, 6]]);
|
|
27545
27813
|
}));
|
|
27546
27814
|
function _initializeAudioContextImpl() {
|
|
27547
27815
|
return _initializeAudioContextImpl2.apply(this, arguments);
|
|
@@ -27555,17 +27823,17 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27555
27823
|
}, {
|
|
27556
27824
|
key: "_primeAudioContext",
|
|
27557
27825
|
value: (function () {
|
|
27558
|
-
var _primeAudioContext2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function
|
|
27826
|
+
var _primeAudioContext2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee0() {
|
|
27559
27827
|
var sampleRate, silentBuffer, source, _t5;
|
|
27560
|
-
return _regenerator().w(function (
|
|
27561
|
-
while (1) switch (
|
|
27828
|
+
return _regenerator().w(function (_context1) {
|
|
27829
|
+
while (1) switch (_context1.p = _context1.n) {
|
|
27562
27830
|
case 0:
|
|
27563
|
-
|
|
27831
|
+
_context1.p = 0;
|
|
27564
27832
|
if (!(!this.audioContext || this._audioContextPrimed)) {
|
|
27565
|
-
|
|
27833
|
+
_context1.n = 1;
|
|
27566
27834
|
break;
|
|
27567
27835
|
}
|
|
27568
|
-
return
|
|
27836
|
+
return _context1.a(2);
|
|
27569
27837
|
case 1:
|
|
27570
27838
|
sampleRate = this.audioContext.sampleRate;
|
|
27571
27839
|
silentBuffer = this.audioContext.createBuffer(1, sampleRate * 0.15, sampleRate);
|
|
@@ -27576,23 +27844,23 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27576
27844
|
source.connect(this.gainNode || this.audioContext.destination);
|
|
27577
27845
|
source.start();
|
|
27578
27846
|
this._ensureHtmlAudioPlaying();
|
|
27579
|
-
|
|
27847
|
+
_context1.n = 2;
|
|
27580
27848
|
return new Promise(function (resolve) {
|
|
27581
27849
|
return setTimeout(resolve, 150);
|
|
27582
27850
|
});
|
|
27583
27851
|
case 2:
|
|
27584
27852
|
this._audioContextPrimed = true;
|
|
27585
27853
|
console.log('✅ AudioPlayer: Audio hardware primed with silent buffer');
|
|
27586
|
-
|
|
27854
|
+
_context1.n = 4;
|
|
27587
27855
|
break;
|
|
27588
27856
|
case 3:
|
|
27589
|
-
|
|
27590
|
-
_t5 =
|
|
27857
|
+
_context1.p = 3;
|
|
27858
|
+
_t5 = _context1.v;
|
|
27591
27859
|
console.error('❌ AudioPlayer: Audio priming failed:', _t5);
|
|
27592
27860
|
case 4:
|
|
27593
|
-
return
|
|
27861
|
+
return _context1.a(2);
|
|
27594
27862
|
}
|
|
27595
|
-
},
|
|
27863
|
+
}, _callee0, this, [[0, 3]]);
|
|
27596
27864
|
}));
|
|
27597
27865
|
function _primeAudioContext() {
|
|
27598
27866
|
return _primeAudioContext2.apply(this, arguments);
|
|
@@ -27619,6 +27887,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27619
27887
|
this.gainNode = null;
|
|
27620
27888
|
}
|
|
27621
27889
|
this._teardownHtmlAudioRoute();
|
|
27890
|
+
this._teardownLoopbackRoute();
|
|
27622
27891
|
|
|
27623
27892
|
// Remove state change listener first
|
|
27624
27893
|
if (this._audioContextStateChangeHandler && this.audioContext) {
|
|
@@ -27659,7 +27928,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27659
27928
|
* Handles mic permission grants, tab switching, browser suspension, etc.
|
|
27660
27929
|
*/
|
|
27661
27930
|
function setupAudioContextStateMonitoring() {
|
|
27662
|
-
var
|
|
27931
|
+
var _this11 = this;
|
|
27663
27932
|
if (!this.audioContext) {
|
|
27664
27933
|
return;
|
|
27665
27934
|
}
|
|
@@ -27672,38 +27941,38 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27672
27941
|
// Create handler that references this.audioContext dynamically
|
|
27673
27942
|
this._audioContextStateChangeHandler = function () {
|
|
27674
27943
|
// Null check required because audioContext may be cleaned up while handler is queued
|
|
27675
|
-
if (!
|
|
27944
|
+
if (!_this11.audioContext) {
|
|
27676
27945
|
console.warn('⚠️ AudioPlayer: State change handler fired but AudioContext is null');
|
|
27677
27946
|
return;
|
|
27678
27947
|
}
|
|
27679
|
-
console.log("\uD83C\uDFB5 AudioPlayer: AudioContext state changed to: ".concat(
|
|
27680
|
-
if (
|
|
27681
|
-
|
|
27948
|
+
console.log("\uD83C\uDFB5 AudioPlayer: AudioContext state changed to: ".concat(_this11.audioContext.state));
|
|
27949
|
+
if (_this11.audioContext.state === 'running') {
|
|
27950
|
+
_this11._ensureHtmlAudioPlaying();
|
|
27682
27951
|
}
|
|
27683
|
-
if (
|
|
27952
|
+
if (_this11.audioContext.state === 'suspended' && _this11.isPlaying) {
|
|
27684
27953
|
// AudioContext was suspended during playback (tab switch, mic permission, etc.)
|
|
27685
27954
|
console.warn('⚠️ AudioPlayer: AudioContext suspended during playback');
|
|
27686
27955
|
// Note: Playback will pause automatically, but we should handle queue processing
|
|
27687
27956
|
// The state change will be handled when we try to process next frame
|
|
27688
|
-
} else if (
|
|
27957
|
+
} else if (_this11.audioContext.state === 'running' && !_this11.isPlaying && (_this11.audioQueue.length > 0 || _this11.pcmChunkQueue.length > 0 || _this11.preparedBuffer.length > 0)) {
|
|
27689
27958
|
// AudioContext resumed and we have queued frames
|
|
27690
27959
|
// This handles: mic permission grant, tab switching back, browser resume, etc.
|
|
27691
27960
|
console.log('✅ AudioPlayer: AudioContext resumed - resuming queue processing');
|
|
27692
27961
|
|
|
27693
27962
|
// Resume queue processing if we have frames
|
|
27694
|
-
if (
|
|
27963
|
+
if (_this11.audioQueue.length > 0 && !_this11.isProcessingQueue) {
|
|
27695
27964
|
setTimeout(function () {
|
|
27696
|
-
return
|
|
27965
|
+
return _this11.processQueue();
|
|
27697
27966
|
}, 50);
|
|
27698
27967
|
}
|
|
27699
|
-
if (
|
|
27968
|
+
if (_this11.pcmChunkQueue.length > 0 && !_this11.isProcessingPcmQueue) {
|
|
27700
27969
|
setTimeout(function () {
|
|
27701
|
-
return
|
|
27970
|
+
return _this11.processPcmQueue();
|
|
27702
27971
|
}, 50);
|
|
27703
27972
|
}
|
|
27704
|
-
if (
|
|
27973
|
+
if (_this11.preparedBuffer.length > 0 && !_this11.isSchedulingFrames) {
|
|
27705
27974
|
setTimeout(function () {
|
|
27706
|
-
return
|
|
27975
|
+
return _this11.scheduleFrames();
|
|
27707
27976
|
}, 50);
|
|
27708
27977
|
}
|
|
27709
27978
|
}
|
|
@@ -27721,49 +27990,49 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27721
27990
|
}, {
|
|
27722
27991
|
key: "processQueue",
|
|
27723
27992
|
value: (function () {
|
|
27724
|
-
var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function
|
|
27725
|
-
var
|
|
27993
|
+
var _processQueue = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee1() {
|
|
27994
|
+
var _this12 = this;
|
|
27726
27995
|
var audioBlob, wasFirstPlay, audioContext, arrayBuffer, audioBuffer, shouldEmitStart, source, _t6;
|
|
27727
|
-
return _regenerator().w(function (
|
|
27728
|
-
while (1) switch (
|
|
27996
|
+
return _regenerator().w(function (_context10) {
|
|
27997
|
+
while (1) switch (_context10.p = _context10.n) {
|
|
27729
27998
|
case 0:
|
|
27730
27999
|
if (!this._isStopped) {
|
|
27731
|
-
|
|
28000
|
+
_context10.n = 1;
|
|
27732
28001
|
break;
|
|
27733
28002
|
}
|
|
27734
28003
|
console.log('🛑 AudioPlayer: Not processing queue - playback was stopped (barge-in)');
|
|
27735
|
-
return
|
|
28004
|
+
return _context10.a(2);
|
|
27736
28005
|
case 1:
|
|
27737
28006
|
if (!(this.isProcessingQueue || this.audioQueue.length === 0)) {
|
|
27738
|
-
|
|
28007
|
+
_context10.n = 2;
|
|
27739
28008
|
break;
|
|
27740
28009
|
}
|
|
27741
|
-
return
|
|
28010
|
+
return _context10.a(2);
|
|
27742
28011
|
case 2:
|
|
27743
28012
|
this.isProcessingQueue = true;
|
|
27744
28013
|
this._ensureHtmlAudioPlaying();
|
|
27745
28014
|
audioBlob = this.audioQueue.shift();
|
|
27746
28015
|
if (audioBlob) {
|
|
27747
|
-
|
|
28016
|
+
_context10.n = 3;
|
|
27748
28017
|
break;
|
|
27749
28018
|
}
|
|
27750
28019
|
this.isProcessingQueue = false;
|
|
27751
|
-
return
|
|
28020
|
+
return _context10.a(2);
|
|
27752
28021
|
case 3:
|
|
27753
|
-
|
|
28022
|
+
_context10.p = 3;
|
|
27754
28023
|
wasFirstPlay = !this.isPlaying && this.currentSource === null; // Ensure context exists and is running (handles lazy init + suspended shared context)
|
|
27755
|
-
|
|
28024
|
+
_context10.n = 4;
|
|
27756
28025
|
return this.waitForAudioContextReady();
|
|
27757
28026
|
case 4:
|
|
27758
28027
|
audioContext = this.audioContext; // Decode audio
|
|
27759
|
-
|
|
28028
|
+
_context10.n = 5;
|
|
27760
28029
|
return audioBlob.arrayBuffer();
|
|
27761
28030
|
case 5:
|
|
27762
|
-
arrayBuffer =
|
|
27763
|
-
|
|
28031
|
+
arrayBuffer = _context10.v;
|
|
28032
|
+
_context10.n = 6;
|
|
27764
28033
|
return audioContext.decodeAudioData(arrayBuffer);
|
|
27765
28034
|
case 6:
|
|
27766
|
-
audioBuffer =
|
|
28035
|
+
audioBuffer = _context10.v;
|
|
27767
28036
|
shouldEmitStart = wasFirstPlay && !this.isPlaying && this.currentSource === null; // Create source
|
|
27768
28037
|
source = audioContext.createBufferSource();
|
|
27769
28038
|
source.buffer = audioBuffer;
|
|
@@ -27782,22 +28051,22 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27782
28051
|
// Handle end
|
|
27783
28052
|
|
|
27784
28053
|
source.onended = function () {
|
|
27785
|
-
|
|
27786
|
-
|
|
28054
|
+
_this12.currentSource = null;
|
|
28055
|
+
_this12.isProcessingQueue = false;
|
|
27787
28056
|
|
|
27788
28057
|
// Process next chunk
|
|
27789
28058
|
|
|
27790
|
-
if (
|
|
28059
|
+
if (_this12.audioQueue.length > 0) {
|
|
27791
28060
|
setTimeout(function () {
|
|
27792
|
-
return
|
|
28061
|
+
return _this12.processQueue();
|
|
27793
28062
|
}, 50);
|
|
27794
28063
|
} else {
|
|
27795
28064
|
// No more chunks - stop after delay
|
|
27796
28065
|
|
|
27797
28066
|
setTimeout(function () {
|
|
27798
|
-
if (
|
|
27799
|
-
|
|
27800
|
-
|
|
28067
|
+
if (_this12.audioQueue.length === 0 && !_this12.currentSource) {
|
|
28068
|
+
_this12.isPlaying = false;
|
|
28069
|
+
_this12.emit('playbackStopped');
|
|
27801
28070
|
}
|
|
27802
28071
|
}, 100);
|
|
27803
28072
|
}
|
|
@@ -27806,11 +28075,11 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27806
28075
|
// Start playback - AudioContext should now be fully ready
|
|
27807
28076
|
|
|
27808
28077
|
source.start();
|
|
27809
|
-
|
|
28078
|
+
_context10.n = 8;
|
|
27810
28079
|
break;
|
|
27811
28080
|
case 7:
|
|
27812
|
-
|
|
27813
|
-
_t6 =
|
|
28081
|
+
_context10.p = 7;
|
|
28082
|
+
_t6 = _context10.v;
|
|
27814
28083
|
console.error('❌ AudioPlayer v2: Error processing queue:', _t6);
|
|
27815
28084
|
this.currentSource = null;
|
|
27816
28085
|
this.emit('playbackError', _t6);
|
|
@@ -27820,7 +28089,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27820
28089
|
if (this.audioQueue.length > 0) {
|
|
27821
28090
|
this.isProcessingQueue = false;
|
|
27822
28091
|
setTimeout(function () {
|
|
27823
|
-
return
|
|
28092
|
+
return _this12.processQueue();
|
|
27824
28093
|
}, 100);
|
|
27825
28094
|
} else {
|
|
27826
28095
|
this.isPlaying = false;
|
|
@@ -27828,9 +28097,9 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
27828
28097
|
this.emit('playbackStopped');
|
|
27829
28098
|
}
|
|
27830
28099
|
case 8:
|
|
27831
|
-
return
|
|
28100
|
+
return _context10.a(2);
|
|
27832
28101
|
}
|
|
27833
|
-
},
|
|
28102
|
+
}, _callee1, this, [[3, 7]]);
|
|
27834
28103
|
}));
|
|
27835
28104
|
function processQueue() {
|
|
27836
28105
|
return _processQueue.apply(this, arguments);
|
|
@@ -28043,7 +28312,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28043
28312
|
}, {
|
|
28044
28313
|
key: "markNewSentence",
|
|
28045
28314
|
value: function markNewSentence(text, synced, segmentId) {
|
|
28046
|
-
var
|
|
28315
|
+
var _this13 = this;
|
|
28047
28316
|
var wasStopped = this._isStopped;
|
|
28048
28317
|
var isCurrentlyPlaying = this.isPlaying || this.scheduledSources.size > 0;
|
|
28049
28318
|
|
|
@@ -28102,34 +28371,34 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28102
28371
|
var sentenceText = text; // Capture for timeout callback
|
|
28103
28372
|
this._emptySentenceTimeout = setTimeout(function () {
|
|
28104
28373
|
// Check if this sentence still has no chunks after timeout
|
|
28105
|
-
if (
|
|
28374
|
+
if (_this13.pendingSentenceText === sentenceText && _this13.scheduledBuffers === 0 && _this13.preparedBuffer.length === 0 && _this13.pcmChunkQueue.length === 0 && !_this13._isStopped) {
|
|
28106
28375
|
console.warn("\u26A0\uFE0F AudioPlayer: Empty sentence detected after 5s timeout - no chunks received for: \"".concat(sentenceText.substring(0, 40), "...\""));
|
|
28107
28376
|
// If this empty sentence carried a segment id, report it done (nothing was heard) and
|
|
28108
28377
|
// adopt it as current so the coarse stop below matches the backend's last-sent id.
|
|
28109
|
-
if (
|
|
28110
|
-
var emptySegId =
|
|
28111
|
-
|
|
28112
|
-
|
|
28113
|
-
|
|
28378
|
+
if (_this13.pendingSegmentId != null) {
|
|
28379
|
+
var emptySegId = _this13.pendingSegmentId;
|
|
28380
|
+
_this13.currentSegmentId = emptySegId;
|
|
28381
|
+
_this13.pendingSegmentId = null;
|
|
28382
|
+
_this13.emit('segmentDone', {
|
|
28114
28383
|
segmentId: emptySegId,
|
|
28115
28384
|
status: 'finished',
|
|
28116
28385
|
playedMs: 0
|
|
28117
28386
|
});
|
|
28118
28387
|
}
|
|
28119
28388
|
// Clear pending sentence to unblock next sentence
|
|
28120
|
-
if (
|
|
28121
|
-
|
|
28389
|
+
if (_this13.pendingSentenceText === sentenceText) {
|
|
28390
|
+
_this13.pendingSentenceText = null;
|
|
28122
28391
|
}
|
|
28123
28392
|
// Emit playbackStopped to allow next sentence to start
|
|
28124
28393
|
// Only if we're not currently playing (to avoid interrupting real playback)
|
|
28125
|
-
if (!
|
|
28394
|
+
if (!_this13.isPlaying && _this13.scheduledSources.size === 0) {
|
|
28126
28395
|
console.log('🛑 AudioPlayer: Emitting playbackStopped for empty sentence timeout');
|
|
28127
|
-
|
|
28128
|
-
segmentId:
|
|
28396
|
+
_this13.emit('playbackStopped', {
|
|
28397
|
+
segmentId: _this13.currentSegmentId
|
|
28129
28398
|
});
|
|
28130
28399
|
}
|
|
28131
28400
|
}
|
|
28132
|
-
|
|
28401
|
+
_this13._emptySentenceTimeout = null;
|
|
28133
28402
|
}, 5000); // 5 second timeout - adjust based on expected chunk arrival rate
|
|
28134
28403
|
}
|
|
28135
28404
|
|
|
@@ -28139,14 +28408,14 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28139
28408
|
}, {
|
|
28140
28409
|
key: "startTranscriptChecker",
|
|
28141
28410
|
value: function startTranscriptChecker() {
|
|
28142
|
-
var
|
|
28411
|
+
var _this14 = this;
|
|
28143
28412
|
if (this.isCheckingTranscripts) return;
|
|
28144
28413
|
this.isCheckingTranscripts = true;
|
|
28145
28414
|
console.log('📝 AudioPlayer: Transcript checker started');
|
|
28146
28415
|
var _checkLoop = function checkLoop() {
|
|
28147
|
-
if (!
|
|
28148
|
-
var currentTime =
|
|
28149
|
-
var _iterator2 = _createForOfIteratorHelper(
|
|
28416
|
+
if (!_this14.isCheckingTranscripts || !_this14.audioContext) return;
|
|
28417
|
+
var currentTime = _this14.audioContext.currentTime;
|
|
28418
|
+
var _iterator2 = _createForOfIteratorHelper(_this14.sentenceTimings),
|
|
28150
28419
|
_step2;
|
|
28151
28420
|
try {
|
|
28152
28421
|
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
@@ -28160,13 +28429,13 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28160
28429
|
eventData.synced = _timing.synced;
|
|
28161
28430
|
}
|
|
28162
28431
|
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, ")"));
|
|
28163
|
-
|
|
28432
|
+
_this14.emit('transcriptDisplay', eventData);
|
|
28164
28433
|
}
|
|
28165
28434
|
// Per-segment natural finish: this segment's audio window has fully elapsed.
|
|
28166
28435
|
if (!_timing.doneReported && _timing.endTime != null && currentTime >= _timing.endTime) {
|
|
28167
28436
|
_timing.doneReported = true;
|
|
28168
28437
|
var _playedMs = Math.round((_timing.endTime - _timing.startTime) * 1000);
|
|
28169
|
-
|
|
28438
|
+
_this14.emit('segmentDone', {
|
|
28170
28439
|
segmentId: _timing.segmentId,
|
|
28171
28440
|
status: 'finished',
|
|
28172
28441
|
playedMs: _playedMs
|
|
@@ -28178,12 +28447,12 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28178
28447
|
} finally {
|
|
28179
28448
|
_iterator2.f();
|
|
28180
28449
|
}
|
|
28181
|
-
if (
|
|
28450
|
+
if (_this14.isPlaying || _this14.scheduledBuffers > 0) {
|
|
28182
28451
|
requestAnimationFrame(_checkLoop);
|
|
28183
28452
|
} else {
|
|
28184
28453
|
// Playback drained naturally — flush any segment whose finish tick we may have missed
|
|
28185
28454
|
// (the last buffer's onended can flip isPlaying=false before this loop's next tick).
|
|
28186
|
-
var _iterator3 = _createForOfIteratorHelper(
|
|
28455
|
+
var _iterator3 = _createForOfIteratorHelper(_this14.sentenceTimings),
|
|
28187
28456
|
_step3;
|
|
28188
28457
|
try {
|
|
28189
28458
|
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
@@ -28192,7 +28461,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28192
28461
|
timing.doneReported = true;
|
|
28193
28462
|
var end = timing.endTime != null ? timing.endTime : timing.startTime;
|
|
28194
28463
|
var playedMs = Math.round((end - timing.startTime) * 1000);
|
|
28195
|
-
|
|
28464
|
+
_this14.emit('segmentDone', {
|
|
28196
28465
|
segmentId: timing.segmentId,
|
|
28197
28466
|
status: 'finished',
|
|
28198
28467
|
playedMs: playedMs
|
|
@@ -28204,7 +28473,7 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28204
28473
|
} finally {
|
|
28205
28474
|
_iterator3.f();
|
|
28206
28475
|
}
|
|
28207
|
-
|
|
28476
|
+
_this14.isCheckingTranscripts = false;
|
|
28208
28477
|
console.log('📝 AudioPlayer: Transcript checker stopped');
|
|
28209
28478
|
}
|
|
28210
28479
|
};
|
|
@@ -28235,43 +28504,43 @@ var AudioPlayer = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28235
28504
|
}, {
|
|
28236
28505
|
key: "resumeAudioContext",
|
|
28237
28506
|
value: (function () {
|
|
28238
|
-
var _resumeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function
|
|
28507
|
+
var _resumeAudioContext = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee10() {
|
|
28239
28508
|
var _t7;
|
|
28240
|
-
return _regenerator().w(function (
|
|
28241
|
-
while (1) switch (
|
|
28509
|
+
return _regenerator().w(function (_context11) {
|
|
28510
|
+
while (1) switch (_context11.p = _context11.n) {
|
|
28242
28511
|
case 0:
|
|
28243
28512
|
if (!(!this.audioContext || this.audioContext.state === 'closed')) {
|
|
28244
|
-
|
|
28513
|
+
_context11.n = 2;
|
|
28245
28514
|
break;
|
|
28246
28515
|
}
|
|
28247
|
-
|
|
28516
|
+
_context11.n = 1;
|
|
28248
28517
|
return this.initializeAudioContext();
|
|
28249
28518
|
case 1:
|
|
28250
|
-
|
|
28519
|
+
_context11.n = 6;
|
|
28251
28520
|
break;
|
|
28252
28521
|
case 2:
|
|
28253
28522
|
if (!(this.audioContext.state === 'suspended')) {
|
|
28254
|
-
|
|
28523
|
+
_context11.n = 6;
|
|
28255
28524
|
break;
|
|
28256
28525
|
}
|
|
28257
|
-
|
|
28258
|
-
|
|
28526
|
+
_context11.p = 3;
|
|
28527
|
+
_context11.n = 4;
|
|
28259
28528
|
return this.audioContext.resume();
|
|
28260
28529
|
case 4:
|
|
28261
28530
|
console.log('✅ AudioPlayer v2: AudioContext resumed after mic permission');
|
|
28262
|
-
|
|
28531
|
+
_context11.n = 6;
|
|
28263
28532
|
break;
|
|
28264
28533
|
case 5:
|
|
28265
|
-
|
|
28266
|
-
_t7 =
|
|
28534
|
+
_context11.p = 5;
|
|
28535
|
+
_t7 = _context11.v;
|
|
28267
28536
|
console.warn('⚠️ AudioPlayer v2: Failed to resume AudioContext:', _t7);
|
|
28268
28537
|
case 6:
|
|
28269
|
-
|
|
28538
|
+
_context11.n = 7;
|
|
28270
28539
|
return this.waitForAudioContextReady();
|
|
28271
28540
|
case 7:
|
|
28272
|
-
return
|
|
28541
|
+
return _context11.a(2);
|
|
28273
28542
|
}
|
|
28274
|
-
},
|
|
28543
|
+
}, _callee10, this, [[3, 5]]);
|
|
28275
28544
|
}));
|
|
28276
28545
|
function resumeAudioContext() {
|
|
28277
28546
|
return _resumeAudioContext.apply(this, arguments);
|
|
@@ -28368,8 +28637,9 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
28368
28637
|
/* harmony import */ var _core_ClientToolsRegistry_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../core/ClientToolsRegistry.js */ "./src/core/ClientToolsRegistry.js");
|
|
28369
28638
|
/* harmony import */ var _core_ClientScriptManager_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../core/ClientScriptManager.js */ "./src/core/ClientScriptManager.js");
|
|
28370
28639
|
/* harmony import */ var _core_helloFlavor_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../core/helloFlavor.js */ "./src/core/helloFlavor.js");
|
|
28371
|
-
/* harmony import */ var
|
|
28372
|
-
/* harmony import */ var
|
|
28640
|
+
/* harmony import */ var _hopPlatform_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./hopPlatform.js */ "./src/v2/hopPlatform.js");
|
|
28641
|
+
/* harmony import */ var _utils_screenshot_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../utils/screenshot.js */ "./src/utils/screenshot.js");
|
|
28642
|
+
/* harmony import */ var _utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../utils/visual-tools.js */ "./src/utils/visual-tools.js");
|
|
28373
28643
|
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); }
|
|
28374
28644
|
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; } } }; }
|
|
28375
28645
|
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; } }
|
|
@@ -28405,6 +28675,7 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
|
|
|
28405
28675
|
|
|
28406
28676
|
|
|
28407
28677
|
|
|
28678
|
+
|
|
28408
28679
|
/**
|
|
28409
28680
|
|
|
28410
28681
|
* VoiceSDK v2 - Multi-codec speech-to-speech SDK
|
|
@@ -28502,27 +28773,29 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28502
28773
|
// Audio constraints for getUserMedia (optional)
|
|
28503
28774
|
// If not provided, defaults will be used: echoCancellation: true, noiseSuppression: true, autoGainControl: true
|
|
28504
28775
|
audioConstraints: config.audioConstraints || null,
|
|
28505
|
-
// Last-hop TTS playback via hidden <audio> (AEC far-end).
|
|
28506
|
-
//
|
|
28507
|
-
// QA/debug override wins over config: ?ttp_hop=0
|
|
28508
|
-
//
|
|
28776
|
+
// Last-hop TTS playback via hidden <audio> (AEC far-end). Platform default
|
|
28777
|
+
// (hop on mobile/macOS only) applies when unset; set explicitly to force.
|
|
28778
|
+
// QA/debug override wins over config: ?ttp_hop=0|1 — mirrored to
|
|
28779
|
+
// sessionStorage 'ttp_hop_pref' so it survives same-origin full page loads
|
|
28780
|
+
// (Borusan navigate_to_form) — then localStorage 'ttp_hop'.
|
|
28509
28781
|
htmlAudioPlayback: function () {
|
|
28510
28782
|
try {
|
|
28511
|
-
var
|
|
28512
|
-
|
|
28513
|
-
|
|
28514
|
-
|
|
28515
|
-
return flag === '1';
|
|
28516
|
-
}
|
|
28517
|
-
if (flag !== null && flag !== undefined) {
|
|
28518
|
-
console.log("\uD83D\uDD27 VoiceSDK v2: ttp_hop ignored (value '".concat(flag, "', expected '0' or '1')"));
|
|
28783
|
+
var override = (0,_hopPlatform_js__WEBPACK_IMPORTED_MODULE_7__.readTtpHopOverride)(typeof window !== 'undefined' ? window : null);
|
|
28784
|
+
if (override !== null) {
|
|
28785
|
+
console.log("\uD83D\uDD27 VoiceSDK v2: ttp_hop override \u2192 htmlAudioPlayback=".concat(override));
|
|
28786
|
+
return override;
|
|
28519
28787
|
}
|
|
28520
28788
|
} catch (e) {
|
|
28521
|
-
console.log('🔧 VoiceSDK v2: ttp_hop check threw
|
|
28789
|
+
console.log('🔧 VoiceSDK v2: ttp_hop check threw:', e && e.message);
|
|
28522
28790
|
}
|
|
28523
28791
|
// Pass through undefined so AudioPlayer applies the platform default
|
|
28524
28792
|
return config.htmlAudioPlayback;
|
|
28525
28793
|
}(),
|
|
28794
|
+
// Opt-in: play TTS through a pair of local RTCPeerConnections so Chrome's
|
|
28795
|
+
// echo canceller can actually see it as a far-end reference (it ignores
|
|
28796
|
+
// <audio> elements). Default off — AudioPlayer also honours
|
|
28797
|
+
// ?ttp_loopback=1 / localStorage 'ttp_loopback' on its own.
|
|
28798
|
+
loopbackAec: config.loopbackAec,
|
|
28526
28799
|
// Protocol version
|
|
28527
28800
|
|
|
28528
28801
|
protocolVersion: config.protocolVersion || 2,
|
|
@@ -28665,7 +28938,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28665
28938
|
key: "_registerBuiltInTools",
|
|
28666
28939
|
value: function _registerBuiltInTools() {
|
|
28667
28940
|
try {
|
|
28668
|
-
(0,
|
|
28941
|
+
(0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_9__.registerVisualTools)(this.clientToolsRegistry);
|
|
28669
28942
|
} catch (error) {
|
|
28670
28943
|
console.error('❌ VoiceSDK: Error registering built-in tools:', error);
|
|
28671
28944
|
console.error(' Error details:', error.message, error.stack);
|
|
@@ -28991,6 +29264,12 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
28991
29264
|
key: "setupAudioPlayerEvents",
|
|
28992
29265
|
value: function setupAudioPlayerEvents() {
|
|
28993
29266
|
var _this3 = this;
|
|
29267
|
+
// The initial client_audio_info snapshot (at recordingStarted) predates
|
|
29268
|
+
// HTML-hop establishment, so its htmlAudioRouteActive/htmlAudioPlayOk are
|
|
29269
|
+
// always false. Re-report once the route outcome is known.
|
|
29270
|
+
this.audioPlayer.on('htmlAudioRoute', function (info) {
|
|
29271
|
+
_this3._sendClientAudioInfo(info);
|
|
29272
|
+
});
|
|
28994
29273
|
this.audioPlayer.on('playbackStarted', function (info) {
|
|
28995
29274
|
_this3.isPlaying = true;
|
|
28996
29275
|
|
|
@@ -29417,7 +29696,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29417
29696
|
// iOS in-app webviews lack "Safari" in the UA (real Safari always has it).
|
|
29418
29697
|
var webview = isAndroid && (/\bwv\b/.test(ua) || /Version\/[\d.]+.*Chrome/.test(ua)) || isIos && !/Safari/i.test(ua) || false;
|
|
29419
29698
|
var env = {
|
|
29420
|
-
sdkVersion: true ? "2.48.
|
|
29699
|
+
sdkVersion: true ? "2.48.20" : 0,
|
|
29421
29700
|
ua: ua,
|
|
29422
29701
|
platform: (uaData === null || uaData === void 0 ? void 0 : uaData.platform) || navigator.platform || '',
|
|
29423
29702
|
mobile: (_uaData$mobile = uaData === null || uaData === void 0 ? void 0 : uaData.mobile) !== null && _uaData$mobile !== void 0 ? _uaData$mobile : isAndroid || isIos,
|
|
@@ -29432,7 +29711,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29432
29711
|
} catch (e) {
|
|
29433
29712
|
console.warn('⚠️ VoiceSDK v2: Failed to build client env:', e);
|
|
29434
29713
|
return {
|
|
29435
|
-
sdkVersion: true ? "2.48.
|
|
29714
|
+
sdkVersion: true ? "2.48.20" : 0
|
|
29436
29715
|
};
|
|
29437
29716
|
}
|
|
29438
29717
|
}
|
|
@@ -29446,12 +29725,14 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29446
29725
|
*/
|
|
29447
29726
|
}, {
|
|
29448
29727
|
key: "_sendClientAudioInfo",
|
|
29449
|
-
value: function _sendClientAudioInfo() {
|
|
29728
|
+
value: function _sendClientAudioInfo(routeUpdate) {
|
|
29450
29729
|
try {
|
|
29451
29730
|
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;
|
|
29452
29731
|
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];
|
|
29453
|
-
|
|
29454
|
-
|
|
29732
|
+
// Route updates must go out even before the mic track exists — the hop
|
|
29733
|
+
// state is the whole point of the follow-up.
|
|
29734
|
+
if (!track && !routeUpdate) return;
|
|
29735
|
+
var s = track && track.getSettings ? track.getSettings() : {};
|
|
29455
29736
|
var audio = _objectSpread({
|
|
29456
29737
|
aec: (_s$echoCancellation = s.echoCancellation) !== null && _s$echoCancellation !== void 0 ? _s$echoCancellation : null,
|
|
29457
29738
|
ns: (_s$noiseSuppression = s.noiseSuppression) !== null && _s$noiseSuppression !== void 0 ? _s$noiseSuppression : null,
|
|
@@ -29459,11 +29740,15 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29459
29740
|
voiceIsolation: (_s$voiceIsolation = s.voiceIsolation) !== null && _s$voiceIsolation !== void 0 ? _s$voiceIsolation : null,
|
|
29460
29741
|
trackRate: (_s$sampleRate = s.sampleRate) !== null && _s$sampleRate !== void 0 ? _s$sampleRate : null,
|
|
29461
29742
|
channels: (_s$channelCount = s.channelCount) !== null && _s$channelCount !== void 0 ? _s$channelCount : null,
|
|
29462
|
-
mic: track.label || null,
|
|
29743
|
+
mic: track ? track.label || null : null,
|
|
29463
29744
|
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,
|
|
29464
29745
|
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,
|
|
29465
29746
|
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
|
|
29466
29747
|
}, ((_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)) || {});
|
|
29748
|
+
if (routeUpdate && routeUpdate.state) {
|
|
29749
|
+
audio.routeUpdate = routeUpdate.state; // 'established' | 'fallback' | 'play_pending'
|
|
29750
|
+
if (routeUpdate.reason) audio.routeFallbackReason = routeUpdate.reason;
|
|
29751
|
+
}
|
|
29467
29752
|
this.sendMessage({
|
|
29468
29753
|
t: 'client_audio_info',
|
|
29469
29754
|
audio: audio
|
|
@@ -29574,7 +29859,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29574
29859
|
|
|
29575
29860
|
// Include SDK build time for debugging
|
|
29576
29861
|
if (true) {
|
|
29577
|
-
helloMessage.lastBuildTime = "2026-08-
|
|
29862
|
+
helloMessage.lastBuildTime = "2026-08-27T16:45:39.666Z";
|
|
29578
29863
|
}
|
|
29579
29864
|
|
|
29580
29865
|
// Client environment (device/browser/webview) for backend logs + Langfuse metadata
|
|
@@ -29818,7 +30103,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29818
30103
|
case 'get_page_context':
|
|
29819
30104
|
// Handle get_page_context request from backend
|
|
29820
30105
|
console.log('📖 [VISUAL ASSISTANT] Backend requested page context');
|
|
29821
|
-
(0,
|
|
30106
|
+
(0,_utils_visual_tools_js__WEBPACK_IMPORTED_MODULE_9__.extractPageContext)().then(function (pageContext) {
|
|
29822
30107
|
// Use existing DOM scanner
|
|
29823
30108
|
_this6.sendMessage({
|
|
29824
30109
|
t: 'page_context',
|
|
@@ -29839,7 +30124,7 @@ var VoiceSDK_v2 = /*#__PURE__*/function (_EventEmitter) {
|
|
|
29839
30124
|
console.log('📸 [VISUAL ASSISTANT] Backend requested screenshot');
|
|
29840
30125
|
try {
|
|
29841
30126
|
// Use existing screenshot capture function
|
|
29842
|
-
(0,
|
|
30127
|
+
(0,_utils_screenshot_js__WEBPACK_IMPORTED_MODULE_8__.captureScreenshot)().then(function (screenshot) {
|
|
29843
30128
|
_this6.sendMessage({
|
|
29844
30129
|
t: 'screenshot',
|
|
29845
30130
|
screenshot: {
|
|
@@ -32199,6 +32484,376 @@ var PCMUCodec = /*#__PURE__*/function () {
|
|
|
32199
32484
|
|
|
32200
32485
|
/***/ }),
|
|
32201
32486
|
|
|
32487
|
+
/***/ "./src/v2/hopPlatform.js":
|
|
32488
|
+
/*!*******************************!*\
|
|
32489
|
+
!*** ./src/v2/hopPlatform.js ***!
|
|
32490
|
+
\*******************************/
|
|
32491
|
+
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
32492
|
+
|
|
32493
|
+
"use strict";
|
|
32494
|
+
__webpack_require__.r(__webpack_exports__);
|
|
32495
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
32496
|
+
/* harmony export */ TTP_HOP_SESSION_KEY: () => (/* binding */ TTP_HOP_SESSION_KEY),
|
|
32497
|
+
/* harmony export */ isMacDesktopUa: () => (/* binding */ isMacDesktopUa),
|
|
32498
|
+
/* harmony export */ isMobileUa: () => (/* binding */ isMobileUa),
|
|
32499
|
+
/* harmony export */ needsHtmlAudioHopForAec: () => (/* binding */ needsHtmlAudioHopForAec),
|
|
32500
|
+
/* harmony export */ readTtpHopOverride: () => (/* binding */ readTtpHopOverride)
|
|
32501
|
+
/* harmony export */ });
|
|
32502
|
+
/**
|
|
32503
|
+
* HTML-audio-hop platform policy + the ttp_hop QA override.
|
|
32504
|
+
*
|
|
32505
|
+
* The "hop" is AudioContext → MediaStreamDestination → hidden <audio>, used so
|
|
32506
|
+
* the browser's echo canceller sees agent TTS as a far-end reference. Chrome
|
|
32507
|
+
* renders that live stream with adaptive jitter-buffer speed control, which
|
|
32508
|
+
* audibly wobbles on Windows/Linux desktop — so the hop defaults ON only where
|
|
32509
|
+
* Web Audio → destination genuinely lacks an AEC reference (mobile + macOS).
|
|
32510
|
+
*
|
|
32511
|
+
* Kept DOM-free (all inputs passed in) so the policy is unit-testable without
|
|
32512
|
+
* a browser: see test/hop-platform.test.mjs.
|
|
32513
|
+
*/
|
|
32514
|
+
|
|
32515
|
+
/** sessionStorage key mirroring an explicit ?ttp_hop= URL value (see readTtpHopOverride). */
|
|
32516
|
+
var TTP_HOP_SESSION_KEY = 'ttp_hop_pref';
|
|
32517
|
+
function isMobileUa(ua) {
|
|
32518
|
+
return /Android|iPhone|iPad|iPod/i.test(ua || '');
|
|
32519
|
+
}
|
|
32520
|
+
function isMacDesktopUa(ua, platform) {
|
|
32521
|
+
if (/iPhone|iPad|iPod/i.test(ua || '')) return false;
|
|
32522
|
+
return /mac/i.test(platform || '') || /Mac OS X/i.test(ua || '');
|
|
32523
|
+
}
|
|
32524
|
+
|
|
32525
|
+
/**
|
|
32526
|
+
* Hop default: on only where Web Audio destination lacks an AEC far-end
|
|
32527
|
+
* reference (mobile + macOS). Windows/Linux/ChromeOS desktop: off — Chrome-wide
|
|
32528
|
+
* AEC already covers speaker echo there, and the hop only adds speed wobble.
|
|
32529
|
+
* Unknown/empty environment: off (fail toward the stable direct path).
|
|
32530
|
+
*/
|
|
32531
|
+
function needsHtmlAudioHopForAec(ua, platform) {
|
|
32532
|
+
return isMobileUa(ua) || isMacDesktopUa(ua, platform);
|
|
32533
|
+
}
|
|
32534
|
+
|
|
32535
|
+
/**
|
|
32536
|
+
* QA/debug override for the hop route. Returns true (force hop on), false
|
|
32537
|
+
* (force off), or null (no override — apply config/platform default).
|
|
32538
|
+
*
|
|
32539
|
+
* Precedence: URL ?ttp_hop=0|1 → sessionStorage mirror → localStorage 'ttp_hop'.
|
|
32540
|
+
*
|
|
32541
|
+
* An explicit URL value is mirrored into sessionStorage (TTP_HOP_SESSION_KEY)
|
|
32542
|
+
* so it survives same-origin full page loads that drop the query string —
|
|
32543
|
+
* Borusan's navigate_to_form does window.location.href = '/path' (call
|
|
32544
|
+
* 84b5af7d), which used to silently re-apply the platform default mid-call.
|
|
32545
|
+
* The mirror dies with the tab; localStorage remains the sticky manual knob
|
|
32546
|
+
* and ranks below the mirror because the URL value is the more recent intent.
|
|
32547
|
+
*
|
|
32548
|
+
* Every storage touch is individually guarded: browsers can block one storage
|
|
32549
|
+
* area but not another, and a blocked sessionStorage must not cost us the
|
|
32550
|
+
* localStorage fallback.
|
|
32551
|
+
*/
|
|
32552
|
+
function readTtpHopOverride(win) {
|
|
32553
|
+
var w = win || (typeof window !== 'undefined' ? window : null);
|
|
32554
|
+
if (!w) return null;
|
|
32555
|
+
var flag = null;
|
|
32556
|
+
try {
|
|
32557
|
+
flag = new URLSearchParams(w.location.search).get('ttp_hop');
|
|
32558
|
+
} catch (e) {/* no location in this env */}
|
|
32559
|
+
if (flag === '0' || flag === '1') {
|
|
32560
|
+
try {
|
|
32561
|
+
w.sessionStorage.setItem(TTP_HOP_SESSION_KEY, flag);
|
|
32562
|
+
} catch (e) {/* storage blocked */}
|
|
32563
|
+
return flag === '1';
|
|
32564
|
+
}
|
|
32565
|
+
var mirrored = null;
|
|
32566
|
+
try {
|
|
32567
|
+
mirrored = w.sessionStorage.getItem(TTP_HOP_SESSION_KEY);
|
|
32568
|
+
} catch (e) {/* storage blocked */}
|
|
32569
|
+
if (mirrored === '0' || mirrored === '1') return mirrored === '1';
|
|
32570
|
+
var stored = null;
|
|
32571
|
+
try {
|
|
32572
|
+
stored = w.localStorage.getItem('ttp_hop');
|
|
32573
|
+
} catch (e) {/* storage blocked */}
|
|
32574
|
+
if (stored === '0' || stored === '1') return stored === '1';
|
|
32575
|
+
return null;
|
|
32576
|
+
}
|
|
32577
|
+
|
|
32578
|
+
/***/ }),
|
|
32579
|
+
|
|
32580
|
+
/***/ "./src/v2/loopbackAec.js":
|
|
32581
|
+
/*!*******************************!*\
|
|
32582
|
+
!*** ./src/v2/loopbackAec.js ***!
|
|
32583
|
+
\*******************************/
|
|
32584
|
+
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
32585
|
+
|
|
32586
|
+
"use strict";
|
|
32587
|
+
__webpack_require__.r(__webpack_exports__);
|
|
32588
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
32589
|
+
/* harmony export */ TTP_LOOPBACK_SESSION_KEY: () => (/* binding */ TTP_LOOPBACK_SESSION_KEY),
|
|
32590
|
+
/* harmony export */ createAecLoopback: () => (/* binding */ createAecLoopback),
|
|
32591
|
+
/* harmony export */ isLoopbackAecSupported: () => (/* binding */ isLoopbackAecSupported),
|
|
32592
|
+
/* harmony export */ preferHighQualityOpus: () => (/* binding */ preferHighQualityOpus),
|
|
32593
|
+
/* harmony export */ readTtpLoopbackOverride: () => (/* binding */ readTtpLoopbackOverride)
|
|
32594
|
+
/* harmony export */ });
|
|
32595
|
+
function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
|
|
32596
|
+
function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
|
|
32597
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
32598
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
32599
|
+
/**
|
|
32600
|
+
* Local WebRTC loopback for echo cancellation.
|
|
32601
|
+
*
|
|
32602
|
+
* WHY THIS EXISTS. Chrome's echo canceller only cancels audio that arrived
|
|
32603
|
+
* through a WebRTC peer connection — not <audio> elements, not Web Audio.
|
|
32604
|
+
* (Firefox/Safari/Edge consider all browser audio; Chrome does not.) That is
|
|
32605
|
+
* why the HTML hop — AudioContext → MediaStreamDestination → hidden <audio> —
|
|
32606
|
+
* never reliably fixed speaker echo on Chrome: where it appeared to work, what
|
|
32607
|
+
* actually helped was Chrome-wide echo cancellation, a separate system-level
|
|
32608
|
+
* feature that is not always available. Two people at the same customer, same
|
|
32609
|
+
* OS class, both on speakers, got opposite results from the same build.
|
|
32610
|
+
*
|
|
32611
|
+
* The fix is to make TTS *actually be* peer-connection audio: wire two
|
|
32612
|
+
* RTCPeerConnections together inside this tab and push playback through them.
|
|
32613
|
+
* The remote track is then far-end reference material Chrome's AEC will
|
|
32614
|
+
* cancel. No server, no SFU, no TURN, no ports — both ends are local and ICE
|
|
32615
|
+
* never leaves the host.
|
|
32616
|
+
*
|
|
32617
|
+
* Cost is one Opus encode/decode (~20-40 ms added latency, and lossy
|
|
32618
|
+
* compression of TTS that was already lossy over the wire). Bitrate is munged
|
|
32619
|
+
* up because the "link" is a memcpy; see preferHighQualityOpus.
|
|
32620
|
+
*
|
|
32621
|
+
* Kept free of AudioPlayer state and injectable so the peer-connection dance
|
|
32622
|
+
* is unit-testable without a browser: see test/loopback-aec.test.mjs.
|
|
32623
|
+
*/
|
|
32624
|
+
|
|
32625
|
+
/** sessionStorage key mirroring an explicit ?ttp_loopback= URL value. */
|
|
32626
|
+
var TTP_LOOPBACK_SESSION_KEY = 'ttp_loopback_pref';
|
|
32627
|
+
|
|
32628
|
+
/**
|
|
32629
|
+
* QA/rollout override for the loopback route. true (force on), false (force
|
|
32630
|
+
* off), or null (no override — apply config, which defaults off).
|
|
32631
|
+
*
|
|
32632
|
+
* Precedence and the sessionStorage mirror match readTtpHopOverride for the
|
|
32633
|
+
* same reason: Borusan's navigate_to_form does window.location.href = '/path',
|
|
32634
|
+
* a same-origin full load that drops the query string mid-call (call
|
|
32635
|
+
* 84b5af7d). Without the mirror the route would silently revert on the very
|
|
32636
|
+
* navigation the tester is exercising.
|
|
32637
|
+
*/
|
|
32638
|
+
function readTtpLoopbackOverride(win) {
|
|
32639
|
+
var w = win || (typeof window !== 'undefined' ? window : null);
|
|
32640
|
+
if (!w) return null;
|
|
32641
|
+
var flag = null;
|
|
32642
|
+
try {
|
|
32643
|
+
flag = new URLSearchParams(w.location.search).get('ttp_loopback');
|
|
32644
|
+
} catch (e) {/* no location in this env */}
|
|
32645
|
+
if (flag === '0' || flag === '1') {
|
|
32646
|
+
try {
|
|
32647
|
+
w.sessionStorage.setItem(TTP_LOOPBACK_SESSION_KEY, flag);
|
|
32648
|
+
} catch (e) {/* storage blocked */}
|
|
32649
|
+
return flag === '1';
|
|
32650
|
+
}
|
|
32651
|
+
var mirrored = null;
|
|
32652
|
+
try {
|
|
32653
|
+
mirrored = w.sessionStorage.getItem(TTP_LOOPBACK_SESSION_KEY);
|
|
32654
|
+
} catch (e) {/* storage blocked */}
|
|
32655
|
+
if (mirrored === '0' || mirrored === '1') return mirrored === '1';
|
|
32656
|
+
var stored = null;
|
|
32657
|
+
try {
|
|
32658
|
+
stored = w.localStorage.getItem('ttp_loopback');
|
|
32659
|
+
} catch (e) {/* storage blocked */}
|
|
32660
|
+
if (stored === '0' || stored === '1') return stored === '1';
|
|
32661
|
+
return null;
|
|
32662
|
+
}
|
|
32663
|
+
function isLoopbackAecSupported(win) {
|
|
32664
|
+
var w = win || (typeof window !== 'undefined' ? window : null);
|
|
32665
|
+
if (!w) return false;
|
|
32666
|
+
return typeof w.RTCPeerConnection === 'function' && typeof w.MediaStream === 'function';
|
|
32667
|
+
}
|
|
32668
|
+
|
|
32669
|
+
/**
|
|
32670
|
+
* Raise Opus quality on a loopback that has no bandwidth cost, and disable the
|
|
32671
|
+
* two features that are wrong for synthesized speech on a local link:
|
|
32672
|
+
*
|
|
32673
|
+
* - usedtx=0: discontinuous transmission replaces "silence" with comfort
|
|
32674
|
+
* noise. TTS leading/trailing silence is real audio the segment ledger
|
|
32675
|
+
* accounts for; letting the codec invent it would desynchronize playedMs.
|
|
32676
|
+
* - maxaveragebitrate: the default ~32 kbps is chosen for real networks. This
|
|
32677
|
+
* link is a memcpy, so spend bits instead of degrading a voice the customer
|
|
32678
|
+
* is paying for.
|
|
32679
|
+
*
|
|
32680
|
+
* Returns the SDP unchanged if there is no Opus line to munge — never throws,
|
|
32681
|
+
* because a failed munge must not cost us the route.
|
|
32682
|
+
*/
|
|
32683
|
+
function preferHighQualityOpus(sdp) {
|
|
32684
|
+
var bitrate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 128000;
|
|
32685
|
+
if (typeof sdp !== 'string' || !sdp) return sdp;
|
|
32686
|
+
var rtpmap = sdp.match(/^a=rtpmap:(\d+)\s+opus\/48000(?:\/2)?/mi);
|
|
32687
|
+
if (!rtpmap) return sdp;
|
|
32688
|
+
var pt = rtpmap[1];
|
|
32689
|
+
var params = "stereo=0;useinbandfec=1;usedtx=0;maxaveragebitrate=".concat(bitrate);
|
|
32690
|
+
var fmtpRe = new RegExp("^a=fmtp:".concat(pt, " (.*)$"), 'mi');
|
|
32691
|
+
if (fmtpRe.test(sdp)) {
|
|
32692
|
+
return sdp.replace(fmtpRe, function (line, existing) {
|
|
32693
|
+
// Drop any key we are about to set, keep everything else the browser chose.
|
|
32694
|
+
var kept = existing.split(';').filter(function (kv) {
|
|
32695
|
+
return !/^\s*(stereo|useinbandfec|usedtx|maxaveragebitrate)\s*=/i.test(kv);
|
|
32696
|
+
}).filter(function (kv) {
|
|
32697
|
+
return kv.trim().length > 0;
|
|
32698
|
+
});
|
|
32699
|
+
return "a=fmtp:".concat(pt, " ").concat(kept.concat(params.split(';')).join(';'));
|
|
32700
|
+
});
|
|
32701
|
+
}
|
|
32702
|
+
return sdp.replace(rtpmap[0], "".concat(rtpmap[0], "\r\na=fmtp:").concat(pt, " ").concat(params));
|
|
32703
|
+
}
|
|
32704
|
+
|
|
32705
|
+
/**
|
|
32706
|
+
* Wire two peer connections together in this tab and return the remote end of
|
|
32707
|
+
* the given stream.
|
|
32708
|
+
*
|
|
32709
|
+
* Resolves { remoteStream, close } once the receiving side reports connected
|
|
32710
|
+
* AND a track has arrived — both, because either alone has been seen without
|
|
32711
|
+
* audible audio. Rejects on timeout so a stalled negotiation degrades to the
|
|
32712
|
+
* caller's existing route rather than leaving the call silent.
|
|
32713
|
+
*
|
|
32714
|
+
* Dependencies are injected (win) so this runs under node --test.
|
|
32715
|
+
*/
|
|
32716
|
+
function createAecLoopback() {
|
|
32717
|
+
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
32718
|
+
stream = _ref.stream,
|
|
32719
|
+
win = _ref.win,
|
|
32720
|
+
_ref$timeoutMs = _ref.timeoutMs,
|
|
32721
|
+
timeoutMs = _ref$timeoutMs === void 0 ? 4000 : _ref$timeoutMs,
|
|
32722
|
+
_ref$bitrate = _ref.bitrate,
|
|
32723
|
+
bitrate = _ref$bitrate === void 0 ? 128000 : _ref$bitrate;
|
|
32724
|
+
var w = win || (typeof window !== 'undefined' ? window : null);
|
|
32725
|
+
if (!w) return Promise.reject(new Error('loopback: no window'));
|
|
32726
|
+
if (!isLoopbackAecSupported(w)) return Promise.reject(new Error('loopback: RTCPeerConnection unavailable'));
|
|
32727
|
+
var tracks = stream && typeof stream.getAudioTracks === 'function' ? stream.getAudioTracks() : [];
|
|
32728
|
+
if (!tracks.length) return Promise.reject(new Error('loopback: source stream has no audio track'));
|
|
32729
|
+
|
|
32730
|
+
// No ICE servers: both ends are in this process, so host candidates are the
|
|
32731
|
+
// only ones that can possibly be useful. Asking for STUN would leak the
|
|
32732
|
+
// user's IP to a third party for a connection that never leaves the tab.
|
|
32733
|
+
var pcSend = new w.RTCPeerConnection({
|
|
32734
|
+
iceServers: []
|
|
32735
|
+
});
|
|
32736
|
+
var pcRecv = new w.RTCPeerConnection({
|
|
32737
|
+
iceServers: []
|
|
32738
|
+
});
|
|
32739
|
+
var settled = false;
|
|
32740
|
+
var timer = null;
|
|
32741
|
+
var remoteStream = new w.MediaStream();
|
|
32742
|
+
var close = function close() {
|
|
32743
|
+
if (timer) {
|
|
32744
|
+
clearTimeout(timer);
|
|
32745
|
+
timer = null;
|
|
32746
|
+
}
|
|
32747
|
+
try {
|
|
32748
|
+
pcSend.close();
|
|
32749
|
+
} catch (e) {/* already closed */}
|
|
32750
|
+
try {
|
|
32751
|
+
pcRecv.close();
|
|
32752
|
+
} catch (e) {/* already closed */}
|
|
32753
|
+
};
|
|
32754
|
+
return new Promise(function (resolve, reject) {
|
|
32755
|
+
var fail = function fail(err) {
|
|
32756
|
+
if (settled) return;
|
|
32757
|
+
settled = true;
|
|
32758
|
+
close();
|
|
32759
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
32760
|
+
};
|
|
32761
|
+
var maybeResolve = function maybeResolve() {
|
|
32762
|
+
if (settled) return;
|
|
32763
|
+
if (!remoteStream.getAudioTracks().length) return;
|
|
32764
|
+
var state = pcRecv.connectionState || pcRecv.iceConnectionState;
|
|
32765
|
+
if (state !== 'connected' && state !== 'completed') return;
|
|
32766
|
+
settled = true;
|
|
32767
|
+
if (timer) {
|
|
32768
|
+
clearTimeout(timer);
|
|
32769
|
+
timer = null;
|
|
32770
|
+
}
|
|
32771
|
+
resolve({
|
|
32772
|
+
remoteStream: remoteStream,
|
|
32773
|
+
close: close,
|
|
32774
|
+
pcSend: pcSend,
|
|
32775
|
+
pcRecv: pcRecv
|
|
32776
|
+
});
|
|
32777
|
+
};
|
|
32778
|
+
timer = setTimeout(function () {
|
|
32779
|
+
return fail(new Error('loopback: negotiation timed out'));
|
|
32780
|
+
}, timeoutMs);
|
|
32781
|
+
pcSend.onicecandidate = function (e) {
|
|
32782
|
+
if (e.candidate) {
|
|
32783
|
+
try {
|
|
32784
|
+
pcRecv.addIceCandidate(e.candidate);
|
|
32785
|
+
} catch (err) {/* late candidate */}
|
|
32786
|
+
}
|
|
32787
|
+
};
|
|
32788
|
+
pcRecv.onicecandidate = function (e) {
|
|
32789
|
+
if (e.candidate) {
|
|
32790
|
+
try {
|
|
32791
|
+
pcSend.addIceCandidate(e.candidate);
|
|
32792
|
+
} catch (err) {/* late candidate */}
|
|
32793
|
+
}
|
|
32794
|
+
};
|
|
32795
|
+
pcRecv.ontrack = function (e) {
|
|
32796
|
+
var track = e.track || e.streams && e.streams[0] && e.streams[0].getAudioTracks()[0];
|
|
32797
|
+
if (track) remoteStream.addTrack(track);
|
|
32798
|
+
maybeResolve();
|
|
32799
|
+
};
|
|
32800
|
+
pcRecv.onconnectionstatechange = function () {
|
|
32801
|
+
var s = pcRecv.connectionState;
|
|
32802
|
+
if (s === 'failed' || s === 'closed') return fail(new Error("loopback: receiver ".concat(s)));
|
|
32803
|
+
maybeResolve();
|
|
32804
|
+
};
|
|
32805
|
+
pcRecv.oniceconnectionstatechange = function () {
|
|
32806
|
+
var s = pcRecv.iceConnectionState;
|
|
32807
|
+
if (s === 'failed') return fail(new Error('loopback: ice failed'));
|
|
32808
|
+
maybeResolve();
|
|
32809
|
+
};
|
|
32810
|
+
_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
32811
|
+
var offer, tunedOffer, answer, tunedAnswer;
|
|
32812
|
+
return _regenerator().w(function (_context) {
|
|
32813
|
+
while (1) switch (_context.n) {
|
|
32814
|
+
case 0:
|
|
32815
|
+
tracks.forEach(function (t) {
|
|
32816
|
+
return pcSend.addTrack(t, stream);
|
|
32817
|
+
});
|
|
32818
|
+
_context.n = 1;
|
|
32819
|
+
return pcSend.createOffer();
|
|
32820
|
+
case 1:
|
|
32821
|
+
offer = _context.v;
|
|
32822
|
+
tunedOffer = {
|
|
32823
|
+
type: offer.type,
|
|
32824
|
+
sdp: preferHighQualityOpus(offer.sdp, bitrate)
|
|
32825
|
+
};
|
|
32826
|
+
_context.n = 2;
|
|
32827
|
+
return pcSend.setLocalDescription(tunedOffer);
|
|
32828
|
+
case 2:
|
|
32829
|
+
_context.n = 3;
|
|
32830
|
+
return pcRecv.setRemoteDescription(tunedOffer);
|
|
32831
|
+
case 3:
|
|
32832
|
+
_context.n = 4;
|
|
32833
|
+
return pcRecv.createAnswer();
|
|
32834
|
+
case 4:
|
|
32835
|
+
answer = _context.v;
|
|
32836
|
+
tunedAnswer = {
|
|
32837
|
+
type: answer.type,
|
|
32838
|
+
sdp: preferHighQualityOpus(answer.sdp, bitrate)
|
|
32839
|
+
};
|
|
32840
|
+
_context.n = 5;
|
|
32841
|
+
return pcRecv.setLocalDescription(tunedAnswer);
|
|
32842
|
+
case 5:
|
|
32843
|
+
_context.n = 6;
|
|
32844
|
+
return pcSend.setRemoteDescription(tunedAnswer);
|
|
32845
|
+
case 6:
|
|
32846
|
+
maybeResolve();
|
|
32847
|
+
case 7:
|
|
32848
|
+
return _context.a(2);
|
|
32849
|
+
}
|
|
32850
|
+
}, _callee);
|
|
32851
|
+
}))().catch(fail);
|
|
32852
|
+
});
|
|
32853
|
+
}
|
|
32854
|
+
|
|
32855
|
+
/***/ }),
|
|
32856
|
+
|
|
32202
32857
|
/***/ "./src/v2/utils/AudioFormatConverter.js":
|
|
32203
32858
|
/*!**********************************************!*\
|
|
32204
32859
|
!*** ./src/v2/utils/AudioFormatConverter.js ***!
|
|
@@ -35803,7 +36458,7 @@ var TTPChatWidget = /*#__PURE__*/function () {
|
|
|
35803
36458
|
return;
|
|
35804
36459
|
}
|
|
35805
36460
|
this._ensureAboutStyles();
|
|
35806
|
-
var version = true ? "2.48.
|
|
36461
|
+
var version = true ? "2.48.20" : 0;
|
|
35807
36462
|
var convId = this._getLastConversationId();
|
|
35808
36463
|
var t = function t(k, fb) {
|
|
35809
36464
|
try {
|