ov-components: enhance participant name handling with preferred name resolution and fallback logic

pull/900/head
CSantosM 2026-05-12 19:42:37 +02:00
parent a3eaeee003
commit b08ae47b34
6 changed files with 79 additions and 51 deletions

View File

@ -1,6 +1,6 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Subscription } from 'rxjs';
import { ParticipantService } from '../../../services/participant/participant.service'; import { ParticipantService } from '../../../services/participant/participant.service';
import { OpenViduService } from '../../../services/openvidu/openvidu.service';
import { StorageService } from '../../../services/storage/storage.service'; import { StorageService } from '../../../services/storage/storage.service';
/** /**
@ -14,7 +14,6 @@ import { StorageService } from '../../../services/storage/storage.service';
}) })
export class ParticipantNameInputComponent implements OnInit { export class ParticipantNameInputComponent implements OnInit {
name: string; name: string;
localParticipantSubscription: Subscription;
@Input() isPrejoinPage: boolean; @Input() isPrejoinPage: boolean;
@Input() error: boolean; @Input() error: boolean;
@Output() onNameUpdated = new EventEmitter<string>(); @Output() onNameUpdated = new EventEmitter<string>();
@ -22,15 +21,15 @@ export class ParticipantNameInputComponent implements OnInit {
constructor( constructor(
private participantService: ParticipantService, private participantService: ParticipantService,
private openviduService: OpenViduService,
private storageSrv: StorageService private storageSrv: StorageService
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
this.subscribeToParticipantProperties();
const myName = this.participantService.getMyName(); const myName = this.participantService.getMyName();
const storedName = this.storageSrv.getParticipantName(); const storedName = this.storageSrv.getParticipantName();
this.name = myName ?? storedName ?? this.generateRandomName(); this.name = myName ?? storedName ?? this.openviduService.generateFallbackParticipantName();
if (!myName && !storedName) { if (!myName && !storedName) {
this.storageSrv.setParticipantName(this.name); this.storageSrv.setParticipantName(this.name);
@ -63,16 +62,4 @@ export class ParticipantNameInputComponent implements OnInit {
this.onEnterPressed.emit(); 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);
}
} }

View File

@ -928,9 +928,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
// Always initialize the room when ready to join // Always initialize the room when ready to join
this.openviduService.initRoom(); this.openviduService.initRoom();
// Get the most current participant name from the service const participantName = this.openviduService.ensurePreferredLocalParticipantName();
// This ensures we have the latest value after any batch updates
const participantName = this.libService.getCurrentParticipantName() || this.latestParticipantName;
if (this.componentState.isRoomReady) { if (this.componentState.isRoomReady) {
// Room is ready, hide prejoin and proceed // Room is ready, hide prejoin and proceed
@ -941,22 +939,8 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
}); });
} else { } else {
// Room not ready, request token if we have a participant name // Room not ready, request token if we have a participant name
if (participantName) { this.log.d(`Requesting token for participant: ${participantName}`);
this.log.d(`Requesting token for participant: ${participantName}`); this.onTokenRequested.emit(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);
}
} }
// Emit onReadyToJoin event only if prejoin page was actually shown // 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 // Add safety timeout in case name never arrives
setTimeout(() => { setTimeout(() => {
if (!this.latestParticipantName) { if (!this.latestParticipantName) {
this.log.w('No participant name received after timeout, proceeding anyway'); this.log.w('No participant name received after timeout, using fallback');
const storedName = this.storageSrv.getParticipantName(); this.latestParticipantName = this.openviduService.ensurePreferredLocalParticipantName();
if (storedName) {
this.latestParticipantName = storedName;
this.libService.updateGeneralConfig({ participantName: storedName });
}
this._onReadyToJoin(); this._onReadyToJoin();
} }
}, VideoconferenceComponent.PARTICIPANT_NAME_TIMEOUT_MS); }, VideoconferenceComponent.PARTICIPANT_NAME_TIMEOUT_MS);

View File

@ -536,9 +536,7 @@ export class ParticipantNameDirective implements AfterViewInit, OnDestroy {
* @ignore * @ignore
*/ */
update(participantName: string) { update(participantName: string) {
if (participantName) { this.libService.updateGeneralConfig({ participantName: participantName?.trim() || '' });
this.libService.updateGeneralConfig({ participantName });
}
} }
} }

View File

@ -84,6 +84,12 @@ export interface ParticipantTrackPublication extends TrackPublication {
* Interface defining properties of a participant. * Interface defining properties of a participant.
*/ */
export interface ParticipantProperties { 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. * The participant instance, which can be either a local participant or a remote participant.
*/ */
@ -134,10 +140,12 @@ export class ParticipantModel {
private customVideoTrack: Partial<ParticipantTrackPublication>; private customVideoTrack: Partial<ParticipantTrackPublication>;
private _hasEncryptionError: boolean = false; private _hasEncryptionError: boolean = false;
private _decryptedName: string | undefined; private _decryptedName: string | undefined;
private _preferredNameResolver: (() => string | undefined) | undefined;
private _fallbackName: string | undefined; private _fallbackName: string | undefined;
constructor(props: ParticipantProperties) { constructor(props: ParticipantProperties) {
this.participant = props.participant; this.participant = props.participant;
this._preferredNameResolver = props.preferredNameResolver;
this._fallbackName = props.fallbackName?.trim() || undefined; this._fallbackName = props.fallbackName?.trim() || undefined;
this.colorProfile = props.colorProfile ?? `hsl(${Math.random() * 360}, 100%, 80%)`; this.colorProfile = props.colorProfile ?? `hsl(${Math.random() * 360}, 100%, 80%)`;
this.room = props.room; this.room = props.room;
@ -178,7 +186,13 @@ export class ParticipantModel {
* @returns string * @returns string
*/ */
get name(): string | undefined { 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 { getProperties(): ParticipantProperties {
return { return {
preferredNameResolver: this._preferredNameResolver,
participant: this.participant, participant: this.participant,
fallbackName: this._fallbackName, fallbackName: this._fallbackName,
room: this.room, room: this.room,

View File

@ -32,6 +32,8 @@ import { StorageService } from '../storage/storage.service';
providedIn: 'root' providedIn: 'root'
}) })
export class OpenViduService { export class OpenViduService {
private static readonly FALLBACK_PARTICIPANT_PREFIX = 'Participant';
/* /*
* @internal * @internal
*/ */
@ -56,6 +58,7 @@ export class OpenViduService {
private localTracks: LocalTrack[] = []; private localTracks: LocalTrack[] = [];
private livekitToken = ''; private livekitToken = '';
private livekitUrl = ''; private livekitUrl = '';
private tokenParticipantName: string | undefined;
private log: ILogger; private log: ILogger;
/** /**
@ -202,8 +205,9 @@ export class OpenViduService {
await this.room.connect(this.livekitUrl, this.livekitToken); await this.room.connect(this.livekitUrl, this.livekitToken);
this.log.d(`Successfully connected to room ${this.room.name}`); this.log.d(`Successfully connected to room ${this.room.name}`);
const participantName = this.storageService.getParticipantName(); const participantName = this.getPreferredLocalParticipantName();
if (participantName) { if (participantName) {
this.storageService.setParticipantName(participantName);
this.room.localParticipant.setName(participantName); this.room.localParticipant.setName(participantName);
} }
} catch (error) { } catch (error) {
@ -256,6 +260,41 @@ export class OpenViduService {
return this.room?.name; 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 if local participant is connected to the room
* @returns * @returns
@ -279,11 +318,15 @@ export class OpenViduService {
const { livekitUrl: urlFromToken, participantName: participantNameFromToken } = this.extractLivekitData(token); const { livekitUrl: urlFromToken, participantName: participantNameFromToken } = this.extractLivekitData(token);
this.livekitToken = token; this.livekitToken = token;
this.tokenParticipantName = participantNameFromToken;
const url = livekitUrl || urlFromToken; const url = livekitUrl || urlFromToken;
const currentParticipantName = this.configService.getCurrentParticipantName() || this.storageService.getParticipantName() || undefined; const currentParticipantName = this.configService.getCurrentParticipantName()?.trim() || undefined;
if (participantNameFromToken && participantNameFromToken !== currentParticipantName) { const storedParticipantName = this.storageService.getParticipantName()?.trim() || undefined;
this.storageService.setParticipantName(participantNameFromToken); if (participantNameFromToken && !currentParticipantName) {
this.configService.updateGeneralConfig({ participantName: participantNameFromToken }); this.configService.updateGeneralConfig({ participantName: participantNameFromToken });
if (!storedParticipantName) {
this.storageService.setParticipantName(participantNameFromToken);
}
} }
if (!url) { if (!url) {

View File

@ -99,8 +99,13 @@ export class ParticipantService {
*/ */
setLocalParticipant(participant: LocalParticipant) { setLocalParticipant(participant: LocalParticipant) {
const room = this.openviduService.getRoom(); const room = this.openviduService.getRoom();
const fallbackName = this.directiveService.getCurrentParticipantName() || this.storageSrv.getParticipantName() || undefined; const fallbackName = this.storageSrv.getParticipantName() || undefined;
this.localParticipant = this.newParticipant({ participant, room, fallbackName }); this.localParticipant = this.newParticipant({
participant,
room,
fallbackName,
preferredNameResolver: () => this.openviduService.getPreferredLocalParticipantName()
});
} }
/** /**