feat: ensure mic ON on pre-join + voice greeting via Gateway TTS
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
aec15602c2
commit
ad8c858ce4
1 changed files with 80 additions and 2 deletions
|
|
@ -137,6 +137,9 @@ export class BotOrchestrator {
|
|||
// STEP 1: Navigate to meeting URL and click "Continue on this browser"
|
||||
await this._joinProcedure!.startMeetingLauncherFlow(this._meetingUrl);
|
||||
|
||||
// Ensure microphone is ON (required for voice playback)
|
||||
await this._ensureMicOn();
|
||||
|
||||
// STEP 2: Enter bot name and click "Join now"
|
||||
await this._joinProcedure!.joinMeetingLobbyFlow();
|
||||
|
||||
|
|
@ -282,6 +285,9 @@ export class BotOrchestrator {
|
|||
// Camera stays OFF — no video injection, focus on communication stability
|
||||
this._logger.info('Camera left OFF (video disabled for stability)');
|
||||
|
||||
// Ensure microphone is ON (required for voice playback)
|
||||
await this._ensureMicOn();
|
||||
|
||||
await this._page!.waitForTimeout(2000);
|
||||
|
||||
const joinNowSelectors = [
|
||||
|
|
@ -470,6 +476,67 @@ export class BotOrchestrator {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the microphone is turned on in the pre-join screen.
|
||||
* Required for voice playback (TTS audio is injected into the mic stream).
|
||||
*
|
||||
* Teams pre-join uses a fui-Switch input:
|
||||
* <input data-tid="toggle-audio" role="switch" type="checkbox" checked>
|
||||
* - checked present = mic ON
|
||||
* - checked absent = mic OFF
|
||||
*/
|
||||
private async _ensureMicOn(): Promise<void> {
|
||||
try {
|
||||
let micToggle = await this._page!.$('input[data-tid="toggle-audio"]');
|
||||
|
||||
if (!micToggle) {
|
||||
const fallbacks = [
|
||||
'[data-tid="toggle-audio"]',
|
||||
'input[role="switch"][title*="microphone" i]',
|
||||
'input[role="switch"][title*="Mikrofon" i]',
|
||||
'input[role="switch"][title*="mic" i]',
|
||||
'input[role="switch"][title*="audio" i]',
|
||||
];
|
||||
for (const sel of fallbacks) {
|
||||
micToggle = await this._page!.$(sel);
|
||||
if (micToggle) {
|
||||
this._logger.info(`Mic toggle found via fallback: ${sel}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!micToggle) {
|
||||
this._logger.warn('Mic toggle not found on pre-join screen');
|
||||
return;
|
||||
}
|
||||
|
||||
const state = await micToggle.evaluate((el: HTMLInputElement) => ({
|
||||
checked: el.checked,
|
||||
dataCid: el.getAttribute('data-cid') || '',
|
||||
title: el.getAttribute('title') || '',
|
||||
}));
|
||||
|
||||
this._logger.info(`Mic state: checked=${state.checked}, data-cid="${state.dataCid}", title="${state.title}"`);
|
||||
|
||||
if (!state.checked) {
|
||||
await micToggle.click();
|
||||
this._logger.info('Mic toggled ON');
|
||||
await this._page!.waitForTimeout(1000);
|
||||
|
||||
const afterState = await micToggle.evaluate((el: HTMLInputElement) => ({
|
||||
checked: el.checked,
|
||||
dataCid: el.getAttribute('data-cid') || '',
|
||||
}));
|
||||
this._logger.info(`Mic after toggle: checked=${afterState.checked}, data-cid="${afterState.dataCid}"`);
|
||||
} else {
|
||||
this._logger.info('Mic already ON');
|
||||
}
|
||||
} catch (err) {
|
||||
this._logger.warn(`Could not toggle mic: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a keepalive timer that periodically moves the mouse and sends
|
||||
* a WebSocket ping. Prevents Teams from detecting the bot as idle
|
||||
|
|
@ -1055,8 +1122,9 @@ export class BotOrchestrator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Send a greeting message in the meeting chat after joining.
|
||||
* Send a greeting message in the meeting chat AND via voice after joining.
|
||||
* Uses the bot's display name and the configured language.
|
||||
* Voice greeting confirms that the audio pipeline (TTS -> mic) is working.
|
||||
*/
|
||||
private async _sendJoinGreeting(): Promise<void> {
|
||||
try {
|
||||
|
|
@ -1074,8 +1142,18 @@ export class BotOrchestrator {
|
|||
greeting = `Hello, this is ${firstName}. I'm ready.`;
|
||||
}
|
||||
|
||||
this._logger.info(`Sending join greeting: ${greeting}`);
|
||||
this._logger.info(`Sending join greeting (chat + voice): ${greeting}`);
|
||||
|
||||
// Chat greeting
|
||||
await this.sendChatMessageToMeeting(greeting);
|
||||
|
||||
// Voice greeting — ask Gateway to generate TTS and send back playAudio
|
||||
this._sendToGateway({
|
||||
type: 'voiceGreeting',
|
||||
sessionId: this._sessionId,
|
||||
text: greeting,
|
||||
language: this._options.language || 'de-DE',
|
||||
});
|
||||
} catch (error) {
|
||||
this._logger.warn('Could not send join greeting:', error);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue