openvidu-testapp: update and refactor all RoomEvent, ParticipantEvent and TrackEvent management

- Updates all available events to latest
- Refactor event listeners to use shared utilities with early registration and safe add/remove pattern
pull/900/head
pabloFuente 2026-05-26 14:29:42 +02:00
parent 052b110776
commit f037f31da1
7 changed files with 871 additions and 482 deletions

View File

@ -6,40 +6,49 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatDividerModule } from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
export interface EventGroup {
label: string;
eventCollection: Map<string, boolean>;
eventArray: string[];
checkAll: boolean;
}
@Component({ @Component({
selector: 'app-events-dialog', selector: 'app-events-dialog',
template: ` template: `
<h2 mat-dialog-title>{{target}} events</h2> <h2 mat-dialog-title>{{target}} events</h2>
<mat-dialog-content> <mat-dialog-content>
<mat-slide-toggle [(ngModel)]="checkAll" (change)="updateAll()" [color]="'warn'"><i>ALL</i></mat-slide-toggle> @for (group of eventGroups; track group.label) {
<mat-divider></mat-divider> <h3 class="group-label">{{group.label}}</h3>
<div class="row no-wrap-row"> <mat-slide-toggle [(ngModel)]="group.checkAll" (change)="updateAll(group)" [color]="'warn'"><i>ALL</i></mat-slide-toggle>
<div class="col-50"> <mat-divider></mat-divider>
@for (event of eventArray | slice:0:(eventArray.length/2); track event) { <div class="row no-wrap-row">
<div class="toggle"> <div class="col-50">
<mat-slide-toggle @for (event of group.eventArray | slice:0:Math.ceil(group.eventArray.length/2); track event) {
(change)="toggleEvent($event)" <div class="toggle">
[checked]="eventCollection.get(event)" <mat-slide-toggle
[name]="event" (change)="toggleEvent($event, group)"
color="warn">{{event}} [checked]="group.eventCollection.get(event)"
</mat-slide-toggle> [name]="event"
</div> color="warn">{{event}}
} </mat-slide-toggle>
</div>
}
</div>
<div class="col-50">
@for (event of group.eventArray | slice:Math.ceil(group.eventArray.length/2):group.eventArray.length; track event) {
<div class="toggle">
<mat-slide-toggle
(change)="toggleEvent($event, group)"
[checked]="group.eventCollection.get(event)"
[name]="event"
color="warn">{{event}}
</mat-slide-toggle>
</div>
}
</div>
</div> </div>
<div class="col-50"> }
@for (event of eventArray | slice:(eventArray.length/2 + 1):(eventArray.length); track event) {
<div class="toggle">
<mat-slide-toggle
(change)="toggleEvent($event)"
[checked]="eventCollection.get(event)"
[name]="event"
color="warn">{{event}}
</mat-slide-toggle>
</div>
}
</div>
</div>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions> <mat-dialog-actions>
<button mat-button id="close-dialog-btn" mat-dialog-close="">CLOSE</button> <button mat-button id="close-dialog-btn" mat-dialog-close="">CLOSE</button>
@ -49,34 +58,50 @@ import { MatButtonModule } from '@angular/material/button';
'mat-dialog-content { display: inline; }', 'mat-dialog-content { display: inline; }',
'mat-divider { margin-top: 5px; margin-bottom: 5px; }', 'mat-divider { margin-top: 5px; margin-bottom: 5px; }',
'.col-50 {flex-basis: 50%; box-sizing: border-box; padding-left: 20px; }', '.col-50 {flex-basis: 50%; box-sizing: border-box; padding-left: 20px; }',
'.toggle { }' '.toggle { }',
'.group-label { margin-top: 15px; margin-bottom: 5px; }',
'.group-label:first-child { margin-top: 0; }'
], ],
imports: [SlicePipe, FormsModule, MatDialogModule, MatSlideToggleModule, MatDividerModule, MatButtonModule], imports: [SlicePipe, FormsModule, MatDialogModule, MatSlideToggleModule, MatDividerModule, MatButtonModule],
}) })
export class EventsDialogComponent { export class EventsDialogComponent {
Math = Math;
target = ''; target = '';
checkAll = true; eventGroups: EventGroup[] = [];
eventCollection: Map<string, boolean>;
eventArray: string[];
private dialogData = inject(MAT_DIALOG_DATA); private dialogData = inject(MAT_DIALOG_DATA);
constructor(public dialogRef: MatDialogRef<EventsDialogComponent>) { constructor(public dialogRef: MatDialogRef<EventsDialogComponent>) {
const data = this.dialogData; const data = this.dialogData;
this.target = data.target; this.target = data.target;
this.eventCollection = data.eventCollection; if (data.eventGroups) {
this.eventArray = Array.from(this.eventCollection.keys()); this.eventGroups = data.eventGroups.map((g: { label: string; eventCollection: Map<string, boolean> }) => ({
label: g.label,
eventCollection: g.eventCollection,
eventArray: Array.from(g.eventCollection.keys()),
checkAll: Array.from(g.eventCollection.values()).every(v => v),
}));
} else {
// Backward compatibility: single eventCollection
const eventCollection = data.eventCollection;
this.eventGroups = [{
label: data.target,
eventCollection,
eventArray: Array.from(eventCollection.keys()),
checkAll: Array.from(eventCollection.values()).every(v => v),
}];
}
} }
updateAll() { updateAll(group: EventGroup) {
this.eventCollection.forEach((value: boolean, key: string) => { group.eventCollection.forEach((_value: boolean, key: string) => {
this.eventCollection.set(key, this.checkAll); group.eventCollection.set(key, group.checkAll);
}); });
} }
toggleEvent(event: any) { toggleEvent(event: any, group: EventGroup) {
this.eventCollection.set(event.source.name, event.checked); group.eventCollection.set(event.source.name, event.checked);
} }
} }

View File

@ -27,7 +27,7 @@
<mat-icon class="mat-icon-custom-ic" aria-label="Room API button">cloud_circle</mat-icon> <mat-icon class="mat-icon-custom-ic" aria-label="Room API button">cloud_circle</mat-icon>
</button> </button>
<button mat-icon-button title="Room events" [id]="'room-events-btn-' + index" <button mat-icon-button title="Room events" [id]="'room-events-btn-' + index"
class="mat-icon-custom" (click)="openRoomEventsDialog()"> class="mat-icon-custom" (click)="openAllEventsDialog()">
<mat-icon class="mat-icon-custom-ic" <mat-icon class="mat-icon-custom-ic"
aria-label="Room events button">notifications</mat-icon> aria-label="Room events button">notifications</mat-icon>
</button> </button>
@ -89,10 +89,15 @@
</div> </div>
<div> <div>
<app-participant class="local-participant" [participant]="room.localParticipant" [room]="room" <app-participant class="local-participant" [participant]="room.localParticipant" [room]="room"
[index]="index"></app-participant> [index]="index" [participantEvents]="participantEvents" [trackEvents]="trackEvents"
[earlyParticipantEvents]="earlyParticipantEvents" [earlyParticipantListeners]="earlyParticipantListeners"
[earlyTrackEvents]="earlyTrackEvents" [earlyTrackListeners]="earlyTrackListeners"></app-participant>
@for (participant of room.remoteParticipants | keyvalue; track participant) { @for (participant of room.remoteParticipants | keyvalue; track participant) {
<app-participant class="remote-participant" <app-participant class="remote-participant"
[participant]="participant.value" [room]="room" [index]="index" [participant]="participant.value" [room]="room" [index]="index"
[participantEvents]="participantEvents" [trackEvents]="trackEvents"
[earlyParticipantEvents]="earlyParticipantEvents" [earlyParticipantListeners]="earlyParticipantListeners"
[earlyTrackEvents]="earlyTrackEvents" [earlyTrackListeners]="earlyTrackListeners"
(sendReliableDataToOneParticipant)="sendDataReliable($event)" (sendReliableDataToOneParticipant)="sendDataReliable($event)"
(sendLossyDataToOneParticipant)="sendDataLossy($event)"></app-participant> (sendLossyDataToOneParticipant)="sendDataLossy($event)"></app-participant>
} }

View File

@ -28,6 +28,7 @@ import {
LocalVideoTrack, LocalVideoTrack,
MediaDeviceFailure, MediaDeviceFailure,
Participant, Participant,
ParticipantEvent,
RemoteAudioTrack, RemoteAudioTrack,
RemoteDataTrack, RemoteDataTrack,
RemoteParticipant, RemoteParticipant,
@ -42,6 +43,7 @@ import {
SubscriptionError, SubscriptionError,
TextStreamReader, TextStreamReader,
Track, Track,
TrackEvent,
TrackPublication, TrackPublication,
TrackPublishOptions, TrackPublishOptions,
} from 'livekit-client'; } from 'livekit-client';
@ -58,6 +60,11 @@ import { InfoDialogComponent } from '../dialogs/info-dialog/info-dialog.componen
import { ParticipantComponent } from '../participant/participant.component'; import { ParticipantComponent } from '../participant/participant.component';
import { RoomEventCallbacks } from 'node_modules/livekit-client/dist/src/room/Room'; import { RoomEventCallbacks } from 'node_modules/livekit-client/dist/src/room/Room';
import PCTransport from 'node_modules/livekit-client/dist/src/room/PCTransport'; import PCTransport from 'node_modules/livekit-client/dist/src/room/PCTransport';
import {
registerParticipantEventListeners,
registerTrackEventListeners,
removeAllManagedListeners,
} from 'src/app/utils/event-listener-utils';
@Component({ @Component({
selector: 'app-openvidu-instance', selector: 'app-openvidu-instance',
@ -74,6 +81,18 @@ export class OpenviduInstanceComponent {
room?: Room; room?: Room;
roomEvents: Map<RoomEvent, Boolean> = new Map<RoomEvent, boolean>(); roomEvents: Map<RoomEvent, Boolean> = new Map<RoomEvent, boolean>();
participantEvents: Map<ParticipantEvent, boolean> = new Map<ParticipantEvent, boolean>();
trackEvents: Map<TrackEvent, boolean> = new Map<TrackEvent, boolean>();
private roomEventListeners: Map<string, (...args: any[]) => void> = new Map();
// Early event registration: buffers events for participant/track components
// that don't yet exist when the SDK fires events during connect()
earlyParticipantEvents: Map<string, TestAppEvent[]> = new Map();
earlyParticipantListeners: Map<string, Map<string, (...args: any[]) => void>> = new Map();
earlyTrackEvents: Map<string, TestAppEvent[]> = new Map();
earlyTrackListeners: Map<string, Map<string, (...args: any[]) => void>> = new Map();
private earlyParticipantConnectedListener: ((...args: any[]) => void) | undefined;
roomName: string = 'TestRoom'; roomName: string = 'TestRoom';
participantName: string = 'TestParticipant'; participantName: string = 'TestParticipant';
@ -142,6 +161,14 @@ export class OpenviduInstanceComponent {
this.roomEvents.set(RoomEvent[event as keyof typeof RoomEvent], true); this.roomEvents.set(RoomEvent[event as keyof typeof RoomEvent], true);
this.roomEvents.set(RoomEvent.ActiveSpeakersChanged, false); this.roomEvents.set(RoomEvent.ActiveSpeakersChanged, false);
} }
for (let event of Object.keys(ParticipantEvent)) {
this.participantEvents.set(ParticipantEvent[event as keyof typeof ParticipantEvent], true);
this.participantEvents.set(ParticipantEvent.IsSpeakingChanged, false);
}
for (let event of Object.keys(TrackEvent)) {
this.trackEvents.set(TrackEvent[event as keyof typeof TrackEvent], true);
this.trackEvents.set(TrackEvent.TimeSyncUpdate, false);
}
this.participantName += this.index; this.participantName += this.index;
if (this.roomConf.startSession) { if (this.roomConf.startSession) {
const token = await this.roomApiService.createToken( const token = await this.roomApiService.createToken(
@ -177,6 +204,16 @@ export class OpenviduInstanceComponent {
this.room = new Room(this.roomOptions); this.room = new Room(this.roomOptions);
(window as any)['room_' + this.index] = this.room; (window as any)['room_' + this.index] = this.room;
// Register early participant event listeners on local participant BEFORE connect
this.registerEarlyParticipantListeners(this.room.localParticipant);
// Register early participant event listeners on remote participants as they connect
// This fires during connect() for participants already in the room
this.earlyParticipantConnectedListener = (participant: RemoteParticipant) => {
this.registerEarlyParticipantListeners(participant);
};
this.room.addListener(RoomEvent.ParticipantConnected, this.earlyParticipantConnectedListener as any);
this.setupRoomEventListeners(new Map(), true); this.setupRoomEventListeners(new Map(), true);
// connect to room // connect to room
@ -216,6 +253,19 @@ export class OpenviduInstanceComponent {
} }
} }
private registerRoomListener(event: RoomEvent, listener: (...args: any[]) => void) {
this.room!.addListener(event, listener as any);
this.roomEventListeners.set(event, listener);
}
private unregisterRoomListener(event: RoomEvent | string) {
const existing = this.roomEventListeners.get(event as string);
if (existing) {
this.room?.removeListener(event as RoomEvent, existing as any);
this.roomEventListeners.delete(event as string);
}
}
setupRoomEventListeners(oldValues: Map<string, boolean>, firstTime: boolean) { setupRoomEventListeners(oldValues: Map<string, boolean>, firstTime: boolean) {
// This is a link to the complete list of Room events // This is a link to the complete list of Room events
let callbacks: RoomEventCallbacks; let callbacks: RoomEventCallbacks;
@ -226,9 +276,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.Connected) !== this.roomEvents.get(RoomEvent.Connected) !==
oldValues.get(RoomEvent.Connected) oldValues.get(RoomEvent.Connected)
) { ) {
this.room?.removeAllListeners(RoomEvent.Connected); this.unregisterRoomListener(RoomEvent.Connected);
if (this.roomEvents.get(RoomEvent.Connected)) { if (this.roomEvents.get(RoomEvent.Connected)) {
this.room!.on(RoomEvent.Connected, () => { this.registerRoomListener(RoomEvent.Connected, () => {
this.updateEventList(RoomEvent.Connected, {}, ''); this.updateEventList(RoomEvent.Connected, {}, '');
this.room!.remoteParticipants.forEach( this.room!.remoteParticipants.forEach(
(remoteParticipant: RemoteParticipant) => { (remoteParticipant: RemoteParticipant) => {
@ -251,22 +301,35 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.Reconnecting) !== this.roomEvents.get(RoomEvent.Reconnecting) !==
oldValues.get(RoomEvent.Reconnecting) oldValues.get(RoomEvent.Reconnecting)
) { ) {
this.room?.removeAllListeners(RoomEvent.Reconnecting); this.unregisterRoomListener(RoomEvent.Reconnecting);
if (this.roomEvents.get(RoomEvent.Reconnecting)) { if (this.roomEvents.get(RoomEvent.Reconnecting)) {
this.room!.on(RoomEvent.Reconnecting, () => { this.registerRoomListener(RoomEvent.Reconnecting, () => {
this.updateEventList(RoomEvent.Reconnecting, {}, ''); this.updateEventList(RoomEvent.Reconnecting, {}, '');
}); });
} }
} }
if (
firstTime ||
this.roomEvents.get(RoomEvent.SignalReconnecting) !==
oldValues.get(RoomEvent.SignalReconnecting)
) {
this.unregisterRoomListener(RoomEvent.SignalReconnecting);
if (this.roomEvents.get(RoomEvent.SignalReconnecting)) {
this.registerRoomListener(RoomEvent.SignalReconnecting, () => {
this.updateEventList(RoomEvent.SignalReconnecting, {}, '');
});
}
}
if ( if (
firstTime || firstTime ||
this.roomEvents.get(RoomEvent.Reconnected) !== this.roomEvents.get(RoomEvent.Reconnected) !==
oldValues.get(RoomEvent.Reconnected) oldValues.get(RoomEvent.Reconnected)
) { ) {
this.room?.removeAllListeners(RoomEvent.Reconnected); this.unregisterRoomListener(RoomEvent.Reconnected);
if (this.roomEvents.get(RoomEvent.Reconnected)) { if (this.roomEvents.get(RoomEvent.Reconnected)) {
this.room!.on(RoomEvent.Reconnected, () => { this.registerRoomListener(RoomEvent.Reconnected, () => {
this.updateEventList(RoomEvent.Reconnected, {}, ''); this.updateEventList(RoomEvent.Reconnected, {}, '');
}); });
} }
@ -277,9 +340,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.Disconnected) !== this.roomEvents.get(RoomEvent.Disconnected) !==
oldValues.get(RoomEvent.Disconnected) oldValues.get(RoomEvent.Disconnected)
) { ) {
this.room?.removeAllListeners(RoomEvent.Disconnected); this.unregisterRoomListener(RoomEvent.Disconnected);
if (this.roomEvents.get(RoomEvent.Disconnected)) { if (this.roomEvents.get(RoomEvent.Disconnected)) {
this.room!.on(RoomEvent.Disconnected, (reason?: DisconnectReason) => { this.registerRoomListener(RoomEvent.Disconnected, (reason?: DisconnectReason) => {
this.updateEventList( this.updateEventList(
RoomEvent.Disconnected, RoomEvent.Disconnected,
{}, {},
@ -294,9 +357,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.ConnectionStateChanged) !== this.roomEvents.get(RoomEvent.ConnectionStateChanged) !==
oldValues.get(RoomEvent.ConnectionStateChanged) oldValues.get(RoomEvent.ConnectionStateChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.ConnectionStateChanged); this.unregisterRoomListener(RoomEvent.ConnectionStateChanged);
if (this.roomEvents.get(RoomEvent.ConnectionStateChanged)) { if (this.roomEvents.get(RoomEvent.ConnectionStateChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ConnectionStateChanged, RoomEvent.ConnectionStateChanged,
(state: ConnectionState) => { (state: ConnectionState) => {
this.updateEventList( this.updateEventList(
@ -309,14 +372,31 @@ export class OpenviduInstanceComponent {
} }
} }
if (
firstTime ||
this.roomEvents.get(RoomEvent.Moved) !==
oldValues.get(RoomEvent.Moved)
) {
this.unregisterRoomListener(RoomEvent.Moved);
if (this.roomEvents.get(RoomEvent.Moved)) {
this.registerRoomListener(RoomEvent.Moved, (name: string) => {
this.updateEventList(
RoomEvent.Moved,
{ name },
`moved to room: ${name}`
);
});
}
}
if ( if (
firstTime || firstTime ||
this.roomEvents.get(RoomEvent.MediaDevicesChanged) !== this.roomEvents.get(RoomEvent.MediaDevicesChanged) !==
oldValues.get(RoomEvent.MediaDevicesChanged) oldValues.get(RoomEvent.MediaDevicesChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.MediaDevicesChanged); this.unregisterRoomListener(RoomEvent.MediaDevicesChanged);
if (this.roomEvents.get(RoomEvent.MediaDevicesChanged)) { if (this.roomEvents.get(RoomEvent.MediaDevicesChanged)) {
this.room!.on(RoomEvent.MediaDevicesChanged, () => { this.registerRoomListener(RoomEvent.MediaDevicesChanged, () => {
this.updateEventList(RoomEvent.MediaDevicesChanged, {}, ''); this.updateEventList(RoomEvent.MediaDevicesChanged, {}, '');
}); });
} }
@ -327,9 +407,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.ParticipantConnected) !== this.roomEvents.get(RoomEvent.ParticipantConnected) !==
oldValues.get(RoomEvent.ParticipantConnected) oldValues.get(RoomEvent.ParticipantConnected)
) { ) {
this.room?.removeAllListeners(RoomEvent.ParticipantConnected); this.unregisterRoomListener(RoomEvent.ParticipantConnected);
if (this.roomEvents.get(RoomEvent.ParticipantConnected)) { if (this.roomEvents.get(RoomEvent.ParticipantConnected)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ParticipantConnected, RoomEvent.ParticipantConnected,
(participant: RemoteParticipant) => { (participant: RemoteParticipant) => {
this.updateEventList( this.updateEventList(
@ -347,9 +427,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.ParticipantActive) !== this.roomEvents.get(RoomEvent.ParticipantActive) !==
oldValues.get(RoomEvent.ParticipantActive) oldValues.get(RoomEvent.ParticipantActive)
) { ) {
this.room?.removeAllListeners(RoomEvent.ParticipantActive); this.unregisterRoomListener(RoomEvent.ParticipantActive);
if (this.roomEvents.get(RoomEvent.ParticipantActive)) { if (this.roomEvents.get(RoomEvent.ParticipantActive)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ParticipantActive, RoomEvent.ParticipantActive,
(participant: Participant) => { (participant: Participant) => {
this.updateEventList( this.updateEventList(
@ -367,9 +447,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.ParticipantDisconnected) !== this.roomEvents.get(RoomEvent.ParticipantDisconnected) !==
oldValues.get(RoomEvent.ParticipantDisconnected) oldValues.get(RoomEvent.ParticipantDisconnected)
) { ) {
this.room?.removeAllListeners(RoomEvent.ParticipantDisconnected); this.unregisterRoomListener(RoomEvent.ParticipantDisconnected);
if (this.roomEvents.get(RoomEvent.ParticipantDisconnected)) { if (this.roomEvents.get(RoomEvent.ParticipantDisconnected)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ParticipantDisconnected, RoomEvent.ParticipantDisconnected,
(participant: RemoteParticipant) => { (participant: RemoteParticipant) => {
this.updateEventList( this.updateEventList(
@ -388,9 +468,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.TrackPublished) !== this.roomEvents.get(RoomEvent.TrackPublished) !==
oldValues.get(RoomEvent.TrackPublished) oldValues.get(RoomEvent.TrackPublished)
) { ) {
this.room?.removeAllListeners(RoomEvent.TrackPublished); this.unregisterRoomListener(RoomEvent.TrackPublished);
if (this.roomEvents.get(RoomEvent.TrackPublished)) { if (this.roomEvents.get(RoomEvent.TrackPublished)) {
this.room!.on( this.registerRoomListener(
RoomEvent.TrackPublished, RoomEvent.TrackPublished,
( (
publication: RemoteTrackPublication, publication: RemoteTrackPublication,
@ -414,9 +494,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.TrackSubscribed) !== this.roomEvents.get(RoomEvent.TrackSubscribed) !==
oldValues.get(RoomEvent.TrackSubscribed) oldValues.get(RoomEvent.TrackSubscribed)
) { ) {
this.room?.removeAllListeners(RoomEvent.TrackSubscribed); this.unregisterRoomListener(RoomEvent.TrackSubscribed);
if (this.roomEvents.get(RoomEvent.TrackSubscribed)) { if (this.roomEvents.get(RoomEvent.TrackSubscribed)) {
this.room!.on( this.registerRoomListener(
RoomEvent.TrackSubscribed, RoomEvent.TrackSubscribed,
( (
track: RemoteTrack, track: RemoteTrack,
@ -453,9 +533,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.TrackSubscriptionFailed) !== this.roomEvents.get(RoomEvent.TrackSubscriptionFailed) !==
oldValues.get(RoomEvent.TrackSubscriptionFailed) oldValues.get(RoomEvent.TrackSubscriptionFailed)
) { ) {
this.room?.removeAllListeners(RoomEvent.TrackSubscriptionFailed); this.unregisterRoomListener(RoomEvent.TrackSubscriptionFailed);
if (this.roomEvents.get(RoomEvent.TrackSubscriptionFailed)) { if (this.roomEvents.get(RoomEvent.TrackSubscriptionFailed)) {
this.room!.on( this.registerRoomListener(
RoomEvent.TrackSubscriptionFailed, RoomEvent.TrackSubscriptionFailed,
( (
trackSid: string, trackSid: string,
@ -479,9 +559,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.TrackUnpublished) !== this.roomEvents.get(RoomEvent.TrackUnpublished) !==
oldValues.get(RoomEvent.TrackUnpublished) oldValues.get(RoomEvent.TrackUnpublished)
) { ) {
this.room?.removeAllListeners(RoomEvent.TrackUnpublished); this.unregisterRoomListener(RoomEvent.TrackUnpublished);
if (this.roomEvents.get(RoomEvent.TrackUnpublished)) { if (this.roomEvents.get(RoomEvent.TrackUnpublished)) {
this.room!.on( this.registerRoomListener(
RoomEvent.TrackUnpublished, RoomEvent.TrackUnpublished,
( (
publication: RemoteTrackPublication, publication: RemoteTrackPublication,
@ -502,9 +582,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.TrackUnsubscribed) !== this.roomEvents.get(RoomEvent.TrackUnsubscribed) !==
oldValues.get(RoomEvent.TrackUnsubscribed) oldValues.get(RoomEvent.TrackUnsubscribed)
) { ) {
this.room?.removeAllListeners(RoomEvent.TrackUnsubscribed); this.unregisterRoomListener(RoomEvent.TrackUnsubscribed);
if (this.roomEvents.get(RoomEvent.TrackUnsubscribed)) { if (this.roomEvents.get(RoomEvent.TrackUnsubscribed)) {
this.room!.on( this.registerRoomListener(
RoomEvent.TrackUnsubscribed, RoomEvent.TrackUnsubscribed,
( (
track: Track, track: Track,
@ -543,9 +623,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.TrackMuted) !== this.roomEvents.get(RoomEvent.TrackMuted) !==
oldValues.get(RoomEvent.TrackMuted) oldValues.get(RoomEvent.TrackMuted)
) { ) {
this.room?.removeAllListeners(RoomEvent.TrackMuted); this.unregisterRoomListener(RoomEvent.TrackMuted);
if (this.roomEvents.get(RoomEvent.TrackMuted)) { if (this.roomEvents.get(RoomEvent.TrackMuted)) {
this.room!.on( this.registerRoomListener(
RoomEvent.TrackMuted, RoomEvent.TrackMuted,
(publication: TrackPublication, participant: Participant) => { (publication: TrackPublication, participant: Participant) => {
this.updateEventList( this.updateEventList(
@ -563,9 +643,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.TrackUnmuted) !== this.roomEvents.get(RoomEvent.TrackUnmuted) !==
oldValues.get(RoomEvent.TrackUnmuted) oldValues.get(RoomEvent.TrackUnmuted)
) { ) {
this.room?.removeAllListeners(RoomEvent.TrackUnmuted); this.unregisterRoomListener(RoomEvent.TrackUnmuted);
if (this.roomEvents.get(RoomEvent.TrackUnmuted)) { if (this.roomEvents.get(RoomEvent.TrackUnmuted)) {
this.room!.on( this.registerRoomListener(
RoomEvent.TrackUnmuted, RoomEvent.TrackUnmuted,
(publication: TrackPublication, participant: Participant) => { (publication: TrackPublication, participant: Participant) => {
this.updateEventList( this.updateEventList(
@ -583,9 +663,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.LocalTrackPublished) !== this.roomEvents.get(RoomEvent.LocalTrackPublished) !==
oldValues.get(RoomEvent.LocalTrackPublished) oldValues.get(RoomEvent.LocalTrackPublished)
) { ) {
this.room?.removeAllListeners(RoomEvent.LocalTrackPublished); this.unregisterRoomListener(RoomEvent.LocalTrackPublished);
if (this.roomEvents.get(RoomEvent.LocalTrackPublished)) { if (this.roomEvents.get(RoomEvent.LocalTrackPublished)) {
this.room!.on( this.registerRoomListener(
RoomEvent.LocalTrackPublished, RoomEvent.LocalTrackPublished,
( (
publication: LocalTrackPublication, publication: LocalTrackPublication,
@ -611,9 +691,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.LocalTrackUnpublished) !== this.roomEvents.get(RoomEvent.LocalTrackUnpublished) !==
oldValues.get(RoomEvent.LocalTrackUnpublished) oldValues.get(RoomEvent.LocalTrackUnpublished)
) { ) {
this.room?.removeAllListeners(RoomEvent.LocalTrackUnpublished); this.unregisterRoomListener(RoomEvent.LocalTrackUnpublished);
if (this.roomEvents.get(RoomEvent.LocalTrackUnpublished)) { if (this.roomEvents.get(RoomEvent.LocalTrackUnpublished)) {
this.room!.on( this.registerRoomListener(
RoomEvent.LocalTrackUnpublished, RoomEvent.LocalTrackUnpublished,
( (
publication: LocalTrackPublication, publication: LocalTrackPublication,
@ -635,9 +715,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.LocalAudioSilenceDetected) !== this.roomEvents.get(RoomEvent.LocalAudioSilenceDetected) !==
oldValues.get(RoomEvent.LocalAudioSilenceDetected) oldValues.get(RoomEvent.LocalAudioSilenceDetected)
) { ) {
this.room?.removeAllListeners(RoomEvent.LocalAudioSilenceDetected); this.unregisterRoomListener(RoomEvent.LocalAudioSilenceDetected);
if (this.roomEvents.get(RoomEvent.LocalAudioSilenceDetected)) { if (this.roomEvents.get(RoomEvent.LocalAudioSilenceDetected)) {
this.room!.on( this.registerRoomListener(
RoomEvent.LocalAudioSilenceDetected, RoomEvent.LocalAudioSilenceDetected,
(publication: LocalTrackPublication) => { (publication: LocalTrackPublication) => {
this.updateEventList( this.updateEventList(
@ -655,9 +735,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.ParticipantMetadataChanged) !== this.roomEvents.get(RoomEvent.ParticipantMetadataChanged) !==
oldValues.get(RoomEvent.ParticipantMetadataChanged) oldValues.get(RoomEvent.ParticipantMetadataChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.ParticipantMetadataChanged); this.unregisterRoomListener(RoomEvent.ParticipantMetadataChanged);
if (this.roomEvents.get(RoomEvent.ParticipantMetadataChanged)) { if (this.roomEvents.get(RoomEvent.ParticipantMetadataChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ParticipantMetadataChanged, RoomEvent.ParticipantMetadataChanged,
(metadata: string | undefined, participant: Participant) => { (metadata: string | undefined, participant: Participant) => {
this.updateEventList( this.updateEventList(
@ -675,9 +755,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.ParticipantNameChanged) !== this.roomEvents.get(RoomEvent.ParticipantNameChanged) !==
oldValues.get(RoomEvent.ParticipantNameChanged) oldValues.get(RoomEvent.ParticipantNameChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.ParticipantNameChanged); this.unregisterRoomListener(RoomEvent.ParticipantNameChanged);
if (this.roomEvents.get(RoomEvent.ParticipantNameChanged)) { if (this.roomEvents.get(RoomEvent.ParticipantNameChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ParticipantNameChanged, RoomEvent.ParticipantNameChanged,
(name: string, participant: Participant) => { (name: string, participant: Participant) => {
this.updateEventList( this.updateEventList(
@ -690,14 +770,37 @@ export class OpenviduInstanceComponent {
} }
} }
if (
firstTime ||
this.roomEvents.get(RoomEvent.ParticipantAttributesChanged) !==
oldValues.get(RoomEvent.ParticipantAttributesChanged)
) {
this.unregisterRoomListener(RoomEvent.ParticipantAttributesChanged);
if (this.roomEvents.get(RoomEvent.ParticipantAttributesChanged)) {
this.registerRoomListener(
RoomEvent.ParticipantAttributesChanged,
(
changedAttributes: Record<string, string>,
participant: RemoteParticipant | LocalParticipant
) => {
this.updateEventList(
RoomEvent.ParticipantAttributesChanged,
{ changedAttributes, participant },
`${participant.identity} ${JSON.stringify(changedAttributes)}`
);
}
);
}
}
if ( if (
firstTime || firstTime ||
this.roomEvents.get(RoomEvent.ParticipantPermissionsChanged) !== this.roomEvents.get(RoomEvent.ParticipantPermissionsChanged) !==
oldValues.get(RoomEvent.ParticipantPermissionsChanged) oldValues.get(RoomEvent.ParticipantPermissionsChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.ParticipantPermissionsChanged); this.unregisterRoomListener(RoomEvent.ParticipantPermissionsChanged);
if (this.roomEvents.get(RoomEvent.ParticipantPermissionsChanged)) { if (this.roomEvents.get(RoomEvent.ParticipantPermissionsChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ParticipantPermissionsChanged, RoomEvent.ParticipantPermissionsChanged,
( (
prevPermissions: ParticipantPermission | undefined, prevPermissions: ParticipantPermission | undefined,
@ -722,9 +825,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.ActiveSpeakersChanged) !== this.roomEvents.get(RoomEvent.ActiveSpeakersChanged) !==
oldValues.get(RoomEvent.ActiveSpeakersChanged) oldValues.get(RoomEvent.ActiveSpeakersChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.ActiveSpeakersChanged); this.unregisterRoomListener(RoomEvent.ActiveSpeakersChanged);
if (this.roomEvents.get(RoomEvent.ActiveSpeakersChanged)) { if (this.roomEvents.get(RoomEvent.ActiveSpeakersChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ActiveSpeakersChanged, RoomEvent.ActiveSpeakersChanged,
(speakers: Participant[]) => { (speakers: Participant[]) => {
this.updateEventList( this.updateEventList(
@ -746,9 +849,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.RoomMetadataChanged) !== this.roomEvents.get(RoomEvent.RoomMetadataChanged) !==
oldValues.get(RoomEvent.RoomMetadataChanged) oldValues.get(RoomEvent.RoomMetadataChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.RoomMetadataChanged); this.unregisterRoomListener(RoomEvent.RoomMetadataChanged);
if (this.roomEvents.get(RoomEvent.RoomMetadataChanged)) { if (this.roomEvents.get(RoomEvent.RoomMetadataChanged)) {
this.room!.on(RoomEvent.RoomMetadataChanged, (metadata: string) => { this.registerRoomListener(RoomEvent.RoomMetadataChanged, (metadata: string) => {
this.updateEventList( this.updateEventList(
RoomEvent.RoomMetadataChanged, RoomEvent.RoomMetadataChanged,
{ metadata }, { metadata },
@ -763,9 +866,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.DataReceived) !== this.roomEvents.get(RoomEvent.DataReceived) !==
oldValues.get(RoomEvent.DataReceived) oldValues.get(RoomEvent.DataReceived)
) { ) {
this.room?.removeAllListeners(RoomEvent.DataReceived); this.unregisterRoomListener(RoomEvent.DataReceived);
if (this.roomEvents.get(RoomEvent.DataReceived)) { if (this.roomEvents.get(RoomEvent.DataReceived)) {
this.room!.on( this.registerRoomListener(
RoomEvent.DataReceived, RoomEvent.DataReceived,
( (
payload: Uint8Array, payload: Uint8Array,
@ -785,14 +888,34 @@ export class OpenviduInstanceComponent {
} }
} }
if (
firstTime ||
this.roomEvents.get(RoomEvent.SipDTMFReceived) !==
oldValues.get(RoomEvent.SipDTMFReceived)
) {
this.unregisterRoomListener(RoomEvent.SipDTMFReceived);
if (this.roomEvents.get(RoomEvent.SipDTMFReceived)) {
this.registerRoomListener(
RoomEvent.SipDTMFReceived,
(dtmf: any, participant?: RemoteParticipant) => {
this.updateEventList(
RoomEvent.SipDTMFReceived,
{ dtmf, participant },
`${participant?.identity ?? 'unknown'} ${JSON.stringify(dtmf)}`
);
}
);
}
}
if ( if (
firstTime || firstTime ||
this.roomEvents.get(RoomEvent.ConnectionQualityChanged) !== this.roomEvents.get(RoomEvent.ConnectionQualityChanged) !==
oldValues.get(RoomEvent.ConnectionQualityChanged) oldValues.get(RoomEvent.ConnectionQualityChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.ConnectionQualityChanged); this.unregisterRoomListener(RoomEvent.ConnectionQualityChanged);
if (this.roomEvents.get(RoomEvent.ConnectionQualityChanged)) { if (this.roomEvents.get(RoomEvent.ConnectionQualityChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ConnectionQualityChanged, RoomEvent.ConnectionQualityChanged,
(quality: ConnectionQuality, participant: Participant) => { (quality: ConnectionQuality, participant: Participant) => {
this.updateEventList( this.updateEventList(
@ -810,9 +933,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.MediaDevicesError) !== this.roomEvents.get(RoomEvent.MediaDevicesError) !==
oldValues.get(RoomEvent.MediaDevicesError) oldValues.get(RoomEvent.MediaDevicesError)
) { ) {
this.room?.removeAllListeners(RoomEvent.MediaDevicesError); this.unregisterRoomListener(RoomEvent.MediaDevicesError);
if (this.roomEvents.get(RoomEvent.MediaDevicesError)) { if (this.roomEvents.get(RoomEvent.MediaDevicesError)) {
this.room!.on(RoomEvent.MediaDevicesError, (error: Error) => { this.registerRoomListener(RoomEvent.MediaDevicesError, (error: Error) => {
this.updateEventList( this.updateEventList(
RoomEvent.MediaDevicesError, RoomEvent.MediaDevicesError,
{ {
@ -830,9 +953,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.TrackStreamStateChanged) !== this.roomEvents.get(RoomEvent.TrackStreamStateChanged) !==
oldValues.get(RoomEvent.TrackStreamStateChanged) oldValues.get(RoomEvent.TrackStreamStateChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.TrackStreamStateChanged); this.unregisterRoomListener(RoomEvent.TrackStreamStateChanged);
if (this.roomEvents.get(RoomEvent.TrackStreamStateChanged)) { if (this.roomEvents.get(RoomEvent.TrackStreamStateChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.TrackStreamStateChanged, RoomEvent.TrackStreamStateChanged,
( (
publication: RemoteTrackPublication, publication: RemoteTrackPublication,
@ -854,11 +977,11 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.TrackSubscriptionPermissionChanged) !== this.roomEvents.get(RoomEvent.TrackSubscriptionPermissionChanged) !==
oldValues.get(RoomEvent.TrackSubscriptionPermissionChanged) oldValues.get(RoomEvent.TrackSubscriptionPermissionChanged)
) { ) {
this.room?.removeAllListeners( this.unregisterRoomListener(
RoomEvent.TrackSubscriptionPermissionChanged RoomEvent.TrackSubscriptionPermissionChanged
); );
if (this.roomEvents.get(RoomEvent.TrackSubscriptionPermissionChanged)) { if (this.roomEvents.get(RoomEvent.TrackSubscriptionPermissionChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.TrackSubscriptionPermissionChanged, RoomEvent.TrackSubscriptionPermissionChanged,
( (
publication: RemoteTrackPublication, publication: RemoteTrackPublication,
@ -880,9 +1003,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.TrackSubscriptionStatusChanged) !== this.roomEvents.get(RoomEvent.TrackSubscriptionStatusChanged) !==
oldValues.get(RoomEvent.TrackSubscriptionStatusChanged) oldValues.get(RoomEvent.TrackSubscriptionStatusChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.TrackSubscriptionStatusChanged); this.unregisterRoomListener(RoomEvent.TrackSubscriptionStatusChanged);
if (this.roomEvents.get(RoomEvent.TrackSubscriptionStatusChanged)) { if (this.roomEvents.get(RoomEvent.TrackSubscriptionStatusChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.TrackSubscriptionStatusChanged, RoomEvent.TrackSubscriptionStatusChanged,
( (
publication: RemoteTrackPublication, publication: RemoteTrackPublication,
@ -904,9 +1027,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.AudioPlaybackStatusChanged) !== this.roomEvents.get(RoomEvent.AudioPlaybackStatusChanged) !==
oldValues.get(RoomEvent.AudioPlaybackStatusChanged) oldValues.get(RoomEvent.AudioPlaybackStatusChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.AudioPlaybackStatusChanged); this.unregisterRoomListener(RoomEvent.AudioPlaybackStatusChanged);
if (this.roomEvents.get(RoomEvent.AudioPlaybackStatusChanged)) { if (this.roomEvents.get(RoomEvent.AudioPlaybackStatusChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.AudioPlaybackStatusChanged, RoomEvent.AudioPlaybackStatusChanged,
(playing: boolean) => { (playing: boolean) => {
this.updateEventList( this.updateEventList(
@ -919,14 +1042,34 @@ export class OpenviduInstanceComponent {
} }
} }
if (
firstTime ||
this.roomEvents.get(RoomEvent.VideoPlaybackStatusChanged) !==
oldValues.get(RoomEvent.VideoPlaybackStatusChanged)
) {
this.unregisterRoomListener(RoomEvent.VideoPlaybackStatusChanged);
if (this.roomEvents.get(RoomEvent.VideoPlaybackStatusChanged)) {
this.registerRoomListener(
RoomEvent.VideoPlaybackStatusChanged,
(playing: boolean) => {
this.updateEventList(
RoomEvent.VideoPlaybackStatusChanged,
{ playing },
`canPlaybackVideo: ${playing}`
);
}
);
}
}
if ( if (
firstTime || firstTime ||
this.roomEvents.get(RoomEvent.SignalConnected) !== this.roomEvents.get(RoomEvent.SignalConnected) !==
oldValues.get(RoomEvent.SignalConnected) oldValues.get(RoomEvent.SignalConnected)
) { ) {
this.room?.removeAllListeners(RoomEvent.SignalConnected); this.unregisterRoomListener(RoomEvent.SignalConnected);
if (this.roomEvents.get(RoomEvent.SignalConnected)) { if (this.roomEvents.get(RoomEvent.SignalConnected)) {
this.room!.on(RoomEvent.SignalConnected, () => { this.registerRoomListener(RoomEvent.SignalConnected, () => {
this.updateEventList(RoomEvent.SignalConnected, {}, ''); this.updateEventList(RoomEvent.SignalConnected, {}, '');
}); });
} }
@ -937,9 +1080,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.RecordingStatusChanged) !== this.roomEvents.get(RoomEvent.RecordingStatusChanged) !==
oldValues.get(RoomEvent.RecordingStatusChanged) oldValues.get(RoomEvent.RecordingStatusChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.RecordingStatusChanged); this.unregisterRoomListener(RoomEvent.RecordingStatusChanged);
if (this.roomEvents.get(RoomEvent.RecordingStatusChanged)) { if (this.roomEvents.get(RoomEvent.RecordingStatusChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.RecordingStatusChanged, RoomEvent.RecordingStatusChanged,
(recording: boolean) => { (recording: boolean) => {
this.updateEventList( this.updateEventList(
@ -957,11 +1100,11 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.ParticipantEncryptionStatusChanged) !== this.roomEvents.get(RoomEvent.ParticipantEncryptionStatusChanged) !==
oldValues.get(RoomEvent.ParticipantEncryptionStatusChanged) oldValues.get(RoomEvent.ParticipantEncryptionStatusChanged)
) { ) {
this.room?.removeAllListeners( this.unregisterRoomListener(
RoomEvent.ParticipantEncryptionStatusChanged RoomEvent.ParticipantEncryptionStatusChanged
); );
if (this.roomEvents.get(RoomEvent.ParticipantEncryptionStatusChanged)) { if (this.roomEvents.get(RoomEvent.ParticipantEncryptionStatusChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ParticipantEncryptionStatusChanged, RoomEvent.ParticipantEncryptionStatusChanged,
(encrypted: boolean, participant?: Participant) => { (encrypted: boolean, participant?: Participant) => {
this.updateEventList( this.updateEventList(
@ -979,9 +1122,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.EncryptionError) !== this.roomEvents.get(RoomEvent.EncryptionError) !==
oldValues.get(RoomEvent.EncryptionError) oldValues.get(RoomEvent.EncryptionError)
) { ) {
this.room?.removeAllListeners(RoomEvent.EncryptionError); this.unregisterRoomListener(RoomEvent.EncryptionError);
if (this.roomEvents.get(RoomEvent.EncryptionError)) { if (this.roomEvents.get(RoomEvent.EncryptionError)) {
this.room!.on(RoomEvent.EncryptionError, (error: Error) => { this.registerRoomListener(RoomEvent.EncryptionError, (error: Error) => {
this.updateEventList( this.updateEventList(
RoomEvent.EncryptionError, RoomEvent.EncryptionError,
{ error: error.message }, { error: error.message },
@ -996,9 +1139,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.DCBufferStatusChanged) !== this.roomEvents.get(RoomEvent.DCBufferStatusChanged) !==
oldValues.get(RoomEvent.DCBufferStatusChanged) oldValues.get(RoomEvent.DCBufferStatusChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.DCBufferStatusChanged); this.unregisterRoomListener(RoomEvent.DCBufferStatusChanged);
if (this.roomEvents.get(RoomEvent.DCBufferStatusChanged)) { if (this.roomEvents.get(RoomEvent.DCBufferStatusChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.DCBufferStatusChanged, RoomEvent.DCBufferStatusChanged,
(isLow: boolean, kind: any) => { (isLow: boolean, kind: any) => {
this.updateEventList( this.updateEventList(
@ -1016,9 +1159,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.ActiveDeviceChanged) !== this.roomEvents.get(RoomEvent.ActiveDeviceChanged) !==
oldValues.get(RoomEvent.ActiveDeviceChanged) oldValues.get(RoomEvent.ActiveDeviceChanged)
) { ) {
this.room?.removeAllListeners(RoomEvent.ActiveDeviceChanged); this.unregisterRoomListener(RoomEvent.ActiveDeviceChanged);
if (this.roomEvents.get(RoomEvent.ActiveDeviceChanged)) { if (this.roomEvents.get(RoomEvent.ActiveDeviceChanged)) {
this.room!.on( this.registerRoomListener(
RoomEvent.ActiveDeviceChanged, RoomEvent.ActiveDeviceChanged,
(kind: MediaDeviceKind, deviceId: string) => { (kind: MediaDeviceKind, deviceId: string) => {
this.updateEventList( this.updateEventList(
@ -1036,9 +1179,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.LocalTrackSubscribed) !== this.roomEvents.get(RoomEvent.LocalTrackSubscribed) !==
oldValues.get(RoomEvent.LocalTrackSubscribed) oldValues.get(RoomEvent.LocalTrackSubscribed)
) { ) {
this.room?.removeAllListeners(RoomEvent.LocalTrackSubscribed); this.unregisterRoomListener(RoomEvent.LocalTrackSubscribed);
if (this.roomEvents.get(RoomEvent.LocalTrackSubscribed)) { if (this.roomEvents.get(RoomEvent.LocalTrackSubscribed)) {
this.room!.on( this.registerRoomListener(
RoomEvent.LocalTrackSubscribed, RoomEvent.LocalTrackSubscribed,
( (
publication: LocalTrackPublication, publication: LocalTrackPublication,
@ -1054,6 +1197,46 @@ export class OpenviduInstanceComponent {
} }
} }
if (
firstTime ||
this.roomEvents.get(RoomEvent.ChatMessage) !==
oldValues.get(RoomEvent.ChatMessage)
) {
this.unregisterRoomListener(RoomEvent.ChatMessage);
if (this.roomEvents.get(RoomEvent.ChatMessage)) {
this.registerRoomListener(
RoomEvent.ChatMessage,
(message: any, participant?: RemoteParticipant | LocalParticipant) => {
this.updateEventList(
RoomEvent.ChatMessage,
{ message, participant },
`${participant?.identity ?? 'unknown'}: ${message.message}`
);
}
);
}
}
if (
firstTime ||
this.roomEvents.get(RoomEvent.MetricsReceived) !==
oldValues.get(RoomEvent.MetricsReceived)
) {
this.unregisterRoomListener(RoomEvent.MetricsReceived);
if (this.roomEvents.get(RoomEvent.MetricsReceived)) {
this.registerRoomListener(
RoomEvent.MetricsReceived,
(metrics: any, participant?: Participant) => {
this.updateEventList(
RoomEvent.MetricsReceived,
{ metrics, participant },
`${participant?.identity ?? 'unknown'}`
);
}
);
}
}
if ( if (
firstTime || firstTime ||
this.roomEvents.get(RoomEvent.TranscriptionReceived) !== this.roomEvents.get(RoomEvent.TranscriptionReceived) !==
@ -1087,9 +1270,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.DataTrackPublished) !== this.roomEvents.get(RoomEvent.DataTrackPublished) !==
oldValues.get(RoomEvent.DataTrackPublished) oldValues.get(RoomEvent.DataTrackPublished)
) { ) {
this.room?.removeAllListeners(RoomEvent.DataTrackPublished); this.unregisterRoomListener(RoomEvent.DataTrackPublished);
if (this.roomEvents.get(RoomEvent.DataTrackPublished)) { if (this.roomEvents.get(RoomEvent.DataTrackPublished)) {
this.room!.on( this.registerRoomListener(
RoomEvent.DataTrackPublished, RoomEvent.DataTrackPublished,
(track: RemoteDataTrack) => { (track: RemoteDataTrack) => {
this.updateEventList( this.updateEventList(
@ -1107,9 +1290,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.DataTrackUnpublished) !== this.roomEvents.get(RoomEvent.DataTrackUnpublished) !==
oldValues.get(RoomEvent.DataTrackUnpublished) oldValues.get(RoomEvent.DataTrackUnpublished)
) { ) {
this.room?.removeAllListeners(RoomEvent.DataTrackUnpublished); this.unregisterRoomListener(RoomEvent.DataTrackUnpublished);
if (this.roomEvents.get(RoomEvent.DataTrackUnpublished)) { if (this.roomEvents.get(RoomEvent.DataTrackUnpublished)) {
this.room!.on( this.registerRoomListener(
RoomEvent.DataTrackUnpublished, RoomEvent.DataTrackUnpublished,
(sid: string) => { (sid: string) => {
this.updateEventList( this.updateEventList(
@ -1127,9 +1310,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.LocalDataTrackPublished) !== this.roomEvents.get(RoomEvent.LocalDataTrackPublished) !==
oldValues.get(RoomEvent.LocalDataTrackPublished) oldValues.get(RoomEvent.LocalDataTrackPublished)
) { ) {
this.room?.removeAllListeners(RoomEvent.LocalDataTrackPublished); this.unregisterRoomListener(RoomEvent.LocalDataTrackPublished);
if (this.roomEvents.get(RoomEvent.LocalDataTrackPublished)) { if (this.roomEvents.get(RoomEvent.LocalDataTrackPublished)) {
this.room!.on( this.registerRoomListener(
RoomEvent.LocalDataTrackPublished, RoomEvent.LocalDataTrackPublished,
(track: LocalDataTrack) => { (track: LocalDataTrack) => {
this.updateEventList( this.updateEventList(
@ -1147,9 +1330,9 @@ export class OpenviduInstanceComponent {
this.roomEvents.get(RoomEvent.LocalDataTrackUnpublished) !== this.roomEvents.get(RoomEvent.LocalDataTrackUnpublished) !==
oldValues.get(RoomEvent.LocalDataTrackUnpublished) oldValues.get(RoomEvent.LocalDataTrackUnpublished)
) { ) {
this.room?.removeAllListeners(RoomEvent.LocalDataTrackUnpublished); this.unregisterRoomListener(RoomEvent.LocalDataTrackUnpublished);
if (this.roomEvents.get(RoomEvent.LocalDataTrackUnpublished)) { if (this.roomEvents.get(RoomEvent.LocalDataTrackUnpublished)) {
this.room!.on( this.registerRoomListener(
RoomEvent.LocalDataTrackUnpublished, RoomEvent.LocalDataTrackUnpublished,
(sid: string) => { (sid: string) => {
this.updateEventList( this.updateEventList(
@ -1181,6 +1364,32 @@ export class OpenviduInstanceComponent {
async disconnectRoom() { async disconnectRoom() {
if (this.room) { if (this.room) {
// Clean up early listeners that were never handed off
if (this.earlyParticipantConnectedListener) {
this.room.removeListener(
RoomEvent.ParticipantConnected,
this.earlyParticipantConnectedListener as any
);
this.earlyParticipantConnectedListener = undefined;
}
for (const [key, listeners] of this.earlyParticipantListeners) {
const participant =
this.room.localParticipant.sid === key || this.room.localParticipant.identity === key
? this.room.localParticipant
: this.room.remoteParticipants.get(key);
if (participant) {
removeAllManagedListeners(participant, listeners);
}
}
this.earlyParticipantEvents.clear();
this.earlyParticipantListeners.clear();
for (const [, listeners] of this.earlyTrackListeners) {
// Track listeners are cleaned up by disconnect, but clear maps
listeners.clear();
}
this.earlyTrackEvents.clear();
this.earlyTrackListeners.clear();
await this.room.disconnect(); await this.room.disconnect();
delete this.room; delete this.room;
delete this.localTracks.audioTrack; delete this.localTracks.audioTrack;
@ -1189,6 +1398,58 @@ export class OpenviduInstanceComponent {
} }
} }
private registerEarlyParticipantListeners(participant: Participant) {
const key = participant.sid || participant.identity;
const buffer: TestAppEvent[] = [];
this.earlyParticipantEvents.set(key, buffer);
const listeners = registerParticipantEventListeners(
participant,
(eventType, eventContent, eventDescription) => {
if (this.participantEvents.size > 0 && !this.participantEvents.get(eventType)) return;
const event: TestAppEvent = {
eventType,
eventCategory: 'ParticipantEvent',
eventContent,
eventDescription,
};
buffer.push(event);
this.testFeedService.pushNewEvent({ user: this.index, event });
// When a track becomes available during early registration, register early track listeners too
if (eventType === ParticipantEvent.TrackSubscribed && eventContent.track) {
this.registerEarlyTrackListeners(eventContent.track);
} else if (eventType === ParticipantEvent.LocalTrackPublished && eventContent.publication?.track) {
this.registerEarlyTrackListeners(eventContent.publication.track);
}
},
this.decoder
);
this.earlyParticipantListeners.set(key, listeners);
}
private registerEarlyTrackListeners(track: Track) {
const key = track.sid || track.mediaStreamID;
const buffer: TestAppEvent[] = [];
this.earlyTrackEvents.set(key, buffer);
const listeners = registerTrackEventListeners(
track,
(eventType, eventContent, eventDescription) => {
if (this.trackEvents.size > 0 && !this.trackEvents.get(eventType)) return;
const event: TestAppEvent = {
eventType,
eventCategory: 'TrackEvent',
eventContent,
eventDescription,
};
buffer.push(event);
this.testFeedService.pushNewEvent({ user: this.index, event });
}
);
this.earlyTrackListeners.set(key, listeners);
}
async setCameraEnabled() { async setCameraEnabled() {
this.room!.localParticipant.setCameraEnabled(true); this.room!.localParticipant.setCameraEnabled(true);
} }
@ -1253,15 +1514,18 @@ export class OpenviduInstanceComponent {
}); });
} }
openRoomEventsDialog() { openAllEventsDialog() {
const oldValues: Map<string, boolean> = new Map( const oldRoomValues: Map<string, boolean> = new Map(
JSON.parse(JSON.stringify([...this.roomEvents])) JSON.parse(JSON.stringify([...this.roomEvents]))
); );
const dialogRef = this.dialog.open(EventsDialogComponent, { const dialogRef = this.dialog.open(EventsDialogComponent, {
data: { data: {
eventCollection: this.roomEvents, eventGroups: [
target: 'Session', { label: 'RoomEvent', eventCollection: this.roomEvents },
{ label: 'ParticipantEvent', eventCollection: this.participantEvents },
{ label: 'TrackEvent', eventCollection: this.trackEvents },
],
target: 'All',
}, },
width: '800px', width: '800px',
autoFocus: false, autoFocus: false,
@ -1272,9 +1536,9 @@ export class OpenviduInstanceComponent {
if ( if (
!!this.room && !!this.room &&
JSON.stringify(Array.from(this.roomEvents.entries())) !== JSON.stringify(Array.from(this.roomEvents.entries())) !==
JSON.stringify(Array.from(oldValues.entries())) JSON.stringify(Array.from(oldRoomValues.entries()))
) { ) {
this.setupRoomEventListeners(oldValues, false); this.setupRoomEventListeners(oldRoomValues, false);
} }
}); });
} }

View File

@ -105,13 +105,17 @@
@for (trackPublication of participant.audioTrackPublications| keyvalue; track trackPublication) { @for (trackPublication of participant.audioTrackPublications| keyvalue; track trackPublication) {
<app-audio-track <app-audio-track
[index]="index" [trackPublication]="trackPublication.value" [track]="trackPublication.value.audioTrack" [index]="index" [trackPublication]="trackPublication.value" [track]="trackPublication.value.audioTrack"
[localParticipant]="localParticipant" (newTrackEvent)="onTrackEvent($event)"></app-audio-track> [localParticipant]="localParticipant"
[earlyTrackEvents]="earlyTrackEvents" [earlyTrackListeners]="earlyTrackListeners"
(newTrackEvent)="onTrackEvent($event)"></app-audio-track>
} }
</div> </div>
@for (trackPublication of participant.videoTrackPublications | keyvalue; track trackPublication) { @for (trackPublication of participant.videoTrackPublications | keyvalue; track trackPublication) {
<app-video-track <app-video-track
[index]="index" [trackPublication]="trackPublication.value" [track]="trackPublication.value.videoTrack" [index]="index" [trackPublication]="trackPublication.value" [track]="trackPublication.value.videoTrack"
[localParticipant]="localParticipant" (newTrackEvent)="onTrackEvent($event)"></app-video-track> [localParticipant]="localParticipant"
[earlyTrackEvents]="earlyTrackEvents" [earlyTrackListeners]="earlyTrackListeners"
(newTrackEvent)="onTrackEvent($event)"></app-video-track>
} }
</div> </div>
} }

View File

@ -6,9 +6,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { MatExpansionModule } from '@angular/material/expansion'; import { MatExpansionModule } from '@angular/material/expansion';
import { import {
AudioCaptureOptions, AudioCaptureOptions,
ConnectionQuality,
CreateLocalTracksOptions, CreateLocalTracksOptions,
DataPacket_Kind,
LocalAudioTrack, LocalAudioTrack,
LocalDataTrack, LocalDataTrack,
LocalParticipant, LocalParticipant,
@ -23,7 +21,6 @@ import {
Room, Room,
RoomEvent, RoomEvent,
ScreenShareCaptureOptions, ScreenShareCaptureOptions,
SubscriptionError,
Track, Track,
TrackEvent, TrackEvent,
TrackPublication, TrackPublication,
@ -33,7 +30,6 @@ import {
createLocalScreenTracks, createLocalScreenTracks,
createLocalVideoTrack, createLocalVideoTrack,
} from 'livekit-client'; } from 'livekit-client';
import { ParticipantPermission } from 'livekit-server-sdk';
import { import {
TestAppEvent, TestAppEvent,
TestFeedService, TestFeedService,
@ -42,7 +38,10 @@ import { OptionsDialogComponent } from '../dialogs/options-dialog/options-dialog
import { VideoTrackComponent } from '../video-track/video-track.component'; import { VideoTrackComponent } from '../video-track/video-track.component';
import { AudioTrackComponent } from '../audio-track/audio-track.component'; import { AudioTrackComponent } from '../audio-track/audio-track.component';
import { DataTrackComponent } from '../data-track/data-track.component'; import { DataTrackComponent } from '../data-track/data-track.component';
import { ParticipantEventCallbacks } from 'node_modules/livekit-client/dist/src/room/participant/Participant'; import {
registerParticipantEventListeners,
removeAllManagedListeners,
} from 'src/app/utils/event-listener-utils';
@Component({ @Component({
selector: 'app-participant', selector: 'app-participant',
@ -60,6 +59,24 @@ export class ParticipantComponent {
@Input() @Input()
index: number; index: number;
@Input()
participantEvents: Map<string, boolean> = new Map();
@Input()
trackEvents: Map<string, boolean> = new Map();
@Input()
earlyParticipantEvents: Map<string, TestAppEvent[]> = new Map();
@Input()
earlyParticipantListeners: Map<string, Map<string, (...args: any[]) => void>> = new Map();
@Input()
earlyTrackEvents: Map<string, TestAppEvent[]> = new Map();
@Input()
earlyTrackListeners: Map<string, Map<string, (...args: any[]) => void>> = new Map();
@Output() @Output()
sendReliableDataToOneParticipant = new EventEmitter<string>(); sendReliableDataToOneParticipant = new EventEmitter<string>();
@ -80,6 +97,8 @@ export class ParticipantComponent {
trackPublishOptions?: TrackPublishOptions; trackPublishOptions?: TrackPublishOptions;
private decoder = new TextDecoder(); private decoder = new TextDecoder();
private participantEventListeners: Map<string, (...args: any[]) => void> = new Map();
private roomListenersFromParticipant: Map<string, (...args: any[]) => void> = new Map();
private dialog = inject(MatDialog); private dialog = inject(MatDialog);
@ -89,6 +108,19 @@ export class ParticipantComponent {
) {} ) {}
ngOnInit() { ngOnInit() {
// Drain early participant events buffered before this component existed
const key = this.participant.sid || this.participant.identity;
const earlyEvents = this.earlyParticipantEvents?.get(key);
if (earlyEvents) {
this.events.push(...earlyEvents);
this.earlyParticipantEvents.delete(key);
}
// Remove early listeners and replace with component-owned ones
const earlyListeners = this.earlyParticipantListeners?.get(key);
if (earlyListeners) {
removeAllManagedListeners(this.participant, earlyListeners);
this.earlyParticipantListeners.delete(key);
}
this.setupParticipantEventListeners(); this.setupParticipantEventListeners();
this.localParticipant = this.participant.isLocal this.localParticipant = this.participant.isLocal
? (this.participant as LocalParticipant) ? (this.participant as LocalParticipant)
@ -106,7 +138,11 @@ export class ParticipantComponent {
} }
onTrackEvent(event: TestAppEvent) { onTrackEvent(event: TestAppEvent) {
if (this.trackEvents.size > 0 && !this.trackEvents.get(event.eventType)) {
return;
}
this.events.push(event); this.events.push(event);
this.testFeedService.pushNewEvent({ user: this.index, event });
this.cdr.detectChanges(); this.cdr.detectChanges();
} }
@ -246,251 +282,16 @@ export class ParticipantComponent {
* [ParticipantEventCallbacks] * [ParticipantEventCallbacks]
*/ */
setupParticipantEventListeners() { setupParticipantEventListeners() {
// This is a link to the complete list of Participant events // Remove any previous listeners
let callbacks: ParticipantEventCallbacks; removeAllManagedListeners(this.participant, this.participantEventListeners);
let events: ParticipantEvent;
this.participant this.participantEventListeners = registerParticipantEventListeners(
this.participant,
.on( (eventType, eventContent, eventDescription) => {
ParticipantEvent.TrackPublished, this.updateEventList(eventType, 'ParticipantEvent', eventContent, eventDescription);
(publication: RemoteTrackPublication) => { },
this.updateEventList( this.decoder
ParticipantEvent.TrackPublished, );
'ParticipantEvent',
{ publication },
publication.source
);
}
)
.on(
ParticipantEvent.TrackSubscribed,
(track: RemoteTrack, publication: RemoteTrackPublication) => {
this.updateEventList(
ParticipantEvent.TrackSubscribed,
'ParticipantEvent',
{ track, publication },
publication.source
);
}
)
.on(
ParticipantEvent.TrackSubscriptionFailed,
(trackSid: string, reason?: SubscriptionError) => {
this.updateEventList(
ParticipantEvent.TrackSubscriptionFailed,
'ParticipantEvent',
{ trackSid, reason },
trackSid +
' . Reason: ' +
(reason ? SubscriptionError[reason] : reason)
);
}
)
.on(
ParticipantEvent.TrackUnpublished,
(publication: RemoteTrackPublication) => {
this.updateEventList(
ParticipantEvent.TrackUnpublished,
'ParticipantEvent',
{ publication },
publication.source
);
}
)
.on(
ParticipantEvent.TrackUnsubscribed,
(track: RemoteTrack, publication: RemoteTrackPublication) => {
this.updateEventList(
ParticipantEvent.TrackUnsubscribed,
'ParticipantEvent',
{ track, publication },
track.source
);
}
)
.on(ParticipantEvent.TrackMuted, (publication: TrackPublication) => {
this.updateEventList(
ParticipantEvent.TrackMuted,
'ParticipantEvent',
{ publication },
publication.source
);
})
.on(ParticipantEvent.TrackUnmuted, (publication: TrackPublication) => {
this.updateEventList(
ParticipantEvent.TrackUnmuted,
'ParticipantEvent',
{ publication },
publication.source
);
})
.on(
ParticipantEvent.LocalTrackPublished,
(publication: LocalTrackPublication) => {
this.updateEventList(
ParticipantEvent.LocalTrackPublished,
'ParticipantEvent',
{ publication },
publication.source
);
}
)
.on(
ParticipantEvent.LocalTrackUnpublished,
(publication: LocalTrackPublication) => {
this.updateEventList(
ParticipantEvent.LocalTrackUnpublished,
'ParticipantEvent',
{ publication },
publication.source
);
}
)
.on(
ParticipantEvent.ParticipantMetadataChanged,
(prevMetadata: string | undefined) => {
this.updateEventList(
ParticipantEvent.ParticipantMetadataChanged,
'ParticipantEvent',
{ prevMetadata },
`previous: ${prevMetadata}, new: ${this.participant.metadata}`
);
}
)
.on(ParticipantEvent.ParticipantNameChanged, (name: string) => {
this.updateEventList(
ParticipantEvent.ParticipantNameChanged,
'ParticipantEvent',
{ name },
`${name}`
);
})
.on(
ParticipantEvent.DataReceived,
(payload: Uint8Array, kind: DataPacket_Kind) => {
let decodedPayload = this.decoder.decode(payload);
decodedPayload += ` (kind: ${DataPacket_Kind[kind]})`;
this.updateEventList(
ParticipantEvent.DataReceived,
'ParticipantEvent',
{ payload: decodedPayload, kind },
decodedPayload
);
}
)
.on(ParticipantEvent.IsSpeakingChanged, (speaking: boolean) => {
this.updateEventList(
ParticipantEvent.IsSpeakingChanged,
'ParticipantEvent',
{ speaking },
`${speaking}`
);
})
.on(
ParticipantEvent.ConnectionQualityChanged,
(connectionQuality: ConnectionQuality) => {
this.updateEventList(
ParticipantEvent.ConnectionQualityChanged,
'ParticipantEvent',
{ connectionQuality },
`${connectionQuality}`
);
}
)
.on(
ParticipantEvent.TrackStreamStateChanged,
(
publication: RemoteTrackPublication,
streamState: Track.StreamState
) => {
this.updateEventList(
ParticipantEvent.TrackStreamStateChanged,
'ParticipantEvent',
{ publication, streamState },
`${publication.source}: ${streamState}`
);
}
)
.on(
ParticipantEvent.TrackSubscriptionPermissionChanged,
(
publication: RemoteTrackPublication,
status: TrackPublication.PermissionStatus
) => {
this.updateEventList(
ParticipantEvent.TrackSubscriptionPermissionChanged,
'ParticipantEvent',
{ publication, status },
`${publication.source}: ${status}`
);
}
)
.on(ParticipantEvent.MediaDevicesError, (error: Error) => {
this.updateEventList(
ParticipantEvent.MediaDevicesError,
'ParticipantEvent',
{ error },
`${error.message}`
);
})
.on(
ParticipantEvent.ParticipantPermissionsChanged,
(prevPermissions?: ParticipantPermission) => {
this.updateEventList(
ParticipantEvent.ParticipantPermissionsChanged,
'ParticipantEvent',
{ prevPermissions },
`previous: ${prevPermissions}, new: ${JSON.stringify(
this.participant.permissions
)}`
);
}
)
.on(
ParticipantEvent.TrackSubscriptionStatusChanged,
(
publication: RemoteTrackPublication,
status: TrackPublication.SubscriptionStatus
) => {
this.updateEventList(
ParticipantEvent.TrackSubscriptionStatusChanged,
'ParticipantEvent',
{ publication, status },
`${publication.source}: ${status}`
);
}
)
.on(
ParticipantEvent.LocalTrackSubscribed,
(trackPublication: LocalTrackPublication) => {
this.updateEventList(
ParticipantEvent.LocalTrackSubscribed,
'ParticipantEvent',
{ trackPublication },
trackPublication.source
);
}
);
} }
updateEventList( updateEventList(
@ -499,6 +300,9 @@ export class ParticipantComponent {
eventContent: any, eventContent: any,
eventDescription: string eventDescription: string
) { ) {
if (this.participantEvents.size > 0 && !this.participantEvents.get(eventType)) {
return;
}
const event: TestAppEvent = { const event: TestAppEvent = {
eventType, eventType,
eventCategory, eventCategory,
@ -518,23 +322,22 @@ export class ParticipantComponent {
} }
private setupDataTrackListeners() { private setupDataTrackListeners() {
this.room.on( const publishedListener = (track: RemoteDataTrack) => {
RoomEvent.DataTrackPublished, if (track.publisherIdentity === this.participant.identity) {
(track: RemoteDataTrack) => { this.remoteDataTracks.push(track);
if (track.publisherIdentity === this.participant.identity) {
this.remoteDataTracks.push(track);
this.cdr.detectChanges();
}
}
);
this.room.on(
RoomEvent.DataTrackUnpublished,
(sid: string) => {
this.remoteDataTracks = this.remoteDataTracks.filter(
(t) => t.info.sid !== sid
);
this.cdr.detectChanges(); this.cdr.detectChanges();
} }
); };
this.room.addListener(RoomEvent.DataTrackPublished, publishedListener);
this.roomListenersFromParticipant.set(RoomEvent.DataTrackPublished, publishedListener as any);
const unpublishedListener = (sid: string) => {
this.remoteDataTracks = this.remoteDataTracks.filter(
(t) => t.info.sid !== sid
);
this.cdr.detectChanges();
};
this.room.addListener(RoomEvent.DataTrackUnpublished, unpublishedListener);
this.roomListenersFromParticipant.set(RoomEvent.DataTrackUnpublished, unpublishedListener as any);
} }
} }

View File

@ -13,7 +13,6 @@ import {
TrackEvent, TrackEvent,
LocalTrack, LocalTrack,
RemoteTrack, RemoteTrack,
TrackEventCallbacks,
RemoteTrackPublication, RemoteTrackPublication,
AudioTrack, AudioTrack,
VideoTrack, VideoTrack,
@ -22,6 +21,10 @@ import {
TestAppEvent, TestAppEvent,
TestFeedService, TestFeedService,
} from 'src/app/services/test-feed.service'; } from 'src/app/services/test-feed.service';
import {
registerTrackEventListeners,
removeAllManagedListeners,
} from 'src/app/utils/event-listener-utils';
@Component({ @Component({
selector: 'app-track', selector: 'app-track',
@ -42,6 +45,12 @@ export class TrackComponent {
@Input() @Input()
localParticipant: LocalParticipant | undefined; localParticipant: LocalParticipant | undefined;
@Input()
earlyTrackEvents: Map<string, TestAppEvent[]> = new Map();
@Input()
earlyTrackListeners: Map<string, Map<string, (...args: any[]) => void>> = new Map();
protected finalElementRefId: string = ''; protected finalElementRefId: string = '';
private indexId: string; private indexId: string;
private trackId: string; private trackId: string;
@ -52,6 +61,8 @@ export class TrackComponent {
trackSubscribed: boolean = true; trackSubscribed: boolean = true;
trackEnabled: boolean = true; trackEnabled: boolean = true;
private trackEventListeners: Map<string, (...args: any[]) => void> = new Map();
constructor(protected testFeedService: TestFeedService) {} constructor(protected testFeedService: TestFeedService) {}
@Input() set index(index: number) { @Input() set index(index: number) {
@ -62,6 +73,23 @@ export class TrackComponent {
@Input() set track(track: AudioTrack | VideoTrack | undefined) { @Input() set track(track: AudioTrack | VideoTrack | undefined) {
this._track = track; this._track = track;
// Drain early track events buffered before this component existed
if (this._track) {
const key = this._track.sid || this._track.mediaStreamID;
const earlyEvents = this.earlyTrackEvents?.get(key);
if (earlyEvents) {
for (const event of earlyEvents) {
this.newTrackEvent.emit(event as any);
}
this.earlyTrackEvents.delete(key);
}
const earlyListeners = this.earlyTrackListeners?.get(key);
if (earlyListeners) {
removeAllManagedListeners(this._track, earlyListeners);
this.earlyTrackListeners.delete(key);
}
}
this.setupTrackEventListeners(); this.setupTrackEventListeners();
this.trackId = `-${this.getTrackOrigin()}--${this._track?.kind}--${ this.trackId = `-${this.getTrackOrigin()}--${this._track?.kind}--${
@ -115,93 +143,22 @@ export class TrackComponent {
} }
protected setupTrackEventListeners() { protected setupTrackEventListeners() {
// This is a link to the complete list of Track events if (!this._track) return;
let callbacks: TrackEventCallbacks;
let events: TrackEvent;
this._track // Clear previous listeners (in case track changed)
?.on(TrackEvent.Message, () => { removeAllManagedListeners(this._track, this.trackEventListeners);
this.trackEventListeners = registerTrackEventListeners(
this._track,
(eventType, eventContent, eventDescription) => {
this.newTrackEvent.emit({ this.newTrackEvent.emit({
eventType: TrackEvent.Message, eventType,
eventCategory: 'TrackEvent', eventCategory: 'TrackEvent',
eventContent: {}, eventContent,
eventDescription: this._track!.source, eventDescription,
}); });
}) }
.on(TrackEvent.Muted, () => { );
this.newTrackEvent.emit({
eventType: TrackEvent.Muted,
eventCategory: 'TrackEvent',
eventContent: {},
eventDescription: this._track!.source,
});
})
.on(TrackEvent.Unmuted, () => {
this.newTrackEvent.emit({
eventType: TrackEvent.Unmuted,
eventCategory: 'TrackEvent',
eventContent: {},
eventDescription: this._track!.source,
});
})
.on(TrackEvent.AudioSilenceDetected, () => {
this.newTrackEvent.emit({
eventType: TrackEvent.AudioSilenceDetected,
eventCategory: 'TrackEvent',
eventContent: {},
eventDescription: this._track!.source,
});
})
.on(TrackEvent.Restarted, () => {
this.newTrackEvent.emit({
eventType: TrackEvent.Restarted,
eventCategory: 'TrackEvent',
eventContent: {},
eventDescription: this._track!.source,
});
})
.on(TrackEvent.Ended, () => {
this.newTrackEvent.emit({
eventType: TrackEvent.Ended,
eventCategory: 'TrackEvent',
eventContent: {},
eventDescription: this._track!.source,
});
})
.on(TrackEvent.VisibilityChanged, (visible: boolean) => {
this.newTrackEvent.emit({
eventType: TrackEvent.VisibilityChanged,
eventCategory: 'TrackEvent',
eventContent: { visible, track: this._track },
eventDescription: `${this._track!.source} is visible: ${visible}`,
});
})
.on(TrackEvent.VideoDimensionsChanged, (dimensions: Track.Dimensions) => {
this.newTrackEvent.emit({
eventType: TrackEvent.VideoDimensionsChanged,
eventCategory: 'TrackEvent',
eventContent: { dimensions, track: this._track },
eventDescription: `${this._track?.source} ${JSON.stringify(
dimensions
)}`,
});
})
.on(TrackEvent.UpstreamPaused, () => {
this.newTrackEvent.emit({
eventType: TrackEvent.UpstreamPaused,
eventCategory: 'TrackEvent',
eventContent: {},
eventDescription: this._track!.source,
});
})
.on(TrackEvent.UpstreamResumed, () => {
this.newTrackEvent.emit({
eventType: TrackEvent.UpstreamResumed,
eventCategory: 'TrackEvent',
eventContent: {},
eventDescription: this._track!.source,
});
});
} }
protected getTrackOrigin(): string { protected getTrackOrigin(): string {

View File

@ -0,0 +1,331 @@
import {
ConnectionQuality,
DataPacket_Kind,
LocalTrackPublication,
LocalVideoTrack,
Participant,
ParticipantEvent,
RemoteTrack,
RemoteTrackPublication,
SubscriptionError,
Track,
TrackEvent,
TrackPublication,
} from 'livekit-client';
import type { TranscriptionSegment, ChatMessage } from 'livekit-client';
import { ParticipantPermission } from 'livekit-server-sdk';
export type ParticipantOnEvent = (
eventType: ParticipantEvent,
eventContent: any,
eventDescription: string
) => void;
export type TrackOnEvent = (
eventType: TrackEvent,
eventContent: any,
eventDescription: string
) => void;
/**
* Registers ALL participant event listeners on the given participant.
* Returns the listener map for later cleanup/replacement.
*/
export function registerParticipantEventListeners(
participant: Participant,
onEvent: ParticipantOnEvent,
decoder: TextDecoder
): Map<string, (...args: any[]) => void> {
const listeners = new Map<string, (...args: any[]) => void>();
const reg = (event: ParticipantEvent, listener: (...args: any[]) => void) => {
participant.addListener(event as any, listener as any);
listeners.set(event, listener);
};
reg(ParticipantEvent.TrackPublished, (publication: RemoteTrackPublication) => {
onEvent(ParticipantEvent.TrackPublished, { publication }, publication.source);
});
reg(ParticipantEvent.TrackSubscribed, (track: RemoteTrack, publication: RemoteTrackPublication) => {
onEvent(ParticipantEvent.TrackSubscribed, { track, publication }, publication.source);
});
reg(ParticipantEvent.TrackSubscriptionFailed, (trackSid: string, reason?: SubscriptionError) => {
onEvent(
ParticipantEvent.TrackSubscriptionFailed,
{ trackSid, reason },
trackSid + ' . Reason: ' + (reason ? SubscriptionError[reason] : reason)
);
});
reg(ParticipantEvent.TrackUnpublished, (publication: RemoteTrackPublication) => {
onEvent(ParticipantEvent.TrackUnpublished, { publication }, publication.source);
});
reg(ParticipantEvent.TrackUnsubscribed, (track: RemoteTrack, publication: RemoteTrackPublication) => {
onEvent(ParticipantEvent.TrackUnsubscribed, { track, publication }, track.source);
});
reg(ParticipantEvent.TrackMuted, (publication: TrackPublication) => {
onEvent(ParticipantEvent.TrackMuted, { publication }, publication.source);
});
reg(ParticipantEvent.TrackUnmuted, (publication: TrackPublication) => {
onEvent(ParticipantEvent.TrackUnmuted, { publication }, publication.source);
});
reg(ParticipantEvent.LocalTrackPublished, (publication: LocalTrackPublication) => {
onEvent(ParticipantEvent.LocalTrackPublished, { publication }, publication.source);
});
reg(ParticipantEvent.LocalTrackUnpublished, (publication: LocalTrackPublication) => {
onEvent(ParticipantEvent.LocalTrackUnpublished, { publication }, publication.source);
});
reg(ParticipantEvent.ParticipantMetadataChanged, (prevMetadata: string | undefined) => {
onEvent(
ParticipantEvent.ParticipantMetadataChanged,
{ prevMetadata },
`previous: ${prevMetadata}, new: ${participant.metadata}`
);
});
reg(ParticipantEvent.ParticipantNameChanged, (name: string) => {
onEvent(ParticipantEvent.ParticipantNameChanged, { name }, `${name}`);
});
reg(ParticipantEvent.DataReceived, (payload: Uint8Array, kind: DataPacket_Kind) => {
let decodedPayload = decoder.decode(payload);
decodedPayload += ` (kind: ${DataPacket_Kind[kind]})`;
onEvent(ParticipantEvent.DataReceived, { payload: decodedPayload, kind }, decodedPayload);
});
reg(ParticipantEvent.IsSpeakingChanged, (speaking: boolean) => {
onEvent(ParticipantEvent.IsSpeakingChanged, { speaking }, `${speaking}`);
});
reg(ParticipantEvent.ConnectionQualityChanged, (connectionQuality: ConnectionQuality) => {
onEvent(ParticipantEvent.ConnectionQualityChanged, { connectionQuality }, `${connectionQuality}`);
});
reg(
ParticipantEvent.TrackStreamStateChanged,
(publication: RemoteTrackPublication, streamState: Track.StreamState) => {
onEvent(
ParticipantEvent.TrackStreamStateChanged,
{ publication, streamState },
`${publication.source}: ${streamState}`
);
}
);
reg(
ParticipantEvent.TrackSubscriptionPermissionChanged,
(publication: RemoteTrackPublication, status: TrackPublication.PermissionStatus) => {
onEvent(
ParticipantEvent.TrackSubscriptionPermissionChanged,
{ publication, status },
`${publication.source}: ${status}`
);
}
);
reg(ParticipantEvent.MediaDevicesError, (error: Error) => {
onEvent(ParticipantEvent.MediaDevicesError, { error }, `${error.message}`);
});
reg(ParticipantEvent.ParticipantPermissionsChanged, (prevPermissions?: ParticipantPermission) => {
onEvent(
ParticipantEvent.ParticipantPermissionsChanged,
{ prevPermissions },
`previous: ${prevPermissions}, new: ${JSON.stringify(participant.permissions)}`
);
});
reg(
ParticipantEvent.TrackSubscriptionStatusChanged,
(publication: RemoteTrackPublication, status: TrackPublication.SubscriptionStatus) => {
onEvent(
ParticipantEvent.TrackSubscriptionStatusChanged,
{ publication, status },
`${publication.source}: ${status}`
);
}
);
reg(ParticipantEvent.LocalTrackSubscribed, (trackPublication: LocalTrackPublication) => {
onEvent(ParticipantEvent.LocalTrackSubscribed, { trackPublication }, trackPublication.source);
});
reg(
ParticipantEvent.LocalTrackCpuConstrained,
(track: LocalVideoTrack, publication: LocalTrackPublication) => {
onEvent(ParticipantEvent.LocalTrackCpuConstrained, { track, publication }, publication.source);
}
);
reg(ParticipantEvent.SipDTMFReceived, (dtmf: any) => {
onEvent(ParticipantEvent.SipDTMFReceived, { dtmf }, JSON.stringify(dtmf));
});
reg(
ParticipantEvent.TranscriptionReceived,
(transcription: TranscriptionSegment[], publication?: TrackPublication) => {
onEvent(
ParticipantEvent.TranscriptionReceived,
{ transcription, publication },
`segments: ${transcription.length}, source: ${publication?.source ?? 'unknown'}`
);
}
);
reg(ParticipantEvent.AudioStreamAcquired, () => {
onEvent(ParticipantEvent.AudioStreamAcquired, {}, '');
});
reg(ParticipantEvent.AttributesChanged, (changedAttributes: Record<string, string>) => {
onEvent(ParticipantEvent.AttributesChanged, { changedAttributes }, JSON.stringify(changedAttributes));
});
reg(ParticipantEvent.ChatMessage, (msg: ChatMessage) => {
onEvent(ParticipantEvent.ChatMessage, { msg }, msg.message);
});
reg(ParticipantEvent.Active, () => {
onEvent(ParticipantEvent.Active, {}, '');
});
return listeners;
}
/**
* Removes all listeners in the given map from the target emitter.
*/
export function removeAllManagedListeners(
target: any,
listeners: Map<string, (...args: any[]) => void>
) {
for (const [event, listener] of listeners) {
target.removeListener(event, listener);
}
listeners.clear();
}
/**
* Registers ALL track event listeners on the given track.
* Returns the listener map for later cleanup/replacement.
*/
export function registerTrackEventListeners(
track: Track,
onEvent: TrackOnEvent
): Map<string, (...args: any[]) => void> {
const listeners = new Map<string, (...args: any[]) => void>();
const reg = (event: TrackEvent, listener: (...args: any[]) => void) => {
track.addListener(event as any, listener as any);
listeners.set(event, listener);
};
reg(TrackEvent.Message, () => {
onEvent(TrackEvent.Message, {}, track.source);
});
reg(TrackEvent.Muted, () => {
onEvent(TrackEvent.Muted, {}, track.source);
});
reg(TrackEvent.Unmuted, () => {
onEvent(TrackEvent.Unmuted, {}, track.source);
});
reg(TrackEvent.Restarted, () => {
onEvent(TrackEvent.Restarted, {}, track.source);
});
reg(TrackEvent.Ended, () => {
onEvent(TrackEvent.Ended, {}, track.source);
});
reg(TrackEvent.CpuConstrained, () => {
onEvent(TrackEvent.CpuConstrained, {}, track.source);
});
reg(TrackEvent.UpdateSettings, () => {
onEvent(TrackEvent.UpdateSettings, {}, track.source);
});
reg(TrackEvent.UpdateSubscription, () => {
onEvent(TrackEvent.UpdateSubscription, {}, track.source);
});
reg(TrackEvent.AudioPlaybackStarted, () => {
onEvent(TrackEvent.AudioPlaybackStarted, {}, track.source);
});
reg(TrackEvent.AudioPlaybackFailed, (error?: Error) => {
onEvent(TrackEvent.AudioPlaybackFailed, { error }, `${track.source} ${error?.message ?? ''}`);
});
reg(TrackEvent.AudioSilenceDetected, () => {
onEvent(TrackEvent.AudioSilenceDetected, {}, track.source);
});
reg(TrackEvent.VisibilityChanged, (visible: boolean) => {
onEvent(TrackEvent.VisibilityChanged, { visible, track }, `${track.source} is visible: ${visible}`);
});
reg(TrackEvent.VideoDimensionsChanged, (dimensions: Track.Dimensions) => {
onEvent(
TrackEvent.VideoDimensionsChanged,
{ dimensions, track },
`${track.source} ${JSON.stringify(dimensions)}`
);
});
reg(TrackEvent.VideoPlaybackStarted, () => {
onEvent(TrackEvent.VideoPlaybackStarted, {}, track.source);
});
reg(TrackEvent.VideoPlaybackFailed, (error?: Error) => {
onEvent(TrackEvent.VideoPlaybackFailed, { error }, `${track.source} ${error?.message ?? ''}`);
});
reg(TrackEvent.ElementAttached, (element: HTMLMediaElement) => {
onEvent(TrackEvent.ElementAttached, { element }, track.source);
});
reg(TrackEvent.ElementDetached, (element: HTMLMediaElement) => {
onEvent(TrackEvent.ElementDetached, { element }, track.source);
});
reg(TrackEvent.UpstreamPaused, () => {
onEvent(TrackEvent.UpstreamPaused, {}, track.source);
});
reg(TrackEvent.UpstreamResumed, () => {
onEvent(TrackEvent.UpstreamResumed, {}, track.source);
});
reg(TrackEvent.TrackProcessorUpdate, (processor?: any) => {
onEvent(TrackEvent.TrackProcessorUpdate, { processor }, track.source);
});
reg(TrackEvent.AudioTrackFeatureUpdate, (_track: any, feature: any, enabled: boolean) => {
onEvent(
TrackEvent.AudioTrackFeatureUpdate,
{ feature, enabled },
`${track.source} feature: ${feature}, enabled: ${enabled}`
);
});
reg(TrackEvent.TimeSyncUpdate, (update: { timestamp: number; rtpTimestamp: number }) => {
onEvent(TrackEvent.TimeSyncUpdate, { update }, `${track.source} timestamp: ${update.timestamp}`);
});
reg(TrackEvent.PreConnectBufferFlushed, (buffer: any) => {
onEvent(TrackEvent.PreConnectBufferFlushed, { buffer }, track.source);
});
return listeners;
}