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 { 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<string>();
@ -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);
}
}

View File

@ -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);

View File

@ -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() || '' });
}
}

View File

@ -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<ParticipantTrackPublication>;
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,

View File

@ -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) {

View File

@ -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()
});
}
/**