openvidu-testapp: test scenarios. Full LiveKit options dialog

pull/842/head
pabloFuente 2024-09-17 01:32:41 +02:00
parent bd6ba55504
commit 7e7b6d2915
31 changed files with 3440 additions and 1292 deletions

File diff suppressed because it is too large Load Diff

View File

@ -24,8 +24,8 @@
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.0",
"json-stringify-safe": "5.0.1",
"livekit-client": "2.1.0",
"livekit-server-sdk": "2.1.2",
"livekit-client": "2.5.1",
"livekit-server-sdk": "2.6.2",
"lodash": "^4.17.21",
"rxjs": "~7.8.1",
"stream-browserify": "^3.0.0",

View File

@ -1,10 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -2,6 +2,8 @@
<mat-toolbar class="mat-elevation-z5" color="primary">
<a id="nav-logo" routerLink="/"><img id="nav-img"
src="assets/images/openvidu_vert_white_bg_trans_cropped.png" />LiveKit TestApp</a>
<a mat-button id="toolbar-sessions" routerLink="/test-sessions"><span>SESSIONS</span></a>
<a mat-button id="toolbar-scenarios" routerLink="/test-scenarios"><span>SCENARIOS</span></a>
</mat-toolbar>
<main>
<mat-form-field>
@ -19,6 +21,6 @@
<input id="livekit-api-secret" matInput type="password" placeholder="LiveKit Api Secret"
[ngModel]="livekitApiSecret" (ngModelChange)="updateApiSecret($event)">
</mat-form-field>
<app-test-sessions></app-test-sessions>
<router-outlet></router-outlet>
</main>
</mat-sidenav-container>

View File

