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 3610b706..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
@@ -2,10 +2,20 @@
@if (showCaptionsButton()) {
@if (isMobile()) {
-
}
}
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.scss b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.scss
index 8a820dcc..f72022ed 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.scss
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/customization/meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component.scss
@@ -3,3 +3,7 @@
#captions-button.active {
background-color: var(--ov-accent-action-color);
}
+
+#captions-button.mat-mdc-button-disabled {
+ opacity: 0.5;
+}
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 2b3b2929..bcaf47be 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
@@ -34,9 +34,19 @@ export class MeetingToolbarExtraButtonsComponent {
protected showCopyLinkButton = computed(() => this.meetingContextService.canModerateRoom());
/**
- * Whether to show the captions button
+ * Captions status based on room and global configuration
*/
- protected showCaptionsButton = computed(() => this.meetingContextService.areCaptionsAllowed());
+ protected captionsStatus = computed(() => this.meetingContextService.getCaptionsStatus());
+
+ /**
+ * Whether to show the captions button (visible when not HIDDEN)
+ */
+ protected showCaptionsButton = computed(() => this.captionsStatus() !== 'HIDDEN');
+
+ /**
+ * Whether captions button is disabled (true when DISABLED_WITH_WARNING)
+ */
+ protected isCaptionsButtonDisabled = computed(() => this.captionsStatus() === 'DISABLED_WITH_WARNING');
/**
* Whether the device is mobile (affects button style)
@@ -56,6 +66,11 @@ export class MeetingToolbarExtraButtonsComponent {
}
onCaptionsClick(): void {
+ // 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() ? this.captionService.disable() : this.captionService.enable();
}
}
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts
index 8bcf2118..a3462edf 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts
@@ -120,7 +120,10 @@ export class MeetingComponent implements OnInit {
if (token && this.showLobby) {
// The meeting view must be shown before loading the appearance config
this.showLobby = false;
- await this.configService.loadRoomsAppearanceConfig();
+ await Promise.all([
+ this.configService.loadRoomsAppearanceConfig(),
+ this.configService.loadCaptionsConfig()
+ ]);
}
});
}
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 d62a162a..7f8a38c1 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
@@ -187,11 +187,11 @@ export class MeetingContextService {
}
/**
- * Returns whether captions feature is allowed in the room
- * @returns true if captions feature is allowed, false otherwise
+ * Returns the captions status based on room and global configuration
+ * @returns CaptionsStatus ('HIDDEN' | 'ENABLED' | 'DISABLED_WITH_WARNING')
*/
- areCaptionsAllowed(): boolean {
- return this.featureConfigService.features().showCaptions;
+ getCaptionsStatus() {
+ return this.featureConfigService.features().captionsStatus;
}
/**
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 d6d2787e..1085259e 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
@@ -16,6 +16,11 @@ export enum Edition {
PRO = 'PRO'
}
+/**
+ * Status of captions feature based on room and global configuration
+ */
+export type CaptionsStatus = 'HIDDEN' | 'ENABLED' | 'DISABLED_WITH_WARNING';
+
/**
* Interface that defines all available features in the application
*/
@@ -31,7 +36,7 @@ export interface ApplicationFeatures {
showRecordingPanel: boolean;
showChat: boolean;
showBackgrounds: boolean;
- showCaptions: boolean;
+ captionsStatus: CaptionsStatus;
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 ae930f1d..b4da83b8 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
@@ -1,13 +1,14 @@
import { computed, Injectable, signal } from '@angular/core';
import {
MeetAppearanceConfig,
+ MeetRoomCaptionsConfig,
MeetRoomConfig,
MeetRoomMemberPermissions,
MeetRoomMemberRole,
TrackSource
} from '@openvidu-meet/typings';
import { LoggerService } from 'openvidu-components-angular';
-import { ApplicationFeatures } from '../models/app.model';
+import { ApplicationFeatures, CaptionsStatus } from '../models/app.model';
/**
* Base configuration for default features
@@ -22,7 +23,7 @@ const DEFAULT_FEATURES: ApplicationFeatures = {
showRecordingPanel: true,
showChat: true,
showBackgrounds: true,
- showCaptions: false,
+ captionsStatus: 'ENABLED',
showParticipantList: true,
showSettings: true,
showFullscreen: true,
@@ -52,6 +53,7 @@ export class FeatureConfigurationService {
protected roomMemberRole = signal(undefined);
protected roomMemberPermissions = signal(undefined);
protected appearanceConfig = signal(undefined);
+ protected captionsGlobalConfig = signal(false);
// Computed signal to derive features based on current configurations
public readonly features = computed(() =>
@@ -59,7 +61,8 @@ export class FeatureConfigurationService {
this.roomConfig(),
this.roomMemberRole(),
this.roomMemberPermissions(),
- this.appearanceConfig()
+ this.appearanceConfig(),
+ this.captionsGlobalConfig()
)
);
@@ -99,6 +102,14 @@ export class FeatureConfigurationService {
this.appearanceConfig.set(config);
}
+ /**
+ * Updates captions global config
+ */
+ setCaptionsGlobalConfig(enabled: boolean): void {
+ this.log.d('Updating captions global config', enabled);
+ this.captionsGlobalConfig.set(enabled);
+ }
+
/**
* Core logic to calculate features based on all configurations
*/
@@ -106,7 +117,8 @@ export class FeatureConfigurationService {
roomConfig?: MeetRoomConfig,
role?: MeetRoomMemberRole,
permissions?: MeetRoomMemberPermissions,
- appearanceConfig?: MeetAppearanceConfig
+ appearanceConfig?: MeetAppearanceConfig,
+ captionsGlobalEnabled: boolean = false
): ApplicationFeatures {
// Start with default configuration
const features: ApplicationFeatures = { ...DEFAULT_FEATURES };
@@ -116,7 +128,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;
+ features.captionsStatus = this.computeCaptionsStatus(roomConfig.captions, captionsGlobalEnabled);
}
// Apply room member permissions (these can restrict enabled features)
@@ -164,6 +176,22 @@ export class FeatureConfigurationService {
return features;
}
+ /**
+ * Computes the captions status based on room and global configuration
+ * HIDDEN: room config disabled
+ * ENABLED: room config enabled AND global config enabled
+ * DISABLED_WITH_WARNING: room config enabled BUT global config disabled
+ */
+ protected computeCaptionsStatus(
+ roomCaptionsConfig: MeetRoomCaptionsConfig | undefined,
+ globalEnabled: boolean
+ ): CaptionsStatus {
+ if (!roomCaptionsConfig?.enabled) {
+ return 'HIDDEN';
+ }
+ return globalEnabled ? 'ENABLED' : 'DISABLED_WITH_WARNING';
+ }
+
/**
* Resets all configurations to their initial values
*/
@@ -172,5 +200,6 @@ export class FeatureConfigurationService {
this.roomMemberRole.set(undefined);
this.roomMemberPermissions.set(undefined);
this.appearanceConfig.set(undefined);
+ this.captionsGlobalConfig.set(false);
}
}
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/global-config.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/global-config.service.ts
index 799ffa98..b092e616 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/global-config.service.ts
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/global-config.service.ts
@@ -76,4 +76,19 @@ export class GlobalConfigService {
const path = `${this.GLOBAL_CONFIG_API}/rooms/appearance`;
await this.httpService.putRequest(path, { appearance: config });
}
+
+ private async getCaptionsConfig(): Promise<{ enabled: boolean }> {
+ const path = `${this.GLOBAL_CONFIG_API}/captions`;
+ return await this.httpService.getRequest<{ enabled: boolean }>(path);
+ }
+
+ async loadCaptionsConfig(): Promise {
+ try {
+ const config = await this.getCaptionsConfig();
+ this.featureConfService.setCaptionsGlobalConfig(config.enabled);
+ } catch (error) {
+ this.log.e('Error loading captions config:', error);
+ throw error;
+ }
+ }
}