openvidu-components: Added fault tolerant support

On media node crashed, openvidu-copmponents emits an event for requesting new tokens to the client.
pull/758/head
Carlos Santos 2022-11-04 16:24:42 +01:00
parent dd62c033df
commit fb5c56cd87
7 changed files with 95 additions and 48 deletions

View File

@ -64,6 +64,8 @@ export class SessionComponent implements OnInit {
@Input() usedInPrejoinPage = false;
@Output() onSessionCreated = new EventEmitter<any>();
@Output() onNodeCrashed = new EventEmitter<any>();
session: Session;
sessionScreen: Session;
@ -378,7 +380,14 @@ export class SessionComponent implements OnInit {
this.actionService.closeDialog();
});
this.session.on('sessionDisconnected', (event: SessionDisconnectedEvent) => {
if (event.reason === 'networkDisconnect') {
if (event.reason === 'nodeCrashed') {
this.actionService.openDialog(
this.translateService.translate('ERRORS.CONNECTION'),
this.translateService.translate('ERRORS.RECONNECT'),
false
);
this.onNodeCrashed.emit();
} else if (event.reason === 'networkDisconnect') {
this.actionService.closeDialog();
this.leaveSession();
}

View File

@ -68,7 +68,6 @@ export class VideoDevicesComponent implements OnInit, OnDestroy {
this.videoMuteChanging = true;
const publish = this.isVideoMuted;
await this.openviduService.publishVideo(publish);
this.storageSrv.setVideoMuted(this.isVideoMuted);
if (this.isVideoMuted && this.panelService.isExternalPanelOpened()) {
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
}
@ -107,6 +106,7 @@ export class VideoDevicesComponent implements OnInit, OnDestroy {
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p: ParticipantAbstractModel) => {
if (p) {
this.isVideoMuted = !p.isCameraVideoActive();
this.storageSrv.setVideoMuted(this.isVideoMuted);
}
});
}

View File

@ -39,6 +39,7 @@ import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { ParticipantService } from '../../services/participant/participant.service';
import { PlatformService } from '../../services/platform/platform.service';
import { RecordingService } from '../../services/recording/recording.service';
import { StorageService } from '../../services/storage/storage.service';
import { TranslateService } from '../../services/translate/translate.service';
/**
@ -383,7 +384,8 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private libService: OpenViduAngularConfigService,
private platformService: PlatformService,
private recordingService: RecordingService,
private translateService: TranslateService
private translateService: TranslateService,
private storageSrv: StorageService
) {
this.log = this.loggerSrv.get('ToolbarComponent');
}
@ -627,6 +629,8 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.isAudioActive = p.hasAudioActive();
this.isScreenShareActive = p.isScreenActive();
this.isSessionCreator = p.getRole() === OpenViduRole.MODERATOR;
this.storageSrv.setAudioMuted(!this.isAudioActive);
this.storageSrv.setVideoMuted(!this.isWebcamVideoActive);
this.cd.markForCheck();
}
});
@ -637,7 +641,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
.pipe(skip(1))
.subscribe((ev: { info: RecordingInfo; time?: Date }) => {
this.recordingStatus = ev.info.status;
this.recordingTime = ev.time;
if (ev.time) {
this.recordingTime = ev.time;
}
this.cd.markForCheck();
});
}

View File

@ -15,7 +15,7 @@
</div>
<div [@inOutAnimation] id="vc-container" *ngIf="showVideoconference || (!showPrejoin && !loading && !error)">
<ov-session (onSessionCreated)="_onSessionCreated($event)" *ngIf="isSessionInitialized">
<ov-session *ngIf="isSessionInitialized && !nodeCrashed" (onSessionCreated)="_onSessionCreated($event)" (onNodeCrashed)="_onNodeCrashed()">
<ng-template #toolbar>
<ng-container *ngIf="openviduAngularToolbarTemplate">
<ng-container *ngTemplateOutlet="openviduAngularToolbarTemplate"></ng-container>

View File

@ -383,6 +383,13 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
*/
@Output() onParticipantCreated: EventEmitter<ParticipantAbstractModel> = new EventEmitter<ParticipantAbstractModel>();
/**
* Provides event notifications that fire when Media Node crash OpenVidu Pro from the OpenVidu Pro cluster.
* OpenVidu Pro delegates the recovery of the sessions to the application in the event of a Media Node crash.
* See {@link https://docs.openvidu.io/en/stable/openvidu-pro/fault-tolerance/ OpenVidu Pro Fault tolerance}.
*/
@Output() onNodeCrashed: EventEmitter<void> = new EventEmitter<void>();
/**
* @internal
*/
@ -414,6 +421,7 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
* @internal
*/
loading = true;
private nodeCrashed: boolean = false;
private externalParticipantName: string;
private prejoinSub: Subscription;
private participantNameSub: Subscription;
@ -440,40 +448,6 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
this.subscribeToVideconferenceDirectives();
}
private async start() {
await this.deviceSrv.forceInitDevices();
const nickname = this.externalParticipantName || this.storageSrv.getNickname() || `OpenVidu_User${Math.floor(Math.random() * 100)}`;
this.participantService.initLocalParticipant({ local: true, nickname });
this.openviduService.initialize();
if (this.deviceSrv.hasVideoDeviceAvailable() || this.deviceSrv.hasAudioDeviceAvailable()) {
await this.initwebcamPublisher();
}
this.isSessionInitialized = true;
this.onParticipantCreated.emit(this.participantService.getLocalParticipant());
this.loading = false;
this.participantReady = true;
}
private async initwebcamPublisher(): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
const publisher = await this.openviduService.initDefaultPublisher();
if (publisher) {
publisher.once('accessDenied', async (e: any) => {
await this.handlePublisherError(e);
resolve();
});
publisher.once('accessAllowed', () => resolve());
}
} catch (error) {
this.actionService.openDialog(error.name.replace(/_/g, ' '), error.message, true);
this.log.e(error);
reject();
}
});
}
async ngOnDestroy() {
if (this.prejoinSub) this.prejoinSub.unsubscribe();
if (this.participantNameSub) this.participantNameSub.unsubscribe();
@ -566,6 +540,44 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
}
}
private async start() {
await this.deviceSrv.forceInitDevices();
const nickname = this.externalParticipantName || this.storageSrv.getNickname() || `OpenVidu_User${Math.floor(Math.random() * 100)}`;
this.participantService.initLocalParticipant({ local: true, nickname });
this.openviduService.initialize();
if (this.deviceSrv.hasVideoDeviceAvailable() || this.deviceSrv.hasAudioDeviceAvailable()) {
await this.initwebcamPublisher();
}
this.isSessionInitialized = true;
this.onParticipantCreated.emit(this.participantService.getLocalParticipant());
this.loading = false;
this.participantReady = true;
if (this.nodeCrashed) {
this.nodeCrashed = false;
this.actionService.closeDialog();
}
}
private async initwebcamPublisher(): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
const publisher = await this.openviduService.initDefaultPublisher();
if (publisher) {
publisher.once('accessDenied', async (e: any) => {
await this.handlePublisherError(e);
resolve();
});
publisher.once('accessAllowed', () => resolve());
}
} catch (error) {
this.actionService.openDialog(error.name.replace(/_/g, ' '), error.message, true);
this.log.e(error);
reject();
}
});
}
/**
* @internal
*/
@ -661,6 +673,14 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
this.onSessionCreated.emit(session);
}
/**
* @internal
*/
_onNodeCrashed() {
this.nodeCrashed = true;
this.onNodeCrashed.emit();
}
private async handlePublisherError(e: any): Promise<void> {
let message: string = '';
if (e.name === OpenViduErrorName.DEVICE_ALREADY_IN_USE) {

View File

@ -13,10 +13,11 @@
(onActivitiesPanelStartRecordingClicked)="onStartRecordingClicked()"
(onActivitiesPanelStopRecordingClicked)="onStopRecordingClicked()"
(onActivitiesPanelDeleteRecordingClicked)="onDeleteRecordingClicked($event)"
(onNodeCrashed)="onNodeCrashed()"
[minimal]="false"
[prejoin]="false"
[videoMuted]="false"
[audioMuted]="true"
[audioMuted]="false"
[activitiesPanelRecordingActivity]="true"
[recordingActivityRecordingsList]="recordingList"
[recordingActivityRecordingError]="recordingError"

View File

@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { RecordingInfo, TokenModel, RecordingService } from 'openvidu-angular';
import { RecordingInfo, RecordingService, TokenModel } from 'openvidu-angular';
import { RestService } from '../services/rest.service';
@Component({
@ -21,13 +21,12 @@ export class CallComponent implements OnInit {
constructor(private restService: RestService, private recordingService: RecordingService) { }
async ngOnInit() {
const response = await this.restService.getTokensFromBackend(this.sessionId);
this.recordingList = response.recordings;
this.tokens = {
webcam: response.cameraToken,
screen: response.screenToken
};
console.log(this.tokens);
await this.requestForTokens();
}
async onNodeCrashed() {
// Request the tokens again for reconnect to the session
await this.requestForTokens();
}
onJoinClicked() {
@ -115,4 +114,16 @@ export class CallComponent implements OnInit {
}
}
private async requestForTokens() {
const response = await this.restService.getTokensFromBackend(this.sessionId);
this.recordingList = response.recordings;
this.tokens = {
webcam: response.cameraToken,
screen: response.screenToken
};
console.log('Token requested: ', this.tokens);
}
}