frontend: update recording permissions handling and improve UI feature flags

This commit is contained in:
juancarmore 2025-08-13 23:10:12 +02:00
parent 5f71b0c28a
commit 2478845fb6
4 changed files with 97 additions and 28 deletions

View File

@ -11,16 +11,15 @@
[toolbarScreenshareButton]="features().showScreenShare"
[toolbarLeaveButton]="!features().canModerateRoom"
[toolbarRecordingButton]="features().canRecordRoom"
[toolbarViewRecordingsButton]="features().showRecordings"
[toolbarViewRecordingsButton]="features().canRetrieveRecordings && hasRecordings"
[toolbarBroadcastingButton]="false"
[toolbarChatPanelButton]="features().showChat"
[toolbarBackgroundEffectsButton]="features().showBackgrounds"
[toolbarParticipantsPanelButton]="features().showParticipantList"
[toolbarSettingsButton]="features().showSettings"
[toolbarFullscreenButton]="features().showFullscreen"
[toolbarActivitiesPanelButton]="features().showRecordings"
[activitiesPanelRecordingActivity]="features().showRecordings"
[recordingActivityReadOnly]="!features().canRecordRoom"
[toolbarActivitiesPanelButton]="features().showRecordingPanel"
[activitiesPanelRecordingActivity]="features().showRecordingPanel"
[recordingActivityShowControls]="{
play: false,
download: false,
@ -28,7 +27,7 @@
externalView: true
}"
[recordingActivityStartStopRecordingButton]="features().canRecordRoom"
[recordingActivityViewRecordingsButton]="features().showRecordings"
[recordingActivityViewRecordingsButton]="features().canRetrieveRecordings && hasRecordings"
[recordingActivityShowRecordingsList]="false"
[activitiesPanelBroadcastingActivity]="false"
[showDisconnectionDialog]="false"
@ -37,8 +36,7 @@
(onParticipantLeft)="onParticipantLeft($event)"
(onRecordingStartRequested)="onRecordingStartRequested($event)"
(onRecordingStopRequested)="onRecordingStopRequested($event)"
(onViewRecordingsClicked)="onViewRecordingsClicked(undefined)"
(onViewRecordingClicked)="onViewRecordingsClicked($event)"
(onViewRecordingsClicked)="onViewRecordingsClicked()"
>
@if (features().canModerateRoom) {
<div *ovToolbarAdditionalButtons>

View File

@ -84,6 +84,8 @@ export class MeetingComponent implements OnInit {
participantForm = new FormGroup({
name: new FormControl('', [Validators.required, Validators.minLength(4)])
});
hasRecordings = false;
showRecordingCard = false;
showBackButton = true;
@ -175,13 +177,23 @@ export class MeetingComponent implements OnInit {
*/
private async checkForRecordings() {
try {
await this.recordingService.generateRecordingToken(this.roomId, this.roomSecret);
const { canRetrieveRecordings } = await this.recordingService.generateRecordingToken(
this.roomId,
this.roomSecret
);
if (!canRetrieveRecordings) {
this.showRecordingCard = false;
return;
}
const { recordings } = await this.recordingService.listRecordings({
maxItems: 1,
roomId: this.roomId,
fields: 'recordingId'
});
this.showRecordingCard = recordings.length > 0;
this.hasRecordings = recordings.length > 0;
this.showRecordingCard = this.hasRecordings;
} catch (error) {
console.error('Error checking for recordings:', error);
this.showRecordingCard = false;
@ -326,10 +338,34 @@ export class MeetingComponent implements OnInit {
const event = JSON.parse(new TextDecoder().decode(payload));
switch (topic) {
case 'recordingStopped': {
// If a 'recordingStopped' event is received and there was no previous recordings,
// update the hasRecordings flag and refresh the recording token
if (this.hasRecordings) return;
this.hasRecordings = true;
try {
await this.recordingService.generateRecordingToken(this.roomId, this.roomSecret);
} catch (error) {
console.error('Error refreshing recording token:', error);
}
break;
}
case MeetSignalType.MEET_ROOM_PREFERENCES_UPDATED: {
// Update room preferences
const { preferences } = event as MeetRoomPreferencesUpdatedPayload;
this.featureConfService.setRoomPreferences(preferences);
// Refresh recording token if recording is enabled
if (preferences.recordingPreferences.enabled) {
try {
await this.recordingService.generateRecordingToken(this.roomId, this.roomSecret);
} catch (error) {
console.error('Error refreshing recording token:', error);
}
}
break;
}
case MeetSignalType.MEET_PARTICIPANT_ROLE_UPDATED: {
@ -504,11 +540,7 @@ Remember that by default, a recording uses 4 CPUs for each room.`
}
}
async onViewRecordingsClicked(recordingId?: any) {
if (recordingId) {
await this.recordingService.playRecording(recordingId);
} else {
window.open(`/room/${this.roomId}/recordings?secret=${this.roomSecret}`, '_blank');
}
async onViewRecordingsClicked() {
window.open(`/room/${this.roomId}/recordings?secret=${this.roomSecret}`, '_blank');
}
}

View File

@ -1,5 +1,11 @@
import { computed, Injectable, signal } from '@angular/core';
import { MeetRoomPreferences, ParticipantPermissions, ParticipantRole, TrackSource } from '@lib/typings/ce';
import {
MeetRoomPreferences,
ParticipantPermissions,
ParticipantRole,
RecordingPermissions,
TrackSource
} from '@lib/typings/ce';
import { LoggerService } from 'openvidu-components-angular';
/**
@ -14,7 +20,7 @@ export interface ApplicationFeatures {
showScreenShare: boolean;
// UI Features
showRecordings: boolean;
showRecordingPanel: boolean;
showChat: boolean;
showBackgrounds: boolean;
showParticipantList: boolean;
@ -24,6 +30,7 @@ export interface ApplicationFeatures {
// Permissions
canModerateRoom: boolean;
canRecordRoom: boolean;
canRetrieveRecordings: boolean;
}
/**
@ -36,7 +43,7 @@ const DEFAULT_FEATURES: ApplicationFeatures = {
showMicrophone: true,
showScreenShare: true,
showRecordings: true,
showRecordingPanel: true,
showChat: true,
showBackgrounds: true,
showParticipantList: true,
@ -44,7 +51,8 @@ const DEFAULT_FEATURES: ApplicationFeatures = {
showFullscreen: true,
canModerateRoom: false,
canRecordRoom: false
canRecordRoom: false,
canRetrieveRecordings: false
};
/**
@ -61,10 +69,16 @@ export class FeatureConfigurationService {
protected roomPreferences = signal<MeetRoomPreferences | undefined>(undefined);
protected participantPermissions = signal<ParticipantPermissions | undefined>(undefined);
protected participantRole = signal<ParticipantRole | undefined>(undefined);
protected recordingPermissions = signal<RecordingPermissions | undefined>(undefined);
// Computed signal to derive features based on current configurations
public readonly features = computed<ApplicationFeatures>(() =>
this.calculateFeatures(this.roomPreferences(), this.participantPermissions(), this.participantRole())
this.calculateFeatures(
this.roomPreferences(),
this.participantPermissions(),
this.participantRole(),
this.recordingPermissions()
)
);
constructor(protected loggerService: LoggerService) {
@ -95,6 +109,14 @@ export class FeatureConfigurationService {
this.participantRole.set(role);
}
/**
* Updates recording permissions
*/
setRecordingPermissions(permissions: RecordingPermissions): void {
this.log.d('Updating recording permissions', permissions);
this.recordingPermissions.set(permissions);
}
/**
* Checks if a specific feature is enabled
*/
@ -108,14 +130,15 @@ export class FeatureConfigurationService {
protected calculateFeatures(
roomPrefs?: MeetRoomPreferences,
participantPerms?: ParticipantPermissions,
role?: ParticipantRole
role?: ParticipantRole,
recordingPerms?: RecordingPermissions
): ApplicationFeatures {
// Start with default configuration
const features: ApplicationFeatures = { ...DEFAULT_FEATURES };
// Apply room configurations
if (roomPrefs) {
features.showRecordings = roomPrefs.recordingPreferences.enabled;
features.showRecordingPanel = roomPrefs.recordingPreferences.enabled;
features.showChat = roomPrefs.chatPreferences.enabled;
features.showBackgrounds = roomPrefs.virtualBackgroundPreferences.enabled;
}
@ -123,8 +146,7 @@ export class FeatureConfigurationService {
// Apply participant permissions (these can restrict enabled features)
if (participantPerms) {
// Only restrict if the feature is already enabled
if (features.showRecordings) {
// features.showRecordings = !!recordingRole;
if (features.showRecordingPanel) {
features.canRecordRoom = participantPerms.openvidu.canRecord;
}
if (features.showChat) {
@ -149,6 +171,11 @@ export class FeatureConfigurationService {
features.canModerateRoom = role === ParticipantRole.MODERATOR;
}
// Apply recording permissions
if (recordingPerms) {
features.canRetrieveRecordings = recordingPerms.canRetrieveRecordings;
}
this.log.d('Calculated features', features);
return features;
}

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ShareRecordingDialogComponent } from '@lib/components';
import { AuthService, HttpService, ParticipantService } from '@lib/services';
import { AuthService, FeatureConfigurationService, HttpService, ParticipantService } from '@lib/services';
import { MeetRecordingFilters, MeetRecordingInfo, RecordingPermissions } from '@lib/typings/ce';
import { getValidDecodedToken } from '@lib/utils';
import { LoggerService } from 'openvidu-components-angular';
@ -25,6 +25,7 @@ export class RecordingService {
private httpService: HttpService,
protected participantService: ParticipantService,
protected authService: AuthService,
protected featureConfService: FeatureConfigurationService,
protected dialog: MatDialog
) {
this.log = this.loggerService.get('OpenVidu Meet - RecordingManagerService');
@ -159,10 +160,18 @@ export class RecordingService {
*/
async generateRecordingToken(roomId: string, secret: string): Promise<RecordingPermissions> {
const path = `${HttpService.INTERNAL_API_PATH_PREFIX}/rooms/${roomId}/recording-token`;
const { token } = await this.httpService.postRequest<{ token: string }>(path, { secret });
this.setRecordingPermissionsFromToken(token);
return this.recordingPermissions;
try {
const { token } = await this.httpService.postRequest<{ token: string }>(path, { secret });
this.setRecordingPermissionsFromToken(token);
return this.recordingPermissions;
} catch (error) {
this.featureConfService.setRecordingPermissions({
canRetrieveRecordings: false,
canDeleteRecordings: false
});
throw error;
}
}
/**
@ -174,6 +183,9 @@ export class RecordingService {
try {
const decodedToken = getValidDecodedToken(token);
this.recordingPermissions = decodedToken.metadata.recordingPermissions;
// Update feature configuration
this.featureConfService.setRecordingPermissions(this.recordingPermissions);
} catch (error) {
this.log.e('Error setting recording permissions from token', error);
throw new Error('Error setting recording permissions from token');