@hivegpt/hiveai-angular 0.0.457 → 0.0.459

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.
@@ -1264,13 +1264,10 @@
1264
1264
  * - Create and manage Daily CallObject
1265
1265
  * - Join Daily room using room_url
1266
1266
  * - Handle mic capture + speaker playback
1267
- * - Provide real-time speaking detection via active-speaker-change (primary)
1268
- * and track-started/track-stopped (fallback for immediate feedback)
1267
+ * - Bot speaking detection via AnalyserNode on remote track (instant)
1268
+ * - User speaking detection via active-speaker-change
1269
1269
  * - Expose speaking$ (bot speaking), userSpeaking$ (user speaking), micMuted$
1270
1270
  * - Expose localStream$ for waveform visualization (AudioAnalyzerService)
1271
- *
1272
- * Speaking state flips immediately when agent audio starts playing.
1273
- * If user speaks while bot is talking, state switches to listening.
1274
1271
  */
1275
1272
  var DailyVoiceClientService = /** @class */ (function () {
1276
1273
  function DailyVoiceClientService(ngZone) {
@@ -1280,6 +1277,9 @@
1280
1277
  this.localSessionId = null;
1281
1278
  /** Explicit playback of remote (bot) audio; required in some browsers. */
1282
1279
  this.remoteAudioElement = null;
1280
+ /** AnalyserNode-based remote audio monitor for instant bot speaking detection. */
1281
+ this.remoteAudioContext = null;
1282
+ this.remoteSpeakingRAF = null;
1283
1283
  this.speakingSubject = new rxjs.BehaviorSubject(false);
1284
1284
  this.userSpeakingSubject = new rxjs.BehaviorSubject(false);
1285
1285
  this.micMutedSubject = new rxjs.BehaviorSubject(false);
@@ -1353,25 +1353,21 @@
1353
1353
  };
1354
1354
  DailyVoiceClientService.prototype.setupEventHandlers = function (call) {
1355
1355
  var _this = this;
1356
- // active-speaker-change: primary source for real-time speaking detection.
1357
- // Emits when the loudest participant changes; bot speaking = remote is active.
1356
+ // active-speaker-change: used ONLY for user speaking detection.
1357
+ // Bot speaking is detected by our own AnalyserNode (instant, no debounce).
1358
1358
  call.on('active-speaker-change', function (event) {
1359
1359
  _this.ngZone.run(function () {
1360
1360
  var _a;
1361
1361
  var peerId = (_a = event === null || event === void 0 ? void 0 : event.activeSpeaker) === null || _a === void 0 ? void 0 : _a.peerId;
1362
1362
  if (!peerId || !_this.localSessionId) {
1363
- _this.speakingSubject.next(false);
1364
1363
  _this.userSpeakingSubject.next(false);
1365
1364
  return;
1366
1365
  }
1367
1366
  var isLocal = peerId === _this.localSessionId;
1368
1367
  _this.userSpeakingSubject.next(isLocal);
1369
- _this.speakingSubject.next(!isLocal);
1370
1368
  });
1371
1369
  });
1372
- // track-started / track-stopped: play remote (bot) audio explicitly and update speaking state.
1373
- // Browsers often do not auto-play remote WebRTC audio; attaching to an HTMLAudioElement and
1374
- // calling play() ensures the user hears the agent.
1370
+ // track-started / track-stopped: set up remote audio playback + AnalyserNode monitor.
1375
1371
  call.on('track-started', function (event) {
1376
1372
  _this.ngZone.run(function () {
1377
1373
  var _a, _b, _c, _d;
@@ -1379,11 +1375,11 @@
1379
1375
  var type = (_a = event === null || event === void 0 ? void 0 : event.type) !== null && _a !== void 0 ? _a : (_b = event === null || event === void 0 ? void 0 : event.track) === null || _b === void 0 ? void 0 : _b.kind;
1380
1376
  var track = event === null || event === void 0 ? void 0 : event.track;
1381
1377
  if (p && !p.local && type === 'audio') {
1382
- console.log("[VoiceDebug] Got audio track from backend (track-started) \u2014 " + new Date().toISOString());
1383
- _this.speakingSubject.next(true);
1378
+ console.log("[VoiceDebug] Got audio track from backend (track-started) \u2014 readyState=" + (track === null || track === void 0 ? void 0 : track.readyState) + ", muted=" + (track === null || track === void 0 ? void 0 : track.muted) + " \u2014 " + new Date().toISOString());
1384
1379
  var audioTrack = track !== null && track !== void 0 ? track : (_d = (_c = p.tracks) === null || _c === void 0 ? void 0 : _c.audio) === null || _d === void 0 ? void 0 : _d.track;
1385
1380
  if (audioTrack && typeof audioTrack === 'object') {
1386
1381
  _this.playRemoteTrack(audioTrack);
1382
+ _this.monitorRemoteAudio(audioTrack);
1387
1383
  }
1388
1384
  }
1389
1385
  });
@@ -1394,7 +1390,7 @@
1394
1390
  var p = event === null || event === void 0 ? void 0 : event.participant;
1395
1391
  var type = (_a = event === null || event === void 0 ? void 0 : event.type) !== null && _a !== void 0 ? _a : (_b = event === null || event === void 0 ? void 0 : event.track) === null || _b === void 0 ? void 0 : _b.kind;
1396
1392
  if (p && !p.local && type === 'audio') {
1397
- _this.speakingSubject.next(false);
1393
+ _this.stopRemoteAudioMonitor();
1398
1394
  _this.stopRemoteAudio();
1399
1395
  }
1400
1396
  });
@@ -1417,14 +1413,30 @@
1417
1413
  DailyVoiceClientService.prototype.playRemoteTrack = function (track) {
1418
1414
  this.stopRemoteAudio();
1419
1415
  try {
1416
+ console.log("[VoiceDebug] playRemoteTrack called \u2014 track.readyState=" + track.readyState + ", track.muted=" + track.muted + " \u2014 " + new Date().toISOString());
1417
+ track.onunmute = function () {
1418
+ console.log("[VoiceDebug] Remote audio track UNMUTED (audio data arriving) \u2014 " + new Date().toISOString());
1419
+ };
1420
1420
  var stream = new MediaStream([track]);
1421
1421
  var audio = new Audio();
1422
1422
  audio.autoplay = true;
1423
1423
  audio.srcObject = stream;
1424
1424
  this.remoteAudioElement = audio;
1425
+ audio.onplaying = function () {
1426
+ console.log("[VoiceDebug] Audio element PLAYING (browser started playback) \u2014 " + new Date().toISOString());
1427
+ };
1428
+ var firstTimeUpdate_1 = true;
1429
+ audio.ontimeupdate = function () {
1430
+ if (firstTimeUpdate_1) {
1431
+ firstTimeUpdate_1 = false;
1432
+ console.log("[VoiceDebug] Audio element first TIMEUPDATE (actual audio output) \u2014 " + new Date().toISOString());
1433
+ }
1434
+ };
1425
1435
  var p = audio.play();
1426
- if (p && typeof p.catch === 'function') {
1427
- p.catch(function (err) {
1436
+ if (p && typeof p.then === 'function') {
1437
+ p.then(function () {
1438
+ console.log("[VoiceDebug] audio.play() resolved \u2014 " + new Date().toISOString());
1439
+ }).catch(function (err) {
1428
1440
  console.warn('DailyVoiceClient: remote audio play failed (may need user gesture)', err);
1429
1441
  });
1430
1442
  }
@@ -1433,6 +1445,66 @@
1433
1445
  console.warn('DailyVoiceClient: failed to create remote audio element', err);
1434
1446
  }
1435
1447
  };
1448
+ /**
1449
+ * Monitor remote audio track energy via AnalyserNode.
1450
+ * Polls at ~60fps and flips speakingSubject based on actual audio energy.
1451
+ */
1452
+ DailyVoiceClientService.prototype.monitorRemoteAudio = function (track) {
1453
+ var _this = this;
1454
+ this.stopRemoteAudioMonitor();
1455
+ try {
1456
+ var ctx = new AudioContext();
1457
+ var source = ctx.createMediaStreamSource(new MediaStream([track]));
1458
+ var analyser_1 = ctx.createAnalyser();
1459
+ analyser_1.fftSize = 256;
1460
+ source.connect(analyser_1);
1461
+ this.remoteAudioContext = ctx;
1462
+ var dataArray_1 = new Uint8Array(analyser_1.frequencyBinCount);
1463
+ var THRESHOLD_1 = 8;
1464
+ var SILENCE_MS_1 = 300;
1465
+ var lastSoundTime_1 = 0;
1466
+ var isSpeaking_1 = false;
1467
+ var poll_1 = function () {
1468
+ if (!_this.remoteAudioContext)
1469
+ return;
1470
+ analyser_1.getByteFrequencyData(dataArray_1);
1471
+ var sum = 0;
1472
+ for (var i = 0; i < dataArray_1.length; i++) {
1473
+ sum += dataArray_1[i];
1474
+ }
1475
+ var avg = sum / dataArray_1.length;
1476
+ var now = Date.now();
1477
+ if (avg > THRESHOLD_1) {
1478
+ lastSoundTime_1 = now;
1479
+ if (!isSpeaking_1) {
1480
+ isSpeaking_1 = true;
1481
+ console.log("[VoiceDebug] Bot audio energy detected (speaking=true) \u2014 avg=" + avg.toFixed(1) + " \u2014 " + new Date().toISOString());
1482
+ _this.ngZone.run(function () { return _this.speakingSubject.next(true); });
1483
+ }
1484
+ }
1485
+ else if (isSpeaking_1 && now - lastSoundTime_1 > SILENCE_MS_1) {
1486
+ isSpeaking_1 = false;
1487
+ console.log("[VoiceDebug] Bot audio silence detected (speaking=false) \u2014 " + new Date().toISOString());
1488
+ _this.ngZone.run(function () { return _this.speakingSubject.next(false); });
1489
+ }
1490
+ _this.remoteSpeakingRAF = requestAnimationFrame(poll_1);
1491
+ };
1492
+ this.remoteSpeakingRAF = requestAnimationFrame(poll_1);
1493
+ }
1494
+ catch (err) {
1495
+ console.warn('DailyVoiceClient: failed to create remote audio monitor', err);
1496
+ }
1497
+ };
1498
+ DailyVoiceClientService.prototype.stopRemoteAudioMonitor = function () {
1499
+ if (this.remoteSpeakingRAF) {
1500
+ cancelAnimationFrame(this.remoteSpeakingRAF);
1501
+ this.remoteSpeakingRAF = null;
1502
+ }
1503
+ if (this.remoteAudioContext) {
1504
+ this.remoteAudioContext.close().catch(function () { });
1505
+ this.remoteAudioContext = null;
1506
+ }
1507
+ };
1436
1508
  DailyVoiceClientService.prototype.stopRemoteAudio = function () {
1437
1509
  if (this.remoteAudioElement) {
1438
1510
  try {
@@ -1479,6 +1551,7 @@
1479
1551
  });
1480
1552
  };
1481
1553
  DailyVoiceClientService.prototype.cleanup = function () {
1554
+ this.stopRemoteAudioMonitor();
1482
1555
  this.stopRemoteAudio();
1483
1556
  if (this.callObject) {
1484
1557
  this.callObject.destroy().catch(function () { });