fix: verify camera is ON after joining meeting using in-meeting video-button

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ValueOn AG 2026-02-17 22:03:57 +01:00
parent 6e712858dc
commit aef99057b2

View file

@ -156,6 +156,9 @@ export class BotOrchestrator {
// Dismiss any post-join permission modals (e.g. "Manage windows on all displays")
await this._joinProcedure!.dismissBrowserPermissionModals();
// Verify camera is on in the meeting
await this._ensureCameraOnInMeeting();
// Initialize audio playback
await this._audioProcedure!.initialize();
@ -311,6 +314,9 @@ export class BotOrchestrator {
// Start keepalive to prevent idle disconnect
this._startKeepAlive();
// Verify camera is on in the meeting
await this._ensureCameraOnInMeeting();
// Initialize audio playback
await this._audioProcedure!.initialize();
@ -384,6 +390,83 @@ export class BotOrchestrator {
}
}
/**
* Verify camera is on after joining the meeting, and turn it on if not.
*
* In-meeting camera button (from Teams DOM):
* <button id="video-button" data-state="call-video-off" aria-label="Turn camera on">
* <button id="video-button" data-state="call-video-on" aria-label="Turn camera off">
*/
private async _ensureCameraOnInMeeting(): Promise<void> {
try {
// Wait a moment for meeting controls to render
await this._page!.waitForTimeout(2000);
// Find the in-meeting video button
let videoBtn = await this._page!.$('button#video-button');
if (!videoBtn) {
// Fallback selectors
const fallbacks = [
'button[data-inp="video-button"]',
'button[id="video-button"]',
'button[aria-label*="camera" i]',
'button[aria-label*="Camera" i]',
'button[aria-label*="Video" i]',
];
for (const sel of fallbacks) {
videoBtn = await this._page!.$(sel);
if (videoBtn) {
this._logger.info(`In-meeting video button found via fallback: ${sel}`);
break;
}
}
}
if (!videoBtn) {
this._logger.warn('In-meeting video button not found');
return;
}
// Read current state
const state = await videoBtn.evaluate((el) => ({
dataState: el.getAttribute('data-state') || '',
ariaLabel: el.getAttribute('aria-label') || '',
id: el.id,
}));
this._logger.info(
`In-meeting camera: data-state="${state.dataState}", ` +
`aria-label="${state.ariaLabel}", id="${state.id}"`,
);
// Camera is off if data-state is "call-video-off"
const isOff = state.dataState === 'call-video-off'
|| state.ariaLabel.toLowerCase().includes('turn camera on')
|| state.ariaLabel.toLowerCase().includes('kamera einschalten');
if (isOff) {
await videoBtn.click();
this._logger.info('In-meeting camera was OFF — clicked to turn ON');
await this._page!.waitForTimeout(2000);
// Verify
const afterState = await videoBtn.evaluate((el) => ({
dataState: el.getAttribute('data-state') || '',
ariaLabel: el.getAttribute('aria-label') || '',
}));
this._logger.info(
`In-meeting camera after toggle: data-state="${afterState.dataState}", ` +
`aria-label="${afterState.ariaLabel}"`,
);
} else {
this._logger.info('In-meeting camera already ON');
}
} catch (err) {
this._logger.warn(`Could not verify in-meeting camera: ${err}`);
}
}
/**
* Start a keepalive timer that periodically moves the mouse and sends
* a WebSocket ping. Prevents Teams from detecting the bot as idle