mirror of https://github.com/OpenVidu/openvidu.git
openvidu-testapp: add CreateIngressOptions to dialog
parent
738431b6bf
commit
18343fa47d
|
@ -151,7 +151,7 @@
|
||||||
<mat-form-field id="trackPublish-videoCodec">
|
<mat-form-field id="trackPublish-videoCodec">
|
||||||
<mat-label>videoCodec</mat-label>
|
<mat-label>videoCodec</mat-label>
|
||||||
<mat-select [(value)]="trackPublishOptions.videoCodec">
|
<mat-select [(value)]="trackPublishOptions.videoCodec">
|
||||||
<mat-option *ngFor="let codec of ['vp8','h264','vp9','av1']" [value]="codec">{{codec}}</mat-option>
|
<mat-option *ngFor="let codec of ['VP8','H264','VP9','AV1']" [value]="codec">{{codec}}</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field id="trackPublish-scalabilityMode">
|
<mat-form-field id="trackPublish-scalabilityMode">
|
||||||
|
|
|
@ -33,7 +33,7 @@ mat-dialog-content button {
|
||||||
color: rgba(0, 0, 0, 0.54);
|
color: rgba(0, 0, 0, 0.54);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
margin-top: 13px
|
margin-top: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner-text-input {
|
.inner-text-input {
|
||||||
|
@ -82,7 +82,7 @@ mat-checkbox ::ng-deep .mdc-checkbox__background {
|
||||||
min-width: 22%;
|
min-width: 22%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.egress-output-container>mat-checkbox {
|
.egress-output-container > mat-checkbox {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 7px;
|
margin-bottom: 7px;
|
||||||
}
|
}
|
||||||
|
@ -120,4 +120,16 @@ mat-chip-row {
|
||||||
|
|
||||||
.room-composite-options mat-checkbox {
|
.room-composite-options mat-checkbox {
|
||||||
margin-left: 7px;
|
margin-left: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ingress-options {
|
||||||
|
margin-bottom: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ingress-simulcast {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ingress-video-codec-select {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
|
@ -163,9 +163,10 @@
|
||||||
|
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button mat-button id="list-ingress-api-btn" (click)="listIngress()">List Ingress</button>
|
<button mat-button id="list-ingress-api-btn" (click)="listIngress()">List Ingress</button>
|
||||||
<span style="display: inline-block" matTooltip='"Room" required' [matTooltipDisabled]="!!ingressRoomName">
|
<span style="display: inline-block" [matTooltip]="!ingressRoomName ? 'Room required' : 'At least one of audio and video required'"
|
||||||
<button mat-button id="create-ingress-api-btn" (click)="createIngress()" [disabled]="!ingressRoomName">Create
|
[matTooltipDisabled]="!!ingressRoomName && (ingressWithAudio || ingressWithVideo)">
|
||||||
Ingress</button>
|
<button mat-button id="create-ingress-api-btn" (click)="createIngress()"
|
||||||
|
[disabled]="!ingressRoomName || (!ingressWithAudio && !ingressWithVideo)">Create Ingress</button>
|
||||||
</span>
|
</span>
|
||||||
<span style="display: inline-block" matTooltip='"Ingress ID" required' [matTooltipDisabled]="!!ingressId">
|
<span style="display: inline-block" matTooltip='"Ingress ID" required' [matTooltipDisabled]="!!ingressId">
|
||||||
<button mat-button id="delete-ingress-api-btn" (click)="deleteIngress()" [disabled]="!ingressId">Delete
|
<button mat-button id="delete-ingress-api-btn" (click)="deleteIngress()" [disabled]="!ingressId">Delete
|
||||||
|
@ -184,6 +185,38 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<label class="label">Ingress options</label>
|
||||||
|
<div class="ingress-options">
|
||||||
|
<mat-checkbox id="ingress-with-audio" [(ngModel)]="ingressWithAudio">With audio</mat-checkbox>
|
||||||
|
<mat-checkbox id="ingress-with-video" [(ngModel)]="ingressWithVideo">With video</mat-checkbox>
|
||||||
|
<span style="display: inline-block" [matTooltip]="!ingressWithVideo ? 'Only with video' : 'Preset overrides this value'"
|
||||||
|
[matTooltipDisabled]="!!ingressWithVideo && ingressVideoEncodingPresetSelected == undefined">
|
||||||
|
<mat-checkbox id="ingress-simulcast" [(ngModel)]="ingressSimulcast"
|
||||||
|
[disabled]="!ingressWithVideo || ingressVideoEncodingPresetSelected != undefined">Simulcast</mat-checkbox>
|
||||||
|
</span>
|
||||||
|
<span style="display: inline-block" [matTooltip]="!ingressWithVideo ? 'Only with video' : 'Preset overrides this value'"
|
||||||
|
[matTooltipDisabled]="!!ingressWithVideo && ingressVideoEncodingPresetSelected == undefined">
|
||||||
|
<mat-form-field id="ingress-video-codec-select">
|
||||||
|
<mat-label>Codec</mat-label>
|
||||||
|
<mat-select [(value)]="ingressVideoCodecSelected" [disabled]="!ingressWithVideo || ingressVideoEncodingPresetSelected != undefined">
|
||||||
|
<mat-option *ngFor="let codec of INGRESS_VIDEO_CODECS" [value]="codec.value">
|
||||||
|
{{codec.viewValue}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</span>
|
||||||
|
<span style="display: inline-block" matTooltip="Only with video" [matTooltipDisabled]="!!ingressWithVideo">
|
||||||
|
<mat-form-field id="ingress-preset-select">
|
||||||
|
<mat-label>Preset</mat-label>
|
||||||
|
<mat-select [(value)]="ingressVideoEncodingPresetSelected" [disabled]="!ingressWithVideo">
|
||||||
|
<mat-option *ngFor="let preset of INGRESS_VIDEO_ENCODING_PRESETS" [value]="preset.value">
|
||||||
|
{{preset.viewValue}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
<mat-form-field id="response-text-area" appearance="fill">
|
<mat-form-field id="response-text-area" appearance="fill">
|
||||||
|
|
|
@ -3,307 +3,384 @@ import { COMMA, ENTER } from '@angular/cdk/keycodes';
|
||||||
import { Component, Inject, inject } from '@angular/core';
|
import { Component, Inject, inject } from '@angular/core';
|
||||||
import { MatChipInputEvent } from '@angular/material/chips';
|
import { MatChipInputEvent } from '@angular/material/chips';
|
||||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
import { VideoCodec } from '@livekit/protocol';
|
||||||
import { LocalParticipant } from 'livekit-client';
|
import { LocalParticipant } from 'livekit-client';
|
||||||
|
|
||||||
import { DirectFileOutput, EgressClient, EncodedFileOutput, EncodedFileType, EncodedOutputs, IngressInfo, IngressInput, Room, RoomCompositeOptions, RoomServiceClient, SegmentedFileOutput, SegmentedFileProtocol, StreamOutput, StreamProtocol } from 'livekit-server-sdk';
|
import {
|
||||||
|
DirectFileOutput,
|
||||||
|
EgressClient,
|
||||||
|
EncodedFileOutput,
|
||||||
|
EncodedFileType,
|
||||||
|
EncodedOutputs,
|
||||||
|
IngressInfo,
|
||||||
|
IngressInput,
|
||||||
|
IngressVideoEncodingPreset,
|
||||||
|
Room,
|
||||||
|
RoomCompositeOptions,
|
||||||
|
RoomServiceClient,
|
||||||
|
SegmentedFileOutput,
|
||||||
|
SegmentedFileProtocol,
|
||||||
|
StreamOutput,
|
||||||
|
StreamProtocol,
|
||||||
|
} from 'livekit-server-sdk';
|
||||||
import { RoomApiService } from 'src/app/services/room-api.service';
|
import { RoomApiService } from 'src/app/services/room-api.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-room-api-dialog',
|
selector: 'app-room-api-dialog',
|
||||||
templateUrl: './room-api-dialog.component.html',
|
templateUrl: './room-api-dialog.component.html',
|
||||||
styleUrls: ['./room-api-dialog.component.css']
|
styleUrls: ['./room-api-dialog.component.css'],
|
||||||
})
|
})
|
||||||
export class RoomApiDialogComponent {
|
export class RoomApiDialogComponent {
|
||||||
|
room: Room;
|
||||||
|
localParticipant: LocalParticipant;
|
||||||
|
roomServiceClient: RoomServiceClient;
|
||||||
|
egressClient: EgressClient;
|
||||||
|
|
||||||
room: Room;
|
apiRoomName: string;
|
||||||
localParticipant: LocalParticipant;
|
apiParticipantIdentity: string;
|
||||||
roomServiceClient: RoomServiceClient;
|
apiTrackSid: string;
|
||||||
egressClient: EgressClient;
|
muteTrack: boolean = true;
|
||||||
|
|
||||||
apiRoomName: string;
|
egressRoomName: string;
|
||||||
apiParticipantIdentity: string;
|
egressId: string;
|
||||||
apiTrackSid: string;
|
audioTrackId: string;
|
||||||
muteTrack: boolean = true;
|
videoTrackId: string;
|
||||||
|
|
||||||
egressRoomName: string;
|
ROOM_COMPOSITE_LAYOUTS = ['grid', 'speaker', 'single-speaker'];
|
||||||
egressId: string;
|
roomCompositeLayoutSelected: string = 'grid';
|
||||||
audioTrackId: string;
|
roomCompositeAudioOnly: boolean = false;
|
||||||
videoTrackId: string;
|
roomCompositeVideoOnly: boolean = false;
|
||||||
|
|
||||||
ROOM_COMPOSITE_LAYOUTS = ['grid', 'speaker', 'single-speaker'];
|
fileOutputSelected: boolean = true;
|
||||||
roomCompositeLayoutSelected: string = 'grid';
|
streamOutputSelected: boolean = false;
|
||||||
roomCompositeAudioOnly: boolean = false;
|
s3Endpoint: string = 'http://localhost:9100'; // 'http://minio:9000'
|
||||||
roomCompositeVideoOnly: boolean = false;
|
rtmpUrls: string[] = ['rtmp://172.17.0.1:1936/live/'];
|
||||||
|
segmentOutputSelected: boolean = false;
|
||||||
|
segmentDuration: number = 6;
|
||||||
|
|
||||||
fileOutputSelected: boolean = true;
|
ingressRoomName: string;
|
||||||
streamOutputSelected: boolean = false;
|
ingressId: string;
|
||||||
s3Endpoint: string = 'http://localhost:9100'; // 'http://minio:9000'
|
inputTypeSelected: IngressInput = IngressInput.URL_INPUT;
|
||||||
rtmpUrls: string[] = ['rtmp://172.17.0.1:1936/live/']
|
ingressWithVideo: boolean = true;
|
||||||
segmentOutputSelected: boolean = false;
|
ingressWithAudio: boolean = false;
|
||||||
segmentDuration: number = 6;
|
ingressVideoCodecSelected: VideoCodec = VideoCodec.H264_BASELINE;
|
||||||
|
ingressSimulcast: boolean = true;
|
||||||
|
ingressVideoEncodingPresetSelected?: IngressVideoEncodingPreset = undefined;
|
||||||
|
|
||||||
ingressRoomName: string;
|
response: string;
|
||||||
ingressId: string;
|
|
||||||
inputTypeSelected: IngressInput = IngressInput.URL_INPUT;
|
|
||||||
INGRESS_INPUT_TYPES: { value: IngressInput, viewValue: string }[] = [
|
|
||||||
{ value: IngressInput.URL_INPUT, viewValue: 'URL' },
|
|
||||||
{ value: IngressInput.RTMP_INPUT, viewValue: 'RTMP' },
|
|
||||||
{ value: IngressInput.WHIP_INPUT, viewValue: 'WHIP' },
|
|
||||||
];
|
|
||||||
|
|
||||||
response: string;
|
INGRESS_INPUT_TYPES: { value: IngressInput; viewValue: string }[] = [
|
||||||
|
{ value: IngressInput.URL_INPUT, viewValue: 'URL' },
|
||||||
|
{ value: IngressInput.RTMP_INPUT, viewValue: 'RTMP' },
|
||||||
|
{ value: IngressInput.WHIP_INPUT, viewValue: 'WHIP' },
|
||||||
|
];
|
||||||
|
INGRESS_VIDEO_CODECS: { value: VideoCodec; viewValue: string }[] = [
|
||||||
|
{ value: VideoCodec.H264_BASELINE, viewValue: 'H264' },
|
||||||
|
{ value: VideoCodec.VP8, viewValue: 'VP8' },
|
||||||
|
];
|
||||||
|
INGRESS_VIDEO_ENCODING_PRESETS: {
|
||||||
|
value: IngressInput | undefined;
|
||||||
|
viewValue: string;
|
||||||
|
}[] = [{ value: undefined, viewValue: 'undefined' }].concat(
|
||||||
|
Object.keys(IngressVideoEncodingPreset)
|
||||||
|
.filter((key) => isNaN(Number(key)))
|
||||||
|
.map((key) => {
|
||||||
|
return {
|
||||||
|
value: IngressVideoEncodingPreset[key as any],
|
||||||
|
viewValue: key.toString(),
|
||||||
|
} as any;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// s3Config = {
|
// s3Config = {
|
||||||
// endpoint: this.s3Endpoint,
|
// endpoint: this.s3Endpoint,
|
||||||
// metadata: { "mytag": "myvalue" },
|
// metadata: { "mytag": "myvalue" },
|
||||||
// tagging: "mytagging"
|
// tagging: "mytagging"
|
||||||
// };
|
// };
|
||||||
|
|
||||||
announcer = inject(LiveAnnouncer);
|
announcer = inject(LiveAnnouncer);
|
||||||
addOnBlur = true;
|
addOnBlur = true;
|
||||||
readonly separatorKeysCodes = [ENTER, COMMA] as const;
|
readonly separatorKeysCodes = [ENTER, COMMA] as const;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private roomApiService: RoomApiService,
|
private roomApiService: RoomApiService,
|
||||||
public dialogRef: MatDialogRef<RoomApiDialogComponent>,
|
public dialogRef: MatDialogRef<RoomApiDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: any
|
@Inject(MAT_DIALOG_DATA) public data: any
|
||||||
) {
|
) {
|
||||||
this.room = data.room;
|
this.room = data.room;
|
||||||
this.localParticipant = data.localParticipant;
|
this.localParticipant = data.localParticipant;
|
||||||
this.apiRoomName = this.room?.name;
|
this.apiRoomName = this.room?.name;
|
||||||
this.apiParticipantIdentity = this.localParticipant?.identity;
|
this.apiParticipantIdentity = this.localParticipant?.identity;
|
||||||
this.apiTrackSid = this.localParticipant?.videoTrackPublications.values().next().value?.trackSid!;
|
this.apiTrackSid = this.localParticipant?.videoTrackPublications
|
||||||
this.egressRoomName = this.room?.name;
|
.values()
|
||||||
this.audioTrackId = this.localParticipant?.audioTrackPublications.values().next().value?.trackSid!;
|
.next().value?.trackSid!;
|
||||||
this.videoTrackId = this.localParticipant?.videoTrackPublications.values().next().value?.trackSid!;
|
this.egressRoomName = this.room?.name;
|
||||||
this.ingressRoomName = this.room?.name;
|
this.audioTrackId = this.localParticipant?.audioTrackPublications
|
||||||
|
.values()
|
||||||
|
.next().value?.trackSid!;
|
||||||
|
this.videoTrackId = this.localParticipant?.videoTrackPublications
|
||||||
|
.values()
|
||||||
|
.next().value?.trackSid!;
|
||||||
|
this.ingressRoomName = this.room?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
async listRooms() {
|
||||||
|
console.log('Listing rooms');
|
||||||
|
try {
|
||||||
|
const rooms = await this.roomApiService.listRooms();
|
||||||
|
this.response = JSON.stringify(rooms, null, 4);
|
||||||
|
} catch (error: any) {
|
||||||
|
this.response = error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async listRooms() {
|
async deleteRoom() {
|
||||||
console.log('Listing rooms');
|
console.log('Deleting room');
|
||||||
try {
|
try {
|
||||||
const rooms = await this.roomApiService.listRooms();
|
await this.roomApiService.deleteRoom(this.apiRoomName);
|
||||||
this.response = JSON.stringify(rooms, null, 4);
|
this.response = 'Room deleted';
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.response = error;
|
this.response = error;
|
||||||
}
|
console.log(JSON.stringify(error));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async deleteRoom() {
|
async deleteAllRooms() {
|
||||||
console.log('Deleting room');
|
console.log('Deleting all rooms');
|
||||||
try {
|
try {
|
||||||
await this.roomApiService.deleteRoom(this.apiRoomName);
|
const promises: Promise<void>[] = [];
|
||||||
this.response = 'Room deleted';
|
const rooms = await this.roomApiService.listRooms();
|
||||||
} catch (error: any) {
|
rooms.forEach((r) => {
|
||||||
this.response = error;
|
promises.push(this.roomApiService.deleteRoom(r.name));
|
||||||
console.log(JSON.stringify(error));
|
});
|
||||||
}
|
await Promise.all(promises);
|
||||||
|
this.response = 'Deleted ' + promises.length + ' rooms';
|
||||||
|
} catch (error: any) {
|
||||||
|
this.response = error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async deleteAllRooms() {
|
async listParticipants() {
|
||||||
console.log('Deleting all rooms');
|
console.log('Listing participants');
|
||||||
try {
|
try {
|
||||||
const promises: Promise<void>[] = [];
|
const participants = await this.roomApiService.listParticipants(
|
||||||
const rooms = await this.roomApiService.listRooms();
|
this.apiRoomName
|
||||||
rooms.forEach(r => {
|
);
|
||||||
promises.push(this.roomApiService.deleteRoom(r.name));
|
this.response = JSON.stringify(participants, null, 4);
|
||||||
});
|
} catch (error: any) {
|
||||||
await Promise.all(promises);
|
this.response = error;
|
||||||
this.response = 'Deleted ' + promises.length + ' rooms';
|
|
||||||
} catch (error: any) {
|
|
||||||
this.response = error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async listParticipants() {
|
async getParticipant() {
|
||||||
console.log('Listing participants');
|
console.log('Getting participant');
|
||||||
try {
|
try {
|
||||||
const participants = await this.roomApiService.listParticipants(this.apiRoomName);
|
const participant = await this.roomApiService.getParticipant(
|
||||||
this.response = JSON.stringify(participants, null, 4);
|
this.apiRoomName,
|
||||||
} catch (error: any) {
|
this.apiParticipantIdentity
|
||||||
this.response = error;
|
);
|
||||||
}
|
this.response = JSON.stringify(participant, null, 4);
|
||||||
|
} catch (error: any) {
|
||||||
|
this.response = error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getParticipant() {
|
async removeParticipant() {
|
||||||
console.log('Getting participant');
|
console.log('Removing participant');
|
||||||
try {
|
try {
|
||||||
const participant = await this.roomApiService.getParticipant(this.apiRoomName, this.apiParticipantIdentity);
|
await this.roomApiService.removeParticipant(
|
||||||
this.response = JSON.stringify(participant, null, 4);
|
this.apiRoomName,
|
||||||
} catch (error: any) {
|
this.apiParticipantIdentity
|
||||||
this.response = error;
|
);
|
||||||
}
|
this.response = 'Participant removed';
|
||||||
|
} catch (error: any) {
|
||||||
|
this.response = error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async removeParticipant() {
|
async mutePublishedTrack() {
|
||||||
console.log('Removing participant');
|
console.log(`${this.muteTrack ? 'Muting' : 'Unmuting'} track`);
|
||||||
try {
|
try {
|
||||||
await this.roomApiService.removeParticipant(this.apiRoomName, this.apiParticipantIdentity);
|
await this.roomApiService.mutePublishedTrack(
|
||||||
this.response = 'Participant removed';
|
this.apiRoomName,
|
||||||
} catch (error: any) {
|
this.apiParticipantIdentity,
|
||||||
this.response = error;
|
this.apiTrackSid,
|
||||||
}
|
this.muteTrack
|
||||||
|
);
|
||||||
|
this.response = `Track ${this.muteTrack ? 'muted' : 'unmuted'}`;
|
||||||
|
this.muteTrack = !this.muteTrack;
|
||||||
|
} catch (error: any) {
|
||||||
|
this.response = error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async mutePublishedTrack() {
|
async listEgress() {
|
||||||
console.log(`${this.muteTrack ? 'Muting' : 'Unmuting'} track`);
|
console.log('Listing egress');
|
||||||
try {
|
try {
|
||||||
await this.roomApiService.mutePublishedTrack(this.apiRoomName, this.apiParticipantIdentity, this.apiTrackSid, this.muteTrack);
|
const egress = await this.roomApiService.listEgress();
|
||||||
this.response = `Track ${this.muteTrack ? 'muted' : 'unmuted'}`;
|
this.response = JSON.stringify(egress, null, 4);
|
||||||
this.muteTrack = !this.muteTrack;
|
} catch (error: any) {
|
||||||
} catch (error: any) {
|
this.response = error;
|
||||||
this.response = error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async listEgress() {
|
async startRoomCompositeEgress() {
|
||||||
console.log('Listing egress');
|
console.log('Starting room composite egress');
|
||||||
try {
|
try {
|
||||||
const egress = await this.roomApiService.listEgress();
|
const encodedOutputs = this.getEncodedOutputs();
|
||||||
this.response = JSON.stringify(egress, null, 4);
|
const roomCompositeOptions: RoomCompositeOptions = {
|
||||||
} catch (error: any) {
|
layout: this.roomCompositeLayoutSelected,
|
||||||
this.response = error;
|
audioOnly: this.roomCompositeAudioOnly,
|
||||||
}
|
videoOnly: this.roomCompositeVideoOnly,
|
||||||
|
};
|
||||||
|
const egress = await this.roomApiService.startRoomCompositeEgress(
|
||||||
|
this.egressRoomName,
|
||||||
|
roomCompositeOptions,
|
||||||
|
encodedOutputs
|
||||||
|
);
|
||||||
|
this.response = JSON.stringify(egress, null, 4);
|
||||||
|
this.egressId = egress.egressId;
|
||||||
|
} catch (error: any) {
|
||||||
|
this.response = error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async startRoomCompositeEgress() {
|
async startTrackCompositeEgress() {
|
||||||
console.log('Starting room composite egress');
|
console.log('Starting track composite egress');
|
||||||
try {
|
try {
|
||||||
const encodedOutputs = this.getEncodedOutputs();
|
const encodedOutputs = this.getEncodedOutputs();
|
||||||
const roomCompositeOptions: RoomCompositeOptions = {
|
const egress = await this.roomApiService.startTrackCompositeEgress(
|
||||||
layout: this.roomCompositeLayoutSelected,
|
this.egressRoomName,
|
||||||
audioOnly: this.roomCompositeAudioOnly,
|
this.audioTrackId,
|
||||||
videoOnly: this.roomCompositeVideoOnly
|
this.videoTrackId,
|
||||||
}
|
encodedOutputs
|
||||||
const egress = await this.roomApiService.startRoomCompositeEgress(this.egressRoomName, roomCompositeOptions, encodedOutputs);
|
);
|
||||||
this.response = JSON.stringify(egress, null, 4);
|
this.response = JSON.stringify(egress, null, 4);
|
||||||
this.egressId = egress.egressId;
|
this.egressId = egress.egressId;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.response = error;
|
this.response = error;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async startTrackCompositeEgress() {
|
async startTrackEgress() {
|
||||||
console.log('Starting track composite egress');
|
console.log('Starting track egress');
|
||||||
try {
|
try {
|
||||||
const encodedOutputs = this.getEncodedOutputs();
|
const egress = await this.roomApiService.startTrackEgress(
|
||||||
const egress = await this.roomApiService.startTrackCompositeEgress(this.egressRoomName, this.audioTrackId, this.videoTrackId, encodedOutputs);
|
this.egressRoomName,
|
||||||
this.response = JSON.stringify(egress, null, 4);
|
!!this.audioTrackId ? this.audioTrackId : this.videoTrackId
|
||||||
this.egressId = egress.egressId;
|
);
|
||||||
} catch (error: any) {
|
this.response = JSON.stringify(egress, null, 4);
|
||||||
this.response = error;
|
this.egressId = egress.egressId;
|
||||||
}
|
} catch (error: any) {
|
||||||
|
this.response = error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async startTrackEgress() {
|
async stopEgress() {
|
||||||
console.log('Starting track egress');
|
console.log('Stopping egress');
|
||||||
try {
|
try {
|
||||||
const egress = await this.roomApiService.startTrackEgress(this.egressRoomName, !!this.audioTrackId ? this.audioTrackId : this.videoTrackId);
|
await this.roomApiService.stopEgress(this.egressId);
|
||||||
this.response = JSON.stringify(egress, null, 4);
|
this.response = 'Egress stopped';
|
||||||
this.egressId = egress.egressId;
|
} catch (error: any) {
|
||||||
} catch (error: any) {
|
this.response = error;
|
||||||
this.response = error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async stopEgress() {
|
async listIngress() {
|
||||||
console.log('Stopping egress');
|
console.log('Listing ingress');
|
||||||
try {
|
try {
|
||||||
await this.roomApiService.stopEgress(this.egressId);
|
const ingress = await this.roomApiService.listIngress();
|
||||||
this.response = 'Egress stopped';
|
this.response = JSON.stringify(ingress, null, 4);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.response = error;
|
this.response = error;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async listIngress() {
|
async createIngress() {
|
||||||
console.log('Listing ingress');
|
console.log('Creating ingress');
|
||||||
try {
|
try {
|
||||||
const ingress = await this.roomApiService.listIngress();
|
const ingress = await this.roomApiService.createIngress(
|
||||||
this.response = JSON.stringify(ingress, null, 4);
|
this.ingressRoomName,
|
||||||
} catch (error: any) {
|
this.inputTypeSelected,
|
||||||
this.response = error;
|
this.ingressWithAudio,
|
||||||
}
|
this.ingressWithVideo,
|
||||||
|
this.ingressVideoCodecSelected,
|
||||||
|
this.ingressSimulcast,
|
||||||
|
this.ingressVideoEncodingPresetSelected
|
||||||
|
);
|
||||||
|
this.response = JSON.stringify(ingress, null, 4);
|
||||||
|
this.ingressId = ingress.ingressId;
|
||||||
|
} catch (error: any) {
|
||||||
|
this.response = error;
|
||||||
|
console.warn(error);
|
||||||
|
console.warn(error.code);
|
||||||
|
console.warn(error.msg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async createIngress() {
|
async deleteIngress() {
|
||||||
console.log('Creating ingress');
|
console.log('Deleting ingress');
|
||||||
try {
|
try {
|
||||||
const ingress = await this.roomApiService.createIngress(this.ingressRoomName, this.inputTypeSelected);
|
await this.roomApiService.deleteIngress(this.ingressId);
|
||||||
this.response = JSON.stringify(ingress, null, 4);
|
this.response = 'Ingress deleted';
|
||||||
this.ingressId = ingress.ingressId;
|
} catch (error: any) {
|
||||||
} catch (error: any) {
|
this.response = error;
|
||||||
this.response = error;
|
|
||||||
console.warn(error);
|
|
||||||
console.warn(error.code);
|
|
||||||
console.warn(error.msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async deleteIngress() {
|
async deleteAllIngress() {
|
||||||
console.log('Deleting ingress');
|
console.log('Deleting all ingress');
|
||||||
try {
|
try {
|
||||||
await this.roomApiService.deleteIngress(this.ingressId);
|
const promises: Promise<IngressInfo>[] = [];
|
||||||
this.response = 'Ingress deleted';
|
const ingresses = await this.roomApiService.listIngress();
|
||||||
} catch (error: any) {
|
ingresses.forEach((i) => {
|
||||||
this.response = error;
|
promises.push(this.roomApiService.deleteIngress(i.ingressId));
|
||||||
}
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
this.response = 'Deleted ' + promises.length + ' ingresses';
|
||||||
|
} catch (error: any) {
|
||||||
|
this.response = error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async deleteAllIngress() {
|
addRtmpUrl(event: MatChipInputEvent): void {
|
||||||
console.log('Deleting all ingress');
|
const value = (event.value || '').trim();
|
||||||
try {
|
if (value) {
|
||||||
const promises: Promise<IngressInfo>[] = [];
|
this.rtmpUrls.push(value);
|
||||||
const ingresses = await this.roomApiService.listIngress();
|
|
||||||
ingresses.forEach(i => {
|
|
||||||
promises.push(this.roomApiService.deleteIngress(i.ingressId));
|
|
||||||
});
|
|
||||||
await Promise.all(promises);
|
|
||||||
this.response = 'Deleted ' + promises.length + ' ingresses';
|
|
||||||
} catch (error: any) {
|
|
||||||
this.response = error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
event.chipInput!.clear();
|
||||||
|
}
|
||||||
|
|
||||||
addRtmpUrl(event: MatChipInputEvent): void {
|
removeRtmpUrl(url: string): void {
|
||||||
const value = (event.value || '').trim();
|
const index = this.rtmpUrls.indexOf(url);
|
||||||
if (value) {
|
if (index >= 0) {
|
||||||
this.rtmpUrls.push(value);
|
this.rtmpUrls.splice(index, 1);
|
||||||
}
|
this.announcer.announce(`Removed ${url}`);
|
||||||
event.chipInput!.clear();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
removeRtmpUrl(url: string): void {
|
private getEncodedOutputs(): EncodedOutputs {
|
||||||
const index = this.rtmpUrls.indexOf(url);
|
// this.s3Config.endpoint = this.s3Endpoint;
|
||||||
if (index >= 0) {
|
const encodedOutputs: EncodedOutputs = {};
|
||||||
this.rtmpUrls.splice(index, 1);
|
if (this.fileOutputSelected) {
|
||||||
this.announcer.announce(`Removed ${url}`);
|
encodedOutputs.file = new EncodedFileOutput({
|
||||||
}
|
fileType: EncodedFileType.DEFAULT_FILETYPE,
|
||||||
|
filepath: 'room-composite-{room_id}-{room_name}-{time}',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
if (this.streamOutputSelected) {
|
||||||
private getEncodedOutputs(): EncodedOutputs {
|
encodedOutputs.stream = new StreamOutput({
|
||||||
// this.s3Config.endpoint = this.s3Endpoint;
|
urls: this.rtmpUrls,
|
||||||
const encodedOutputs: EncodedOutputs = {};
|
protocol: StreamProtocol.RTMP,
|
||||||
if (this.fileOutputSelected) {
|
});
|
||||||
encodedOutputs.file = new EncodedFileOutput({
|
|
||||||
fileType: EncodedFileType.DEFAULT_FILETYPE,
|
|
||||||
filepath: 'room-composite-{room_id}-{room_name}-{time}',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.streamOutputSelected) {
|
|
||||||
encodedOutputs.stream = new StreamOutput({
|
|
||||||
urls: this.rtmpUrls,
|
|
||||||
protocol: StreamProtocol.RTMP
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.segmentOutputSelected) {
|
|
||||||
encodedOutputs.segments = new SegmentedFileOutput({
|
|
||||||
protocol: SegmentedFileProtocol.HLS_PROTOCOL,
|
|
||||||
segmentDuration: this.segmentDuration,
|
|
||||||
// s3: this.s3Config
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return encodedOutputs;
|
|
||||||
}
|
}
|
||||||
|
if (this.segmentOutputSelected) {
|
||||||
|
encodedOutputs.segments = new SegmentedFileOutput({
|
||||||
|
protocol: SegmentedFileProtocol.HLS_PROTOCOL,
|
||||||
|
segmentDuration: this.segmentDuration,
|
||||||
|
// s3: this.s3Config
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return encodedOutputs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,11 +80,11 @@ export class OpenviduInstanceComponent {
|
||||||
autoSubscribe: false,
|
autoSubscribe: false,
|
||||||
};
|
};
|
||||||
createLocalTracksOptions: CreateLocalTracksOptions = {
|
createLocalTracksOptions: CreateLocalTracksOptions = {
|
||||||
audio: true,
|
audio: false,
|
||||||
video: {
|
video: {
|
||||||
resolution: {
|
resolution: {
|
||||||
width: 640,
|
width: 1920,
|
||||||
height: 480,
|
height: 1080,
|
||||||
frameRate: 30,
|
frameRate: 30,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,10 +21,15 @@ import {
|
||||||
RoomServiceClient,
|
RoomServiceClient,
|
||||||
TrackCompositeOptions,
|
TrackCompositeOptions,
|
||||||
TrackInfo,
|
TrackInfo,
|
||||||
TrackSource,
|
|
||||||
VideoGrant,
|
VideoGrant,
|
||||||
} from 'livekit-server-sdk';
|
} from 'livekit-server-sdk';
|
||||||
import { LivekitParamsService } from './livekit-params.service';
|
import { LivekitParamsService } from './livekit-params.service';
|
||||||
|
import { VideoQuality } from 'livekit-client';
|
||||||
|
import {
|
||||||
|
IngressVideoEncodingOptions,
|
||||||
|
VideoCodec,
|
||||||
|
VideoLayer,
|
||||||
|
} from '@livekit/protocol';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
|
@ -134,7 +139,8 @@ export class RoomApiService {
|
||||||
encodingOptions?: EncodingOptionsPreset | EncodingOptions
|
encodingOptions?: EncodingOptionsPreset | EncodingOptions
|
||||||
): Promise<EgressInfo> {
|
): Promise<EgressInfo> {
|
||||||
if (encodedOutputs.file) {
|
if (encodedOutputs.file) {
|
||||||
encodedOutputs.file.filepath = 'RoomComposite-{room_id}-{room_name}-{time}';
|
encodedOutputs.file.filepath =
|
||||||
|
'RoomComposite-{room_id}-{room_name}-{time}';
|
||||||
}
|
}
|
||||||
if (encodingOptions) {
|
if (encodingOptions) {
|
||||||
roomCompositeOptions.encodingOptions = encodingOptions;
|
roomCompositeOptions.encodingOptions = encodingOptions;
|
||||||
|
@ -201,49 +207,78 @@ export class RoomApiService {
|
||||||
|
|
||||||
async createIngress(
|
async createIngress(
|
||||||
room_name: string,
|
room_name: string,
|
||||||
input_type: IngressInput
|
input_type: IngressInput,
|
||||||
|
withAudio: boolean,
|
||||||
|
withVideo: boolean,
|
||||||
|
codec: VideoCodec,
|
||||||
|
simulcast: boolean,
|
||||||
|
preset?: IngressVideoEncodingPreset
|
||||||
): Promise<IngressInfo> {
|
): Promise<IngressInfo> {
|
||||||
const ingressClient: IngressClient = new IngressClient(
|
let url;
|
||||||
this.getRestUrl(),
|
if (!withVideo) {
|
||||||
this.livekitParamsService.getParams().livekitApiKey,
|
url =
|
||||||
this.livekitParamsService.getParams().livekitApiSecret
|
'https://s3.eu-west-1.amazonaws.com/public.openvidu.io/bbb_sunflower_1080p_60fps_normal_onlyaudio.mp3';
|
||||||
);
|
} else {
|
||||||
|
url = withAudio
|
||||||
|
? 'https://s3.eu-west-1.amazonaws.com/public.openvidu.io/bbb_sunflower_1080p_60fps_normal.mp4'
|
||||||
|
: 'https://s3.eu-west-1.amazonaws.com/public.openvidu.io/bbb_sunflower_1080p_60fps_normal_noaudio.mp4';
|
||||||
|
}
|
||||||
let options: CreateIngressOptions = {
|
let options: CreateIngressOptions = {
|
||||||
name: input_type + '-' + room_name,
|
name: input_type + '-' + room_name,
|
||||||
roomName: room_name,
|
roomName: room_name,
|
||||||
participantIdentity: 'IngressParticipantIdentity',
|
participantIdentity: 'IngressParticipantIdentity',
|
||||||
participantName: 'MyIngress',
|
participantName: 'MyIngress',
|
||||||
participantMetadata: 'IngressParticipantMetadata',
|
participantMetadata: 'IngressParticipantMetadata',
|
||||||
url: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
|
url,
|
||||||
// video: {
|
|
||||||
// encodingOptions: {
|
|
||||||
// video_codec: VideoCodec.VP8,
|
|
||||||
// frame_rate: 30,
|
|
||||||
// layers: [
|
|
||||||
// {
|
|
||||||
// quality: VideoQuality.HIGH,
|
|
||||||
// width: 1920,
|
|
||||||
// height: 1080,
|
|
||||||
// bitrate: 4500000,
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// } as any,
|
|
||||||
video: {
|
video: {
|
||||||
name: 'pelicula',
|
|
||||||
source: TrackSource.SCREEN_SHARE,
|
|
||||||
encodingOptions: {
|
encodingOptions: {
|
||||||
case: 'preset',
|
case: preset != undefined ? 'preset' : 'options',
|
||||||
value:
|
value: {},
|
||||||
IngressVideoEncodingPreset.H264_540P_25FPS_2_LAYERS_HIGH_MOTION,
|
|
||||||
},
|
},
|
||||||
} as any,
|
} as any,
|
||||||
// audio: {
|
|
||||||
// source: TrackSource.MICROPHONE,
|
|
||||||
// preset: IngressAudioEncodingPreset.OPUS_MONO_64KBS,
|
|
||||||
// } as any,
|
|
||||||
};
|
};
|
||||||
const ingressInfo = await ingressClient.createIngress(input_type, options);
|
if (preset != undefined) {
|
||||||
|
options.video!.encodingOptions.value = preset;
|
||||||
|
} else {
|
||||||
|
const encodingOptions = options.video!.encodingOptions
|
||||||
|
.value! as IngressVideoEncodingOptions;
|
||||||
|
encodingOptions.videoCodec = codec;
|
||||||
|
encodingOptions.frameRate = 30;
|
||||||
|
let layers: VideoLayer[] = [];
|
||||||
|
if (simulcast) {
|
||||||
|
layers = [
|
||||||
|
{
|
||||||
|
quality: VideoQuality.HIGH,
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quality: VideoQuality.MEDIUM,
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quality: VideoQuality.LOW,
|
||||||
|
width: 640,
|
||||||
|
height: 360,
|
||||||
|
},
|
||||||
|
] as VideoLayer[];
|
||||||
|
} else {
|
||||||
|
layers = [
|
||||||
|
{
|
||||||
|
quality: VideoQuality.HIGH,
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
} as VideoLayer,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
encodingOptions.layers = layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ingressInfo = await this.ingressClient.createIngress(
|
||||||
|
input_type,
|
||||||
|
options
|
||||||
|
);
|
||||||
return ingressInfo;
|
return ingressInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue