mirror of https://github.com/OpenVidu/openvidu.git
openvidu-testapp & openvidu-test-e2e: add Data Track e2e test
parent
8b3bfc3b69
commit
66001d8f11
|
|
@ -381,6 +381,127 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
gracefullyLeaveParticipants(user, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Data Tracks publish, subscribe, send and receive")
|
||||
void dataTracksTest() throws Exception {
|
||||
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome");
|
||||
|
||||
log.info("Data Tracks publish, subscribe, send and receive");
|
||||
|
||||
// Two participants, no audio/video publishing
|
||||
for (int i = 0; i < 2; i++) {
|
||||
WebElement addUserBtn = user.getDriver().findElement(By.id("add-user-btn"));
|
||||
addUserBtn.click();
|
||||
user.getDriver().findElement(By.cssSelector("#openvidu-instance-" + i + " .subscriber-checkbox")).click();
|
||||
user.getDriver().findElement(By.cssSelector("#openvidu-instance-" + i + " .publisher-checkbox")).click();
|
||||
}
|
||||
user.getDriver().findElements(By.className("connect-btn")).forEach(el -> el.sendKeys(Keys.ENTER));
|
||||
user.getEventManager().waitUntilEventReaches("signalConnected", "RoomEvent", 2);
|
||||
user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 2);
|
||||
user.getEventManager().waitUntilEventReaches("participantConnected", "RoomEvent", 1);
|
||||
user.getEventManager().waitUntilEventReaches("participantActive", "RoomEvent", 1);
|
||||
|
||||
// Participant 0 publishes a data track
|
||||
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .add-data-track-btn")).click();
|
||||
|
||||
// Wait for localDataTrackPublished on participant 0 and dataTrackPublished on participant 1
|
||||
user.getEventManager().waitUntilEventReaches(0, "localDataTrackPublished", "RoomEvent", 1);
|
||||
user.getEventManager().waitUntilEventReaches(1, "dataTrackPublished", "RoomEvent", 1);
|
||||
|
||||
// Verify event descriptions
|
||||
user.getDriver().findElements(By.cssSelector(".localDataTrackPublished-TestParticipant0 .event-content"))
|
||||
.forEach(el -> Assertions.assertTrue(el.getText().contains("data_track_1"),
|
||||
"Expected localDataTrackPublished to contain track name 'data_track_1'"));
|
||||
user.getDriver().findElements(By.cssSelector(".dataTrackPublished-TestParticipant1 .event-content"))
|
||||
.forEach(el -> Assertions.assertTrue(
|
||||
el.getText().contains("TestParticipant0") && el.getText().contains("data_track_1"),
|
||||
"Expected dataTrackPublished to contain publisher identity and track name"));
|
||||
|
||||
// Participant 0 does not receive its own dataTrackPublished
|
||||
Assertions.assertEquals(0, user.getEventManager().getNumEvents(0, "dataTrackPublished-RoomEvent").get(),
|
||||
"Publisher should not receive dataTrackPublished for its own data track");
|
||||
|
||||
// Participant 0 sends data frames
|
||||
org.openqa.selenium.JavascriptExecutor js = (org.openqa.selenium.JavascriptExecutor) user.getDriver();
|
||||
js.executeScript("document.querySelector('#openvidu-instance-0 .send-data-frame-btn').click()");
|
||||
js.executeScript("document.querySelector('#openvidu-instance-0 .send-data-frame-btn').click()");
|
||||
js.executeScript("document.querySelector('#openvidu-instance-0 .send-data-frame-btn').click()");
|
||||
|
||||
// Wait for frames to arrive at participant 1
|
||||
Thread.sleep(1000);
|
||||
Long frameCount = (Long) js.executeScript(
|
||||
"var el = document.querySelector('#openvidu-instance-1 .data-track-frame-count');" +
|
||||
"return el ? parseInt(el.textContent) : 0;");
|
||||
Assertions.assertEquals(frameCount, 3, "Expected 3 data track frames received, got " + frameCount);
|
||||
|
||||
String lastPayload = (String) js.executeScript(
|
||||
"var el = document.querySelector('#openvidu-instance-1 .data-track-last-payload');" +
|
||||
"return el ? el.textContent : '';");
|
||||
Assertions.assertTrue(lastPayload.contains("DataTrackFrame from TestParticipant0"),
|
||||
"Expected last payload to contain 'DataTrackFrame from TestParticipant0', got: " + lastPayload);
|
||||
|
||||
// Participant 0 unpublishes the data track
|
||||
js.executeScript("document.querySelector('#openvidu-instance-0 .unpublish-data-track-btn').click()");
|
||||
|
||||
// Wait for unpublish events
|
||||
user.getEventManager().waitUntilEventReaches(0, "localDataTrackUnpublished", "RoomEvent", 1);
|
||||
user.getEventManager().waitUntilEventReaches(1, "dataTrackUnpublished", "RoomEvent", 1);
|
||||
|
||||
// Participant 0 does not receive its own dataTrackUnpublished
|
||||
Assertions.assertEquals(0, user.getEventManager().getNumEvents(0, "dataTrackUnpublished-RoomEvent").get(),
|
||||
"Publisher should not receive dataTrackUnpublished for its own data track");
|
||||
|
||||
user.getEventManager().clearAllCurrentEvents();
|
||||
|
||||
// Now participant 1 publishes a data track with custom name (bidirectional test)
|
||||
String customTrackName = "my_custom_data_track";
|
||||
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .options-data-track-btn")).click();
|
||||
Thread.sleep(500);
|
||||
WebElement trackNameInput = user.getDriver().findElement(By.id("dataTrack-name"));
|
||||
trackNameInput.clear();
|
||||
trackNameInput.sendKeys(customTrackName);
|
||||
user.getDriver().findElement(By.id("close-dialog-btn")).click();
|
||||
Thread.sleep(500);
|
||||
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .add-data-track-btn")).click();
|
||||
|
||||
user.getEventManager().waitUntilEventReaches(1, "localDataTrackPublished", "RoomEvent", 1);
|
||||
user.getEventManager().waitUntilEventReaches(0, "dataTrackPublished", "RoomEvent", 1);
|
||||
|
||||
// Verify participant 0 sees the custom track name on the remote data track
|
||||
Thread.sleep(500);
|
||||
String remoteTrackName = (String) js.executeScript(
|
||||
"var el = document.querySelector('#openvidu-instance-0 .data-track-name');" +
|
||||
"return el ? el.textContent.trim() : '';");
|
||||
Assertions.assertEquals(customTrackName, remoteTrackName,
|
||||
"Remote data track name should be '" + customTrackName + "', got: " + remoteTrackName);
|
||||
|
||||
// Participant 1 sends a data frame
|
||||
js.executeScript("document.querySelector('#openvidu-instance-1 .send-data-frame-btn').click()");
|
||||
|
||||
// Wait for frame to arrive at participant 0
|
||||
Thread.sleep(1000);
|
||||
|
||||
Long frameCount0 = (Long) js.executeScript(
|
||||
"var el = document.querySelector('#openvidu-instance-0 .data-track-frame-count');" +
|
||||
"return el ? parseInt(el.textContent) : 0;");
|
||||
Assertions.assertTrue(frameCount0 >= 1,
|
||||
"Expected at least 1 data track frame received by participant 0, got " + frameCount0);
|
||||
|
||||
String lastPayload0 = (String) js.executeScript(
|
||||
"var el = document.querySelector('#openvidu-instance-0 .data-track-last-payload');" +
|
||||
"return el ? el.textContent : '';");
|
||||
Assertions.assertTrue(lastPayload0.contains("DataTrackFrame from TestParticipant1"),
|
||||
"Expected last payload to contain 'DataTrackFrame from TestParticipant1', got: " + lastPayload0);
|
||||
|
||||
// Participant 1 unpublishes
|
||||
js.executeScript("document.querySelector('#openvidu-instance-1 .unpublish-data-track-btn').click()");
|
||||
user.getEventManager().waitUntilEventReaches(1, "localDataTrackUnpublished", "RoomEvent", 1);
|
||||
user.getEventManager().waitUntilEventReaches(0, "dataTrackUnpublished", "RoomEvent", 1);
|
||||
|
||||
gracefullyLeaveParticipants(user, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("One2One only audio")
|
||||
void oneToOneOnlyAudioSession() throws Exception {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
.parent-div {
|
||||
display: grid;
|
||||
margin-bottom: 4px;
|
||||
background-color: #fcfcfc;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.data-track-name {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.bottom-div {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.data-btn {
|
||||
border: none;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
height: 16px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.data-btn:hover {
|
||||
color: #4d4d4d;
|
||||
}
|
||||
|
||||
.data-btn mat-icon {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.data-track-info {
|
||||
font-size: 10px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.data-track-frame-count {
|
||||
font-weight: bold;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.data-track-last-payload {
|
||||
color: #555;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<div class="parent-div">
|
||||
<span class="data-track-name">{{getTrackName()}}</span>
|
||||
<div class="bottom-div">
|
||||
@if (localParticipant && localDataTrack) {
|
||||
<button (click)="sendDataFrame()" class="data-btn send-data-frame-btn" matTooltip="Send data frame"
|
||||
matTooltipClass="custom-tooltip">
|
||||
<mat-icon aria-label="Send data frame" class="mat-icon material-icons" role="img"
|
||||
aria-hidden="true">send</mat-icon>
|
||||
</button>
|
||||
}
|
||||
@if (localParticipant && localDataTrack) {
|
||||
<button (click)="unpublishDataTrack()" class="data-btn unpublish-data-track-btn" matTooltip="Unpublish data track"
|
||||
matTooltipClass="custom-tooltip">
|
||||
<mat-icon aria-label="Unpublish data track" class="mat-icon material-icons" role="img"
|
||||
aria-hidden="true">stop</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@if (frameCount > 0) {
|
||||
<div class="data-track-info">
|
||||
<span class="data-track-frame-count">{{frameCount}}</span>
|
||||
<span class="data-track-last-payload">{{lastPayload}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import { Component, EventEmitter, Input, Output, ChangeDetectorRef } from '@angular/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import {
|
||||
LocalDataTrack,
|
||||
LocalParticipant,
|
||||
RemoteDataTrack,
|
||||
} from 'livekit-client';
|
||||
import {
|
||||
TestAppEvent,
|
||||
TestFeedService,
|
||||
} from 'src/app/services/test-feed.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-data-track',
|
||||
templateUrl: './data-track.component.html',
|
||||
styleUrl: './data-track.component.css',
|
||||
imports: [MatIconModule, MatTooltipModule],
|
||||
})
|
||||
export class DataTrackComponent {
|
||||
@Input()
|
||||
localParticipant: LocalParticipant | undefined;
|
||||
|
||||
@Input()
|
||||
index: number;
|
||||
|
||||
@Input()
|
||||
localDataTrack?: LocalDataTrack;
|
||||
|
||||
@Input()
|
||||
remoteDataTrack?: RemoteDataTrack;
|
||||
|
||||
@Output()
|
||||
newTrackEvent = new EventEmitter<TestAppEvent>();
|
||||
|
||||
@Output()
|
||||
trackUnpublished = new EventEmitter<LocalDataTrack>();
|
||||
|
||||
// Received frame info
|
||||
frameCount: number = 0;
|
||||
lastPayload: string = '';
|
||||
|
||||
private decoder = new TextDecoder();
|
||||
|
||||
constructor(
|
||||
private testFeedService: TestFeedService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.remoteDataTrack) {
|
||||
this.subscribeToRemoteDataTrack(this.remoteDataTrack);
|
||||
}
|
||||
}
|
||||
|
||||
sendDataFrame() {
|
||||
if (this.localDataTrack) {
|
||||
const payload = new TextEncoder().encode(
|
||||
`DataTrackFrame from ${this.localParticipant?.identity}`
|
||||
);
|
||||
this.localDataTrack.tryPush({ payload });
|
||||
}
|
||||
}
|
||||
|
||||
async unpublishDataTrack() {
|
||||
if (this.localDataTrack) {
|
||||
const track = this.localDataTrack;
|
||||
await track.unpublish();
|
||||
this.trackUnpublished.emit(track);
|
||||
}
|
||||
}
|
||||
|
||||
private async subscribeToRemoteDataTrack(track: RemoteDataTrack) {
|
||||
const stream = track.subscribe();
|
||||
const reader = stream.getReader();
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
this.frameCount++;
|
||||
this.lastPayload = this.decoder.decode(value.payload);
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
} catch (_) {
|
||||
// Stream closed
|
||||
}
|
||||
}
|
||||
|
||||
getTrackName(): string {
|
||||
if (this.localDataTrack?.info) {
|
||||
return this.localDataTrack.info.name;
|
||||
}
|
||||
if (this.remoteDataTrack) {
|
||||
return this.remoteDataTrack.info.name;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
@ -233,6 +233,16 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
@if (dataTrackName !== undefined) {
|
||||
<mat-divider></mat-divider>
|
||||
<div>
|
||||
<label>DataTrackOptions</label><br>
|
||||
<mat-form-field>
|
||||
<mat-label>Track Name</mat-label>
|
||||
<input matInput id="dataTrack-name" placeholder="data_track_1" [(ngModel)]="dataTrackName"/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ export class OptionsDialogComponent {
|
|||
allowDisablingVideo = true;
|
||||
allowDisablingScreen = true;
|
||||
|
||||
dataTrackName?: string;
|
||||
|
||||
videoOption: true | false | 'custom';
|
||||
audioOption: true | false | 'custom';
|
||||
screenOption: true | false | 'custom';
|
||||
|
|
@ -62,6 +64,7 @@ export class OptionsDialogComponent {
|
|||
allowDisablingAudio?: boolean;
|
||||
allowDisablingVideo?: boolean;
|
||||
allowDisablingScreen?: boolean;
|
||||
dataTrackName?: string;
|
||||
}>(MAT_DIALOG_DATA);
|
||||
|
||||
constructor(
|
||||
|
|
@ -114,6 +117,9 @@ export class OptionsDialogComponent {
|
|||
if (this.data.allowDisablingScreen === false) {
|
||||
this.allowDisablingScreen = false;
|
||||
}
|
||||
if (this.data.dataTrackName !== undefined) {
|
||||
this.dataTrackName = this.data.dataTrackName;
|
||||
}
|
||||
Room.getLocalDevices('videoinput').then((devices) => {
|
||||
this.inputVideoDevices = devices;
|
||||
});
|
||||
|
|
@ -152,6 +158,7 @@ export class OptionsDialogComponent {
|
|||
createLocalTracksOptions: this.createLocalTracksOptions,
|
||||
screenShareCaptureOptions: this.screenShareCaptureOptions,
|
||||
trackPublishOptions: this.trackPublishOptions,
|
||||
dataTrackName: this.dataTrackName,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
DataPublishOptions,
|
||||
DisconnectReason,
|
||||
LocalAudioTrack,
|
||||
LocalDataTrack,
|
||||
LocalParticipant,
|
||||
LocalTrack,
|
||||
LocalTrackPublication,
|
||||
|
|
@ -28,6 +29,7 @@ import {
|
|||
MediaDeviceFailure,
|
||||
Participant,
|
||||
RemoteAudioTrack,
|
||||
RemoteDataTrack,
|
||||
RemoteParticipant,
|
||||
RemoteTrack,
|
||||
RemoteTrackPublication,
|
||||
|
|
@ -143,7 +145,7 @@ export class OpenviduInstanceComponent {
|
|||
this.participantName += this.index;
|
||||
if (this.roomConf.startSession) {
|
||||
const token = await this.roomApiService.createToken(
|
||||
{ roomJoin: true },
|
||||
{ roomJoin: true, canPublishData: true },
|
||||
this.participantName,
|
||||
this.roomName
|
||||
);
|
||||
|
|
@ -163,7 +165,7 @@ export class OpenviduInstanceComponent {
|
|||
async createTokenAndConnectRoom() {
|
||||
this.connectRoom(
|
||||
await this.roomApiService.createToken(
|
||||
{ roomJoin: true },
|
||||
{ roomJoin: true, canPublishData: true },
|
||||
this.participantName,
|
||||
this.roomName
|
||||
)
|
||||
|
|
@ -1078,6 +1080,86 @@ export class OpenviduInstanceComponent {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
firstTime ||
|
||||
this.roomEvents.get(RoomEvent.DataTrackPublished) !==
|
||||
oldValues.get(RoomEvent.DataTrackPublished)
|
||||
) {
|
||||
this.room?.removeAllListeners(RoomEvent.DataTrackPublished);
|
||||
if (this.roomEvents.get(RoomEvent.DataTrackPublished)) {
|
||||
this.room!.on(
|
||||
RoomEvent.DataTrackPublished,
|
||||
(track: RemoteDataTrack) => {
|
||||
this.updateEventList(
|
||||
RoomEvent.DataTrackPublished,
|
||||
{ name: track.info.name, sid: track.info.sid, publisherIdentity: track.publisherIdentity },
|
||||
`${track.publisherIdentity} (${track.info.name})`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
firstTime ||
|
||||
this.roomEvents.get(RoomEvent.DataTrackUnpublished) !==
|
||||
oldValues.get(RoomEvent.DataTrackUnpublished)
|
||||
) {
|
||||
this.room?.removeAllListeners(RoomEvent.DataTrackUnpublished);
|
||||
if (this.roomEvents.get(RoomEvent.DataTrackUnpublished)) {
|
||||
this.room!.on(
|
||||
RoomEvent.DataTrackUnpublished,
|
||||
(sid: string) => {
|
||||
this.updateEventList(
|
||||
RoomEvent.DataTrackUnpublished,
|
||||
{ sid },
|
||||
`sid: ${sid}`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
firstTime ||
|
||||
this.roomEvents.get(RoomEvent.LocalDataTrackPublished) !==
|
||||
oldValues.get(RoomEvent.LocalDataTrackPublished)
|
||||
) {
|
||||
this.room?.removeAllListeners(RoomEvent.LocalDataTrackPublished);
|
||||
if (this.roomEvents.get(RoomEvent.LocalDataTrackPublished)) {
|
||||
this.room!.on(
|
||||
RoomEvent.LocalDataTrackPublished,
|
||||
(track: LocalDataTrack) => {
|
||||
this.updateEventList(
|
||||
RoomEvent.LocalDataTrackPublished,
|
||||
{ name: track.info?.name, sid: track.info?.sid },
|
||||
`${track.info?.name}`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
firstTime ||
|
||||
this.roomEvents.get(RoomEvent.LocalDataTrackUnpublished) !==
|
||||
oldValues.get(RoomEvent.LocalDataTrackUnpublished)
|
||||
) {
|
||||
this.room?.removeAllListeners(RoomEvent.LocalDataTrackUnpublished);
|
||||
if (this.roomEvents.get(RoomEvent.LocalDataTrackUnpublished)) {
|
||||
this.room!.on(
|
||||
RoomEvent.LocalDataTrackUnpublished,
|
||||
(sid: string) => {
|
||||
this.updateEventList(
|
||||
RoomEvent.LocalDataTrackUnpublished,
|
||||
{ sid },
|
||||
`sid: ${sid}`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateEventList(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,18 @@
|
|||
<p class="participant-identity" [ngClass]="{'local-participant-identity' : participant.isLocal}">
|
||||
{{participant.identity}}</p>
|
||||
<div class="participant-buttons">
|
||||
@if (participant.isLocal) {
|
||||
<button class="add-data-track-btn" (click)="addDataTrack()" title="New data track"
|
||||
matTooltip="New data track" matTooltipClass="custom-tooltip">
|
||||
<mat-icon aria-label="New data track">stream</mat-icon>
|
||||
</button>
|
||||
}
|
||||
@if (participant.isLocal) {
|
||||
<button class="options-data-track-btn" (click)="openDataTrackOptionsDialog()"
|
||||
title="Data track options" matTooltip="Data track options" matTooltipClass="custom-tooltip">
|
||||
<mat-icon aria-label="Data track options">more_vert</mat-icon>
|
||||
</button>
|
||||
}
|
||||
@if (participant.isLocal) {
|
||||
<button class="add-audio-btn" (click)="addAudioTrack()" title="New audio track"
|
||||
matTooltip="New audio track" matTooltipClass="custom-tooltip">
|
||||
|
|
@ -71,6 +83,24 @@
|
|||
}
|
||||
</mat-accordion>
|
||||
</div>
|
||||
<div class="data-tracks-container">
|
||||
@for (track of localDataTracks; track track.info?.sid) {
|
||||
<app-data-track
|
||||
[index]="index"
|
||||
[localDataTrack]="track"
|
||||
[localParticipant]="localParticipant"
|
||||
(trackUnpublished)="onLocalDataTrackUnpublished($event)"
|
||||
(newTrackEvent)="onTrackEvent($event)">
|
||||
</app-data-track>
|
||||
}
|
||||
@for (track of remoteDataTracks; track track.info.sid) {
|
||||
<app-data-track
|
||||
[index]="index"
|
||||
[remoteDataTrack]="track"
|
||||
(newTrackEvent)="onTrackEvent($event)">
|
||||
</app-data-track>
|
||||
}
|
||||
</div>
|
||||
<div class="audio-tracks-container">
|
||||
@for (trackPublication of participant.audioTrackPublications| keyvalue; track trackPublication) {
|
||||
<app-audio-track
|
||||
|
|
|
|||
|
|
@ -10,15 +10,18 @@ import {
|
|||
CreateLocalTracksOptions,
|
||||
DataPacket_Kind,
|
||||
LocalAudioTrack,
|
||||
LocalDataTrack,
|
||||
LocalParticipant,
|
||||
LocalTrack,
|
||||
LocalTrackPublication,
|
||||
LocalVideoTrack,
|
||||
Participant,
|
||||
ParticipantEvent,
|
||||
RemoteDataTrack,
|
||||
RemoteTrack,
|
||||
RemoteTrackPublication,
|
||||
Room,
|
||||
RoomEvent,
|
||||
ScreenShareCaptureOptions,
|
||||
SubscriptionError,
|
||||
Track,
|
||||
|
|
@ -38,13 +41,14 @@ import {
|
|||
import { OptionsDialogComponent } from '../dialogs/options-dialog/options-dialog.component';
|
||||
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 { ParticipantEventCallbacks } from 'node_modules/livekit-client/dist/src/room/participant/Participant';
|
||||
|
||||
@Component({
|
||||
selector: 'app-participant',
|
||||
templateUrl: './participant.component.html',
|
||||
styleUrl: './participant.component.css',
|
||||
imports: [NgClass, KeyValuePipe, MatIconModule, MatTooltipModule, MatExpansionModule, VideoTrackComponent, AudioTrackComponent],
|
||||
imports: [NgClass, KeyValuePipe, MatIconModule, MatTooltipModule, MatExpansionModule, VideoTrackComponent, AudioTrackComponent, DataTrackComponent],
|
||||
})
|
||||
export class ParticipantComponent {
|
||||
@Input()
|
||||
|
|
@ -66,6 +70,11 @@ export class ParticipantComponent {
|
|||
|
||||
events: TestAppEvent[] = [];
|
||||
|
||||
localDataTracks: LocalDataTrack[] = [];
|
||||
remoteDataTracks: RemoteDataTrack[] = [];
|
||||
dataTrackCounter: number = 1;
|
||||
dataTrackName: string = 'data_track_1';
|
||||
|
||||
createLocalTracksOptions: CreateLocalTracksOptions;
|
||||
screenShareCaptureOptions: ScreenShareCaptureOptions = {};
|
||||
trackPublishOptions?: TrackPublishOptions;
|
||||
|
|
@ -91,6 +100,9 @@ export class ParticipantComponent {
|
|||
this.trackPublishOptions = JSON.parse(
|
||||
JSON.stringify(this.room.options.publishDefaults)
|
||||
);
|
||||
if (!this.participant.isLocal) {
|
||||
this.setupDataTrackListeners();
|
||||
}
|
||||
}
|
||||
|
||||
onTrackEvent(event: TestAppEvent) {
|
||||
|
|
@ -98,6 +110,31 @@ export class ParticipantComponent {
|
|||
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();
|
||||
}
|
||||
|
||||
async addVideoTrack() {
|
||||
const options =
|
||||
this.createLocalTracksOptions.video === true
|
||||
|
|
@ -479,4 +516,25 @@ export class ParticipantComponent {
|
|||
sendDataLossy() {
|
||||
this.sendLossyDataToOneParticipant.emit(this.participant.identity);
|
||||
}
|
||||
|
||||
private setupDataTrackListeners() {
|
||||
this.room.on(
|
||||
RoomEvent.DataTrackPublished,
|
||||
(track: RemoteDataTrack) => {
|
||||
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();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue