ov-components: Refactor prejoin logic and improve observable handling in configuration service

master
Carlos Santos 2025-08-21 19:01:51 +02:00
parent bcbf24b84d
commit 1762c43769
5 changed files with 87 additions and 46 deletions

View File

@ -10,7 +10,7 @@ import {
Output Output
} from '@angular/core'; } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations'; 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 { ILogger } from '../../models/logger.model';
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service'; import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service'; import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
@ -175,15 +175,12 @@ export class PreJoinComponent implements OnInit, OnDestroy {
if (this.participantName?.trim()) { if (this.participantName?.trim()) {
this.libService.updateGeneralConfig({ participantName: 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$ this.libService.participantName$
.pipe( .pipe(
takeUntil(this.destroy$),
filter((name) => name === this.participantName?.trim()), filter((name) => name === this.participantName?.trim()),
tap(() => this.onReadyToJoin.emit()) take(1)
) )
.subscribe(); .subscribe(() => this.onReadyToJoin.emit());
} else { } else {
// No participant name to set, emit immediately // No participant name to set, emit immediately
this.onReadyToJoin.emit(); this.onReadyToJoin.emit();

View File

@ -8,7 +8,7 @@
[(ngModel)]="name" [(ngModel)]="name"
autocomplete="off" autocomplete="off"
[disabled]="!isPrejoinPage" [disabled]="!isPrejoinPage"
(change)="updateName()" (input)="updateName()"
(keypress)="eventKeyPress($event)" (keypress)="eventKeyPress($event)"
[placeholder]="'PREJOIN.NICKNAME' | translate" [placeholder]="'PREJOIN.NICKNAME' | translate"
class="name-input-field" class="name-input-field"

View File

@ -836,7 +836,6 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
*/ */
_onReadyToJoin(): void { _onReadyToJoin(): void {
this.log.d('Ready to join - initializing room and handling prejoin flow'); this.log.d('Ready to join - initializing room and handling prejoin flow');
try { try {
// Mark that user has initiated the join process // Mark that user has initiated the join process
this.updateComponentState({ this.updateComponentState({
@ -921,15 +920,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
this.openviduService.initializeAndSetToken(token, livekitUrl); this.openviduService.initializeAndSetToken(token, livekitUrl);
this.log.d('Token has been successfully set. Room is ready to join'); this.log.d('Token has been successfully set. Room is ready to join');
// Only update showPrejoin if user hasn't initiated join process yet if (this.hasUserInitiatedJoin()) {
// 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 {
// User has initiated join, proceed to hide prejoin and continue // User has initiated join, proceed to hide prejoin and continue
this.log.d('User has initiated join, hiding prejoin and proceeding'); this.log.d('User has initiated join, hiding prejoin and proceeding');
this.updateComponentState({ this.updateComponentState({
@ -937,6 +928,21 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
isRoomReady: true, isRoomReady: true,
showPrejoin: false 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) { } catch (error) {
this.log.e('Error trying to set token', error); this.log.e('Error trying to set token', error);

View File

@ -199,10 +199,7 @@ export class OpenViduComponentsConfigService {
*/ */
private createGeneralConfigItem(initialValue: GeneralConfig): ConfigItem<GeneralConfig> { private createGeneralConfigItem(initialValue: GeneralConfig): ConfigItem<GeneralConfig> {
const subject = new BehaviorSubject<GeneralConfig>(initialValue); const subject = new BehaviorSubject<GeneralConfig>(initialValue);
const observable$ = subject.asObservable().pipe( const observable$ = subject.asObservable();
distinctUntilChanged((prev, curr) => this.compareGeneralConfig(prev, curr)),
shareReplay(1)
);
return { subject, observable$ }; return { subject, observable$ };
} }
@ -297,7 +294,7 @@ export class OpenViduComponentsConfigService {
* Compare GeneralConfig efficiently * Compare GeneralConfig efficiently
*/ */
private compareGeneralConfig(prev: GeneralConfig, curr: GeneralConfig): boolean { private compareGeneralConfig(prev: GeneralConfig, curr: GeneralConfig): boolean {
return ( const equal =
prev.token === curr.token && prev.token === curr.token &&
prev.livekitUrl === curr.livekitUrl && prev.livekitUrl === curr.livekitUrl &&
prev.tokenError === curr.tokenError && prev.tokenError === curr.tokenError &&
@ -306,8 +303,12 @@ export class OpenViduComponentsConfigService {
prev.prejoin === curr.prejoin && prev.prejoin === curr.prejoin &&
prev.prejoinDisplayParticipantName === curr.prejoinDisplayParticipantName && prev.prejoinDisplayParticipantName === curr.prejoinDisplayParticipantName &&
prev.showDisconnectionDialog === curr.showDisconnectionDialog && 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 // Grouped configuration items by domain
@ -380,17 +381,51 @@ export class OpenViduComponentsConfigService {
private layoutRemoteParticipantsConfig = this.createConfigItem<ParticipantModel[] | undefined>(undefined); private layoutRemoteParticipantsConfig = this.createConfigItem<ParticipantModel[] | undefined>(undefined);
// General observables // General observables
token$: Observable<string> = this.generalConfig.observable$.pipe(map((config) => config.token)); token$: Observable<string> = this.generalConfig.observable$.pipe(
livekitUrl$: Observable<string> = this.generalConfig.observable$.pipe(map((config) => config.livekitUrl)); map((config) => config.token),
tokenError$: Observable<any> = this.generalConfig.observable$.pipe(map((config) => config.tokenError)); distinctUntilChanged(),
minimal$: Observable<boolean> = this.generalConfig.observable$.pipe(map((config) => config.minimal)); shareReplay(1)
participantName$: Observable<string> = this.generalConfig.observable$.pipe(map((config) => config.participantName)); );
prejoin$: Observable<boolean> = this.generalConfig.observable$.pipe(map((config) => config.prejoin)); livekitUrl$: Observable<string> = this.generalConfig.observable$.pipe(
prejoinDisplayParticipantName$: Observable<boolean> = this.generalConfig.observable$.pipe( map((config) => config.livekitUrl),
map((config) => config.prejoinDisplayParticipantName) distinctUntilChanged(),
shareReplay(1)
);
tokenError$: Observable<any> = this.generalConfig.observable$.pipe(
map((config) => config.tokenError),
distinctUntilChanged(),
shareReplay(1)
);
minimal$: Observable<boolean> = this.generalConfig.observable$.pipe(
map((config) => config.minimal),
distinctUntilChanged(),
shareReplay(1)
);
participantName$: Observable<string> = this.generalConfig.observable$.pipe(
map((config) => config.participantName),
distinctUntilChanged(),
shareReplay(1)
);
prejoin$: Observable<boolean> = this.generalConfig.observable$.pipe(
map((config) => config.prejoin),
distinctUntilChanged(),
shareReplay(1)
);
prejoinDisplayParticipantName$: Observable<boolean> = this.generalConfig.observable$.pipe(
map((config) => config.prejoinDisplayParticipantName),
distinctUntilChanged(),
shareReplay(1)
);
showDisconnectionDialog$: Observable<boolean> = this.generalConfig.observable$.pipe(
map((config) => config.showDisconnectionDialog),
distinctUntilChanged(),
shareReplay(1)
);
recordingStreamBaseUrl$: Observable<string> = this.generalConfig.observable$.pipe(
map((config) => config.recordingStreamBaseUrl),
distinctUntilChanged(),
shareReplay(1)
); );
showDisconnectionDialog$: Observable<boolean> = this.generalConfig.observable$.pipe(map((config) => config.showDisconnectionDialog));
recordingStreamBaseUrl$: Observable<string> = this.generalConfig.observable$.pipe(map((config) => config.recordingStreamBaseUrl));
// Stream observables // Stream observables
videoEnabled$: Observable<boolean> = this.streamConfig.observable$.pipe(map((config) => config.videoEnabled)); videoEnabled$: Observable<boolean> = this.streamConfig.observable$.pipe(map((config) => config.videoEnabled));

View File

@ -177,16 +177,20 @@ export class OpenViduService {
/** /**
* @internal * @internal
*/ */
initializeAndSetToken(token: string, livekitUrl: string): void { initializeAndSetToken(token: string, livekitUrl?: string): void {
const livekitData = this.extractLivekitData(token, livekitUrl); const { livekitUrl: urlFromToken } = this.extractLivekitData(token);
this.livekitToken = token;
this.livekitUrl = livekitData.livekitUrl;
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'); this.log.e('LiveKit URL is not defined. Please, check the livekitUrl parameter of the VideoConferenceComponent');
throw new Error('Livekit URL is not defined'); throw new Error('Livekit URL is not defined');
} }
this.livekitUrl = url;
// this.livekitRoomAdmin = !!livekitRoomAdmin;
// Initialize room if it doesn't exist yet // Initialize room if it doesn't exist yet
// This ensures that getRoom() won't fail if token is set before onTokenRequested // This ensures that getRoom() won't fail if token is set before onTokenRequested
if (!this.room) { if (!this.room) {
@ -370,9 +374,8 @@ export class OpenViduService {
* @throws Error if there is an error decoding and parsing the token. * @throws Error if there is an error decoding and parsing the token.
* @internal * @internal
*/ */
private extractLivekitData(token: string, livekitUrl: string): { livekitUrl: string; livekitRoomAdmin: boolean } { private extractLivekitData(token: string): { livekitUrl?: string; livekitRoomAdmin: boolean } {
try { try {
const response = { livekitUrl, livekitRoomAdmin: false };
const base64Url = token.split('.')[1]; const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent( const jsonPayload = decodeURIComponent(
@ -388,13 +391,13 @@ export class OpenViduService {
const payload = JSON.parse(jsonPayload); const payload = JSON.parse(jsonPayload);
if (payload?.metadata) { if (payload?.metadata) {
const tokenMetadata = JSON.parse(payload.metadata); const tokenMetadata = JSON.parse(payload.metadata);
if (tokenMetadata.livekitUrl) { return {
response.livekitUrl = tokenMetadata.livekitUrl; livekitUrl: tokenMetadata.livekitUrl,
} livekitRoomAdmin: !!tokenMetadata.roomAdmin
response.livekitRoomAdmin = tokenMetadata.roomAdmin; };
} }
return response; return { livekitRoomAdmin: false };
} catch (error) { } catch (error) {
throw new Error('Error decoding and parsing token: ' + error); throw new Error('Error decoding and parsing token: ' + error);
} }