@hivegpt/hiveai-angular 0.0.582 → 0.0.583

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.
@@ -1357,6 +1357,7 @@
1357
1357
  this.botTranscriptSubject = new rxjs.Subject();
1358
1358
  this.assistantSpeakingSubject = new rxjs.Subject();
1359
1359
  this.serverUserSpeakingSubject = new rxjs.Subject();
1360
+ this.audioChunkSubject = new rxjs.Subject();
1360
1361
  /** Fires once each time the WebSocket reaches OPEN. */
1361
1362
  this.opened$ = this.openedSubject.asObservable();
1362
1363
  /** Fires when the socket closes without a client-initiated {@link disconnect}. */
@@ -1367,6 +1368,8 @@
1367
1368
  this.assistantSpeaking$ = this.assistantSpeakingSubject.asObservable();
1368
1369
  /** User speaking from server-side VAD, if provided. */
1369
1370
  this.serverUserSpeaking$ = this.serverUserSpeakingSubject.asObservable();
1371
+ /** Binary audio frames from server (when backend streams bot audio over WS). */
1372
+ this.audioChunk$ = this.audioChunkSubject.asObservable();
1370
1373
  }
1371
1374
  WebSocketVoiceClientService.prototype.connect = function (wsUrl) {
1372
1375
  var _this = this;
@@ -1389,16 +1392,7 @@
1389
1392
  socket_1.onmessage = function (event) {
1390
1393
  if (_this.ws !== socket_1)
1391
1394
  return;
1392
- if (typeof event.data !== 'string') {
1393
- return;
1394
- }
1395
- try {
1396
- var msg_1 = JSON.parse(event.data);
1397
- _this.ngZone.run(function () { return _this.handleJsonMessage(msg_1); });
1398
- }
1399
- catch (_a) {
1400
- // Ignore non-JSON
1401
- }
1395
+ void _this.handleIncomingMessage(event.data);
1402
1396
  };
1403
1397
  socket_1.onerror = function () {
1404
1398
  _this.ngZone.run(function () {
@@ -1424,6 +1418,65 @@
1424
1418
  throw err;
1425
1419
  }
1426
1420
  };
1421
+ WebSocketVoiceClientService.prototype.handleIncomingMessage = function (payload) {
1422
+ return __awaiter(this, void 0, void 0, function () {
1423
+ var ab;
1424
+ return __generator(this, function (_b) {
1425
+ switch (_b.label) {
1426
+ case 0:
1427
+ if (typeof payload === 'string') {
1428
+ this.handleJsonString(payload);
1429
+ return [2 /*return*/];
1430
+ }
1431
+ if (payload instanceof ArrayBuffer) {
1432
+ this.handleBinaryMessage(payload);
1433
+ return [2 /*return*/];
1434
+ }
1435
+ if (!(payload instanceof Blob)) return [3 /*break*/, 2];
1436
+ return [4 /*yield*/, payload.arrayBuffer()];
1437
+ case 1:
1438
+ ab = _b.sent();
1439
+ this.handleBinaryMessage(ab);
1440
+ _b.label = 2;
1441
+ case 2: return [2 /*return*/];
1442
+ }
1443
+ });
1444
+ });
1445
+ };
1446
+ WebSocketVoiceClientService.prototype.handleJsonString = function (jsonText) {
1447
+ var _this = this;
1448
+ try {
1449
+ var msg_1 = JSON.parse(jsonText);
1450
+ this.ngZone.run(function () { return _this.handleJsonMessage(msg_1); });
1451
+ }
1452
+ catch (_a) {
1453
+ // Ignore non-JSON
1454
+ }
1455
+ };
1456
+ WebSocketVoiceClientService.prototype.handleBinaryMessage = function (buffer) {
1457
+ var _this = this;
1458
+ // Some backends wrap JSON events inside binary WS frames.
1459
+ var maybeText = this.tryDecodeUtf8(buffer);
1460
+ if (maybeText !== null) {
1461
+ this.handleJsonString(maybeText);
1462
+ return;
1463
+ }
1464
+ // Otherwise treat binary as streamed assistant audio.
1465
+ this.ngZone.run(function () { return _this.audioChunkSubject.next(buffer); });
1466
+ };
1467
+ WebSocketVoiceClientService.prototype.tryDecodeUtf8 = function (buffer) {
1468
+ try {
1469
+ var text = new TextDecoder('utf-8', { fatal: true }).decode(buffer);
1470
+ var trimmed = text.trim();
1471
+ if (!trimmed || (trimmed[0] !== '{' && trimmed[0] !== '[')) {
1472
+ return null;
1473
+ }
1474
+ return trimmed;
1475
+ }
1476
+ catch (_a) {
1477
+ return null;
1478
+ }
1479
+ };
1427
1480
  WebSocketVoiceClientService.prototype.handleJsonMessage = function (msg) {
1428
1481
  var type = msg.type;
1429
1482
  var typeStr = typeof type === 'string' ? type : '';
@@ -1529,6 +1582,9 @@
1529
1582
  this.callStartTime = 0;
1530
1583
  this.durationInterval = null;
1531
1584
  this.localMicStream = null;
1585
+ this.remoteAudioContext = null;
1586
+ this.pendingRemoteAudio = [];
1587
+ this.remoteAudioPlaying = false;
1532
1588
  this.endCall$ = new rxjs.Subject();
1533
1589
  this.subscriptions = new rxjs.Subscription();
1534
1590
  this.destroy$ = new rxjs.Subject();
@@ -1544,6 +1600,9 @@
1544
1600
  this.subscriptions.add(this.wsClient.remoteClose$
1545
1601
  .pipe(operators.takeUntil(this.destroy$))
1546
1602
  .subscribe(function () { return void _this.handleRemoteClose(); }));
1603
+ this.subscriptions.add(this.wsClient.audioChunk$
1604
+ .pipe(operators.takeUntil(this.destroy$))
1605
+ .subscribe(function (chunk) { return _this.enqueueRemoteAudio(chunk); }));
1547
1606
  }
1548
1607
  VoiceAgentService.prototype.ngOnDestroy = function () {
1549
1608
  this.destroy$.next();
@@ -1559,6 +1618,7 @@
1559
1618
  this.callStartTime = 0;
1560
1619
  this.audioAnalyzer.stop();
1561
1620
  this.stopLocalMic();
1621
+ this.resetRemoteAudioPlayback();
1562
1622
  this.wsClient.disconnect();
1563
1623
  this.callStateSubject.next('idle');
1564
1624
  this.statusTextSubject.next('');
@@ -1762,6 +1822,92 @@
1762
1822
  this.localMicStream = null;
1763
1823
  }
1764
1824
  };
1825
+ VoiceAgentService.prototype.enqueueRemoteAudio = function (chunk) {
1826
+ this.pendingRemoteAudio.push(chunk.slice(0));
1827
+ if (!this.remoteAudioPlaying) {
1828
+ void this.playRemoteAudioQueue();
1829
+ }
1830
+ };
1831
+ VoiceAgentService.prototype.playRemoteAudioQueue = function () {
1832
+ return __awaiter(this, void 0, void 0, function () {
1833
+ var context, chunk, decoded, _a_1;
1834
+ return __generator(this, function (_b) {
1835
+ switch (_b.label) {
1836
+ case 0:
1837
+ this.remoteAudioPlaying = true;
1838
+ context = this.getOrCreateRemoteAudioContext();
1839
+ _b.label = 1;
1840
+ case 1:
1841
+ if (!(this.pendingRemoteAudio.length > 0)) return [3 /*break*/, 7];
1842
+ chunk = this.pendingRemoteAudio.shift();
1843
+ if (!chunk)
1844
+ return [3 /*break*/, 1];
1845
+ _b.label = 2;
1846
+ case 2:
1847
+ _b.trys.push([2, 5, , 6]);
1848
+ return [4 /*yield*/, this.decodeAudioChunk(context, chunk)];
1849
+ case 3:
1850
+ decoded = _b.sent();
1851
+ this.assistantAudioStarted();
1852
+ return [4 /*yield*/, this.playDecodedBuffer(context, decoded)];
1853
+ case 4:
1854
+ _b.sent();
1855
+ return [3 /*break*/, 6];
1856
+ case 5:
1857
+ _a_1 = _b.sent();
1858
+ return [3 /*break*/, 6];
1859
+ case 6: return [3 /*break*/, 1];
1860
+ case 7:
1861
+ this.remoteAudioPlaying = false;
1862
+ this.assistantAudioStopped();
1863
+ return [2 /*return*/];
1864
+ }
1865
+ });
1866
+ });
1867
+ };
1868
+ VoiceAgentService.prototype.getOrCreateRemoteAudioContext = function () {
1869
+ if (!this.remoteAudioContext || this.remoteAudioContext.state === 'closed') {
1870
+ this.remoteAudioContext = new AudioContext();
1871
+ }
1872
+ if (this.remoteAudioContext.state === 'suspended') {
1873
+ void this.remoteAudioContext.resume();
1874
+ }
1875
+ return this.remoteAudioContext;
1876
+ };
1877
+ VoiceAgentService.prototype.decodeAudioChunk = function (context, chunk) {
1878
+ return new Promise(function (resolve, reject) {
1879
+ context.decodeAudioData(chunk.slice(0), resolve, reject);
1880
+ });
1881
+ };
1882
+ VoiceAgentService.prototype.playDecodedBuffer = function (context, buffer) {
1883
+ return new Promise(function (resolve) {
1884
+ var source = context.createBufferSource();
1885
+ source.buffer = buffer;
1886
+ source.connect(context.destination);
1887
+ source.onended = function () { return resolve(); };
1888
+ source.start();
1889
+ });
1890
+ };
1891
+ VoiceAgentService.prototype.assistantAudioStarted = function () {
1892
+ if (this.callStartTime === 0) {
1893
+ this.callStartTime = Date.now();
1894
+ this.startDurationTimer();
1895
+ }
1896
+ this.callStateSubject.next('talking');
1897
+ };
1898
+ VoiceAgentService.prototype.assistantAudioStopped = function () {
1899
+ if (this.callStateSubject.value === 'talking') {
1900
+ this.callStateSubject.next('connected');
1901
+ }
1902
+ };
1903
+ VoiceAgentService.prototype.resetRemoteAudioPlayback = function () {
1904
+ this.pendingRemoteAudio = [];
1905
+ this.remoteAudioPlaying = false;
1906
+ if (this.remoteAudioContext && this.remoteAudioContext.state !== 'closed') {
1907
+ this.remoteAudioContext.close().catch(function () { });
1908
+ }
1909
+ this.remoteAudioContext = null;
1910
+ };
1765
1911
  VoiceAgentService.prototype.handleRemoteClose = function () {
1766
1912
  return __awaiter(this, void 0, void 0, function () {
1767
1913
  var state;
@@ -1774,6 +1920,7 @@
1774
1920
  this.callStartTime = 0;
1775
1921
  this.audioAnalyzer.stop();
1776
1922
  this.stopLocalMic();
1923
+ this.resetRemoteAudioPlayback();
1777
1924
  this.callStateSubject.next('ended');
1778
1925
  this.statusTextSubject.next('Connection lost');
1779
1926
  return [2 /*return*/];
@@ -1788,6 +1935,7 @@
1788
1935
  this.callStartTime = 0;
1789
1936
  this.audioAnalyzer.stop();
1790
1937
  this.stopLocalMic();
1938
+ this.resetRemoteAudioPlayback();
1791
1939
  this.wsClient.disconnect();
1792
1940
  this.callStateSubject.next('ended');
1793
1941
  this.statusTextSubject.next('Call Ended');