mirror of https://github.com/OpenVidu/openvidu.git
openvidu-testapp: test scenarios. Full LiveKit options dialog
parent
bd6ba55504
commit
7e7b6d2915
File diff suppressed because it is too large
Load Diff
|
@ -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",
|
||||
|
|
|
@ -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 { }
|
|
@ -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>
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 {}
|
|
@ -0,0 +1,13 @@
|
|||
mat-form-field {
|
||||
margin-top: 10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
mat-divider {
|
||||
margin: 20px 0;
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -94,3 +94,9 @@ button.mat-icon-custom .mat-mdc-button-touch-target {
|
|||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.top-div {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
}
|
Loading…
Reference in New Issue