From 1762c43769af400fb25a459cfb560885dcd3f46c Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Thu, 21 Aug 2025 19:01:51 +0200 Subject: [PATCH] ov-components: Refactor prejoin logic and improve observable handling in configuration service --- .../components/pre-join/pre-join.component.ts | 9 +-- .../participant-name-input.component.html | 2 +- .../videoconference.component.ts | 26 ++++--- .../config/directive-config.service.ts | 69 ++++++++++++++----- .../lib/services/openvidu/openvidu.service.ts | 27 ++++---- 5 files changed, 87 insertions(+), 46 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 e837e887..46a12085 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 @@ -10,7 +10,7 @@ import { Output } from '@angular/core'; import { animate, state, style, transition, trigger } from '@angular/animations'; -import { filter, Subject, takeUntil, tap } from 'rxjs'; +import { filter, Subject, take, takeUntil, tap } from 'rxjs'; import { ILogger } from '../../models/logger.model'; import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service'; import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service'; @@ -175,15 +175,12 @@ export class PreJoinComponent implements OnInit, OnDestroy { if (this.participantName?.trim()) { this.libService.updateGeneralConfig({ participantName: this.participantName.trim() }); - // Wait for the next tick to ensure the participant name propagates - // through the observable before emitting onReadyToJoin this.libService.participantName$ .pipe( - takeUntil(this.destroy$), filter((name) => name === this.participantName?.trim()), - tap(() => this.onReadyToJoin.emit()) + take(1) ) - .subscribe(); + .subscribe(() => this.onReadyToJoin.emit()); } else { // No participant name to set, emit immediately this.onReadyToJoin.emit(); diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/participant-name-input/participant-name-input.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/participant-name-input/participant-name-input.component.html index 1130f2db..a586a60e 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/participant-name-input/participant-name-input.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/participant-name-input/participant-name-input.component.html @@ -8,7 +8,7 @@ [(ngModel)]="name" autocomplete="off" [disabled]="!isPrejoinPage" - (change)="updateName()" + (input)="updateName()" (keypress)="eventKeyPress($event)" [placeholder]="'PREJOIN.NICKNAME' | translate" class="name-input-field" 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 421e7107..90264f54 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 @@ -836,7 +836,6 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { */ _onReadyToJoin(): void { this.log.d('Ready to join - initializing room and handling prejoin flow'); - try { // Mark that user has initiated the join process this.updateComponentState({ @@ -921,15 +920,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { this.openviduService.initializeAndSetToken(token, livekitUrl); this.log.d('Token has been successfully set. Room is ready to join'); - // Only update showPrejoin if user hasn't initiated join process yet - // This prevents prejoin from showing again after user clicked join - if (!this.hasUserInitiatedJoin()) { - this.updateComponentState({ - state: VideoconferenceState.PREJOIN_SHOWN, - isRoomReady: true, - showPrejoin: this.libService.showPrejoin() - }); - } else { + if (this.hasUserInitiatedJoin()) { // User has initiated join, proceed to hide prejoin and continue this.log.d('User has initiated join, hiding prejoin and proceeding'); this.updateComponentState({ @@ -937,6 +928,21 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { isRoomReady: true, showPrejoin: false }); + console.log(this.componentState); + console.warn( + this.componentState.isRoomReady && + !this.componentState.showPrejoin && + !this.componentState.isLoading && + !this.componentState.error?.hasError + ); + } else { + // Only update showPrejoin if user hasn't initiated join process yet + // This prevents prejoin from showing again after user clicked join + this.updateComponentState({ + state: VideoconferenceState.PREJOIN_SHOWN, + isRoomReady: true, + showPrejoin: this.libService.showPrejoin() + }); } } catch (error) { this.log.e('Error trying to set token', error); 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 fb1332c4..2b01d0ca 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 @@ -199,10 +199,7 @@ export class OpenViduComponentsConfigService { */ private createGeneralConfigItem(initialValue: GeneralConfig): ConfigItem { const subject = new BehaviorSubject(initialValue); - const observable$ = subject.asObservable().pipe( - distinctUntilChanged((prev, curr) => this.compareGeneralConfig(prev, curr)), - shareReplay(1) - ); + const observable$ = subject.asObservable(); return { subject, observable$ }; } @@ -297,7 +294,7 @@ export class OpenViduComponentsConfigService { * Compare GeneralConfig efficiently */ private compareGeneralConfig(prev: GeneralConfig, curr: GeneralConfig): boolean { - return ( + const equal = prev.token === curr.token && prev.livekitUrl === curr.livekitUrl && prev.tokenError === curr.tokenError && @@ -306,8 +303,12 @@ export class OpenViduComponentsConfigService { prev.prejoin === curr.prejoin && prev.prejoinDisplayParticipantName === curr.prejoinDisplayParticipantName && prev.showDisconnectionDialog === curr.showDisconnectionDialog && - prev.recordingStreamBaseUrl === curr.recordingStreamBaseUrl - ); + prev.recordingStreamBaseUrl === curr.recordingStreamBaseUrl; + + if (!equal) { + console.log('GeneralConfig cambió', { prev, curr }); + } + return equal; } // Grouped configuration items by domain @@ -380,17 +381,51 @@ export class OpenViduComponentsConfigService { 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) + token$: Observable = this.generalConfig.observable$.pipe( + map((config) => config.token), + distinctUntilChanged(), + shareReplay(1) + ); + livekitUrl$: Observable = this.generalConfig.observable$.pipe( + map((config) => config.livekitUrl), + distinctUntilChanged(), + shareReplay(1) + ); + tokenError$: Observable = this.generalConfig.observable$.pipe( + map((config) => config.tokenError), + distinctUntilChanged(), + shareReplay(1) + ); + minimal$: Observable = this.generalConfig.observable$.pipe( + map((config) => config.minimal), + distinctUntilChanged(), + shareReplay(1) + ); + participantName$: Observable = this.generalConfig.observable$.pipe( + map((config) => config.participantName), + distinctUntilChanged(), + shareReplay(1) + ); + prejoin$: Observable = this.generalConfig.observable$.pipe( + map((config) => config.prejoin), + distinctUntilChanged(), + shareReplay(1) + ); + prejoinDisplayParticipantName$: Observable = this.generalConfig.observable$.pipe( + map((config) => config.prejoinDisplayParticipantName), + distinctUntilChanged(), + shareReplay(1) + ); + showDisconnectionDialog$: Observable = this.generalConfig.observable$.pipe( + map((config) => config.showDisconnectionDialog), + distinctUntilChanged(), + shareReplay(1) + ); + recordingStreamBaseUrl$: Observable = this.generalConfig.observable$.pipe( + map((config) => config.recordingStreamBaseUrl), + distinctUntilChanged(), + shareReplay(1) ); - showDisconnectionDialog$: Observable = this.generalConfig.observable$.pipe(map((config) => config.showDisconnectionDialog)); - recordingStreamBaseUrl$: Observable = this.generalConfig.observable$.pipe(map((config) => config.recordingStreamBaseUrl)); // Stream observables videoEnabled$: Observable = this.streamConfig.observable$.pipe(map((config) => config.videoEnabled)); diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts index 2c82f26b..4f6ee3e7 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts @@ -177,16 +177,20 @@ export class OpenViduService { /** * @internal */ - initializeAndSetToken(token: string, livekitUrl: string): void { - const livekitData = this.extractLivekitData(token, livekitUrl); - this.livekitToken = token; - this.livekitUrl = livekitData.livekitUrl; + initializeAndSetToken(token: string, livekitUrl?: string): void { + const { livekitUrl: urlFromToken } = this.extractLivekitData(token); - if (!this.livekitUrl) { + this.livekitToken = token; + const url = livekitUrl || urlFromToken; + + if (!url) { this.log.e('LiveKit URL is not defined. Please, check the livekitUrl parameter of the VideoConferenceComponent'); throw new Error('Livekit URL is not defined'); } + this.livekitUrl = url; + // this.livekitRoomAdmin = !!livekitRoomAdmin; + // Initialize room if it doesn't exist yet // This ensures that getRoom() won't fail if token is set before onTokenRequested if (!this.room) { @@ -370,9 +374,8 @@ export class OpenViduService { * @throws Error if there is an error decoding and parsing the token. * @internal */ - private extractLivekitData(token: string, livekitUrl: string): { livekitUrl: string; livekitRoomAdmin: boolean } { + private extractLivekitData(token: string): { livekitUrl?: string; livekitRoomAdmin: boolean } { try { - const response = { livekitUrl, livekitRoomAdmin: false }; const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent( @@ -388,13 +391,13 @@ export class OpenViduService { const payload = JSON.parse(jsonPayload); if (payload?.metadata) { const tokenMetadata = JSON.parse(payload.metadata); - if (tokenMetadata.livekitUrl) { - response.livekitUrl = tokenMetadata.livekitUrl; - } - response.livekitRoomAdmin = tokenMetadata.roomAdmin; + return { + livekitUrl: tokenMetadata.livekitUrl, + livekitRoomAdmin: !!tokenMetadata.roomAdmin + }; } - return response; + return { livekitRoomAdmin: false }; } catch (error) { throw new Error('Error decoding and parsing token: ' + error); }