From 76c957903f1e2d574e1589675d5e5e2fdcf353ed Mon Sep 17 00:00:00 2001
From: Carlos Santos <4a.santos@gmail.com>
Date: Tue, 29 Jul 2025 12:15:56 +0200
Subject: [PATCH] ov-components: Refactors config service to use RxJS Subjects
Updates the configuration service to use RxJS BehaviorSubjects and Observables for managing configuration values.
This change improves the reactivity and maintainability of the configuration system by providing a consistent and type-safe way to manage application settings.
Specifically, it introduces a helper method to create configuration items with BehaviorSubject and Observable, and uses distinctUntilChanged and shareReplay operators to optimize the observable streams.
ov-components: Refactor configuration management in OpenVidu components
- Updated directive methods to use centralized configuration updates for general, stream, and toolbar settings.
- Replaced individual setter methods with batch update methods for improved performance and maintainability.
- Introduced specific comparison methods for configuration objects to optimize change detection.
- Enhanced the structure of configuration interfaces for better clarity and organization.
- Removed redundant code and streamlined the configuration service for better readability.
ov-components: Enhance participant name handling in PreJoin and Videoconference components
---
.../components/pre-join/pre-join.component.ts | 80 +-
.../components/session/session.component.ts | 8 +-
.../videoconference.component.ts | 29 +-
.../api/activities-panel.directive.ts | 8 +-
.../src/lib/directives/api/admin.directive.ts | 48 +-
.../lib/directives/api/internals.directive.ts | 24 +-
.../api/participant-panel-item.directive.ts | 11 +-
.../lib/directives/api/stream.directive.ts | 12 +-
.../lib/directives/api/toolbar.directive.ts | 60 +-
.../api/videoconference.directive.ts | 24 +-
.../config/directive-config.service.ts | 950 +++++++++---------
11 files changed, 633 insertions(+), 621 deletions(-)
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.ts
index 5bbec423..282a635a 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.ts
@@ -133,9 +133,21 @@ export class PreJoinComponent implements OnInit, OnDestroy {
this.shouldRemoveTracksWhenComponentIsDestroyed = false;
// Assign participant name to the observable if it is defined
- if(this.participantName) this.libService.setParticipantName(this.participantName);
+ if (this.participantName) {
+ this.libService.updateGeneralConfig({ participantName: this.participantName });
- this.onReadyToJoin.emit();
+ // Wait for the next tick to ensure the participant name propagates
+ // through the observable before emitting onReadyToJoin
+ const sub = this.libService.participantName$.pipe(takeUntil(this.destroy$)).subscribe((name) => {
+ if (name === this.participantName) {
+ this.onReadyToJoin.emit();
+ sub.unsubscribe();
+ }
+ });
+ } else {
+ // No participant name to set, emit immediately
+ this.onReadyToJoin.emit();
+ }
}
onParticipantNameChanged(name: string) {
@@ -147,49 +159,37 @@ export class PreJoinComponent implements OnInit, OnDestroy {
}
private subscribeToPrejoinDirectives() {
- this.libService.minimal$
- .pipe(takeUntil(this.destroy$))
- .subscribe((value: boolean) => {
- this.isMinimal = value;
- this.changeDetector.markForCheck();
- });
+ this.libService.minimal$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
+ this.isMinimal = value;
+ this.changeDetector.markForCheck();
+ });
- this.libService.cameraButton$
- .pipe(takeUntil(this.destroy$))
- .subscribe((value: boolean) => {
- this.showCameraButton = value;
- this.changeDetector.markForCheck();
- });
+ this.libService.cameraButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
+ this.showCameraButton = value;
+ this.changeDetector.markForCheck();
+ });
- this.libService.microphoneButton$
- .pipe(takeUntil(this.destroy$))
- .subscribe((value: boolean) => {
- this.showMicrophoneButton = value;
- this.changeDetector.markForCheck();
- });
+ this.libService.microphoneButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
+ this.showMicrophoneButton = value;
+ this.changeDetector.markForCheck();
+ });
- this.libService.displayLogo$
- .pipe(takeUntil(this.destroy$))
- .subscribe((value: boolean) => {
- this.showLogo = value;
- this.changeDetector.markForCheck();
- });
+ this.libService.displayLogo$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
+ this.showLogo = value;
+ this.changeDetector.markForCheck();
+ });
- this.libService.participantName$
- .pipe(takeUntil(this.destroy$))
- .subscribe((value: string) => {
- if (value) {
- this.participantName = value;
- this.changeDetector.markForCheck();
- }
- });
-
- this.libService.prejoinDisplayParticipantName$
- .pipe(takeUntil(this.destroy$))
- .subscribe((value: boolean) => {
- this.showParticipantName = value;
+ this.libService.participantName$.pipe(takeUntil(this.destroy$)).subscribe((value: string) => {
+ if (value) {
+ this.participantName = value;
this.changeDetector.markForCheck();
- });
+ }
+ });
+
+ this.libService.prejoinDisplayParticipantName$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
+ this.showParticipantName = value;
+ this.changeDetector.markForCheck();
+ });
}
async videoEnabledChanged(enabled: boolean) {
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/session/session.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/session/session.component.ts
index 5e238003..eb9d7c8f 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/session/session.component.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/session/session.component.ts
@@ -235,13 +235,13 @@ export class SessionComponent implements OnInit, OnDestroy {
this.subscribeToReconnection();
this.subscribeToVirtualBackground();
- if (this.libService.isRecordingEnabled()) {
+ // if (this.libService.isRecordingEnabled()) {
// this.subscribeToRecordingEvents();
- }
+ // }
- if (this.libService.isBroadcastingEnabled()) {
+ // if (this.libService.isBroadcastingEnabled()) {
// this.subscribeToBroadcastingEvents();
- }
+ // }
try {
await this.participantService.connect();
// Send room created after participant connect for avoiding to send incomplete room payload
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts
index 87e97752..2270a537 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts
@@ -591,7 +591,9 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
// Always initialize the room when ready to join
this.openviduService.initRoom();
- const participantName = this.latestParticipantName;
+ // Get the most current participant name from the service
+ // This ensures we have the latest value after any batch updates
+ const participantName = this.libService.getCurrentParticipantName() || this.latestParticipantName;
if (this.componentState.isRoomReady) {
// Room is ready, hide prejoin and proceed
@@ -607,6 +609,16 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
this.onTokenRequested.emit(participantName);
} else {
this.log.w('No participant name available when requesting token');
+ // Wait a bit and try again in case name is still propagating
+ setTimeout(() => {
+ const retryName = this.libService.getCurrentParticipantName()|| this.latestParticipantName;
+ if (retryName) {
+ this.log.d(`Retrying token request for participant: ${retryName}`);
+ this.onTokenRequested.emit(retryName);
+ } else {
+ this.log.e('Still no participant name available after retry');
+ }
+ }, 10);
}
}
@@ -734,7 +746,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
const storedName = this.storageSrv.getParticipantName();
if (storedName) {
this.latestParticipantName = storedName;
- this.libService.setParticipantName(storedName);
+ this.libService.updateGeneralConfig({ participantName: storedName });
}
this._onReadyToJoin();
}
@@ -747,7 +759,18 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
if (name) {
this.latestParticipantName = name;
this.storageSrv.setParticipantName(name);
+
+ // If we're waiting for a participant name to proceed with joining, do it now
+ if (this.componentState.state === VideoconferenceState.JOINING &&
+ this.componentState.isRoomReady &&
+ !this.componentState.showPrejoin) {
+ this.log.d('Participant name received, proceeding to join');
+ this.updateComponentState({
+ state: VideoconferenceState.READY_TO_CONNECT,
+ showPrejoin: false
+ });
+ }
}
});
}
-}
+}
\ No newline at end of file
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/activities-panel.directive.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/activities-panel.directive.ts
index c3145b91..18b74347 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/activities-panel.directive.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/activities-panel.directive.ts
@@ -49,9 +49,7 @@ export class ActivitiesPanelRecordingActivityDirective implements AfterViewInit,
}
update(value: boolean) {
- if (this.libService.showRecordingActivity() !== value) {
- this.libService.setRecordingActivity(value);
- }
+ this.libService.updateRecordingActivityConfig({ enabled: value });
}
}
@@ -103,8 +101,6 @@ export class ActivitiesPanelBroadcastingActivityDirective implements AfterViewIn
}
update(value: boolean) {
- if (this.libService.showBroadcastingActivity() !== value) {
- this.libService.setBroadcastingActivity(value);
- }
+ this.libService.setBroadcastingActivity(value);
}
}
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/admin.directive.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/admin.directive.ts
index 6bb5ef3f..f8cd47ca 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/admin.directive.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/admin.directive.ts
@@ -16,15 +16,17 @@ import { OpenViduComponentsConfigService } from '../../services/config/directive
standalone: false
})
export class AdminDashboardRecordingsListDirective implements AfterViewInit, OnDestroy {
-
@Input() set recordingsList(value: RecordingInfo[]) {
this.recordingsValue = value;
this.update(this.recordingsValue);
}
- recordingsValue: RecordingInfo [] = [];
+ recordingsValue: RecordingInfo[] = [];
- constructor(public elementRef: ElementRef, private libService: OpenViduComponentsConfigService) {}
+ constructor(
+ public elementRef: ElementRef,
+ private libService: OpenViduComponentsConfigService
+ ) {}
ngAfterViewInit() {
this.update(this.recordingsValue);
@@ -38,9 +40,7 @@ export class AdminDashboardRecordingsListDirective implements AfterViewInit, OnD
}
update(value: RecordingInfo[]) {
- if (this.libService.getAdminRecordingsList() !== value) {
- this.libService.setAdminRecordingsList(value);
- }
+ this.libService.updateAdminConfig({ recordingsList: value });
}
}
@@ -58,7 +58,6 @@ export class AdminDashboardRecordingsListDirective implements AfterViewInit, OnD
standalone: false
})
export class AdminDashboardTitleDirective implements AfterViewInit, OnDestroy {
-
@Input() set navbarTitle(value: string) {
this.navbarTitleValue = value;
this.update(this.navbarTitleValue);
@@ -66,7 +65,10 @@ export class AdminDashboardTitleDirective implements AfterViewInit, OnDestroy {
navbarTitleValue: string = 'OpenVidu Dashboard';
- constructor(public elementRef: ElementRef, private libService: OpenViduComponentsConfigService) {}
+ constructor(
+ public elementRef: ElementRef,
+ private libService: OpenViduComponentsConfigService
+ ) {}
ngAfterViewInit() {
this.update(this.navbarTitleValue);
@@ -80,13 +82,10 @@ export class AdminDashboardTitleDirective implements AfterViewInit, OnDestroy {
}
update(value: any) {
- if (this.libService.getAdminDashboardTitle() !== value) {
- this.libService.setAdminDashboardTitle(value);
- }
+ this.libService.updateAdminConfig({ dashboardTitle: value });
}
}
-
/**
* The **navbarTitle** directive allows customize the title of the navbar in {@link AdminLoginComponent}.
*
@@ -101,7 +100,6 @@ export class AdminDashboardTitleDirective implements AfterViewInit, OnDestroy {
standalone: false
})
export class AdminLoginTitleDirective implements AfterViewInit, OnDestroy {
-
@Input() set navbarTitle(value: any) {
this.navbarTitleValue = value;
this.update(this.navbarTitleValue);
@@ -109,7 +107,10 @@ export class AdminLoginTitleDirective implements AfterViewInit, OnDestroy {
navbarTitleValue: any = null;
- constructor(public elementRef: ElementRef, private libService: OpenViduComponentsConfigService) {}
+ constructor(
+ public elementRef: ElementRef,
+ private libService: OpenViduComponentsConfigService
+ ) {}
ngAfterViewInit() {
this.update(this.navbarTitleValue);
@@ -123,14 +124,10 @@ export class AdminLoginTitleDirective implements AfterViewInit, OnDestroy {
}
update(value: any) {
- if (this.libService.getAdminLoginTitle() !== value) {
- this.libService.setAdminLoginTitle(value);
- }
+ this.libService.updateAdminConfig({ loginTitle: value });
}
}
-
-
/**
* The **error** directive allows show the authentication error in {@link AdminLoginComponent}.
*
@@ -140,12 +137,11 @@ export class AdminLoginTitleDirective implements AfterViewInit, OnDestroy {
*
*
*/
- @Directive({
+@Directive({
selector: 'ov-admin-login[error]',
standalone: false
})
export class AdminLoginErrorDirective implements AfterViewInit, OnDestroy {
-
@Input() set error(value: any) {
this.errorValue = value;
this.update(this.errorValue);
@@ -153,7 +149,10 @@ export class AdminLoginErrorDirective implements AfterViewInit, OnDestroy {
errorValue: any = null;
- constructor(public elementRef: ElementRef, private libService: OpenViduComponentsConfigService) {}
+ constructor(
+ public elementRef: ElementRef,
+ private libService: OpenViduComponentsConfigService
+ ) {}
ngAfterViewInit() {
this.update(this.errorValue);
@@ -167,9 +166,6 @@ export class AdminLoginErrorDirective implements AfterViewInit, OnDestroy {
}
update(value: any) {
- if (this.libService.getAdminLoginError() !== value) {
- this.libService.setAdminLoginError(value);
- }
+ this.libService.updateAdminConfig({ loginError: value });
}
}
-
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/internals.directive.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/internals.directive.ts
index 6caecbe5..b108a5a6 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/internals.directive.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/internals.directive.ts
@@ -122,7 +122,7 @@ export class ToolbarBrandingLogoDirective implements AfterViewInit, OnDestroy {
}
private update(value: string) {
- this.libService.setBrandingLogo(value);
+ this.libService.updateToolbarConfig({ brandingLogo: value });
}
}
@@ -158,7 +158,7 @@ export class PrejoinDisplayParticipantName implements OnDestroy {
}
private update(value: boolean) {
- this.libService.setPrejoinDisplayParticipantName(value);
+ this.libService.updateGeneralConfig({ prejoinDisplayParticipantName: value });
}
}
@@ -213,7 +213,7 @@ export class RecordingActivityReadOnlyDirective implements OnDestroy {
* @ignore
*/
update(value: boolean) {
- this.libService.setRecordingActivityReadOnly(value);
+ this.libService.updateRecordingActivityConfig({ readOnly: value });
}
}
@@ -239,7 +239,7 @@ export class RecordingActivityShowControlsDirective implements OnDestroy {
/**
* @ignore
*/
- @Input() set recordingActivityShowControls(value: { play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean }) {
+ @Input() set recordingActivityShowControls(value: { play: boolean; download: boolean; delete: boolean; externalView: boolean }) {
this.update(value);
}
@@ -268,8 +268,8 @@ export class RecordingActivityShowControlsDirective implements OnDestroy {
/**
* @ignore
*/
- update(value: { play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean }) {
- this.libService.setRecordingActivityShowControls(value);
+ update(value: { play: boolean; download: boolean; delete: boolean; externalView: boolean }) {
+ this.libService.updateRecordingActivityConfig({ showControls: value });
}
}
@@ -334,9 +334,7 @@ export class ToolbarViewRecordingsButtonDirective implements AfterViewInit, OnDe
}
private update(value: boolean) {
- if (this.libService.getToolbarViewRecordingsButton() !== value) {
- this.libService.setToolbarViewRecordingsButton(value);
- }
+ this.libService.updateToolbarConfig({ viewRecordings: value });
}
}
@@ -381,7 +379,7 @@ export class StartStopRecordingButtonsDirective implements OnDestroy {
}
private update(value: boolean) {
- this.libService.setRecordingActivityStartStopRecordingButton(value);
+ this.libService.updateRecordingActivityConfig({ startStopButton: value });
}
}
@@ -427,7 +425,7 @@ export class RecordingActivityViewRecordingsButtonDirective implements AfterView
}
private update(value: boolean) {
- this.libService.setRecordingActivityViewRecordingsButton(value);
+ this.libService.updateRecordingActivityConfig({ viewRecordingsButton: value });
}
}
@@ -473,6 +471,6 @@ export class RecordingActivityShowRecordingsListDirective implements AfterViewIn
}
private update(value: boolean) {
- this.libService.setRecordingActivityShowRecordingsList(value);
+ this.libService.updateRecordingActivityConfig({ showRecordingsList: value });
}
-}
\ No newline at end of file
+}
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/participant-panel-item.directive.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/participant-panel-item.directive.ts
index 26480467..31876575 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/participant-panel-item.directive.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/participant-panel-item.directive.ts
@@ -32,7 +32,10 @@ export class ParticipantPanelItemMuteButtonDirective implements AfterViewInit, O
muteValue: boolean = true;
- constructor(public elementRef: ElementRef, private libService: OpenViduComponentsConfigService) {}
+ constructor(
+ public elementRef: ElementRef,
+ private libService: OpenViduComponentsConfigService
+ ) {}
ngAfterViewInit() {
this.update(this.muteValue);
@@ -46,8 +49,6 @@ export class ParticipantPanelItemMuteButtonDirective implements AfterViewInit, O
}
update(value: boolean) {
- if (this.libService.showParticipantItemMuteButton() !== value) {
- this.libService.setParticipantItemMuteButton(value);
- }
+ this.libService.updateStreamConfig({ participantItemMuteButton: value });
}
-}
\ No newline at end of file
+}
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/stream.directive.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/stream.directive.ts
index 14040f6e..a0418699 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/stream.directive.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/stream.directive.ts
@@ -46,9 +46,7 @@ export class StreamDisplayParticipantNameDirective implements AfterViewInit, OnD
}
update(value: boolean) {
- if (this.libService.isParticipantNameDisplayed() !== value) {
- this.libService.setDisplayParticipantName(value);
- }
+ this.libService.updateStreamConfig({ displayParticipantName: value });
}
clear() {
@@ -100,9 +98,7 @@ export class StreamDisplayAudioDetectionDirective implements AfterViewInit, OnDe
}
update(value: boolean) {
- if (this.libService.isAudioDetectionDisplayed() !== value) {
- this.libService.setDisplayAudioDetection(value);
- }
+ this.libService.updateStreamConfig({ displayAudioDetection: value });
}
clear() {
this.update(true);
@@ -154,9 +150,7 @@ export class StreamVideoControlsDirective implements AfterViewInit, OnDestroy {
}
update(value: boolean) {
- if (this.libService.showStreamVideoControls() !== value) {
- this.libService.setStreamVideoControls(value);
- }
+ this.libService.updateStreamConfig({ videoControls: value });
}
clear() {
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/toolbar.directive.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/toolbar.directive.ts
index c7495a3f..d8a27b80 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/toolbar.directive.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/toolbar.directive.ts
@@ -62,9 +62,7 @@ export class ToolbarCameraButtonDirective implements AfterViewInit, OnDestroy {
}
private update(value: boolean) {
- if (this.libService.showCameraButton() !== value) {
- this.libService.setCameraButton(value);
- }
+ this.libService.updateToolbarConfig({ camera: value });
}
}
@@ -128,9 +126,7 @@ export class ToolbarMicrophoneButtonDirective implements AfterViewInit, OnDestro
}
private update(value: boolean) {
- if (this.libService.showMicrophoneButton() !== value) {
- this.libService.setMicrophoneButton(value);
- }
+ this.libService.updateToolbarConfig({ microphone: value });
}
}
@@ -194,9 +190,7 @@ export class ToolbarScreenshareButtonDirective implements AfterViewInit, OnDestr
}
private update(value: boolean) {
- if (this.libService.showScreenshareButton() !== value) {
- this.libService.setScreenshareButton(value);
- }
+ this.libService.updateToolbarConfig({ screenshare: value });
}
}
@@ -257,9 +251,7 @@ export class ToolbarRecordingButtonDirective implements AfterViewInit, OnDestroy
}
private update(value: boolean) {
- if (this.libService.showRecordingButton() !== value) {
- this.libService.setRecordingButton(value);
- }
+ this.libService.updateToolbarConfig({ recording: value });
}
}
@@ -321,9 +313,7 @@ export class ToolbarBroadcastingButtonDirective implements AfterViewInit, OnDest
}
private update(value: boolean) {
- if (this.libService.showBroadcastingButton() !== value) {
- this.libService.setBroadcastingButton(value);
- }
+ this.libService.setBroadcastingButton(value);
}
}
@@ -384,9 +374,7 @@ export class ToolbarFullscreenButtonDirective implements AfterViewInit, OnDestro
}
private update(value: boolean) {
- if (this.libService.showFullscreenButton() !== value) {
- this.libService.setFullscreenButton(value);
- }
+ this.libService.updateToolbarConfig({ fullscreen: value });
}
}
@@ -447,9 +435,7 @@ export class ToolbarBackgroundEffectsButtonDirective implements AfterViewInit, O
}
private update(value: boolean) {
- if (this.libService.showBackgroundEffectsButton() !== value) {
- this.libService.setBackgroundEffectsButton(value);
- }
+ this.libService.updateToolbarConfig({ backgroundEffects: value });
}
}
@@ -569,9 +555,7 @@ export class ToolbarSettingsButtonDirective implements AfterViewInit, OnDestroy
}
private update(value: boolean) {
- if (this.libService.showToolbarSettingsButton() !== value) {
- this.libService.setToolbarSettingsButton(value);
- }
+ this.libService.updateToolbarConfig({ settings: value });
}
}
@@ -633,9 +617,7 @@ export class ToolbarLeaveButtonDirective implements AfterViewInit, OnDestroy {
}
private update(value: boolean) {
- if (this.libService.showLeaveButton() !== value) {
- this.libService.setLeaveButton(value);
- }
+ this.libService.updateToolbarConfig({ leave: value });
}
}
@@ -698,9 +680,7 @@ export class ToolbarParticipantsPanelButtonDirective implements AfterViewInit, O
}
private update(value: boolean) {
- if (this.libService.showParticipantsPanelButton() !== value) {
- this.libService.setParticipantsPanelButton(value);
- }
+ this.libService.updateToolbarConfig({ participantsPanel: value });
}
}
@@ -761,9 +741,7 @@ export class ToolbarChatPanelButtonDirective implements AfterViewInit, OnDestroy
}
private update(value: boolean) {
- if (this.libService.showChatPanelButton() !== value) {
- this.libService.setChatPanelButton(value);
- }
+ this.libService.updateToolbarConfig({ chatPanel: value });
}
}
@@ -824,9 +802,7 @@ export class ToolbarActivitiesPanelButtonDirective implements AfterViewInit, OnD
}
private update(value: boolean) {
- if (this.libService.showActivitiesPanelButton() !== value) {
- this.libService.setActivitiesPanelButton(value);
- }
+ this.libService.updateToolbarConfig({ activitiesPanel: value });
}
}
@@ -888,9 +864,7 @@ export class ToolbarDisplayRoomNameDirective implements AfterViewInit, OnDestroy
}
private update(value: boolean) {
- if (this.libService.showRoomName() !== value) {
- this.libService.setDisplayRoomName(value);
- }
+ this.libService.updateToolbarConfig({ displayRoomName: value });
}
}
@@ -952,9 +926,7 @@ export class ToolbarDisplayLogoDirective implements AfterViewInit, OnDestroy {
}
private update(value: boolean) {
- if (this.libService.showLogo() !== value) {
- this.libService.setDisplayLogo(value);
- }
+ this.libService.updateToolbarConfig({ displayLogo: value });
}
}
@@ -1009,8 +981,6 @@ export class ToolbarAdditionalButtonsPossitionDirective implements AfterViewInit
}
private update(value: ToolbarAdditionalButtonsPosition) {
- if (this.libService.getToolbarAdditionalButtonsPosition() !== value) {
- this.libService.setToolbarAdditionalButtonsPosition(value);
- }
+ this.libService.updateToolbarConfig({ additionalButtonsPosition: value });
}
}
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/videoconference.directive.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/videoconference.directive.ts
index 48ff7022..4a8036b4 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/videoconference.directive.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/api/videoconference.directive.ts
@@ -55,7 +55,7 @@ export class LivekitUrlDirective implements OnDestroy {
* @ignore
*/
update(value: string) {
- this.libService.setLivekitUrl(value);
+ this.libService.updateGeneralConfig({ livekitUrl: value });
}
}
@@ -108,7 +108,7 @@ export class TokenDirective implements OnDestroy {
* @ignore
*/
update(value: string) {
- this.libService.setToken(value);
+ this.libService.updateGeneralConfig({ token: value });
}
}
@@ -160,7 +160,7 @@ export class TokenErrorDirective implements OnDestroy {
* @ignore
*/
update(value: any) {
- this.libService.setTokenError(value);
+ this.libService.updateGeneralConfig({ tokenError: value });
}
}
@@ -212,9 +212,7 @@ export class MinimalDirective implements OnDestroy {
* @ignore
*/
update(value: boolean) {
- if (this.libService.isMinimal() !== value) {
- this.libService.setMinimal(value);
- }
+ this.libService.updateGeneralConfig({ minimal: value });
}
}
@@ -538,7 +536,7 @@ export class ParticipantNameDirective implements AfterViewInit, OnDestroy {
* @ignore
*/
update(value: string) {
- if (value) this.libService.setParticipantName(value);
+ if (value) this.libService.updateGeneralConfig({ participantName: value });
}
}
@@ -590,9 +588,7 @@ export class PrejoinDirective implements OnDestroy {
* @ignore
*/
update(value: boolean) {
- if (this.libService.showPrejoin() !== value) {
- this.libService.setPrejoin(value);
- }
+ this.libService.updateGeneralConfig({ prejoin: value });
}
}
@@ -663,7 +659,7 @@ export class VideoEnabledDirective implements OnDestroy {
// Ensure libService state is consistent with the final enabled state
if (this.libService.isVideoEnabled() !== finalEnabledState) {
- this.libService.setVideoEnabled(finalEnabledState);
+ this.libService.updateStreamConfig({ videoEnabled: finalEnabledState });
}
}
}
@@ -731,7 +727,7 @@ export class AudioEnabledDirective implements OnDestroy {
this.storageService.setMicrophoneEnabled(finalEnabledState);
if (this.libService.isAudioEnabled() !== enabled) {
- this.libService.setAudioEnabled(enabled);
+ this.libService.updateStreamConfig({ audioEnabled: enabled });
}
}
}
@@ -785,7 +781,7 @@ export class ShowDisconnectionDialogDirective implements OnDestroy {
*/
update(value: boolean) {
if (this.libService.getShowDisconnectionDialog() !== value) {
- this.libService.setShowDisconnectionDialog(value);
+ this.libService.updateGeneralConfig({ showDisconnectionDialog: value });
}
}
}
@@ -857,6 +853,6 @@ export class RecordingStreamBaseUrlDirective implements AfterViewInit, OnDestroy
* @ignore
*/
update(value: string) {
- if (value) this.libService.setRecordingStreamBaseUrl(value);
+ if (value) this.libService.updateGeneralConfig({ recordingStreamBaseUrl: value });
}
}
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/config/directive-config.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/config/directive-config.service.ts
index 95d3f0ed..a4e6bcff 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/config/directive-config.service.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/config/directive-config.service.ts
@@ -1,9 +1,101 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
+import { distinctUntilChanged, shareReplay, map } from 'rxjs/operators';
import { RecordingInfo } from '../../models/recording.model';
import { ToolbarAdditionalButtonsPosition } from '../../models/toolbar.model';
import { ParticipantModel } from '../../models/participant.model';
+/**
+ * Configuration item for the service
+ */
+interface ConfigItem {
+ subject: BehaviorSubject;
+ observable$: Observable;
+}
+
+/**
+ * Recording activity controls configuration
+ */
+interface RecordingControls {
+ play: boolean;
+ download: boolean;
+ delete: boolean;
+ externalView: boolean;
+}
+
+/**
+ * Toolbar configuration grouped by domain
+ */
+interface ToolbarConfig {
+ camera: boolean;
+ microphone: boolean;
+ screenshare: boolean;
+ fullscreen: boolean;
+ captions: boolean;
+ settings: boolean;
+ leave: boolean;
+ participantsPanel: boolean;
+ chatPanel: boolean;
+ activitiesPanel: boolean;
+ displayRoomName: boolean;
+ displayLogo: boolean;
+ backgroundEffects: boolean;
+ recording: boolean;
+ viewRecordings: boolean;
+ broadcasting: boolean;
+ brandingLogo: string;
+ additionalButtonsPosition: ToolbarAdditionalButtonsPosition;
+}
+
+/**
+ * Stream/Video configuration
+ */
+interface StreamConfig {
+ videoEnabled: boolean;
+ audioEnabled: boolean;
+ displayParticipantName: boolean;
+ displayAudioDetection: boolean;
+ videoControls: boolean;
+ participantItemMuteButton: boolean;
+}
+
+/**
+ * Recording activity configuration
+ */
+interface RecordingActivityConfig {
+ enabled: boolean;
+ readOnly: boolean;
+ showControls: RecordingControls;
+ startStopButton: boolean;
+ viewRecordingsButton: boolean;
+ showRecordingsList: boolean;
+}
+
+/**
+ * Admin dashboard configuration
+ */
+interface AdminConfig {
+ recordingsList: RecordingInfo[];
+ loginError: any;
+ loginTitle: string;
+ dashboardTitle: string;
+}
+
+/**
+ * General application configuration
+ */
+interface GeneralConfig {
+ token: string;
+ livekitUrl: string;
+ tokenError: any;
+ minimal: boolean;
+ participantName: string;
+ prejoin: boolean;
+ prejoinDisplayParticipantName: boolean;
+ showDisconnectionDialog: boolean;
+ recordingStreamBaseUrl: string;
+}
+
/**
* @internal
*/
@@ -11,530 +103,476 @@ import { ParticipantModel } from '../../models/participant.model';
providedIn: 'root'
})
export class OpenViduComponentsConfigService {
- private token = >new BehaviorSubject('');
- token$: Observable;
+ /**
+ * Helper method to create a configuration item with BehaviorSubject and Observable
+ */
+ private createConfigItem(initialValue: T): ConfigItem {
+ const subject = new BehaviorSubject(initialValue);
+ const observable$ = subject.asObservable().pipe(distinctUntilChanged(), shareReplay(1));
+ return { subject, observable$ };
+ }
- private livekitUrl = >new BehaviorSubject('');
- livekitUrl$: Observable;
+ /**
+ * Helper method for array configurations with optimized comparison
+ */
+ private createArrayConfigItem(initialValue: T[]): ConfigItem {
+ const subject = new BehaviorSubject(initialValue);
+ const observable$ = subject.asObservable().pipe(
+ distinctUntilChanged((prev, curr) => {
+ if (prev.length !== curr.length) return false;
+ return prev.every((item, index) => this.deepEqual(item, curr[index]));
+ }),
+ shareReplay(1)
+ );
+ return { subject, observable$ };
+ }
- private tokenError = >new BehaviorSubject(null);
- tokenError$: Observable;
- private minimal = >new BehaviorSubject(false);
- minimal$: Observable;
- private participantName = >new BehaviorSubject('');
- participantName$: Observable;
- private prejoin = >new BehaviorSubject(true);
- prejoin$: Observable;
- private prejoinDisplayParticipantName = >new BehaviorSubject(true);
- prejoinDisplayParticipantName$: Observable;
+ /**
+ * Helper method for RecordingControls with specific comparison
+ */
+ private createRecordingControlsConfigItem(initialValue: RecordingControls): ConfigItem {
+ const subject = new BehaviorSubject(initialValue);
+ const observable$ = subject.asObservable().pipe(
+ distinctUntilChanged(
+ (prev, curr) =>
+ prev.play === curr.play &&
+ prev.download === curr.download &&
+ prev.delete === curr.delete &&
+ prev.externalView === curr.externalView
+ ),
+ shareReplay(1)
+ );
+ return { subject, observable$ };
+ }
- private videoEnabled = >new BehaviorSubject(true);
- videoEnabled$: Observable;
- private audioEnabled = >new BehaviorSubject(true);
- audioEnabled$: Observable;
+ /**
+ * Helper method for ToolbarConfig with specific comparison
+ */
+ private createToolbarConfigItem(initialValue: ToolbarConfig): ConfigItem {
+ const subject = new BehaviorSubject(initialValue);
+ const observable$ = subject.asObservable().pipe(
+ distinctUntilChanged((prev, curr) => this.compareToolbarConfig(prev, curr)),
+ shareReplay(1)
+ );
+ return { subject, observable$ };
+ }
- private showDisconnectionDialog = >new BehaviorSubject(true);
- showDisconnectionDialog$: Observable;
+ /**
+ * Helper method for StreamConfig with specific comparison
+ */
+ private createStreamConfigItem(initialValue: StreamConfig): ConfigItem {
+ const subject = new BehaviorSubject(initialValue);
+ const observable$ = subject.asObservable().pipe(
+ distinctUntilChanged((prev, curr) => this.compareStreamConfig(prev, curr)),
+ shareReplay(1)
+ );
+ return { subject, observable$ };
+ }
- private recordingStreamBaseUrl = >new BehaviorSubject('call/api/recordings');
- recordingStreamBaseUrl$: Observable;
+ /**
+ * Helper method for RecordingActivityConfig with specific comparison
+ */
+ private createRecordingActivityConfigItem(initialValue: RecordingActivityConfig): ConfigItem {
+ const subject = new BehaviorSubject(initialValue);
+ const observable$ = subject.asObservable().pipe(
+ distinctUntilChanged((prev, curr) => this.compareRecordingActivityConfig(prev, curr)),
+ shareReplay(1)
+ );
+ return { subject, observable$ };
+ }
- //Toolbar settings
- private cameraButton = >new BehaviorSubject(true);
- cameraButton$: Observable;
+ /**
+ * Helper method for AdminConfig with specific comparison
+ */
+ private createAdminConfigItem(initialValue: AdminConfig): ConfigItem {
+ const subject = new BehaviorSubject(initialValue);
+ const observable$ = subject.asObservable().pipe(
+ distinctUntilChanged((prev, curr) => this.compareAdminConfig(prev, curr)),
+ shareReplay(1)
+ );
+ return { subject, observable$ };
+ }
- private microphoneButton = >new BehaviorSubject(true);
- microphoneButton$: Observable;
+ /**
+ * Helper method for GeneralConfig with specific comparison
+ */
+ private createGeneralConfigItem(initialValue: GeneralConfig): ConfigItem {
+ const subject = new BehaviorSubject(initialValue);
+ const observable$ = subject.asObservable().pipe(
+ distinctUntilChanged((prev, curr) => this.compareGeneralConfig(prev, curr)),
+ shareReplay(1)
+ );
+ return { subject, observable$ };
+ }
- private screenshareButton = >new BehaviorSubject(true);
- screenshareButton$: Observable;
+ /**
+ * Optimized deep equality check
+ */
+ private deepEqual(a: any, b: any): boolean {
+ if (a === b) return true;
+ if (a == null || b == null) return a === b;
+ if (typeof a !== typeof b) return false;
+ if (typeof a !== 'object') return a === b;
- private fullscreenButton = >new BehaviorSubject(true);
- fullscreenButton$: Observable;
+ const keysA = Object.keys(a);
+ const keysB = Object.keys(b);
+ if (keysA.length !== keysB.length) return false;
- private captionsButton = >new BehaviorSubject(true);
- captionsButton$: Observable;
+ return keysA.every((key) => this.deepEqual(a[key], b[key]));
+ }
- private toolbarSettingsButton = >new BehaviorSubject(true);
- toolbarSettingsButton$: Observable;
+ /**
+ * Compare ToolbarConfig efficiently
+ */
+ private compareToolbarConfig(prev: ToolbarConfig, curr: ToolbarConfig): boolean {
+ return (
+ prev.camera === curr.camera &&
+ prev.microphone === curr.microphone &&
+ prev.screenshare === curr.screenshare &&
+ prev.fullscreen === curr.fullscreen &&
+ prev.captions === curr.captions &&
+ prev.settings === curr.settings &&
+ prev.leave === curr.leave &&
+ prev.participantsPanel === curr.participantsPanel &&
+ prev.chatPanel === curr.chatPanel &&
+ prev.activitiesPanel === curr.activitiesPanel &&
+ prev.displayRoomName === curr.displayRoomName &&
+ prev.displayLogo === curr.displayLogo &&
+ prev.backgroundEffects === curr.backgroundEffects &&
+ prev.recording === curr.recording &&
+ prev.viewRecordings === curr.viewRecordings &&
+ prev.broadcasting === curr.broadcasting &&
+ prev.brandingLogo === curr.brandingLogo &&
+ prev.additionalButtonsPosition === curr.additionalButtonsPosition
+ );
+ }
- private leaveButton = >new BehaviorSubject(true);
- leaveButton$: Observable;
+ /**
+ * Compare StreamConfig efficiently
+ */
+ private compareStreamConfig(prev: StreamConfig, curr: StreamConfig): boolean {
+ return (
+ prev.videoEnabled === curr.videoEnabled &&
+ prev.audioEnabled === curr.audioEnabled &&
+ prev.displayParticipantName === curr.displayParticipantName &&
+ prev.displayAudioDetection === curr.displayAudioDetection &&
+ prev.videoControls === curr.videoControls &&
+ prev.participantItemMuteButton === curr.participantItemMuteButton
+ );
+ }
- private participantsPanelButton = >new BehaviorSubject(true);
- participantsPanelButton$: Observable;
+ /**
+ * Compare RecordingActivityConfig efficiently
+ */
+ private compareRecordingActivityConfig(prev: RecordingActivityConfig, curr: RecordingActivityConfig): boolean {
+ return (
+ prev.enabled === curr.enabled &&
+ prev.readOnly === curr.readOnly &&
+ prev.startStopButton === curr.startStopButton &&
+ prev.viewRecordingsButton === curr.viewRecordingsButton &&
+ prev.showRecordingsList === curr.showRecordingsList &&
+ prev.showControls.play === curr.showControls.play &&
+ prev.showControls.download === curr.showControls.download &&
+ prev.showControls.delete === curr.showControls.delete &&
+ prev.showControls.externalView === curr.showControls.externalView
+ );
+ }
- private chatPanelButton = >new BehaviorSubject(true);
- chatPanelButton$: Observable;
+ /**
+ * Compare AdminConfig efficiently
+ */
+ private compareAdminConfig(prev: AdminConfig, curr: AdminConfig): boolean {
+ return (
+ prev.loginError === curr.loginError &&
+ prev.loginTitle === curr.loginTitle &&
+ prev.dashboardTitle === curr.dashboardTitle &&
+ prev.recordingsList.length === curr.recordingsList.length &&
+ prev.recordingsList.every((item, index) => this.deepEqual(item, curr.recordingsList[index]))
+ );
+ }
- private activitiesPanelButton = >new BehaviorSubject(true);
- activitiesPanelButton$: Observable;
+ /**
+ * Compare GeneralConfig efficiently
+ */
+ private compareGeneralConfig(prev: GeneralConfig, curr: GeneralConfig): boolean {
+ return (
+ prev.token === curr.token &&
+ prev.livekitUrl === curr.livekitUrl &&
+ prev.tokenError === curr.tokenError &&
+ prev.minimal === curr.minimal &&
+ prev.participantName === curr.participantName &&
+ prev.prejoin === curr.prejoin &&
+ prev.prejoinDisplayParticipantName === curr.prejoinDisplayParticipantName &&
+ prev.showDisconnectionDialog === curr.showDisconnectionDialog &&
+ prev.recordingStreamBaseUrl === curr.recordingStreamBaseUrl
+ );
+ }
- private displayRoomName = >new BehaviorSubject(true);
- displayRoomName$: Observable;
+ // Grouped configuration items by domain
+ private generalConfig = this.createGeneralConfigItem({
+ token: '',
+ livekitUrl: '',
+ tokenError: null,
+ minimal: false,
+ participantName: '',
+ prejoin: true,
+ prejoinDisplayParticipantName: true,
+ showDisconnectionDialog: true,
+ recordingStreamBaseUrl: 'call/api/recordings'
+ });
- private brandingLogo = >new BehaviorSubject('');
- brandingLogo$: Observable;
+ private toolbarConfig = this.createToolbarConfigItem({
+ camera: true,
+ microphone: true,
+ screenshare: true,
+ fullscreen: true,
+ captions: true,
+ settings: true,
+ leave: true,
+ participantsPanel: true,
+ chatPanel: true,
+ activitiesPanel: true,
+ displayRoomName: true,
+ displayLogo: true,
+ backgroundEffects: true,
+ recording: true,
+ viewRecordings: false,
+ broadcasting: true,
+ brandingLogo: '',
+ additionalButtonsPosition: ToolbarAdditionalButtonsPosition.AFTER_MENU
+ });
- private displayLogo = >new BehaviorSubject(true);
- displayLogo$: Observable;
+ private streamConfig = this.createStreamConfigItem({
+ videoEnabled: true,
+ audioEnabled: true,
+ displayParticipantName: true,
+ displayAudioDetection: true,
+ videoControls: true,
+ participantItemMuteButton: true
+ });
- private toolbarAdditionalButtonsPosition = >(
- new BehaviorSubject(ToolbarAdditionalButtonsPosition.AFTER_MENU)
+ private recordingActivityConfig = this.createRecordingActivityConfigItem({
+ enabled: true,
+ readOnly: false,
+ showControls: {
+ play: true,
+ download: true,
+ delete: true,
+ externalView: false
+ },
+ startStopButton: true,
+ viewRecordingsButton: false,
+ showRecordingsList: false
+ });
+
+ private adminConfig = this.createAdminConfigItem({
+ recordingsList: [],
+ loginError: null,
+ loginTitle: '',
+ dashboardTitle: ''
+ });
+
+ // Individual configs that don't fit into groups
+ private broadcastingActivityConfig = this.createConfigItem(true);
+ private layoutRemoteParticipantsConfig = this.createConfigItem(undefined);
+
+ // General observables
+ token$: Observable = this.generalConfig.observable$.pipe(map((config) => config.token));
+ livekitUrl$: Observable = this.generalConfig.observable$.pipe(map((config) => config.livekitUrl));
+ tokenError$: Observable = this.generalConfig.observable$.pipe(map((config) => config.tokenError));
+ minimal$: Observable = this.generalConfig.observable$.pipe(map((config) => config.minimal));
+ participantName$: Observable = this.generalConfig.observable$.pipe(map((config) => config.participantName));
+ prejoin$: Observable = this.generalConfig.observable$.pipe(map((config) => config.prejoin));
+ prejoinDisplayParticipantName$: Observable = this.generalConfig.observable$.pipe(
+ map((config) => config.prejoinDisplayParticipantName)
);
- toolbarAdditionalButtonsPosition$: Observable;
+ showDisconnectionDialog$: Observable = this.generalConfig.observable$.pipe(map((config) => config.showDisconnectionDialog));
+ recordingStreamBaseUrl$: Observable = this.generalConfig.observable$.pipe(map((config) => config.recordingStreamBaseUrl));
- private displayParticipantName = >new BehaviorSubject(true);
- displayParticipantName$: Observable;
- private displayAudioDetection = >new BehaviorSubject(true);
- displayAudioDetection$: Observable;
- private streamVideoControls = >new BehaviorSubject(true);
- streamVideoControls$: Observable;
- private participantItemMuteButton = >new BehaviorSubject(true);
- participantItemMuteButton$: Observable;
- private backgroundEffectsButton = >new BehaviorSubject(true);
- backgroundEffectsButton$: Observable;
- private recordingButton = >new BehaviorSubject(true);
- recordingButton$: Observable;
- private toolbarViewRecordingsButton = >new BehaviorSubject(false);
- toolbarViewRecordingsButton$: Observable;
- private broadcastingButton = >new BehaviorSubject(true);
- broadcastingButton$: Observable;
- private recordingActivity = >new BehaviorSubject(true);
- recordingActivity$: Observable;
- private broadcastingActivity = >new BehaviorSubject(true);
- broadcastingActivity$: Observable;
+ // Stream observables
+ videoEnabled$: Observable = this.streamConfig.observable$.pipe(map((config) => config.videoEnabled));
+ audioEnabled$: Observable = this.streamConfig.observable$.pipe(map((config) => config.audioEnabled));
+ displayParticipantName$: Observable = this.streamConfig.observable$.pipe(map((config) => config.displayParticipantName));
+ displayAudioDetection$: Observable = this.streamConfig.observable$.pipe(map((config) => config.displayAudioDetection));
+ streamVideoControls$: Observable = this.streamConfig.observable$.pipe(map((config) => config.videoControls));
+ participantItemMuteButton$: Observable = this.streamConfig.observable$.pipe(map((config) => config.participantItemMuteButton));
- // Recording activity configuration
- private recordingActivityReadOnly = >new BehaviorSubject(false);
- recordingActivityReadOnly$: Observable;
- private recordingActivityShowControls = >(
- new BehaviorSubject({ play: true, download: true, delete: true })
+ // Toolbar observables
+ cameraButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.camera));
+ microphoneButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.microphone));
+ screenshareButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.screenshare));
+ fullscreenButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.fullscreen));
+ captionsButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.captions));
+ toolbarSettingsButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.settings));
+ leaveButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.leave));
+ participantsPanelButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.participantsPanel));
+ chatPanelButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.chatPanel));
+ activitiesPanelButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.activitiesPanel));
+ displayRoomName$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.displayRoomName));
+ brandingLogo$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.brandingLogo));
+ displayLogo$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.displayLogo));
+ toolbarAdditionalButtonsPosition$: Observable = this.toolbarConfig.observable$.pipe(
+ map((config) => config.additionalButtonsPosition)
);
- recordingActivityShowControls$: Observable<{ play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean }>;
+ backgroundEffectsButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.backgroundEffects));
+ recordingButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.recording));
+ toolbarViewRecordingsButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.viewRecordings));
+ broadcastingButton$: Observable = this.toolbarConfig.observable$.pipe(map((config) => config.broadcasting));
- private recordingActivityStartStopRecordingButton = >new BehaviorSubject(true);
- recordingActivityStartStopRecordingButton$: Observable;
+ // Recording activity observables
+ recordingActivity$: Observable = this.recordingActivityConfig.observable$.pipe(map((config) => config.enabled));
+ recordingActivityReadOnly$: Observable = this.recordingActivityConfig.observable$.pipe(map((config) => config.readOnly));
+ recordingActivityShowControls$: Observable = this.recordingActivityConfig.observable$.pipe(
+ map((config) => config.showControls)
+ );
+ recordingActivityStartStopRecordingButton$: Observable = this.recordingActivityConfig.observable$.pipe(
+ map((config) => config.startStopButton)
+ );
+ recordingActivityViewRecordingsButton$: Observable = this.recordingActivityConfig.observable$.pipe(
+ map((config) => config.viewRecordingsButton)
+ );
+ recordingActivityShowRecordingsList$: Observable = this.recordingActivityConfig.observable$.pipe(
+ map((config) => config.showRecordingsList)
+ );
- private recordingActivityViewRecordingsButton = >new BehaviorSubject(false);
- recordingActivityViewRecordingsButton$: Observable;
+ // Admin observables
+ adminRecordingsList$: Observable = this.adminConfig.observable$.pipe(map((config) => config.recordingsList));
+ adminLoginError$: Observable = this.adminConfig.observable$.pipe(map((config) => config.loginError));
+ adminLoginTitle$: Observable = this.adminConfig.observable$.pipe(map((config) => config.loginTitle));
+ adminDashboardTitle$: Observable = this.adminConfig.observable$.pipe(map((config) => config.dashboardTitle));
- private recordingActivityShowRecordingsList = >new BehaviorSubject(false);
- recordingActivityShowRecordingsList$: Observable;
- // Admin
- private adminRecordingsList: BehaviorSubject = new BehaviorSubject([]);
- adminRecordingsList$: Observable;
- private adminLoginError = >new BehaviorSubject(null);
- private adminLoginTitle = >new BehaviorSubject('');
- private adminDashboardTitle = >new BehaviorSubject('');
- adminLoginTitle$: Observable;
- adminDashboardTitle$: Observable;
- adminLoginError$: Observable;
-
- // Internals
- private layoutRemoteParticipants: BehaviorSubject = new BehaviorSubject(undefined);
- layoutRemoteParticipants$: Observable;
+ // Individual observables that don't fit into groups
+ broadcastingActivity$: Observable = this.broadcastingActivityConfig.observable$;
+ layoutRemoteParticipants$: Observable = this.layoutRemoteParticipantsConfig.observable$;
constructor() {
- this.token$ = this.token.asObservable();
- this.livekitUrl$ = this.livekitUrl.asObservable();
- this.tokenError$ = this.tokenError.asObservable();
- this.minimal$ = this.minimal.asObservable();
- this.participantName$ = this.participantName.asObservable();
- this.prejoin$ = this.prejoin.asObservable();
- this.prejoinDisplayParticipantName$ = this.prejoinDisplayParticipantName.asObservable();
- this.videoEnabled$ = this.videoEnabled.asObservable();
- this.audioEnabled$ = this.audioEnabled.asObservable();
- this.recordingStreamBaseUrl$ = this.recordingStreamBaseUrl.asObservable();
- //Toolbar observables
- this.cameraButton$ = this.cameraButton.asObservable();
- this.microphoneButton$ = this.microphoneButton.asObservable();
- this.screenshareButton$ = this.screenshareButton.asObservable();
- this.fullscreenButton$ = this.fullscreenButton.asObservable();
- this.backgroundEffectsButton$ = this.backgroundEffectsButton.asObservable();
- this.leaveButton$ = this.leaveButton.asObservable();
- this.participantsPanelButton$ = this.participantsPanelButton.asObservable();
- this.chatPanelButton$ = this.chatPanelButton.asObservable();
- this.activitiesPanelButton$ = this.activitiesPanelButton.asObservable();
- this.displayRoomName$ = this.displayRoomName.asObservable();
- this.displayLogo$ = this.displayLogo.asObservable();
- this.brandingLogo$ = this.brandingLogo.asObservable();
- this.recordingButton$ = this.recordingButton.asObservable();
- this.toolbarViewRecordingsButton$ = this.toolbarViewRecordingsButton.asObservable();
- this.broadcastingButton$ = this.broadcastingButton.asObservable();
- this.toolbarSettingsButton$ = this.toolbarSettingsButton.asObservable();
- this.captionsButton$ = this.captionsButton.asObservable();
- this.toolbarAdditionalButtonsPosition$ = this.toolbarAdditionalButtonsPosition.asObservable();
- //Stream observables
- this.displayParticipantName$ = this.displayParticipantName.asObservable();
- this.displayAudioDetection$ = this.displayAudioDetection.asObservable();
- this.streamVideoControls$ = this.streamVideoControls.asObservable();
- // Participant item observables
- this.participantItemMuteButton$ = this.participantItemMuteButton.asObservable();
- // Recording activity observables
- this.recordingActivity$ = this.recordingActivity.asObservable();
- this.recordingActivityReadOnly$ = this.recordingActivityReadOnly.asObservable();
- this.recordingActivityShowControls$ = this.recordingActivityShowControls.asObservable();
- this.recordingActivityStartStopRecordingButton$ = this.recordingActivityStartStopRecordingButton.asObservable();
- this.recordingActivityViewRecordingsButton$ = this.recordingActivityViewRecordingsButton.asObservable();
- this.recordingActivityShowRecordingsList$ = this.recordingActivityShowRecordingsList.asObservable();
- // Broadcasting activity
- this.broadcastingActivity$ = this.broadcastingActivity.asObservable();
- // Admin dashboard
- this.adminRecordingsList$ = this.adminRecordingsList.asObservable();
- this.adminLoginError$ = this.adminLoginError.asObservable();
- this.adminLoginTitle$ = this.adminLoginTitle.asObservable();
- this.adminDashboardTitle$ = this.adminDashboardTitle.asObservable();
- // Internals
- this.layoutRemoteParticipants$ = this.layoutRemoteParticipants.asObservable();
+ // Constructor no longer needed - all observables are initialized directly
}
- setToken(token: string) {
- this.token.next(token);
+ // ============================================
+ // BATCH UPDATE METHODS
+ // ============================================
+
+ /**
+ * Update multiple general configuration properties at once
+ */
+ updateGeneralConfig(partialConfig: Partial): void {
+ const current = this.generalConfig.subject.getValue();
+ this.generalConfig.subject.next({ ...current, ...partialConfig });
}
- setLivekitUrl(livekitUrl: string) {
- this.livekitUrl.next(livekitUrl);
+ /**
+ * Update multiple toolbar configuration properties at once
+ */
+ updateToolbarConfig(partialConfig: Partial): void {
+ const current = this.toolbarConfig.subject.getValue();
+ this.toolbarConfig.subject.next({ ...current, ...partialConfig });
}
+ /**
+ * Update multiple stream configuration properties at once
+ */
+ updateStreamConfig(partialConfig: Partial): void {
+ const current = this.streamConfig.subject.getValue();
+ this.streamConfig.subject.next({ ...current, ...partialConfig });
+ }
+
+ /**
+ * Update multiple recording activity configuration properties at once
+ */
+ updateRecordingActivityConfig(partialConfig: Partial): void {
+ const current = this.recordingActivityConfig.subject.getValue();
+ this.recordingActivityConfig.subject.next({ ...current, ...partialConfig });
+ }
+
+ /**
+ * Update multiple admin configuration properties at once
+ */
+ updateAdminConfig(partialConfig: Partial): void {
+ const current = this.adminConfig.subject.getValue();
+ this.adminConfig.subject.next({ ...current, ...partialConfig });
+ }
+
+ /**
+ * Update recording controls specifically with batch support
+ */
+ updateRecordingControls(partialControls: Partial): void {
+ const current = this.recordingActivityConfig.subject.getValue();
+ const updatedControls = { ...current.showControls, ...partialControls };
+ this.updateRecordingActivityConfig({ showControls: updatedControls });
+ }
+
+ // ============================================
+ // DIRECT ACCESS METHODS (for internal use)
+ // ============================================
+
+ /**
+ * @internal
+ * Get current participant name directly
+ */
+ getCurrentParticipantName(): string {
+ return this.generalConfig.subject.getValue().participantName;
+ }
+
+ // ============================================
+ // INDIVIDUAL GETTER/SETTER METHODS
+ // ============================================
+
+ // General configuration methods
+
getLivekitUrl(): string {
- return this.livekitUrl.getValue();
- }
-
- setTokenError(error: any) {
- this.tokenError.next(error);
- }
-
- setMinimal(minimal: boolean) {
- this.minimal.next(minimal);
- }
-
- isMinimal(): boolean {
- return this.minimal.getValue();
- }
-
- setParticipantName(participantName: string) {
- this.participantName.next(participantName);
- }
-
- setPrejoin(prejoin: boolean) {
- this.prejoin.next(prejoin);
- }
-
- setPrejoinDisplayParticipantName(prejoinDisplayParticipantName: boolean) {
- this.prejoinDisplayParticipantName.next(prejoinDisplayParticipantName);
+ return this.generalConfig.subject.getValue().livekitUrl;
}
showPrejoin(): boolean {
- return this.prejoin.getValue();
- }
-
- setVideoEnabled(videoEnabled: boolean) {
- this.videoEnabled.next(videoEnabled);
- }
-
- isVideoEnabled(): boolean {
- return this.videoEnabled.getValue();
- }
-
- setAudioEnabled(audioEnabled: boolean) {
- this.audioEnabled.next(audioEnabled);
- }
-
- isAudioEnabled(): boolean {
- return this.audioEnabled.getValue();
+ return this.generalConfig.subject.getValue().prejoin;
}
getShowDisconnectionDialog(): boolean {
- return this.showDisconnectionDialog.getValue();
- }
-
- setShowDisconnectionDialog(showDisconnectionDialog: boolean) {
- this.showDisconnectionDialog.next(showDisconnectionDialog);
- }
-
- setRecordingStreamBaseUrl(recordingStreamBaseUrl: string) {
- this.recordingStreamBaseUrl.next(recordingStreamBaseUrl);
+ return this.generalConfig.subject.getValue().showDisconnectionDialog;
}
getRecordingStreamBaseUrl(): string {
- let baseUrl = this.recordingStreamBaseUrl.getValue();
+ let baseUrl = this.generalConfig.subject.getValue().recordingStreamBaseUrl;
// Add trailing slash if not present
baseUrl += baseUrl.endsWith('/') ? '' : '/';
return baseUrl;
}
- //Toolbar settings
+ // Stream configuration methods
- setCameraButton(cameraButton: boolean) {
- this.cameraButton.next(cameraButton);
+ isVideoEnabled(): boolean {
+ return this.streamConfig.subject.getValue().videoEnabled;
}
- showCameraButton(): boolean {
- return this.cameraButton.getValue();
+ isAudioEnabled(): boolean {
+ return this.streamConfig.subject.getValue().audioEnabled;
}
- setMicrophoneButton(microphoneButton: boolean) {
- this.microphoneButton.next(microphoneButton);
- }
-
- showMicrophoneButton(): boolean {
- return this.microphoneButton.getValue();
- }
-
- setScreenshareButton(screenshareButton: boolean) {
- this.screenshareButton.next(screenshareButton);
- }
-
- showScreenshareButton(): boolean {
- return this.screenshareButton.getValue();
- }
-
- setFullscreenButton(fullscreenButton: boolean) {
- this.fullscreenButton.next(fullscreenButton);
- }
-
- showFullscreenButton(): boolean {
- return this.fullscreenButton.getValue();
- }
-
- setCaptionsButton(captionsButton: boolean) {
- this.captionsButton.next(captionsButton);
- }
-
- showCaptionsButton(): boolean {
- return this.captionsButton.getValue();
- }
-
- setToolbarSettingsButton(toolbarSettingsButton: boolean) {
- this.toolbarSettingsButton.next(toolbarSettingsButton);
- }
-
- showToolbarSettingsButton(): boolean {
- return this.toolbarSettingsButton.getValue();
- }
-
- setLeaveButton(leaveButton: boolean) {
- this.leaveButton.next(leaveButton);
- }
-
- showLeaveButton(): boolean {
- return this.leaveButton.getValue();
- }
-
- setParticipantsPanelButton(participantsPanelButton: boolean) {
- this.participantsPanelButton.next(participantsPanelButton);
- }
-
- showParticipantsPanelButton(): boolean {
- return this.participantsPanelButton.getValue();
- }
-
- setChatPanelButton(chatPanelButton: boolean) {
- this.chatPanelButton.next(chatPanelButton);
- }
-
- showChatPanelButton(): boolean {
- return this.chatPanelButton.getValue();
- }
-
- setActivitiesPanelButton(activitiesPanelButton: boolean) {
- this.activitiesPanelButton.next(activitiesPanelButton);
- }
-
- showActivitiesPanelButton(): boolean {
- return this.activitiesPanelButton.getValue();
- }
-
- setDisplayRoomName(displayRoomName: boolean) {
- this.displayRoomName.next(displayRoomName);
- }
-
- setBrandingLogo(brandingLogo: string) {
- this.brandingLogo.next(brandingLogo);
- }
-
- showRoomName(): boolean {
- return this.displayRoomName.getValue();
- }
-
- setDisplayLogo(displayLogo: boolean) {
- this.displayLogo.next(displayLogo);
- }
-
- showLogo(): boolean {
- return this.displayLogo.getValue();
- }
- getToolbarAdditionalButtonsPosition(): ToolbarAdditionalButtonsPosition {
- return this.toolbarAdditionalButtonsPosition.getValue();
- }
-
- setToolbarAdditionalButtonsPosition(toolbarAdditionalButtonsPosition: ToolbarAdditionalButtonsPosition) {
- this.toolbarAdditionalButtonsPosition.next(toolbarAdditionalButtonsPosition);
- }
-
- setRecordingButton(recordingButton: boolean) {
- this.recordingButton.next(recordingButton);
- }
-
- showRecordingButton(): boolean {
- return this.recordingButton.getValue();
- }
-
- setToolbarViewRecordingsButton(toolbarViewRecordingsButton: boolean) {
- this.toolbarViewRecordingsButton.next(toolbarViewRecordingsButton);
- }
-
- getToolbarViewRecordingsButton(): boolean {
- return this.toolbarViewRecordingsButton.getValue();
- }
-
- showToolbarViewRecordingsButton(): boolean {
- return this.getToolbarViewRecordingsButton();
- }
+ // Toolbar configuration methods
setBroadcastingButton(broadcastingButton: boolean) {
- this.broadcastingButton.next(broadcastingButton);
- }
-
- showBroadcastingButton(): boolean {
- return this.broadcastingButton.getValue();
- }
-
- setRecordingActivity(recordingActivity: boolean) {
- this.recordingActivity.next(recordingActivity);
- }
-
- showRecordingActivity(): boolean {
- return this.recordingActivity.getValue();
- }
-
- setBroadcastingActivity(broadcastingActivity: boolean) {
- this.broadcastingActivity.next(broadcastingActivity);
- }
-
- showBroadcastingActivity(): boolean {
- return this.broadcastingActivity.getValue();
- }
-
- //Stream settings
- setDisplayParticipantName(displayParticipantName: boolean) {
- this.displayParticipantName.next(displayParticipantName);
- }
-
- isParticipantNameDisplayed(): boolean {
- return this.displayParticipantName.getValue();
- }
-
- setDisplayAudioDetection(displayAudioDetection: boolean) {
- this.displayAudioDetection.next(displayAudioDetection);
- }
-
- isAudioDetectionDisplayed(): boolean {
- return this.displayAudioDetection.getValue();
- }
-
- setStreamVideoControls(streamVideoControls: boolean) {
- this.streamVideoControls.next(streamVideoControls);
- }
-
- showStreamVideoControls(): boolean {
- return this.streamVideoControls.getValue();
- }
-
- setParticipantItemMuteButton(participantItemMuteButton: boolean) {
- this.participantItemMuteButton.next(participantItemMuteButton);
- }
-
- showParticipantItemMuteButton(): boolean {
- return this.participantItemMuteButton.getValue();
- }
-
- setBackgroundEffectsButton(backgroundEffectsButton: boolean) {
- this.backgroundEffectsButton.next(backgroundEffectsButton);
+ this.updateToolbarConfig({ broadcasting: broadcastingButton });
}
showBackgroundEffectsButton(): boolean {
- return this.backgroundEffectsButton.getValue();
+ return this.toolbarConfig.subject.getValue().backgroundEffects;
}
- // Admin dashboard
+ // Activity methods (these remain individual as they don't fit cleanly into toolbar config)
- setAdminRecordingsList(adminRecordingsList: RecordingInfo[]) {
- this.adminRecordingsList.next(adminRecordingsList);
- }
-
- getAdminRecordingsList(): RecordingInfo[] {
- return this.adminRecordingsList.getValue();
- }
-
- setAdminLoginError(adminLoginError: any) {
- this.adminLoginError.next(adminLoginError);
- }
-
- getAdminLoginError(): any {
- return this.adminLoginError.getValue();
- }
-
- getAdminLoginTitle(): string {
- return this.adminLoginTitle.getValue();
- }
-
- setAdminLoginTitle(title: string) {
- this.adminLoginTitle.next(title);
- }
-
- getAdminDashboardTitle(): string {
- return this.adminDashboardTitle.getValue();
- }
-
- setAdminDashboardTitle(title: string) {
- this.adminDashboardTitle.next(title);
- }
-
- isRecordingEnabled(): boolean {
- return this.recordingButton.getValue() && this.recordingActivity.getValue();
- }
-
- isBroadcastingEnabled(): boolean {
- return this.broadcastingButton.getValue() && this.broadcastingActivity.getValue();
+ setBroadcastingActivity(broadcastingActivity: boolean) {
+ this.broadcastingActivityConfig.subject.next(broadcastingActivity);
}
// Internals
setLayoutRemoteParticipants(participants: ParticipantModel[] | undefined) {
- this.layoutRemoteParticipants.next(participants);
+ this.layoutRemoteParticipantsConfig.subject.next(participants);
}
- // Recording Activity Configuration
- setRecordingActivityReadOnly(readOnly: boolean) {
- this.recordingActivityReadOnly.next(readOnly);
- }
-
- isRecordingActivityReadOnly(): boolean {
- return this.recordingActivityReadOnly.getValue();
- }
-
- setRecordingActivityShowControls(controls: { play?: boolean; download?: boolean; delete?: boolean }) {
- this.recordingActivityShowControls.next(controls);
- }
-
- getRecordingActivityShowControls(): { play?: boolean; download?: boolean; delete?: boolean } {
- return this.recordingActivityShowControls.getValue();
- }
-
- setRecordingActivityStartStopRecordingButton(show: boolean) {
- this.recordingActivityStartStopRecordingButton.next(show);
- }
-
- setRecordingActivityViewRecordingsButton(show: boolean) {
- this.recordingActivityViewRecordingsButton.next(show);
- }
-
- setRecordingActivityShowRecordingsList(show: boolean) {
- this.recordingActivityShowRecordingsList.next(show);
- }
+ // Recording Activity Configuration methods
showRecordingActivityRecordingsList(): boolean {
- return this.recordingActivityShowRecordingsList.getValue();
+ return this.recordingActivityConfig.subject.getValue().showRecordingsList;
}
-}
+}
\ No newline at end of file