From 0a56a74433ef63f63ea920f810bd0c7ac1f758f1 Mon Sep 17 00:00:00 2001 From: CSantosM <4a.santos@gmail.com> Date: Thu, 22 Jan 2026 19:28:18 +0100 Subject: [PATCH] frontend: Enables user-controlled live captions Allows users to toggle live captions on or off. Introduces a room configuration setting to enable/disable the captions feature. The captions button visibility is now controlled by the 'showCaptions' feature flag. --- .../meeting-custom-layout.component.html | 6 +-- .../meeting-custom-layout.component.ts | 2 +- ...eting-toolbar-extra-buttons.component.html | 42 +++++++++---------- ...meeting-toolbar-extra-buttons.component.ts | 9 +++- .../services/meeting-captions.service.ts | 21 ++++++---- .../services/meeting-context.service.ts | 8 ++++ .../room-config/room-config.component.html | 24 +++++++++++ .../room-config/room-config.component.ts | 12 ++++++ .../rooms/services/wizard-state.service.ts | 10 ++++- .../src/lib/shared/models/app.model.ts | 1 + .../services/feature-configuration.service.ts | 3 +- 11 files changed, 100 insertions(+), 38 deletions(-) diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-custom-layout/meeting-custom-layout.component.html b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-custom-layout/meeting-custom-layout.component.html index ad07a49a..362b125f 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-custom-layout/meeting-custom-layout.component.html +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-custom-layout/meeting-custom-layout.component.html @@ -1,8 +1,8 @@ @if (meetingContextService.lkRoom()) { -
+
@if (shouldShowLinkOverlay()) { @@ -43,7 +43,7 @@
- @if (shouldShowCaptions()) { + @if (areCaptionsEnabledByUser()) { } } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-custom-layout/meeting-custom-layout.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-custom-layout/meeting-custom-layout.component.ts index 2f5b4d44..04364919 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-custom-layout/meeting-custom-layout.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-custom-layout/meeting-custom-layout.component.ts @@ -50,7 +50,7 @@ export class MeetingCustomLayoutComponent { return this.meetingContextService.canModerateRoom() && hasNoRemotes; }); - protected readonly shouldShowCaptions = computed(() => this.captionsService.areCaptionsEnabled()); + protected readonly areCaptionsEnabledByUser = computed(() => this.captionsService.areCaptionsEnabledByUser()); protected readonly captions = computed(() => this.captionsService.captions()); 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 bd189af0..3610b706 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 @@ -1,25 +1,25 @@ -@if (isMobile()) { - - -} @else { - +@if (showCaptionsButton()) { + @if (isMobile()) { + + + } @else { + + } } 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 7843c626..2b3b2929 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 @@ -33,12 +33,17 @@ export class MeetingToolbarExtraButtonsComponent { */ protected showCopyLinkButton = computed(() => this.meetingContextService.canModerateRoom()); + /** + * Whether to show the captions button + */ + protected showCaptionsButton = computed(() => this.meetingContextService.areCaptionsAllowed()); + /** * Whether the device is mobile (affects button style) */ protected isMobile = computed(() => this.meetingContextService.isMobile()); - protected areCaptionsEnabled = computed(() => this.captionService.areCaptionsEnabled()); + protected areCaptionsEnabledByUser = computed(() => this.captionService.areCaptionsEnabledByUser()); onCopyLinkClick(): void { const room = this.meetingContextService.meetRoom(); @@ -51,6 +56,6 @@ export class MeetingToolbarExtraButtonsComponent { } onCaptionsClick(): void { - this.captionService.areCaptionsEnabled() ? this.captionService.disable() : this.captionService.enable(); + this.captionService.areCaptionsEnabledByUser() ? this.captionService.disable() : this.captionService.enable(); } } 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 ad523233..5094763b 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 @@ -37,11 +37,16 @@ export class MeetingCaptionsService { // Reactive state private readonly _captions = signal([]); - private readonly _isEnabled = signal(false); + private readonly _areCaptionsEnabledByUser = signal(false); - // Public readonly signals + /** + * Current list of active captions + */ readonly captions = this._captions.asReadonly(); - readonly areCaptionsEnabled = this._isEnabled.asReadonly(); + /** + * Whether captions are enabled by the user + */ + readonly areCaptionsEnabledByUser = this._areCaptionsEnabledByUser.asReadonly(); // Map to track expiration timeouts private expirationTimeouts = new Map>(); @@ -83,7 +88,7 @@ export class MeetingCaptionsService { return; } - if (this._isEnabled()) { + if (this._areCaptionsEnabledByUser()) { this.logger.d('Captions already enabled'); return; } @@ -91,7 +96,7 @@ export class MeetingCaptionsService { // Register the LiveKit transcription handler this.room.registerTextStreamHandler('lk.transcription', this.handleTranscription.bind(this)); - this._isEnabled.set(true); + this._areCaptionsEnabledByUser.set(true); this.logger.d('Captions enabled'); } @@ -100,7 +105,7 @@ export class MeetingCaptionsService { * This is called when the user deactivates captions. */ disable(): void { - if (!this._isEnabled()) { + if (!this._areCaptionsEnabledByUser()) { this.logger.d('Captions already disabled'); return; } @@ -108,7 +113,7 @@ export class MeetingCaptionsService { // Clear all active captions this.clearAllCaptions(); - this._isEnabled.set(false); + this._areCaptionsEnabledByUser.set(false); this.room?.unregisterTextStreamHandler('lk.transcription'); this.logger.d('Captions disabled'); } @@ -119,7 +124,7 @@ export class MeetingCaptionsService { destroy(): void { this.clearAllCaptions(); this.room = null; - this._isEnabled.set(false); + this._areCaptionsEnabledByUser.set(false); this.logger.d('Meeting Captions service destroyed'); } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts index 7c5e144b..d62a162a 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts @@ -186,6 +186,14 @@ export class MeetingContextService { return this._e2eeKey().length > 0; } + /** + * Returns whether captions feature is allowed in the room + * @returns true if captions feature is allowed, false otherwise + */ + areCaptionsAllowed(): boolean { + return this.featureConfigService.features().showCaptions; + } + /** * Sets the room secret in context * @param secret The room secret diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/room-config/room-config.component.html b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/room-config/room-config.component.html index fdc50563..504c7e1c 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/room-config/room-config.component.html +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/room-config/room-config.component.html @@ -61,6 +61,30 @@ + + + +
+
+ closed_caption +
+

Captions

+

+ Enable live transcription to display captions during the meeting +

+
+
+ + +
+
+
+ diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/room-config/room-config.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/room-config/room-config.component.ts index 24a2a773..05190dd2 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/room-config/room-config.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/pages/room-wizard/steps/room-config/room-config.component.ts @@ -44,6 +44,9 @@ export class RoomConfigComponent implements OnDestroy { }, e2ee: { enabled: formValue.e2eeEnabled ?? false + }, + captions: { + enabled: formValue.captionsEnabled ?? false } } }; @@ -102,6 +105,11 @@ export class RoomConfigComponent implements OnDestroy { this.configForm.patchValue({ virtualBackgroundEnabled: isEnabled }); } + onCaptionsToggleChange(event: any): void { + const isEnabled = event.checked; + this.configForm.patchValue({ captionsEnabled: isEnabled }); + } + get chatEnabled(): boolean { return this.configForm.value.chatEnabled || false; } @@ -113,4 +121,8 @@ export class RoomConfigComponent implements OnDestroy { get e2eeEnabled(): boolean { return this.configForm.value.e2eeEnabled ?? false; } + + get captionsEnabled(): boolean { + return this.configForm.value.captionsEnabled ?? false; + } } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/wizard-state.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/wizard-state.service.ts index 5b8bccff..31d00ea2 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/wizard-state.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/wizard-state.service.ts @@ -19,7 +19,8 @@ const DEFAULT_CONFIG: MeetRoomConfig = { }, chat: { enabled: true }, virtualBackground: { enabled: true }, - e2ee: { enabled: false } + e2ee: { enabled: false }, + captions: { enabled: false } }; /** @@ -192,7 +193,8 @@ export class RoomWizardStateService { formGroup: this.formBuilder.group({ chatEnabled: initialRoomOptions.config!.chat!.enabled, virtualBackgroundEnabled: initialRoomOptions.config!.virtualBackground!.enabled, - e2eeEnabled: initialRoomOptions.config!.e2ee!.enabled + e2eeEnabled: initialRoomOptions.config!.e2ee!.enabled, + captionsEnabled: initialRoomOptions.config!.captions!.enabled }) } ]; @@ -267,6 +269,10 @@ export class RoomWizardStateService { ...currentOptions.config?.e2ee, ...stepData.config?.e2ee }, + captions: { + ...currentOptions.config?.captions, + ...stepData.config?.captions + }, recording: { ...currentOptions.config?.recording, // If recording is explicitly set in stepData, use it diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/models/app.model.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/models/app.model.ts index e5d848e9..d6d2787e 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/models/app.model.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/models/app.model.ts @@ -31,6 +31,7 @@ export interface ApplicationFeatures { showRecordingPanel: boolean; showChat: boolean; showBackgrounds: boolean; + showCaptions: boolean; showParticipantList: boolean; showSettings: boolean; showFullscreen: boolean; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/feature-configuration.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/feature-configuration.service.ts index a83af6a3..ae930f1d 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/feature-configuration.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/feature-configuration.service.ts @@ -22,6 +22,7 @@ const DEFAULT_FEATURES: ApplicationFeatures = { showRecordingPanel: true, showChat: true, showBackgrounds: true, + showCaptions: false, showParticipantList: true, showSettings: true, showFullscreen: true, @@ -115,6 +116,7 @@ export class FeatureConfigurationService { features.showRecordingPanel = roomConfig.recording.enabled; features.showChat = roomConfig.chat.enabled; features.showBackgrounds = roomConfig.virtualBackground.enabled; + features.showCaptions = roomConfig.captions?.enabled ?? false; } // Apply room member permissions (these can restrict enabled features) @@ -130,7 +132,6 @@ export class FeatureConfigurationService { if (features.showBackgrounds) { features.showBackgrounds = permissions.meet.canChangeVirtualBackground; } - // Media features const canPublish = permissions.livekit.canPublish; const canPublishSources = permissions.livekit.canPublishSources ?? [];