feat: stop command (stopAllAudio), sequential audio queue with stop support

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ValueOn AG 2026-02-16 09:29:02 +01:00
parent a483aa6def
commit 730214d30b
2 changed files with 40 additions and 1 deletions

View file

@ -22,6 +22,7 @@ export class AudioProcedure {
private _initScriptInjected: boolean = false; private _initScriptInjected: boolean = false;
private _audioQueue: Array<{ audioData: string; format: 'mp3' | 'wav' | 'pcm' }> = []; private _audioQueue: Array<{ audioData: string; format: 'mp3' | 'wav' | 'pcm' }> = [];
private _isPlaying: boolean = false; private _isPlaying: boolean = false;
private _stopRequested: boolean = false;
constructor(page: Page, logger: Logger) { constructor(page: Page, logger: Logger) {
this._page = page; this._page = page;
@ -138,8 +139,9 @@ export class AudioProcedure {
private async _processAudioQueue(): Promise<void> { private async _processAudioQueue(): Promise<void> {
if (this._isPlaying) return; if (this._isPlaying) return;
this._isPlaying = true; this._isPlaying = true;
this._stopRequested = false;
while (this._audioQueue.length > 0) { while (this._audioQueue.length > 0 && !this._stopRequested) {
const item = this._audioQueue.shift()!; const item = this._audioQueue.shift()!;
try { try {
await this._playAudioInternal(item.audioData, item.format); await this._playAudioInternal(item.audioData, item.format);
@ -148,7 +150,37 @@ export class AudioProcedure {
} }
} }
if (this._stopRequested) {
this._audioQueue = [];
this._logger.info('Audio queue cleared due to stop request');
}
this._isPlaying = false; this._isPlaying = false;
this._stopRequested = false;
}
/**
* Stop all audio immediately: stop current playback and clear the queue.
* Called when a user says "<botname> STOP" or similar.
*/
async stopAllAudio(): Promise<void> {
this._logger.info('Stop all audio requested');
this._stopRequested = true;
this._audioQueue = [];
try {
await this._page.evaluate(() => {
const ctx = (window as any).__ttsAudioContext as AudioContext;
if (ctx) {
// Suspend immediately stops all audio output
ctx.suspend();
// Resume after a short delay so future audio can play
setTimeout(() => ctx.resume(), 100);
}
});
} catch {
// Page might not be ready
}
} }
/** /**

View file

@ -334,6 +334,13 @@ export class BotOrchestrator {
this.sendChatMessageToMeeting(chatMsg.text); this.sendChatMessageToMeeting(chatMsg.text);
break; break;
case 'stopAudio':
this._logger.info('Stop audio command received from Gateway');
if (this._audioProcedure) {
this._audioProcedure.stopAllAudio();
}
break;
case 'pong': case 'pong':
// Heartbeat response // Heartbeat response
break; break;