diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.html b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.html
index 5b4836da..ad8ecd41 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.html
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.html
@@ -11,10 +11,8 @@
>
subtitles
- @if (captionsStatus() === 'DISABLED_WITH_WARNING') {
+ @if (isCaptionsButtonDisabled()) {
Live captions (disabled by admin)
- } @else if (isCaptionsTogglePending()) {
- {{ areCaptionsEnabledByUser() ? 'Disabling live captions...' : 'Enabling live captions...' }}
} @else {
{{ areCaptionsEnabledByUser() ? 'Disable live captions' : 'Enable live captions' }}
}
@@ -30,16 +28,14 @@
[disabledInteractive]="isCaptionsButtonDisabled()"
[disableRipple]="true"
[matTooltip]="
- captionsStatus() === 'DISABLED_WITH_WARNING'
+ isCaptionsButtonDisabled()
? 'Live captions are disabled by admin'
- : isCaptionsTogglePending()
- ? (areCaptionsEnabledByUser() ? 'Disabling live captions...' : 'Enabling live captions...')
- : areCaptionsEnabledByUser()
- ? 'Disable live captions'
- : 'Enable live captions'
+ : areCaptionsEnabledByUser()
+ ? 'Disable live captions'
+ : 'Enable live captions'
"
>
- @if (captionsStatus() === 'DISABLED_WITH_WARNING') {
+ @if (isCaptionsButtonDisabled()) {
subtitles_off
} @else {
subtitles
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.ts
index 172ec35f..a5926d3b 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.ts
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.ts
@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
-import { Component, computed, inject } from '@angular/core';
+import { Component, computed, inject, signal } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
@@ -34,8 +34,12 @@ export class MeetingToolbarExtraButtonsComponent {
/** Whether to show the captions button (visible when not HIDDEN) */
showCaptionsButton = computed(() => this.meetingContextService.meetingUI().showCaptionsControls);
/** Whether captions button is disabled (true when DISABLED_WITH_WARNING) */
- // TODO: Apply disabled while an enable/disable request is in flight to prevent concurrent calls
isCaptionsButtonDisabled = computed(() => this.meetingContextService.meetingUI().showCaptionsControlsDisabled);
+ /**
+ * True while an enable() or disable() call is in flight.
+ * Use this to prevent concurrent toggle requests.
+ */
+ isCaptionsTogglePending = signal(false);
/** Whether captions are currently enabled by the user */
areCaptionsEnabledByUser = this.captionService.areCaptionsEnabledByUser;
@@ -53,17 +57,27 @@ export class MeetingToolbarExtraButtonsComponent {
}
async onCaptionsClick(): Promise {
+ if (this.isCaptionsTogglePending()) {
+ return;
+ }
+
+ this.isCaptionsTogglePending.set(true);
+
try {
// Don't allow toggling if captions are disabled at system level
if (this.isCaptionsButtonDisabled()) {
this.log.w('Captions are disabled at system level (MEET_CAPTIONS_ENABLED=false)');
return;
}
+
this.captionService.areCaptionsEnabledByUser()
? await this.captionService.disable()
: await this.captionService.enable();
} catch (error) {
this.log.e('Error toggling captions:', error);
+ } finally {
+ // Add a small delay before allowing another toggle to prevent rapid concurrent calls
+ setTimeout(() => this.isCaptionsTogglePending.set(false), 500);
}
}
}
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-captions.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-captions.service.ts
index bc7306c1..111be9fd 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-captions.service.ts
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-captions.service.ts
@@ -1,8 +1,8 @@
import { Injectable, inject, signal } from '@angular/core';
import { ILogger, LoggerService, ParticipantService, Room, TextStreamReader } from 'openvidu-components-angular';
+import { AiAssistantService } from '../../../shared/services/ai-assistant.service';
import { Caption, CaptionsConfig } from '../models/captions.model';
import { CustomParticipantModel } from '../models/custom-participant.model';
-import { AiAssistantService } from '../../../shared/services/ai-assistant.service';
/**
* Service responsible for managing live transcription captions.
@@ -41,7 +41,6 @@ export class MeetingCaptionsService {
private readonly _captions = signal([]);
private readonly _areCaptionsEnabledByUser = signal(false);
private readonly _captionsAgentId = signal(null);
- private readonly _isCaptionsTogglePending = signal(false);
/**
* Current list of active captions
@@ -51,11 +50,6 @@ export class MeetingCaptionsService {
* Whether captions are enabled by the user
*/
readonly areCaptionsEnabledByUser = this._areCaptionsEnabledByUser.asReadonly();
- /**
- * True while an enable() or disable() call is in flight.
- * Use this to prevent concurrent toggle requests.
- */
- readonly isCaptionsTogglePending = this._isCaptionsTogglePending.asReadonly();
// Map to track expiration timeouts
private expirationTimeouts = new Map>();
@@ -102,13 +96,6 @@ export class MeetingCaptionsService {
return;
}
- if (this._isCaptionsTogglePending()) {
- this.logger.d('Captions toggle already in progress');
- return;
- }
-
- this._isCaptionsTogglePending.set(true);
-
try {
// Register the LiveKit transcription handler
const agent = await this.aiAssistantService.createLiveCaptionsAssistant();
@@ -116,9 +103,8 @@ export class MeetingCaptionsService {
this.room.registerTextStreamHandler('lk.transcription', this.handleTranscription.bind(this));
this._areCaptionsEnabledByUser.set(true);
this.logger.d('Captions enabled');
- } finally {
- // Add a small delay before allowing another toggle to prevent rapid concurrent calls
- setTimeout(() => this._isCaptionsTogglePending.set(false), 500);
+ } catch (error) {
+ this.logger.e('Error enabling captions:', error);
}
}
@@ -132,13 +118,6 @@ export class MeetingCaptionsService {
return;
}
- if (this._isCaptionsTogglePending()) {
- this.logger.d('Captions toggle already in progress');
- return;
- }
-
- this._isCaptionsTogglePending.set(true);
-
try {
const agentId = this._captionsAgentId();
@@ -155,9 +134,6 @@ export class MeetingCaptionsService {
this.logger.d('Captions disabled');
} catch (error) {
this.logger.e('Error disabling captions:', error);
- } finally {
- // Add a small delay before allowing another toggle to prevent rapid concurrent calls
- setTimeout(() => this._isCaptionsTogglePending.set(false), 500);
}
}