mirror of https://github.com/OpenVidu/openvidu.git
ov-components: enhance participant name handling with preferred name resolution and fallback logic
parent
a3eaeee003
commit
b08ae47b34
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue