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