@ -1,19 +1,22 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { LogLevel, setLogLevel } from 'livekit-client';
import { LivekitParamsService } from './services/livekit-params.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
styleUrls: ['./app.component.css'],
})
export class AppComponent {
livekitUrl = 'ws://localhost:7880/'; // `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://localhost:1880/`;
livekitApiKey = 'devkey';
livekitApiSecret = 'secret';
constructor(private livekitParamsService: LivekitParamsService) { }
constructor(
private router: Router,
private livekitParamsService: LivekitParamsService
) {}
async ngOnInit() {
// LiveKit client logging. Change here to build with verbose logging.
@ -44,6 +47,10 @@ export class AppComponent {
if (!(myUrl.substring(myUrl.length - 1) === '/')) {
myUrl += '/';
}
await this.livekitParamsService.updateParams({ livekitUrl: myUrl, livekitApiKey: this.livekitApiKey, livekitApiSecret: this.livekitApiSecret });
await this.livekitParamsService.updateParams({
livekitUrl: myUrl,
livekitApiKey: this.livekitApiKey,
livekitApiSecret: this.livekitApiSecret,
});
}
}

View File

@ -3,14 +3,16 @@ import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MAT_FORM_FIELD_DEFAULT_OPTIONS, MatFormFieldModule } from '@angular/material/form-field';
import {
MAT_FORM_FIELD_DEFAULT_OPTIONS,
MatFormFieldModule,
} from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
@ -22,31 +24,43 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatSelectModule } from '@angular/material/select';
import { MatChipsModule } from '@angular/material/chips';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatBadgeModule } from '@angular/material/badge';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatRadioModule } from '@angular/material/radio';
import { TestScenariosComponent } from './components/test-scenarios/test-scenarios.component';
import { TestSessionsComponent } from './components/test-sessions/test-sessions.component';
import { OpenviduInstanceComponent } from './components/openvidu-instance/openvidu-instance.component';
import { VideoTrackComponent } from './components/video-track/video-track.component';
import { ParticipantComponent } from './components/participant/participant.component';
import { AudioTrackComponent } from './components/audio-track/audio-track.component';
import { TrackComponent } from './components/track/track.component';
import { RoomOptionsDialogComponent } from './components/dialogs/room-options-dialog/room-options-dialog.component';
import { RoomApiDialogComponent } from './components/dialogs/room-api-dialog/room-api-dialog.component';
import { OptionsDialogComponent } from './components/dialogs/options-dialog/options-dialog.component';
import { EventsDialogComponent } from './components/dialogs/events-dialog/events-dialog.component';
import { TestFeedService } from './services/test-feed.service';
import { EventsDialogComponent } from './components/dialogs/events-dialog/events-dialog.component';
import { UsersTableComponent } from './components/users-table/users-table.component';
import { TableVideoComponent } from './components/users-table/table-video.component';
import { CallbackPipe } from './pipes/callback.pipe';
import { AppRoutingModule } from './app.routing';
@NgModule({
declarations: [
AppComponent,
TestScenariosComponent,
TestSessionsComponent,
OpenviduInstanceComponent,
ParticipantComponent,
VideoTrackComponent,
AudioTrackComponent,
TrackComponent,
RoomOptionsDialogComponent,
RoomApiDialogComponent,
EventsDialogComponent
EventsDialogComponent,
UsersTableComponent,
TableVideoComponent,
CallbackPipe,
OptionsDialogComponent,
],
imports: [
FormsModule,
@ -66,14 +80,20 @@ import { EventsDialogComponent } from './components/dialogs/events-dialog/events
MatTooltipModule,
MatDialogModule,
MatDividerModule,
MatRadioModule,
MatSelectModule,
MatChipsModule,
MatSlideToggleModule,
MatBadgeModule,
MatProgressSpinnerModule,
],
providers: [
TestFeedService,
{ provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline', subscriptSizing: 'dynamic' } }
{
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
useValue: { appearance: 'outline', subscriptSizing: 'dynamic' },
},
],
bootstrap: [AppComponent]
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}

View File

@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { TestScenariosComponent } from './components/test-scenarios/test-scenarios.component';
import { TestSessionsComponent } from './components/test-sessions/test-sessions.component';
const appRoutes: Routes = [
{
path: '',
redirectTo: '/test-sessions',
pathMatch: 'full',
},
{
path: 'test-sessions',
component: TestSessionsComponent,
},
{
path: 'test-scenarios',
component: TestScenariosComponent,
},
];
@NgModule({
imports: [RouterModule.forRoot(appRoutes, { useHash: true })],
exports: [RouterModule],
})
export class AppRoutingModule {}

View File

@ -0,0 +1,13 @@
mat-form-field {
margin-top: 10px;
margin-right: 5px;
}
a {
color: #007bff;
cursor: pointer;
}
mat-divider {
margin: 20px 0;
}

View File

@ -0,0 +1,179 @@
<div>
<h2 mat-dialog-title>OPTIONS</h2>
<mat-dialog-content>
<mat-divider *ngIf="roomOptions"></mat-divider>
<div *ngIf="roomOptions">
<label><a href="https://docs.livekit.io/client-sdk-js/interfaces/RoomOptions.html" target="_blank">RoomOptions</a></label><br>
<mat-checkbox id="room-adaptiveStream" [(ngModel)]="roomOptions.adaptiveStream" [style.fontSize.px]="14">adaptiveStream</mat-checkbox>
<mat-checkbox id="room-dynacast" [(ngModel)]="roomOptions.dynacast" [style.fontSize.px]="14">dynacast</mat-checkbox>
<mat-checkbox id="room-disconnectOnPageLeave" [(ngModel)]="roomOptions.disconnectOnPageLeave" [style.fontSize.px]="14">disconnectOnPageLeave</mat-checkbox>
<mat-checkbox id="room-stopLocalTrackOnUnpublish" [(ngModel)]="roomOptions.stopLocalTrackOnUnpublish" [style.fontSize.px]="14">stopLocalTrackOnUnpublish</mat-checkbox>
<mat-checkbox id="room-webAudioMix" [(ngModel)]="roomOptions.webAudioMix" [style.fontSize.px]="14">webAudioMix</mat-checkbox>
</div>
<mat-divider *ngIf="createLocalTracksOptions"></mat-divider>
<div *ngIf="createLocalTracksOptions">
<label class="label">CreateLocalTracksOptions</label>
<div>
<a href="https://docs.livekit.io/client-sdk-js/interfaces/VideoCaptureOptions.html" target="_blank">VideoCaptureOptions</a>
<mat-radio-group [(ngModel)]="videoOption">
<mat-radio-button [value]="true">True (default)</mat-radio-button>
<mat-radio-button [value]="false">False (no video)</mat-radio-button>
<mat-radio-button [value]="'custom'">Custom</mat-radio-button>
</mat-radio-group>
<div *ngIf="videoOption === 'custom'">
<mat-form-field class="inner-text-input" [style.fontSize.px]="14">
<mat-label>deviceId</mat-label>
<input matInput id="video-deviceId" placeholder="deviceId" [(ngModel)]="auxVideoCaptureOptions.deviceId"/>
</mat-form-field>
<mat-form-field id="video-facingMode" [style.fontSize.px]="14">
<mat-label>facingMode</mat-label>
<mat-select [(value)]="auxVideoCaptureOptions.facingMode">
<mat-option *ngFor="let mode of ['user','environment','left','right']" [value]="mode">{{mode}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="inner-text-input" [style.fontSize.px]="14">
<mat-label>aspectRatio</mat-label>
<input matInput id="video-aspectRatio" placeholder="aspectRatio" [(ngModel)]="auxVideoCaptureOptions.resolution!.aspectRatio"/>
</mat-form-field>
<mat-form-field class="inner-text-input" [style.fontSize.px]="14">
<mat-label>frameRate</mat-label>
<input matInput id="video-frameRate" placeholder="frameRate" [(ngModel)]="auxVideoCaptureOptions.resolution!.frameRate"/>
</mat-form-field>
<mat-form-field class="inner-text-input" [style.fontSize.px]="14">
<mat-label>width</mat-label>
<input matInput id="video-width" placeholder="width" [(ngModel)]="auxVideoCaptureOptions.resolution!.width"/>
</mat-form-field>
<mat-form-field class="inner-text-input" [style.fontSize.px]="14">
<mat-label>height</mat-label>
<input matInput id="video-height" placeholder="height" [(ngModel)]="auxVideoCaptureOptions.resolution!.height"/>
</mat-form-field>
</div>
</div>
<div>
<label class="label"><a href="https://docs.livekit.io/client-sdk-js/interfaces/AudioCaptureOptions.html" target="_blank">AudioCaptureOptions</a></label>
<mat-radio-group [(ngModel)]="audioOption">
<mat-radio-button [value]="true">True (default)</mat-radio-button>
<mat-radio-button [value]="false">False (no audio)</mat-radio-button>
<mat-radio-button [value]="'custom'">Custom</mat-radio-button>
</mat-radio-group>
<div *ngIf="audioOption === 'custom'">
<div>
<mat-checkbox id="audio-autoGainControl" [(ngModel)]="auxAudioCaptureOptions.autoGainControl" [style.fontSize.px]="14">autoGainControl</mat-checkbox>
<mat-checkbox id="audio-echoCancellation" [(ngModel)]="auxAudioCaptureOptions.echoCancellation" [style.fontSize.px]="14">echoCancellation</mat-checkbox>
<mat-checkbox id="audio-noiseSuppression" [(ngModel)]="auxAudioCaptureOptions.noiseSuppression" [style.fontSize.px]="14">noiseSuppression</mat-checkbox>
</div>
<mat-form-field class="inner-text-input" [style.fontSize.px]="14">
<mat-label>deviceId</mat-label>
<input matInput id="audio-deviceId" placeholder="deviceId" [(ngModel)]="auxAudioCaptureOptions.deviceId"/>
</mat-form-field>
<mat-form-field class="inner-text-input" [style.fontSize.px]="14">
<mat-label>channelCount</mat-label>
<input matInput id="audio-channelCount" type="number" placeholder="channelCount" [(ngModel)]="auxAudioCaptureOptions.channelCount"/>
</mat-form-field>
<mat-form-field class="inner-text-input" [style.fontSize.px]="14">
<mat-label>latency</mat-label>
<input matInput id="audio-latency" type="number" placeholder="latency" [(ngModel)]="auxAudioCaptureOptions.latency"/>
</mat-form-field>
<mat-form-field class="inner-text-input" [style.fontSize.px]="14">
<mat-label>sampleRate</mat-label>
<input matInput id="audio-sampleRate" type="number" placeholder="sampleRate" [(ngModel)]="auxAudioCaptureOptions.sampleRate"/>
</mat-form-field>
<mat-form-field class="inner-text-input" [style.fontSize.px]="14">
<mat-label>sampleSize</mat-label>
<input matInput id="audio-sampleSize" type="number" placeholder="sampleSize" [(ngModel)]="auxAudioCaptureOptions.sampleSize"/>
</mat-form-field>
</div>
</div>
</div>
<mat-divider *ngIf="shareScreen"></mat-divider>
<div *ngIf="shareScreen">
<label><a href="https://docs.livekit.io/client-sdk-js/interfaces/ScreenShareCaptureOptions.html" target="_blank">ScreenShareCaptureOptions</a></label><br>
<mat-radio-group [(ngModel)]="screenOption" (change)="screenOptionChanged($event)">
<mat-radio-button [value]="true">True (default)</mat-radio-button>
<mat-radio-button [value]="false">False (no screen)</mat-radio-button>
<mat-radio-button [value]="'custom'">Custom</mat-radio-button>
</mat-radio-group>
<div *ngIf="screenOption == 'custom'">
<mat-checkbox id="screenShare-video" [(ngModel)]="screenShareCaptureOptions!.video" [style.fontSize.px]="14">video</mat-checkbox>
<mat-form-field *ngIf="screenShareCaptureOptions!.video" id="screenShare-displaySurface">
<mat-label>displaySurface</mat-label>
<mat-select [(value)]="auxScreenDisplaySurface">
<mat-option *ngFor="let surface of ['NONE','window','browser','monitor']" [value]="surface">{{surface}}</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox id="screenShare-audio" [(ngModel)]="screenShareCaptureOptions!.audio" [style.fontSize.px]="14">audio</mat-checkbox>
<mat-checkbox id="screenShare-preferCurrentTab" [(ngModel)]="screenShareCaptureOptions!.preferCurrentTab" [style.fontSize.px]="14">preferCurrentTab</mat-checkbox>
<mat-checkbox id="screenShare-suppressLocalAudioPlayback" [(ngModel)]="screenShareCaptureOptions!.suppressLocalAudioPlayback" [style.fontSize.px]="14">suppressLocalAudioPlayback</mat-checkbox>
<mat-form-field id="screenShare-contentHint">
<mat-label>contentHint</mat-label>
<mat-select [(value)]="screenShareCaptureOptions!.contentHint">
<mat-option *ngFor="let hint of ['text','detail','motion']" [value]="hint">{{hint}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field id="screenShare-selfBrowserSurface">
<mat-label>selfBrowserSurface</mat-label>
<mat-select [(value)]="screenShareCaptureOptions!.selfBrowserSurface">
<mat-option *ngFor="let surface of ['include','exclude']" [value]="surface">{{surface}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field id="screenShare-surfaceSwitching">
<mat-label>surfaceSwitching</mat-label>
<mat-select [(value)]="screenShareCaptureOptions!.surfaceSwitching">
<mat-option *ngFor="let surface of ['include','exclude']" [value]="surface">{{surface}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field id="screenShare-systemAudio">
<mat-label>systemAudio</mat-label>
<mat-select [(value)]="screenShareCaptureOptions!.systemAudio">
<mat-option *ngFor="let audio of ['include','exclude']" [value]="audio">{{audio}}</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<mat-divider *ngIf="trackPublishOptions"></mat-divider>
<div *ngIf="trackPublishOptions">
<label><a href="https://docs.livekit.io/client-sdk-js/interfaces/TrackPublishOptions.html" target="_blank">TrackPublishOptions</a></label><br>
<mat-checkbox id="trackPublish-backupCodec" [(ngModel)]="trackPublishOptions.backupCodec" [style.fontSize.px]="14">backupCodec</mat-checkbox>
<mat-checkbox id="trackPublish-dtx" [(ngModel)]="trackPublishOptions.dtx" [style.fontSize.px]="14">dtx</mat-checkbox>
<mat-checkbox id="trackPublish-forceStereo" [(ngModel)]="trackPublishOptions.forceStereo" [style.fontSize.px]="14">forceStereo</mat-checkbox>
<mat-form-field class="inner-text" [style.fontSize.px]="14">
<mat-label>name</mat-label>
<input matInput id="trackPublish-name" placeholder="name" [(ngModel)]="trackPublishOptions.name"/>
</mat-form-field>
<mat-checkbox id="trackPublish-red" [(ngModel)]="trackPublishOptions.red" [style.fontSize.px]="14">red</mat-checkbox>
<mat-form-field id="trackPublish-scalabilityMode" [style.fontSize.px]="14">
<mat-label>scalabilityMode</mat-label>
<mat-select [(value)]="trackPublishOptions.scalabilityMode">
<mat-option *ngFor="let mode of ['L1T1','L1T2','L1T3','L2T1','L2T1h','L2T1_KEY','L2T2','L2T2h','L2T2_KEY','L2T3','L2T3h','L2T3_KEY','L3T1','L3T1h','L3T1_KEY','L3T2','L3T2h','L3T2_KEY','L3T3','L3T3h','L3T3_KEY']" [value]="mode">{{mode}}</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox id="trackPublish-simulcast" [(ngModel)]="trackPublishOptions.simulcast" [style.fontSize.px]="14">simulcast</mat-checkbox>
<mat-form-field id="trackPublish-source" [style.fontSize.px]="14">
<mat-label>source</mat-label>
<mat-select [(value)]="trackPublishOptions.source">
<mat-option *ngFor="let source of ENUMERATION_SOURCE" [value]="source">{{source}}</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox id="trackPublish-stopMicTrackOnMute" [(ngModel)]="trackPublishOptions.stopMicTrackOnMute" [style.fontSize.px]="14">stopMicTrackOnMute</mat-checkbox>
<mat-form-field id="trackPublish-stream" [style.fontSize.px]="14">
<mat-label>stream</mat-label>
<input matInput id="trackPublish-stream" placeholder="stream" [(ngModel)]="trackPublishOptions.stream"/>
</mat-form-field>
<mat-form-field id="trackPublish-videoCodec" [style.fontSize.px]="14">
<mat-label>videoCodec</mat-label>
<mat-select [(value)]="trackPublishOptions.videoCodec">
<mat-option *ngFor="let codec of ['vp8','h264','vp9','av1']" [value]="codec">{{codec}}</mat-option>
</mat-select>
</mat-form-field>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button id="cancel-dialog-btn" (click)="dialogRef.close()">
CANCEL
</button>
<button mat-button id="close-dialog-btn" (click)="returnValues()">
SAVE
</button>
</mat-dialog-actions>
</div>

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OptionsDialogComponent } from './options-dialog.component';
describe('OptionsDialogComponent', () => {
let component: OptionsDialogComponent;
let fixture: ComponentFixture<OptionsDialogComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [OptionsDialogComponent]
});
fixture = TestBed.createComponent(OptionsDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,124 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatRadioChange } from '@angular/material/radio';
import {
AudioCaptureOptions,
CreateLocalTracksOptions,
Room,
RoomOptions,
ScreenShareCaptureOptions,
Track,
TrackPublishOptions,
VideoCaptureOptions,
} from 'livekit-client';
@Component({
selector: 'app-options-dialog',
templateUrl: './options-dialog.component.html',
styleUrls: ['./options-dialog.component.css'],
})
export class OptionsDialogComponent {
roomOptions?: RoomOptions;
createLocalTracksOptions?: CreateLocalTracksOptions;
shareScreen = false;
screenShareCaptureOptions?: ScreenShareCaptureOptions;
trackPublishOptions?: TrackPublishOptions;
videoOption: true | false | 'custom';
audioOption: true | false | 'custom';
screenOption: true | false | 'custom';
auxVideoCaptureOptions: VideoCaptureOptions;
auxAudioCaptureOptions: AudioCaptureOptions;
auxScreenDisplaySurface: 'NONE' | 'window' | 'browser' | 'monitor';
ENUMERATION_SOURCE = Object.keys(Track.Source);
constructor(
public dialogRef: MatDialogRef<OptionsDialogComponent>,
@Inject(MAT_DIALOG_DATA)
public data: {
roomOptions?: RoomOptions;
createLocalTracksOptions?: CreateLocalTracksOptions;
shareScreen: boolean;
screenShareCaptureOptions?: ScreenShareCaptureOptions;
trackPublishOptions?: TrackPublishOptions;
}
) {
this.roomOptions = data.roomOptions;
this.createLocalTracksOptions = data.createLocalTracksOptions;
this.shareScreen = data.shareScreen;
this.screenShareCaptureOptions = data.screenShareCaptureOptions;
this.trackPublishOptions = data.trackPublishOptions;
if (typeof this.createLocalTracksOptions?.video !== 'boolean') {
this.auxVideoCaptureOptions = this.createLocalTracksOptions!
.video as VideoCaptureOptions;
this.videoOption = 'custom';
} else {
this.videoOption = this.createLocalTracksOptions.video;
this.auxVideoCaptureOptions = new Room().options.videoCaptureDefaults!;
}
if (typeof this.createLocalTracksOptions?.audio !== 'boolean') {
this.auxAudioCaptureOptions = this.createLocalTracksOptions!
.audio as AudioCaptureOptions;
this.audioOption = 'custom';
} else {
this.audioOption = this.createLocalTracksOptions.audio;
this.auxAudioCaptureOptions = new Room().options.audioCaptureDefaults!;
}
if (this.shareScreen) {
if (this.screenShareCaptureOptions == undefined) {
this.screenOption = false;
} else if (Object.keys(this.screenShareCaptureOptions).length > 0) {
this.screenOption = 'custom';
} else {
this.screenOption = true;
this.screenShareCaptureOptions = {};
}
}
}
returnValues() {
if (this.createLocalTracksOptions) {
if (this.videoOption === 'custom') {
this.createLocalTracksOptions.video = this.auxVideoCaptureOptions;
} else {
this.createLocalTracksOptions.video = this.videoOption;
}
if (this.audioOption === 'custom') {
this.createLocalTracksOptions.audio = this.auxAudioCaptureOptions;
} else {
this.createLocalTracksOptions.audio = this.audioOption;
}
}
if (
!!this.auxScreenDisplaySurface &&
this.auxScreenDisplaySurface !== 'NONE'
) {
this.screenShareCaptureOptions!.video = {
displaySurface: this.auxScreenDisplaySurface,
};
}
if (this.screenOption === true) {
this.screenShareCaptureOptions = {};
}
if (this.screenOption === false) {
this.screenShareCaptureOptions = undefined;
}
this.dialogRef.close({
roomOptions: this.roomOptions,
createLocalTracksOptions: this.createLocalTracksOptions,
screenShareCaptureOptions: this.screenShareCaptureOptions,
trackPublishOptions: this.trackPublishOptions,
});
}
screenOptionChanged(event: MatRadioChange) {
if (event.value === 'custom' && !this.screenShareCaptureOptions) {
this.screenShareCaptureOptions = {
video: true
};
}
}
}

View File

@ -1,8 +0,0 @@
<h1 mat-dialog-title>Room options</h1>
<mat-dialog-content></mat-dialog-content>
<mat-dialog-actions>
<button id="cancel-btn" mat-button [mat-dialog-close]="undefined">CANCEL</button>
<button id="save-btn" mat-button [mat-dialog-close]="{roomOptions}">SAVE</button>
</mat-dialog-actions>

View File

@ -1,21 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RoomOptionsDialogComponent } from './room-options-dialog.component';
describe('RoomOptionsDialogComponent', () => {
let component: RoomOptionsDialogComponent;
let fixture: ComponentFixture<RoomOptionsDialogComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [RoomOptionsDialogComponent]
});
fixture = TestBed.createComponent(RoomOptionsDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,18 +0,0 @@
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { RoomOptions } from 'livekit-client';
@Component({
selector: 'app-room-options-dialog',
templateUrl: './room-options-dialog.component.html',
styleUrls: ['./room-options-dialog.component.css']
})
export class RoomOptionsDialogComponent {
roomOptions: RoomOptions;
constructor(public dialogRef: MatDialogRef<RoomOptionsDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {
this.roomOptions = data.roomOptions;
}
}

View File

@ -19,7 +19,7 @@
<div class="room-btns-div">
<button mat-icon-button title="Room options" [id]="'room-options-btn-' + index"
class="mat-icon-custom" (click)="openRoomOptionsDialog()" [disabled]="room">
class="mat-icon-custom" (click)="openOptionsDialog()" [disabled]="room">
<mat-icon class="mat-icon-custom-ic" aria-label="Room options button">settings</mat-icon>
</button>
<button mat-icon-button title="Room API" [id]="'room-api-btn-' + index" class="mat-icon-custom"

View File

@ -1,5 +1,4 @@
import { Component, HostListener, Input } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { RoomConf } from '../test-sessions/test-sessions.component';
import { LivekitParamsService } from 'src/app/services/livekit-params.service';
@ -7,11 +6,13 @@ import { LivekitParamsService } from 'src/app/services/livekit-params.service';
import {
ConnectionQuality,
ConnectionState,
CreateLocalTracksOptions,
DataPacket_Kind,
DataPublishOptions,
DisconnectReason,
LocalAudioTrack,
LocalParticipant,
LocalTrack,
LocalTrackPublication,
LocalVideoTrack,
MediaDeviceFailure,
@ -25,9 +26,11 @@ import {
RoomConnectOptions,
RoomEvent,
RoomOptions,
ScreenShareCaptureOptions,
SubscriptionError,
Track,
TrackPublication,
TrackPublishOptions,
} from 'livekit-client';
import { ParticipantPermission } from 'livekit-server-sdk';
import {
@ -39,6 +42,7 @@ import { MatDialog } from '@angular/material/dialog';
import { RoomApiDialogComponent } from '../dialogs/room-api-dialog/room-api-dialog.component';
import { RoomApiService } from 'src/app/services/room-api.service';
import { EventsDialogComponent } from '../dialogs/events-dialog/events-dialog.component';
import { OptionsDialogComponent } from '../dialogs/options-dialog/options-dialog.component';
@Component({
selector: 'app-openvidu-instance',
@ -54,6 +58,11 @@ export class OpenviduInstanceComponent {
room?: Room;
roomEvents: Map<RoomEvent, Boolean> = new Map<RoomEvent, boolean>();
roomName: string = 'TestRoom';
participantName: string = 'TestParticipant';
// Options
roomOptions: RoomOptions = {
adaptiveStream: true,
dynacast: true,
@ -68,9 +77,18 @@ export class OpenviduInstanceComponent {
roomConnectOptions: RoomConnectOptions = {
autoSubscribe: false,
};
roomName: string = 'TestRoom';
participantName: string = 'TestParticipant';
createLocalTracksOptions: CreateLocalTracksOptions = {
audio: true,
video: {
resolution: {
width: 640,
height: 480,
frameRate: 30,
},
},
};
screenShareCaptureOptions: ScreenShareCaptureOptions;
trackPublishOptions: TrackPublishOptions = {};
localTracks: {
audioTrack: LocalAudioTrack | undefined;
@ -93,7 +111,11 @@ export class OpenviduInstanceComponent {
private testFeedService: TestFeedService,
private roomApiService: RoomApiService,
private dialog: MatDialog
) {}
) {
const roomForDefaults = new Room(this.roomOptions);
this.roomOptions = roomForDefaults.options;
this.trackPublishOptions = roomForDefaults.options.publishDefaults!;
}
async ngOnInit() {
for (let event of Object.keys(RoomEvent)) {
@ -144,8 +166,25 @@ export class OpenviduInstanceComponent {
);
if (this.roomConf.publisher) {
await this.room?.localParticipant.enableCameraAndMicrophone();
// await this.room?.localParticipant.setScreenShareEnabled(true);
const tracks: LocalTrack[] =
await this.room.localParticipant.createTracks(
this.createLocalTracksOptions
);
if (this.screenShareCaptureOptions) {
const screenTracks: LocalTrack[] =
await this.room.localParticipant.createScreenTracks(
this.screenShareCaptureOptions
);
tracks.push(...screenTracks);
}
await Promise.all(
tracks.map((track) =>
this.room!.localParticipant.publishTrack(
track,
this.trackPublishOptions
)
)
);
}
}
@ -685,7 +724,6 @@ export class OpenviduInstanceComponent {
RoomEvent.DataReceived,
{ payload: decodedPayload, participant, kind, topic },
decodedPayload
);
}
);
@ -972,34 +1010,24 @@ export class OpenviduInstanceComponent {
this.room!.localParticipant.setMicrophoneEnabled(true);
}
openRoomOptionsDialog() {
// this.roomOptions.customSessionId = this.sessionName;
// const dialogRef = this.dialog.open(SessionPropertiesDialogComponent, {
// data: {
// sessionProperties: this.sessionProperties,
// turnConf: this.turnConf,
// manualTurnConf: this.manualTurnConf,
// customToken: this.customToken,
// forcePublishing: this.forcePublishing,
// reconnectionOnServerFailure: this.reconnectionOnServerFailure,
// connectionProperties: this.connectionProperties,
// }
// });
// dialogRef.afterClosed().subscribe(result => {
// if (!!result) {
// this.sessionProperties = result.sessionProperties;
// if (!!this.sessionProperties.customSessionId) {
// this.sessionName = this.sessionProperties.customSessionId;
// }
// this.turnConf = result.turnConf;
// this.manualTurnConf = result.manualTurnConf;
// this.customToken = result.customToken;
// this.forcePublishing = result.forcePublishing;
// this.reconnectionOnServerFailure = result.reconnectionOnServerFailure;
// this.connectionProperties = result.connectionProperties;
// }
// document.getElementById('session-settings-btn-' + this.index).classList.remove('cdk-program-focused');
// });
openOptionsDialog() {
const dialogRef = this.dialog.open(OptionsDialogComponent, {
data: {
roomOptions: this.roomOptions,
createLocalTracksOptions: this.createLocalTracksOptions,
shareScreen: true,
screenShareCaptureOptions: this.screenShareCaptureOptions,
trackPublishOptions: this.trackPublishOptions,
},
});
dialogRef.afterClosed().subscribe((result) => {
if (!!result) {
this.roomOptions = result;
this.createLocalTracksOptions = result.createLocalTracksOptions;
this.screenShareCaptureOptions = result.screenShareCaptureOptions;
this.trackPublishOptions = result.trackPublishOptions;
}
});
}
openRoomApiDialog() {
@ -1045,7 +1073,9 @@ export class OpenviduInstanceComponent {
sendData(destinationIdentity?: string) {
let strData = `Message from ${this.room?.localParticipant.identity}`;
strData += destinationIdentity ? ` to ${destinationIdentity}` : ' to all room';
strData += destinationIdentity
? ` to ${destinationIdentity}`
: ' to all room';
const data = new TextEncoder().encode(strData);
let options: DataPublishOptions = {
reliable: true,

View File

@ -0,0 +1,43 @@
app-openvidu-instance {
display: inline-flex;
margin: 25px 5px 0px 0px;
}
.top-div {
display: flex;
align-items: center;
justify-content: space-between;
}
.top-div > div {
display: flex;
align-items: center;
}
.scenario-input {
width: 80px;
}
.scenario-input {
margin-left: 10px;
}
.scenario-input:first-of-type {
margin-right: 25px;
}
.report-div {
margin-top: 50px;
}
.report-field {
width: 100%;
}
.report-btn {
float: right;
}
#n2m-btn {
margin-left: 10px;
}

View File

@ -0,0 +1,25 @@
<div class="top-div">
<div>
<button id="m2m-btn" mat-raised-button color="primary" (click)="loadScenario(MtoM,0,0)" [disabled]="scenarioPlaying">{{MtoM}} To {{MtoM}}</button>
<mat-form-field class="scenario-input">
<input id="m2m-input" matInput [(ngModel)]="MtoM" type="number" [disabled]="scenarioPlaying">
</mat-form-field>
<mat-form-field class="scenario-input">
<input id="n-of-n2m-input" matInput [(ngModel)]="NofNtoM" type="number" [disabled]="scenarioPlaying">
</mat-form-field>
<button id="n2m-btn" mat-raised-button color="primary" (click)="loadScenario(0,NofNtoM,MofNtoM)" [disabled]="scenarioPlaying">{{NofNtoM}} to {{MofNtoM}}</button>
<mat-form-field class="scenario-input">
<input id="m-of-n2m-input" matInput [(ngModel)]="MofNtoM" type="number" [disabled]="scenarioPlaying">
</mat-form-field>
</div>
<div>
<button mat-icon-button id="scenario-options-btn" title="Scenario options" class="mat-icon-custom" (click)="openScenarioOptionsDialog()" [disabled]="scenarioPlaying">
<mat-icon class="mat-icon-custom-ic" aria-label="Room options button">settings</mat-icon>
</button>
<button id="finish-btn" mat-raised-button color="primary" (click)="endScenario()" [disabled]="!scenarioPlaying">FINISH</button>
</div>
</div>
<div class="instance-div">
<app-users-table *ngIf="!!users.length" [users]="users"></app-users-table>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TestScenariosComponent } from './test-scenarios.component';
describe('TestScenariosComponent', () => {
let component: TestScenariosComponent;
let fixture: ComponentFixture<TestScenariosComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TestScenariosComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestScenariosComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,257 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { TestFeedService } from '../../services/test-feed.service';
import {
CreateLocalTracksOptions,
LocalAudioTrack,
LocalVideoTrack,
RemoteParticipant,
RemoteTrackPublication,
Room,
RoomConnectOptions,
RoomEvent,
RoomOptions,
LocalTrack,
TrackPublishOptions,
} from 'livekit-client';
import { LivekitParamsService } from 'src/app/services/livekit-params.service';
import { RoomApiService } from 'src/app/services/room-api.service';
import * as stringify from 'json-stringify-safe';
import { MatDialog } from '@angular/material/dialog';
import { OptionsDialogComponent } from '../dialogs/options-dialog/options-dialog.component';
export interface User {
subscriber: boolean;
publisher: boolean;
room: Room;
localTracks: {
audio?: LocalAudioTrack;
video?: LocalVideoTrack;
};
}
@Component({
selector: 'app-test-scenarios',
templateUrl: './test-scenarios.component.html',
styleUrls: ['./test-scenarios.component.css'],
})
export class TestScenariosComponent implements OnInit, OnDestroy {
fixedRoomId = 'SCENARIO_TEST';
openviduUrl: string;
openviduSecret: string;
scenarioPlaying = false;
eventsInfoSubscription: Subscription;
MtoM = 2;
NofNtoM = 1;
MofNtoM = 4;
users: User[] = [];
roomOptions: RoomOptions = {
adaptiveStream: true,
dynacast: true,
};
roomConnectOptions: RoomConnectOptions = {
autoSubscribe: false,
};
createLocalTracksOptions: CreateLocalTracksOptions = {
video: {
resolution: {
frameRate: 1,
height: 30,
width: 40,
},
},
audio: false,
};
trackPublishOptions: TrackPublishOptions = {};
constructor(
private livekitParamsService: LivekitParamsService,
private testFeedService: TestFeedService,
private roomApiService: RoomApiService,
private dialog: MatDialog
) {
const roomForDefaults = new Room(this.roomOptions);
this.roomOptions = roomForDefaults.options;
this.trackPublishOptions = roomForDefaults.options.publishDefaults!;
}
ngOnInit() {
(window as any).myEvents = '';
this.eventsInfoSubscription = this.testFeedService.newLastEvent$.subscribe(
(newEvent) => {
(window as any).myEvents += '<br>' + stringify(newEvent);
}
);
}
ngOnDestroy() {
this.endScenario();
this.eventsInfoSubscription.unsubscribe();
}
loadScenario(subsPubs: number, pubs: number, subs: number): void {
this.users = [];
this.loadSubsPubs(subsPubs);
this.loadPubs(pubs);
this.loadSubs(subs);
this.startSession();
this.scenarioPlaying = true;
}
endScenario() {
for (const user of this.users) {
user.room.disconnect();
}
this.users = [];
this.scenarioPlaying = false;
}
private loadSubsPubs(n: number): void {
for (let i = 0; i < n; i++) {
this.users.push({
subscriber: true,
publisher: true,
room: new Room(this.roomOptions),
localTracks: {
audio: undefined,
video: undefined,
},
});
}
}
private loadSubs(n: number): void {
for (let i = 0; i < n; i++) {
this.users.push({
subscriber: true,
publisher: false,
room: new Room(this.roomOptions),
localTracks: {
audio: undefined,
video: undefined,
},
});
}
}
private loadPubs(n: number): void {
for (let i = 0; i < n; i++) {
this.users.push({
subscriber: false,
publisher: true,
room: new Room(this.roomOptions),
localTracks: {
audio: undefined,
video: undefined,
},
});
}
}
private async startSession() {
let promises = [];
let i = -1;
for (const user of this.users) {
i++;
const promise = new Promise<void>(async (resolve) => {
try {
const token = await this.roomApiService.createToken(
{
roomJoin: true,
canPublish: user.publisher,
canSubscribe: user.subscriber,
},
`${i}`,
this.fixedRoomId
);
const room: Room = user.room;
room!.on(RoomEvent.Connected, () => {
room.remoteParticipants.forEach(
(remoteParticipant: RemoteParticipant) => {
if (user.subscriber) {
// Subscribe to already existing tracks
remoteParticipant.trackPublications.forEach(
(publication: RemoteTrackPublication) => {
publication.setSubscribed(true);
}
);
}
}
);
});
room.on(
RoomEvent.TrackPublished,
(publication: RemoteTrackPublication) => {
if (user.subscriber) {
// Subscribe to new tracks
publication.setSubscribed(true);
}
}
);
await room.connect(
this.livekitParamsService.getParams().livekitUrl,
token,
this.roomConnectOptions
);
if (user.publisher) {
const tracks: LocalTrack[] =
await room.localParticipant.createTracks(
this.createLocalTracksOptions
);
await Promise.all(
tracks.map((track) =>
room.localParticipant.publishTrack(
track,
this.trackPublishOptions
)
)
);
user.localTracks.audio = tracks.find(
(track) => track.kind === 'audio'
) as LocalAudioTrack;
user.localTracks.video = tracks.find(
(track) => track.kind === 'video'
) as LocalVideoTrack;
}
resolve();
} catch (error) {
console.error(error);
resolve();
}
});
promises.push(promise);
}
await Promise.all(promises);
}
openScenarioOptionsDialog() {
const dialogRef = this.dialog.open(OptionsDialogComponent, {
data: {
roomOptions: this.roomOptions,
createLocalTracksOptions: this.createLocalTracksOptions,
shareScreen: false,
trackPublishOptions: this.trackPublishOptions,
},
disableClose: true,
});
dialogRef.afterClosed().subscribe((result) => {
if (!result) {
return;
}
this.roomOptions = result.roomOptions;
this.createLocalTracksOptions = result.createLocalTracksOptions;
this.trackPublishOptions = result.trackPublishOptions;
});
}
}

View File

@ -3,12 +3,6 @@ app-openvidu-instance {
margin: 15px 5px 0px 0px;
}
.top-div {
display: inline-block;
width: 100%;
margin-top: 12px;
}
.controls-div {
float: left;
}

View File

@ -4,7 +4,6 @@ import { Subscription } from 'rxjs';
import { TestFeedService } from 'src/app/services/test-feed.service';
import * as stringify from 'json-stringify-safe';
export interface RoomConf {
subscriber: boolean;
publisher: boolean;
@ -18,14 +17,13 @@ export interface RoomConf {
animations: [
trigger('fadeAnimation', [
transition(':enter', [
style({ opacity: 0 }), animate('100ms', style({ opacity: 1 }))]
)
])
style({ opacity: 0 }),
animate('100ms', style({ opacity: 1 })),
]),
]),
],
})
export class TestSessionsComponent {
paramsSubscription: Subscription;
eventsInfoSubscription: Subscription;
// OpenViduInstance collection
@ -34,18 +32,18 @@ export class TestSessionsComponent {
numberParticipants = 2;
autoJoin = false;
constructor(private testFeedService: TestFeedService) { }
constructor(private testFeedService: TestFeedService) {}
ngOnInit() {
(window as any).myEvents = '';
this.eventsInfoSubscription = this.testFeedService.newLastEvent$.subscribe(
newEvent => {
(window as any).myEvents += ('<br>' + stringify(newEvent));
});
(newEvent) => {
(window as any).myEvents += '<br>' + stringify(newEvent);
}
);
}
ngOnDestroy() {
this.paramsSubscription.unsubscribe();
this.eventsInfoSubscription.unsubscribe();
}
@ -53,7 +51,7 @@ export class TestSessionsComponent {
this.users.push({
subscriber: true,
publisher: true,
startSession: false
startSession: false,
});
}
@ -70,7 +68,7 @@ export class TestSessionsComponent {
this.users.push({
subscriber: true,
publisher: true,
startSession: this.autoJoin
startSession: this.autoJoin,
});
}
}
@ -80,7 +78,7 @@ export class TestSessionsComponent {
this.users.push({
subscriber: true,
publisher: false,
startSession: this.autoJoin
startSession: this.autoJoin,
});
}
}
@ -90,7 +88,7 @@ export class TestSessionsComponent {
this.users.push({
subscriber: false,
publisher: true,
startSession: this.autoJoin
startSession: this.autoJoin,
});
}
}
@ -101,5 +99,4 @@ export class TestSessionsComponent {
this.loadPubs(pubs);
this.loadSubs(subs);
}
}

View File

@ -0,0 +1,66 @@
import {
Component,
Input,
AfterViewInit,
ViewChild,
ElementRef,
} from '@angular/core';
import { AudioTrack, VideoTrack } from 'livekit-client';
@Component({
selector: 'app-table-video',
template: `
<video #videoElement [id]="videoId" autoplay playsinline></video>
`,
styles: [
`
video {
width: 100px;
}
`,
],
})
export class TableVideoComponent implements AfterViewInit {
@ViewChild('videoElement') elementRef: ElementRef;
@Input() videoId: string;
@Input()
get tracks(): { audio: AudioTrack; video: VideoTrack } {
return this._tracks;
}
set tracks(tracks: { audio: AudioTrack; video: VideoTrack }) {
this._tracks = tracks;
if (this.elementRef) {
if (this.tracks.audio) {
this.tracks.audio.attach(this.elementRef.nativeElement);
}
if (this.tracks.video) {
this.tracks.video.attach(this.elementRef.nativeElement);
}
}
}
private _tracks: { audio: AudioTrack; video: VideoTrack };
ngAfterViewInit() {
if (this.elementRef) {
if (this._tracks.audio) {
this._tracks.audio.attach(this.elementRef.nativeElement);
}
if (this._tracks.video) {
this._tracks.video.attach(this.elementRef.nativeElement);
}
}
}
ngOnDestroy() {
if (this.elementRef) {
if (this._tracks.audio) {
this._tracks.audio.detach(this.elementRef.nativeElement);
}
if (this._tracks.video) {
this._tracks.video.detach(this.elementRef.nativeElement);
}
}
}
}

View File

@ -0,0 +1,29 @@
table {
width: 100%;
margin-top: 20px;
}
tr {
min-height: 90px;
}
th {
position: relative;
}
td {
text-align: center;
position: relative;
}
.mat-cell {
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
tr:last-child .mat-cell {
border-bottom: none;
}
#number-of-streams {
margin-bottom: 28px;
}

View File

@ -0,0 +1,28 @@
<table class="mat-elevation-z8">
<tr class="mat-row">
<th class="mat-cell">
<p id="number-of-streams">STREAMS</p>
<p>
<span [matBadge]="numberOfStreamsOut()" matBadgeOverlap="false">OUT</span>
</p>
<p>
<span [matBadge]="numberOfStreamsIn()" matBadgeOverlap="false">IN</span>
</p>
</th>
<th *ngFor="let publisher of users | callback: filterPublishers" class="mat-cell">
<p>{{publisher.room.localParticipant.identity}}</p>
<app-table-video *ngIf="publisher.localTracks.audio || publisher.localTracks.video"
[tracks]="publisher.localTracks"
[videoId]="'pub-' + publisher.room.localParticipant.identity">
</app-table-video>
</th>
</tr>
<tr *ngFor="let subscriber of users | callback: filterSubscribers" class="mat-cell">
<td class="mat-cell">{{subscriber.room.localParticipant.identity}}</td>
<td *ngFor="let publisher of users | callback: filterPublishers" class="mat-cell">
<app-table-video *ngIf="subscriber.room.localParticipant.identity !== publisher.room.localParticipant.identity && getRemoteTracksForPublisher(subscriber, publisher) as remoteTracks"
[tracks]="remoteTracks" [videoId]="'sub-' + subscriber.room.localParticipant.identity + '-of-pub-' + publisher.room.localParticipant.identity">
</app-table-video>
</td>
</tr>
</table>

View File

@ -0,0 +1,65 @@
import { Component, Input } from '@angular/core';
import { AudioTrack, VideoTrack } from 'livekit-client';
import { User } from '../test-scenarios/test-scenarios.component';
@Component({
selector: 'app-users-table',
styleUrls: ['users-table.component.css'],
templateUrl: 'users-table.component.html',
})
export class UsersTableComponent {
@Input() users: User[] = [];
numberOfStreamsOut() {
return this.users.filter((u) => u.publisher).length;
}
numberOfStreamsIn() {
return this.users
.filter((u) => u.publisher)
.reduce((acc, publisher) => {
return (
acc +
this.users.filter(
(u) =>
u.subscriber &&
u.room.localParticipant.identity !==
publisher.room.localParticipant.identity
).length
);
}, 0);
}
filterPublishers(user: User) {
return user.publisher;
}
filterSubscribers(user: User) {
return user.subscriber;
}
getRemoteTracksForPublisher(
subscriber: User,
publisher: User
): { audio: AudioTrack; video: VideoTrack } | undefined {
{
const remoteParticipant = Array.from(
subscriber.room.remoteParticipants.values()
).find(
(remoteParticipant) =>
remoteParticipant.identity ===
publisher.room.localParticipant.identity
);
if (remoteParticipant) {
return {
audio: remoteParticipant.audioTrackPublications.values().next().value
?.track,
video: remoteParticipant.videoTrackPublications.values().next().value
?.track,
};
} else {
return undefined;
}
}
}
}

View File

@ -1,7 +1,6 @@
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { LocalTrack, VideoTrack, VideoCaptureOptions, ScreenShareCaptureOptions } from 'livekit-client';
import { TrackComponent } from '../track/track.component';
// import { BackgroundBlur } from '@livekit/track-processors';
@Component({
selector: 'app-video-track',

View File

@ -0,0 +1,14 @@
import { PipeTransform, Pipe } from '@angular/core';
@Pipe({
name: 'callback',
pure: false,
})
export class CallbackPipe implements PipeTransform {
transform(items: any[], callback: (item: any) => boolean): any {
if (!items || !callback) {
return items;
}
return items.filter((item) => callback(item));
}
}

View File

@ -4,7 +4,7 @@ import { firstValueFrom } from 'rxjs';
import * as _ from 'lodash';
import { AccessToken, EncodedOutputs, EncodingOptions, EncodingOptionsPreset, IngressInput, RoomCompositeOptions, VideoGrant } from 'livekit-server-sdk';
import { LivekitParams, LivekitParamsService } from './livekit-params.service';
import { LivekitParamsService } from './livekit-params.service';
@Injectable({
providedIn: 'root'

View File

@ -93,4 +93,10 @@ app-participant:last-child>.participant-container {
button.mat-icon-custom .mat-mdc-button-touch-target {
width: 24px;
height: 24px;
}
.top-div {
display: inline-block;
width: 100%;
margin-top: 12px;
}