openvidu/openvidu-testapp/src/app/components/participant/participant.component.ts

344 lines
10 KiB
TypeScript
Raw Normal View History

2026-03-31 12:06:23 +02:00
import { Component, EventEmitter, Input, Output, ChangeDetectorRef, inject } from '@angular/core';
import { NgClass, KeyValuePipe } from '@angular/common';
import { MatDialog } from '@angular/material/dialog';
2026-03-31 12:06:23 +02:00
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatExpansionModule } from '@angular/material/expansion';
2024-07-02 19:19:05 +02:00
import {
AudioCaptureOptions,
CreateLocalTracksOptions,
2024-07-02 19:19:05 +02:00
LocalAudioTrack,
LocalDataTrack,
2024-07-02 19:19:05 +02:00
LocalParticipant,
LocalTrack,
LocalTrackPublication,
LocalVideoTrack,
Participant,
ParticipantEvent,
RemoteDataTrack,
2024-07-02 19:19:05 +02:00
RemoteTrack,
RemoteTrackPublication,
Room,
RoomEvent,
2024-07-02 19:19:05 +02:00
ScreenShareCaptureOptions,
Track,
TrackEvent,
TrackPublication,
TrackPublishOptions,
2024-07-02 19:19:05 +02:00
VideoCaptureOptions,
createLocalAudioTrack,
createLocalScreenTracks,
createLocalVideoTrack,
} from 'livekit-client';
import {
TestAppEvent,
TestFeedService,
} from 'src/app/services/test-feed.service';
import { OptionsDialogComponent } from '../dialogs/options-dialog/options-dialog.component';
2026-03-31 12:06:23 +02:00
import { VideoTrackComponent } from '../video-track/video-track.component';
import { AudioTrackComponent } from '../audio-track/audio-track.component';
import { DataTrackComponent } from '../data-track/data-track.component';
import {
registerParticipantEventListeners,
removeAllManagedListeners,
} from 'src/app/utils/event-listener-utils';
2024-07-02 19:19:05 +02:00
@Component({
2025-02-19 12:03:43 +01:00
selector: 'app-participant',
templateUrl: './participant.component.html',
2026-03-31 12:06:23 +02:00
styleUrl: './participant.component.css',
imports: [NgClass, KeyValuePipe, MatIconModule, MatTooltipModule, MatExpansionModule, VideoTrackComponent, AudioTrackComponent, DataTrackComponent],
2024-07-02 19:19:05 +02:00
})
export class ParticipantComponent {
@Input()
participant: Participant;
@Input()
room: Room;
2024-07-02 19:19:05 +02:00
@Input()
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();
2024-07-02 19:19:05 +02:00
@Output()
sendReliableDataToOneParticipant = new EventEmitter<string>();
@Output()
sendLossyDataToOneParticipant = new EventEmitter<string>();
2024-07-02 19:19:05 +02:00
localParticipant: LocalParticipant | undefined;
events: TestAppEvent[] = [];
localDataTracks: LocalDataTrack[] = [];
remoteDataTracks: RemoteDataTrack[] = [];
dataTrackCounter: number = 1;
dataTrackName: string = 'data_track_1';
createLocalTracksOptions: CreateLocalTracksOptions;
screenShareCaptureOptions: ScreenShareCaptureOptions = {};
trackPublishOptions?: TrackPublishOptions;
2024-07-02 19:19:05 +02:00
private decoder = new TextDecoder();
private participantEventListeners: Map<string, (...args: any[]) => void> = new Map();
private roomListenersFromParticipant: Map<string, (...args: any[]) => void> = new Map();
2024-07-02 19:19:05 +02:00
2026-03-31 12:06:23 +02:00
private dialog = inject(MatDialog);
constructor(
private testFeedService: TestFeedService,
private cdr: ChangeDetectorRef
) {}
2024-07-02 19:19:05 +02:00
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);
}
2024-07-02 19:19:05 +02:00
this.setupParticipantEventListeners();
this.localParticipant = this.participant.isLocal
? (this.participant as LocalParticipant)
: undefined;
this.createLocalTracksOptions = {
audio: JSON.parse(JSON.stringify(this.room.options.audioCaptureDefaults)),
video: JSON.parse(JSON.stringify(this.room.options.videoCaptureDefaults)),
};
this.trackPublishOptions = JSON.parse(
JSON.stringify(this.room.options.publishDefaults)
);
if (!this.participant.isLocal) {
this.setupDataTrackListeners();
}
2024-07-02 19:19:05 +02:00
}
onTrackEvent(event: TestAppEvent) {
if (this.trackEvents.size > 0 && !this.trackEvents.get(event.eventType)) {
return;
}
this.events.push(event);
this.testFeedService.pushNewEvent({ user: this.index, event });
this.cdr.detectChanges();
}
async addDataTrack() {
const localParticipant = this.participant as LocalParticipant;
const track = await localParticipant.publishDataTrack({ name: this.dataTrackName });
this.localDataTracks.push(track);
this.dataTrackCounter++;
this.dataTrackName = 'data_track_' + this.dataTrackCounter;
this.cdr.detectChanges();
}
openDataTrackOptionsDialog() {
const dialogRef = this.dialog.open(OptionsDialogComponent, {
data: { dataTrackName: this.dataTrackName },
});
dialogRef.afterClosed().subscribe((result) => {
if (!!result) {
this.dataTrackName = result.dataTrackName;
}
});
}
onLocalDataTrackUnpublished(track: LocalDataTrack) {
this.localDataTracks = this.localDataTracks.filter((t) => t !== track);
this.cdr.detectChanges();
}
2024-07-02 19:19:05 +02:00
async addVideoTrack() {
const options =
this.createLocalTracksOptions.video === true
? undefined
: (this.createLocalTracksOptions.video as VideoCaptureOptions);
2024-07-02 19:19:05 +02:00
const localVideoTrack: LocalVideoTrack = await createLocalVideoTrack(
options
);
(this.participant as LocalParticipant).publishTrack(
localVideoTrack,
this.trackPublishOptions
2024-07-02 19:19:05 +02:00
);
}
async addAudioTrack() {
const options =
this.createLocalTracksOptions.audio === true
? undefined
: (this.createLocalTracksOptions.audio as AudioCaptureOptions);
2024-07-02 19:19:05 +02:00
const localAudioTrack: LocalAudioTrack = await createLocalAudioTrack(
options
);
(this.participant as LocalParticipant).publishTrack(
localAudioTrack,
this.trackPublishOptions
2024-07-02 19:19:05 +02:00
);
}
async addScreenTrack() {
const localScreenTracks: LocalTrack[] = await createLocalScreenTracks(
this.screenShareCaptureOptions
);
localScreenTracks.forEach((track) =>
(this.participant as LocalParticipant).publishTrack(
track,
this.trackPublishOptions
)
2024-07-02 19:19:05 +02:00
);
}
openVideoTrackOptionsDialog() {
const dialogRef = this.dialog.open(OptionsDialogComponent, {
data: {
createLocalTracksOptions: {
video: this.createLocalTracksOptions.video,
},
allowDisablingVideo: false,
},
});
dialogRef.afterClosed().subscribe((result) => {
if (!!result) {
if (typeof result.createLocalTracksOptions.video === 'boolean') {
this.createLocalTracksOptions.video =
this.room.options.videoCaptureDefaults!;
} else {
this.createLocalTracksOptions.video = result.createLocalTracksOptions
.video as VideoCaptureOptions;
}
}
});
}
openAudioTrackOptionsDialog() {
const dialogRef = this.dialog.open(OptionsDialogComponent, {
data: {
createLocalTracksOptions: {
audio: this.createLocalTracksOptions.audio,
},
allowDisablingAudio: false,
},
});
dialogRef.afterClosed().subscribe((result) => {
if (!!result) {
this.createLocalTracksOptions.audio =
result.createLocalTracksOptions.audio;
}
});
}
2024-07-02 19:19:05 +02:00
openScreenTrackOptionsDialog() {
const dialogRef = this.dialog.open(OptionsDialogComponent, {
data: {
shareScreen: true,
screenShareCaptureOptions: this.screenShareCaptureOptions,
allowDisablingScreen: false,
},
});
dialogRef.afterClosed().subscribe((result) => {
if (!!result) {
this.screenShareCaptureOptions = result.screenShareCaptureOptions;
}
});
}
2024-07-02 19:19:05 +02:00
openTrackPublishOptionsDialog() {
const dialogRef = this.dialog.open(OptionsDialogComponent, {
data: {
trackPublishOptions: this.trackPublishOptions,
},
});
dialogRef.afterClosed().subscribe((result) => {
if (!!result) {
this.trackPublishOptions = result.trackPublishOptions;
}
});
}
2024-07-02 19:19:05 +02:00
/**
* [ParticipantEventCallbacks]
*/
setupParticipantEventListeners() {
// Remove any previous listeners
removeAllManagedListeners(this.participant, this.participantEventListeners);
2024-07-02 19:19:05 +02:00
this.participantEventListeners = registerParticipantEventListeners(
this.participant,
(eventType, eventContent, eventDescription) => {
this.updateEventList(eventType, 'ParticipantEvent', eventContent, eventDescription);
},
this.decoder
);
2024-07-02 19:19:05 +02:00
}
updateEventList(
eventType: ParticipantEvent | TrackEvent,
eventCategory: 'ParticipantEvent' | 'TrackEvent',
eventContent: any,
eventDescription: string
) {
if (this.participantEvents.size > 0 && !this.participantEvents.get(eventType)) {
return;
}
2024-07-02 19:19:05 +02:00
const event: TestAppEvent = {
eventType,
eventCategory,
eventContent,
eventDescription,
};
this.events.push(event);
this.testFeedService.pushNewEvent({ user: this.index, event });
}
sendDataReliable() {
this.sendReliableDataToOneParticipant.emit(this.participant.identity);
}
sendDataLossy() {
this.sendLossyDataToOneParticipant.emit(this.participant.identity);
2024-07-02 19:19:05 +02:00
}
private setupDataTrackListeners() {
const publishedListener = (track: RemoteDataTrack) => {
if (track.publisherIdentity === this.participant.identity) {
this.remoteDataTracks.push(track);
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);
}
2024-07-02 19:19:05 +02:00
}