openvidu-components: Emitted event for updating recording list

Non moderators participants didn't update their recording list because they didn't know when a recording has been deleted or stopped. Now all standard participants know all recording events using custom signals and emitted an event to the application with the aim of updating the recording list.
pull/803/head
Carlos Santos 2023-04-28 17:50:11 +02:00
parent 9be838308c
commit fb101bc2c7
12 changed files with 92 additions and 14 deletions

View File

@ -15,6 +15,7 @@
(onStartRecordingClicked)="_onStartRecordingClicked()"
(onStopRecordingClicked)="_onStopRecordingClicked()"
(onDeleteRecordingClicked)="_onDeleteRecordingClicked($event)"
(onForceRecordingUpdate)="_onForceRecordingUpdate()"
></ov-recording-activity>
<ov-broadcasting-activity
*ngIf="showBroadcastingActivity"

View File

@ -29,6 +29,13 @@ export class ActivitiesPanelComponent implements OnInit {
*/
@Output() onDeleteRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when a participant needs update the recordings information
* (usually when recording is stopped by the session moderator or recording panel is opened).
* The recordings should be updated using the REST API.
*/
@Output() onForceRecordingUpdate: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when start broadcasting button has been clicked.
* The broadcasting should be started using the REST API.
@ -112,6 +119,10 @@ export class ActivitiesPanelComponent implements OnInit {
this.onStopBroadcastingClicked.emit();
}
_onForceRecordingUpdate() {
this.onForceRecordingUpdate.emit();
}
private subscribeToPanelToggling() {
this.panelSubscription = this.panelService.panelOpenedObs.subscribe((ev: PanelEvent) => {
if (ev.type === PanelType.ACTIVITIES && !!ev.expand) {

View File

@ -117,6 +117,7 @@
</button>
<button
*ngIf="isSessionCreator"
mat-icon-button
class="delete-recording-btn"
id="delete-recording-btn"

View File

@ -5,6 +5,8 @@ import { ActionService } from '../../../../services/action/action.service';
import { OpenViduAngularConfigService } from '../../../../services/config/openvidu-angular.config.service';
import { ParticipantService } from '../../../../services/participant/participant.service';
import { RecordingService } from '../../../../services/recording/recording.service';
import { OpenViduService } from '../../../../services/openvidu/openvidu.service';
import { Signal } from '../../../../models/signal.model';
@Component({
selector: 'ov-recording-activity',
@ -36,6 +38,13 @@ export class RecordingActivityComponent implements OnInit {
*/
@Output() onDeleteRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when a participant needs update the recordings information
* (usually when recording is stopped by the session moderator or recording panel is opened).
* The recordings should be updated using the REST API.
*/
@Output() onForceRecordingUpdate: EventEmitter<void> = new EventEmitter<void>();
/**
* @internal
*/
@ -76,6 +85,7 @@ export class RecordingActivityComponent implements OnInit {
private recordingStatusSubscription: Subscription;
private recordingListSubscription: Subscription;
private recordingErrorSub: Subscription;
private forceRecordingUpdateSub: Subscription;
/**
* @internal
@ -85,6 +95,7 @@ export class RecordingActivityComponent implements OnInit {
private participantService: ParticipantService,
private libService: OpenViduAngularConfigService,
private actionService: ActionService,
private openviduService: OpenViduService,
private cd: ChangeDetectorRef
) {}
@ -94,6 +105,7 @@ export class RecordingActivityComponent implements OnInit {
ngOnInit(): void {
this.subscribeToRecordingStatus();
this.subscribeToRecordingActivityDirective();
this.subscribeToForceRecordingUpdate();
this.isSessionCreator = this.participantService.amIModerator();
}
@ -104,6 +116,7 @@ export class RecordingActivityComponent implements OnInit {
if (this.recordingStatusSubscription) this.recordingStatusSubscription.unsubscribe();
if (this.recordingListSubscription) this.recordingListSubscription.unsubscribe();
if (this.recordingErrorSub) this.recordingErrorSub.unsubscribe();
if (this.forceRecordingUpdateSub) this.forceRecordingUpdateSub.unsubscribe();
}
/**
@ -156,8 +169,10 @@ export class RecordingActivityComponent implements OnInit {
deleteRecording(id: string) {
const succsessCallback = () => {
this.onDeleteRecordingClicked.emit(id);
// Sending signal to all participants with the aim of updating their recordings list
this.openviduService.sendSignal(Signal.RECORDING_DELETED, this.openviduService.getRemoteConnections());
};
this.actionService.openDeleteRecordingDialog(succsessCallback);
this.actionService.openDeleteRecordingDialog(succsessCallback.bind(this));
}
/**
@ -180,6 +195,10 @@ export class RecordingActivityComponent implements OnInit {
if (ev?.info) {
if (this.recordingStatus !== RecordingStatus.FAILED) {
this.oldRecordingStatus = this.recordingStatus;
if (ev.info.status === RecordingStatus.STOPPED && !this.isSessionCreator) {
// Force update recording list when recording is stopped by moderator
this.onForceRecordingUpdate.emit();
}
}
this.recordingStatus = ev.info.status;
this.recordingAlive = ev.info.status === RecordingStatus.STARTED;
@ -192,6 +211,8 @@ export class RecordingActivityComponent implements OnInit {
private subscribeToRecordingActivityDirective() {
this.recordingListSubscription = this.libService.recordingsListObs.subscribe((recordingList: RecordingInfo[]) => {
this.recordingsList = recordingList;
// si envio aqui el signal, estos eventos serian infinitos
// this.openviduService.sendSignal(Signal.RECORDING_DELETED, this.openviduService.getRemoteConnections());
this.cd.markForCheck();
});
@ -202,4 +223,10 @@ export class RecordingActivityComponent implements OnInit {
}
});
}
private subscribeToForceRecordingUpdate() {
this.forceRecordingUpdateSub = this.recordingService.forceUpdateRecordingsObs.subscribe(() => {
this.onForceRecordingUpdate.emit();
});
}
}

View File

@ -64,29 +64,23 @@ export class SessionComponent implements OnInit, OnDestroy {
@ContentChild('toolbar', { read: TemplateRef }) toolbarTemplate: TemplateRef<any>;
@ContentChild('panel', { read: TemplateRef }) panelTemplate: TemplateRef<any>;
@ContentChild('layout', { read: TemplateRef }) layoutTemplate: TemplateRef<any>;
@Input() usedInPrejoinPage = false;
@Output() onNodeCrashed = new EventEmitter<any>();
session: Session;
sessionScreen: Session;
sideMenu: MatSidenav;
sidenavMode: SidenavMode = SidenavMode.SIDE;
settingsPanelOpened: boolean;
drawer: MatDrawerContainer;
preparing: boolean = true;
private isSessionCreator: boolean = false;
protected readonly SIDENAV_WIDTH_LIMIT_MODE = 790;
protected menuSubscription: Subscription;
protected layoutWidthSubscription: Subscription;
protected updateLayoutInterval: NodeJS.Timer;
private captionLanguageSubscription: Subscription;
protected log: ILogger;
constructor(
@ -168,6 +162,7 @@ export class SessionComponent implements OnInit, OnDestroy {
async ngOnInit() {
if (!this.usedInPrejoinPage) {
this.isSessionCreator = this.participantService.amIModerator();
if (!this.openviduService.getScreenToken()) {
// Hide screenshare button if screen token does not exist
this.libService.screenshareButton.next(false);
@ -439,6 +434,11 @@ export class SessionComponent implements OnInit, OnDestroy {
private subscribeToBroadcastingEvents() {
this.session.on('broadcastStarted', () => this.broadcastingService.startBroadcasting());
this.session.on('broadcastStopped', () => this.broadcastingService.stopBroadcasting());
if (!this.isSessionCreator) {
// Listen to recording delete events from moderator
this.session.on(`signal:${Signal.RECORDING_DELETED}`, () => this.recordingService.forceUpdateRecordings());
}
}
private startUpdateLayoutInterval() {

View File

@ -100,6 +100,7 @@
(onStartRecordingClicked)="onStartRecordingClicked('panel')"
(onStopRecordingClicked)="onStopRecordingClicked('panel')"
(onDeleteRecordingClicked)="onDeleteRecordingClicked($event)"
(onForceRecordingUpdate)="onForceRecordingUpdate()"
(onStartBroadcastingClicked)="onStartBroadcastingClicked($event)"
(onStopBroadcastingClicked)="onStopBroadcastingClicked('panel')"
></ov-activities-panel>

View File

@ -378,6 +378,13 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
*/
@Output() onActivitiesPanelDeleteRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when a participant needs update the recordings information
* (usually when recording is stopped by the session moderator or recording panel is opened) from {@link ActivitiesPanelComponent}.
* The recordings should be updated using the REST API.
*/
@Output() onActivitiesPanelForceRecordingUpdate: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when play recording button is clicked from {@link ActivitiesPanelComponent}.
*/
@ -692,6 +699,14 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
this.onActivitiesPanelDeleteRecordingClicked.emit(recordingId);
}
/**
* @internal
*/
onForceRecordingUpdate() {
this.onActivitiesPanelForceRecordingUpdate.emit();
}
/**
* @internal
*/

View File

@ -3,5 +3,6 @@
*/
export enum Signal {
NICKNAME_CHANGED = 'nicknameChanged',
CHAT = 'chat'
CHAT = 'chat',
RECORDING_DELETED = 'recordingDeleted',
}

View File

@ -469,6 +469,9 @@ export class OpenViduService {
/**
* @internal
*
* @param type: type of signal
* @param connections: if undefined, the signal will be sent to all participants
*/
sendSignal(type: Signal, connections?: Connection[], data?: any): void {
const signalOptions: SignalOptions = {
@ -611,7 +614,7 @@ export class OpenViduService {
needSendNicknameSignal(): boolean {
let oldNickname: string;
try {
const connData = JSON.parse(this.webcamSession.connection.data.split('%/%')[0]);
const connData = JSON.parse(this.cleanConnectionData(this.webcamSession.connection.data));
oldNickname = connData.clientData;
} catch (error) {
this.log.e(error);
@ -637,7 +640,7 @@ export class OpenViduService {
// Avoid screen connections
const remoteCameraConnections: Connection[] = Array.from(this.webcamSession.remoteConnections.values()).filter((conn) => {
let type: VideoType;
type = JSON.parse(conn.data).type;
type = JSON.parse(this.cleanConnectionData(conn.data)).type;
return type !== VideoType.SCREEN;
});
return remoteCameraConnections;
@ -656,4 +659,8 @@ export class OpenViduService {
}
}
}
private cleanConnectionData(data: string): string {
return data.split('%/%')[0];
}
}

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { RecordingEvent } from 'openvidu-browser';
import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { RecordingInfo, RecordingStatus } from '../../models/recording.model';
import { ActionService } from '../action/action.service';
@ -13,6 +13,10 @@ export class RecordingService {
*/
recordingStatusObs: Observable<{ info: RecordingInfo; time?: Date } | undefined>;
/**
* @internal
*/
forceUpdateRecordingsObs: Subject<void> = new Subject();
private recordingTime: Date | undefined;
private recordingTimeInterval: NodeJS.Timer;
private currentRecording: RecordingInfo = { status: RecordingStatus.STOPPED };
@ -86,7 +90,6 @@ export class RecordingService {
const recordingId = recording.id;
// Only COMPOSED recording is supported. The extension will allways be 'mp4'.
const extension = 'mp4'; //recording.url?.split('.').pop() || 'mp4';
const link = document.createElement('a');
link.href = `/recordings/${recordingId}/${recordingId}.${extension}`;
link.download = `${recordingId}.${extension}`;
@ -104,6 +107,10 @@ export class RecordingService {
}, 100);
}
forceUpdateRecordings() {
this.forceUpdateRecordingsObs.next();
}
private startRecordingTime() {
this.recordingTime = new Date();
this.recordingTime.setHours(0, 0, 0, 0);

View File

@ -4,7 +4,7 @@
[lang]="'en'"
[captionsLang]=""
[captionsLangOptions]="[{ name: 'Spanish', ISO: 'es-ES' },{ name: 'English', ISO: 'en-US' }]"
[prejoin]="false"
[prejoin]="true"
[participantName]="'Participant'"
[videoMuted]="false"
[audioMuted]="false"
@ -41,6 +41,7 @@
(onActivitiesPanelStartRecordingClicked)="onStartRecordingClicked()"
(onActivitiesPanelStopRecordingClicked)="onStopRecordingClicked()"
(onActivitiesPanelDeleteRecordingClicked)="onDeleteRecordingClicked($event)"
(onActivitiesPanelForceRecordingUpdate)="onForceRecordingUpdate()"
(onActivitiesPanelStartBroadcastingClicked)="onStartBroadcastingClicked($event)"
(onActivitiesPanelStopBroadcastingClicked)="onStopBroadcastingClicked()"
(onToolbarStopBroadcastingClicked)="onStopBroadcastingClicked()"

View File

@ -123,6 +123,7 @@ export class CallComponent implements OnInit {
this.recordingList = await this.restService.stopRecording(this.sessionId);
console.log('RECORDING LIST ', this.recordingList);
} catch (error) {
console.log(await this.restService.getRecordings())
this.recordingError = error;
}
}
@ -137,6 +138,11 @@ export class CallComponent implements OnInit {
}
}
async onForceRecordingUpdate() {
console.warn('FORCE RECORDING UPDATE');
this.recordingList = await this.restService.getRecordings();
}
private async requestForTokens() {
const { broadcastingEnabled, recordingEnabled, recordings, cameraToken, screenToken, isRecordingActive, isBroadcastingActive } =
await this.restService.getTokensFromBackend(this.sessionId);