openvidu-testapp: add CreateIngressOptions to dialog

pull/850/merge
pabloFuente 2024-11-21 14:54:49 +01:00
parent 738431b6bf
commit 18343fa47d
6 changed files with 448 additions and 291 deletions

View File

@ -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">

View File

@ -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;
}

View File

@ -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">

View File

@ -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;
}
} }

View File

@ -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,
}, },
}, },

View File

@ -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;
} }