From b08ae47b34a92bce3ddefd5c765321dd5c89875a Mon Sep 17 00:00:00 2001 From: CSantosM <4a.santos@gmail.com> Date: Tue, 12 May 2026 19:42:37 +0200 Subject: [PATCH] ov-components: enhance participant name handling with preferred name resolution and fallback logic --- .../participant-name-input.component.ts | 19 ++----- .../videoconference.component.ts | 30 ++--------- .../api/videoconference.directive.ts | 4 +- .../src/lib/models/participant.model.ts | 17 ++++++- .../lib/services/openvidu/openvidu.service.ts | 51 +++++++++++++++++-- .../participant/participant.service.ts | 9 +++- 6 files changed, 79 insertions(+), 51 deletions(-) diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/participant-name-input/participant-name-input.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/participant-name-input/participant-name-input.component.ts index ab3e60b24..439bd5e88 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/participant-name-input/participant-name-input.component.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/participant-name-input/participant-name-input.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Subscription } from 'rxjs'; import { ParticipantService } from '../../../services/participant/participant.service'; +import { OpenViduService } from '../../../services/openvidu/openvidu.service'; import { StorageService } from '../../../services/storage/storage.service'; /** @@ -14,7 +14,6 @@ import { StorageService } from '../../../services/storage/storage.service'; }) export class ParticipantNameInputComponent implements OnInit { name: string; - localParticipantSubscription: Subscription; @Input() isPrejoinPage: boolean; @Input() error: boolean; @Output() onNameUpdated = new EventEmitter(); @@ -22,15 +21,15 @@ export class ParticipantNameInputComponent implements OnInit { constructor( private participantService: ParticipantService, + private openviduService: OpenViduService, private storageSrv: StorageService ) {} ngOnInit(): void { - this.subscribeToParticipantProperties(); const myName = this.participantService.getMyName(); const storedName = this.storageSrv.getParticipantName(); - this.name = myName ?? storedName ?? this.generateRandomName(); + this.name = myName ?? storedName ?? this.openviduService.generateFallbackParticipantName(); if (!myName && !storedName) { this.storageSrv.setParticipantName(this.name); @@ -63,16 +62,4 @@ export class ParticipantNameInputComponent implements OnInit { this.onEnterPressed.emit(); } } - - private subscribeToParticipantProperties() { - // this.localParticipantSubscription = this.participantService.localParticipant$.subscribe((p: ParticipantModel | undefined) => { - // if (p) { - // this.name = p.name; - // } - // }); - } - - private generateRandomName(): string { - return 'OpenVidu_User_' + Math.floor(Math.random() * 100); - } } 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 8b5256488..ef93217a7 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 @@ -928,9 +928,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { // Always initialize the room when ready to join this.openviduService.initRoom(); - // 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; + const participantName = this.openviduService.ensurePreferredLocalParticipantName(); if (this.componentState.isRoomReady) { // Room is ready, hide prejoin and proceed @@ -941,22 +939,8 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { }); } else { // Room not ready, request token if we have a participant name - if (participantName) { - this.log.d(`Requesting token for participant: ${participantName}`); - 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); - } + this.log.d(`Requesting token for participant: ${participantName}`); + this.onTokenRequested.emit(participantName); } // Emit onReadyToJoin event only if prejoin page was actually shown @@ -1081,12 +1065,8 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { // Add safety timeout in case name never arrives setTimeout(() => { if (!this.latestParticipantName) { - this.log.w('No participant name received after timeout, proceeding anyway'); - const storedName = this.storageSrv.getParticipantName(); - if (storedName) { - this.latestParticipantName = storedName; - this.libService.updateGeneralConfig({ participantName: storedName }); - } + this.log.w('No participant name received after timeout, using fallback'); + this.latestParticipantName = this.openviduService.ensurePreferredLocalParticipantName(); this._onReadyToJoin(); } }, VideoconferenceComponent.PARTICIPANT_NAME_TIMEOUT_MS); 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 74b39ab6d..a22102246 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 @@ -536,9 +536,7 @@ export class ParticipantNameDirective implements AfterViewInit, OnDestroy { * @ignore */ update(participantName: string) { - if (participantName) { - this.libService.updateGeneralConfig({ participantName }); - } + this.libService.updateGeneralConfig({ participantName: participantName?.trim() || '' }); } } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/participant.model.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/participant.model.ts index da88765dc..73247bbbf 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/participant.model.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/participant.model.ts @@ -84,6 +84,12 @@ export interface ParticipantTrackPublication extends TrackPublication { * Interface defining properties of a participant. */ export interface ParticipantProperties { + /** + * Resolves the preferred display name for the participant. + * Used for local participants where the directive/token/storage priority must override SDK defaults. + */ + preferredNameResolver?: () => string | undefined; + /** * The participant instance, which can be either a local participant or a remote participant. */ @@ -134,10 +140,12 @@ export class ParticipantModel { private customVideoTrack: Partial; private _hasEncryptionError: boolean = false; private _decryptedName: string | undefined; + private _preferredNameResolver: (() => string | undefined) | undefined; private _fallbackName: string | undefined; constructor(props: ParticipantProperties) { this.participant = props.participant; + this._preferredNameResolver = props.preferredNameResolver; this._fallbackName = props.fallbackName?.trim() || undefined; this.colorProfile = props.colorProfile ?? `hsl(${Math.random() * 360}, 100%, 80%)`; this.room = props.room; @@ -178,7 +186,13 @@ export class ParticipantModel { * @returns string */ get name(): string | undefined { - return this._decryptedName?.trim() || this.participant.name?.trim() || this._fallbackName || this.participant.identity; + return ( + this._decryptedName?.trim() || + this._preferredNameResolver?.()?.trim() || + this.participant.name?.trim() || + this._fallbackName || + this.participant.identity + ); } /** @@ -318,6 +332,7 @@ export class ParticipantModel { */ getProperties(): ParticipantProperties { return { + preferredNameResolver: this._preferredNameResolver, participant: this.participant, fallbackName: this._fallbackName, room: this.room, 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 a9deb861d..b0a933aba 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 @@ -32,6 +32,8 @@ import { StorageService } from '../storage/storage.service'; providedIn: 'root' }) export class OpenViduService { + private static readonly FALLBACK_PARTICIPANT_PREFIX = 'Participant'; + /* * @internal */ @@ -56,6 +58,7 @@ export class OpenViduService { private localTracks: LocalTrack[] = []; private livekitToken = ''; private livekitUrl = ''; + private tokenParticipantName: string | undefined; private log: ILogger; /** @@ -202,8 +205,9 @@ export class OpenViduService { await this.room.connect(this.livekitUrl, this.livekitToken); this.log.d(`Successfully connected to room ${this.room.name}`); - const participantName = this.storageService.getParticipantName(); + const participantName = this.getPreferredLocalParticipantName(); if (participantName) { + this.storageService.setParticipantName(participantName); this.room.localParticipant.setName(participantName); } } catch (error) { @@ -256,6 +260,41 @@ export class OpenViduService { return this.room?.name; } + getPreferredLocalParticipantName(): string | undefined { + return ( + this.configService.getCurrentParticipantName()?.trim() || + this.tokenParticipantName || + this.storageService.getParticipantName()?.trim() || + undefined + ); + } + + ensurePreferredLocalParticipantName(): string { + return this.getPreferredLocalParticipantName() || this.createFallbackParticipantName(); + } + + generateFallbackParticipantName(): string { + return `${OpenViduService.FALLBACK_PARTICIPANT_PREFIX}_${this.generateRandomSuffix()}`; + } + + private generateRandomSuffix(length: number = 6): string { + return Math.random() + .toString(36) + .slice(2, 2 + length) + .padEnd(length, '0'); + } + + private createFallbackParticipantName(): string { + const storedParticipantName = this.storageService.getParticipantName()?.trim(); + if (storedParticipantName) { + return storedParticipantName; + } + + const fallbackName = this.generateFallbackParticipantName(); + this.storageService.setParticipantName(fallbackName); + return fallbackName; + } + /** * Returns if local participant is connected to the room * @returns @@ -279,11 +318,15 @@ export class OpenViduService { const { livekitUrl: urlFromToken, participantName: participantNameFromToken } = this.extractLivekitData(token); this.livekitToken = token; + this.tokenParticipantName = participantNameFromToken; const url = livekitUrl || urlFromToken; - const currentParticipantName = this.configService.getCurrentParticipantName() || this.storageService.getParticipantName() || undefined; - if (participantNameFromToken && participantNameFromToken !== currentParticipantName) { - this.storageService.setParticipantName(participantNameFromToken); + const currentParticipantName = this.configService.getCurrentParticipantName()?.trim() || undefined; + const storedParticipantName = this.storageService.getParticipantName()?.trim() || undefined; + if (participantNameFromToken && !currentParticipantName) { this.configService.updateGeneralConfig({ participantName: participantNameFromToken }); + if (!storedParticipantName) { + this.storageService.setParticipantName(participantNameFromToken); + } } if (!url) { diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/participant/participant.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/participant/participant.service.ts index 17c8bb2ce..bf189de96 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/participant/participant.service.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/participant/participant.service.ts @@ -99,8 +99,13 @@ export class ParticipantService { */ setLocalParticipant(participant: LocalParticipant) { const room = this.openviduService.getRoom(); - const fallbackName = this.directiveService.getCurrentParticipantName() || this.storageSrv.getParticipantName() || undefined; - this.localParticipant = this.newParticipant({ participant, room, fallbackName }); + const fallbackName = this.storageSrv.getParticipantName() || undefined; + this.localParticipant = this.newParticipant({ + participant, + room, + fallbackName, + preferredNameResolver: () => this.openviduService.getPreferredLocalParticipantName() + }); } /**