From 7e62c2fc651f71613cc0147c0c4d81b2dabe1291 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Fri, 27 Feb 2026 13:32:08 +0100
Subject: [PATCH] Voice fix: clone TTS track to prevent Teams from killing it
via track.stop()
Made-with: Cursor
---
src/bot/audioProcedure.ts | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/bot/audioProcedure.ts b/src/bot/audioProcedure.ts
index d37459f..b301482 100644
--- a/src/bot/audioProcedure.ts
+++ b/src/bot/audioProcedure.ts
@@ -62,8 +62,8 @@ export class AudioProcedure {
// Build a new stream: our TTS audio track + their video tracks
const combinedStream = new MediaStream();
- // Add our controlled audio track (TTS will be piped here)
- streamDest.stream.getAudioTracks().forEach(t => combinedStream.addTrack(t));
+ // Clone the TTS track so Teams can't kill the original via track.stop()
+ streamDest.stream.getAudioTracks().forEach(t => combinedStream.addTrack(t.clone()));
// Keep the real video tracks (from fake camera)
realStream.getVideoTracks().forEach(t => combinedStream.addTrack(t));
@@ -114,14 +114,15 @@ export class AudioProcedure {
// #region agent log
diag.beforeSenderTrackIds.push(sender.track.id);
// #endregion
- await sender.replaceTrack(ttsTrack);
+ const freshClone = ttsTrack.clone();
+ await sender.replaceTrack(freshClone);
replaced++;
// #region agent log
const afterTrack = sender.track;
diag.afterSenderTrackIds.push(afterTrack?.id || 'null');
diag.afterSenderTrackEnabled = afterTrack?.enabled;
diag.afterSenderTrackReadyState = afterTrack?.readyState;
- // Force track enabled just in case
+ diag.originalTrackState = ttsTrack.readyState;
if (afterTrack && !afterTrack.enabled) {
afterTrack.enabled = true;
diag.forcedEnabled = true;