






















@@ -13,6 +13,7 @@ import {
1313import {
1414createRealtimeVoiceAgentTalkbackQueue,
1515createRealtimeVoiceBridgeSession,
16+createRealtimeVoiceOutputActivityTracker,
1617createTalkSessionController,
1718convertPcmToMulaw8k,
1819extendRealtimeVoiceOutputEchoSuppression,
@@ -30,6 +31,7 @@ import {
3031type RealtimeVoiceAgentTalkbackQueue,
3132type RealtimeVoiceBridgeEventLogEntry,
3233type RealtimeVoiceBridgeSession,
34+type RealtimeVoiceOutputActivityTracker,
3335type RealtimeVoiceProviderConfig,
3436type RealtimeVoiceProviderPlugin,
3537type RealtimeVoiceTranscriptEntry,
@@ -163,6 +165,24 @@ export function extendGoogleMeetOutputEchoSuppression(params: {
163165});
164166}
165167168+export function recordGoogleMeetOutputActivity(params: {
169+tracker: RealtimeVoiceOutputActivityTracker;
170+audio: Buffer;
171+audioFormat: GoogleMeetConfig["chrome"]["audioFormat"];
172+nowMs: number;
173+lastOutputPlayableUntilMs: number;
174+suppressInputUntilMs: number;
175+}): { lastOutputPlayableUntilMs: number; suppressInputUntilMs: number; durationMs: number } {
176+const suppression = extendGoogleMeetOutputEchoSuppression(params);
177+params.tracker.markPlaybackStarted();
178+params.tracker.markAudio({
179+audioMs: suppression.durationMs,
180+sourceAudioBytes: params.audio.byteLength,
181+sinkAudioBytes: params.audio.byteLength,
182+});
183+return suppression;
184+}
185+166186export function resolveGoogleMeetRealtimeAudioFormat(config: GoogleMeetConfig) {
167187return config.chrome.audioFormat === "g711-ulaw-8khz"
168188 ? REALTIME_VOICE_AUDIO_FORMAT_G711_ULAW_8KHZ
@@ -477,7 +497,7 @@ export async function startCommandAgentAudioBridge(params: {
477497let lastInputAt: string | undefined;
478498let lastOutputAt: string | undefined;
479499let lastInputBytes = 0;
480-let lastOutputBytes = 0;
500+const outputActivity = createRealtimeVoiceOutputActivityTracker();
481501let suppressedInputBytes = 0;
482502let lastSuppressedInputAt: string | undefined;
483503let suppressInputUntil = 0;
@@ -606,7 +626,8 @@ export async function startCommandAgentAudioBridge(params: {
606626});
607627608628const writeOutputAudio = (audio: Buffer) => {
609-const suppression = extendGoogleMeetOutputEchoSuppression({
629+const suppression = recordGoogleMeetOutputActivity({
630+tracker: outputActivity,
610631 audio,
611632audioFormat: params.config.chrome.audioFormat,
612633nowMs: Date.now(),
@@ -616,7 +637,6 @@ export async function startCommandAgentAudioBridge(params: {
616637suppressInputUntil = suppression.suppressInputUntilMs;
617638lastOutputPlayableUntilMs = suppression.lastOutputPlayableUntilMs;
618639lastOutputAt = new Date().toISOString();
619-lastOutputBytes += audio.byteLength;
620640emitTalkEvent({
621641type: "output.audio.delta",
622642turnId: ensureTalkTurn(),
@@ -787,12 +807,12 @@ export async function startCommandAgentAudioBridge(params: {
787807providerConnected: sttSession?.isConnected() ?? false,
788808 realtimeReady,
789809audioInputActive: lastInputBytes > 0,
790-audioOutputActive: lastOutputBytes > 0,
810+audioOutputActive: outputActivity.isActive(),
791811 lastInputAt,
792812 lastOutputAt,
793813 lastSuppressedInputAt,
794814 lastInputBytes,
795- lastOutputBytes,
815+lastOutputBytes: outputActivity.snapshot().sinkAudioBytes,
796816 suppressedInputBytes,
797817 ...getGoogleMeetRealtimeTranscriptHealth(transcript),
798818recentTalkEvents: summarizeGoogleMeetTalkEvents(recentTalkEvents),
@@ -833,19 +853,19 @@ export async function startCommandRealtimeAudioBridge(params: {
833853let lastInputAt: string | undefined;
834854let lastOutputAt: string | undefined;
835855let lastInputBytes = 0;
836-let lastOutputBytes = 0;
856+const outputActivity = createRealtimeVoiceOutputActivityTracker();
837857let lastClearAt: string | undefined;
838858let clearCount = 0;
839859let suppressedInputBytes = 0;
840860let lastSuppressedInputAt: string | undefined;
841861let suppressInputUntil = 0;
842-let lastOutputAtMs = 0;
843862let lastOutputPlayableUntilMs = 0;
844863let bargeInInputProcess: BridgeProcess | undefined;
845864let agentTalkback: RealtimeVoiceAgentTalkbackQueue | undefined;
846865847866const suppressInputForOutput = (audio: Buffer) => {
848-const suppression = extendGoogleMeetOutputEchoSuppression({
867+const suppression = recordGoogleMeetOutputActivity({
868+tracker: outputActivity,
849869 audio,
850870audioFormat: params.config.chrome.audioFormat,
851871nowMs: Date.now(),
@@ -970,12 +990,13 @@ export async function startCommandRealtimeAudioBridge(params: {
970990stdio: ["ignore", "pipe", "pipe"],
971991});
972992bargeInInputProcess.stdout?.on("data", (chunk) => {
973-if (stopped || lastOutputAtMs === 0) {
993+if (stopped || !outputActivity.isInterruptible()) {
974994return;
975995}
976996const now = Date.now();
977997const playbackActive = now <= Math.max(lastOutputPlayableUntilMs, suppressInputUntil);
978-if (!playbackActive && now - lastOutputAtMs > 1000) {
998+const lastOutputAudioAt = outputActivity.snapshot().lastAudioAt;
999+if (!playbackActive && (lastOutputAudioAt === undefined || now - lastOutputAudioAt > 1_000)) {
9791000return;
9801001}
9811002if (now - lastBargeInAt < params.config.chrome.bargeInCooldownMs) {
@@ -1141,9 +1162,7 @@ export async function startCommandRealtimeAudioBridge(params: {
11411162 turnId,
11421163payload: { byteLength: audio.byteLength },
11431164});
1144-lastOutputAtMs = Date.now();
11451165lastOutputAt = new Date().toISOString();
1146-lastOutputBytes += audio.byteLength;
11471166suppressInputForOutput(audio);
11481167writeOutputAudio(audio);
11491168},
@@ -1315,12 +1334,12 @@ export async function startCommandRealtimeAudioBridge(params: {
13151334providerConnected: bridge?.bridge.isConnected() ?? false,
13161335 realtimeReady,
13171336audioInputActive: lastInputBytes > 0,
1318-audioOutputActive: lastOutputBytes > 0,
1337+audioOutputActive: outputActivity.isActive(),
13191338 lastInputAt,
13201339 lastOutputAt,
13211340 lastSuppressedInputAt,
13221341 lastInputBytes,
1323- lastOutputBytes,
1342+lastOutputBytes: outputActivity.snapshot().sinkAudioBytes,
13241343 suppressedInputBytes,
13251344 ...getGoogleMeetRealtimeTranscriptHealth(transcript),
13261345 ...getGoogleMeetRealtimeEventHealth(realtimeEvents),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。