frontend: update recording permissions handling and improve UI feature flags
This commit is contained in:
parent
5f71b0c28a
commit
2478845fb6
@ -11,16 +11,15 @@
|
|||||||
[toolbarScreenshareButton]="features().showScreenShare"
|
[toolbarScreenshareButton]="features().showScreenShare"
|
||||||
[toolbarLeaveButton]="!features().canModerateRoom"
|
[toolbarLeaveButton]="!features().canModerateRoom"
|
||||||
[toolbarRecordingButton]="features().canRecordRoom"
|
[toolbarRecordingButton]="features().canRecordRoom"
|
||||||
[toolbarViewRecordingsButton]="features().showRecordings"
|
[toolbarViewRecordingsButton]="features().canRetrieveRecordings && hasRecordings"
|
||||||
[toolbarBroadcastingButton]="false"
|
[toolbarBroadcastingButton]="false"
|
||||||
[toolbarChatPanelButton]="features().showChat"
|
[toolbarChatPanelButton]="features().showChat"
|
||||||
[toolbarBackgroundEffectsButton]="features().showBackgrounds"
|
[toolbarBackgroundEffectsButton]="features().showBackgrounds"
|
||||||
[toolbarParticipantsPanelButton]="features().showParticipantList"
|
[toolbarParticipantsPanelButton]="features().showParticipantList"
|
||||||
[toolbarSettingsButton]="features().showSettings"
|
[toolbarSettingsButton]="features().showSettings"
|
||||||
[toolbarFullscreenButton]="features().showFullscreen"
|
[toolbarFullscreenButton]="features().showFullscreen"
|
||||||
[toolbarActivitiesPanelButton]="features().showRecordings"
|
[toolbarActivitiesPanelButton]="features().showRecordingPanel"
|
||||||
[activitiesPanelRecordingActivity]="features().showRecordings"
|
[activitiesPanelRecordingActivity]="features().showRecordingPanel"
|
||||||
[recordingActivityReadOnly]="!features().canRecordRoom"
|
|
||||||
[recordingActivityShowControls]="{
|
[recordingActivityShowControls]="{
|
||||||
play: false,
|
play: false,
|
||||||
download: false,
|
download: false,
|
||||||
@ -28,7 +27,7 @@
|
|||||||
externalView: true
|
externalView: true
|
||||||
}"
|
}"
|
||||||
[recordingActivityStartStopRecordingButton]="features().canRecordRoom"
|
[recordingActivityStartStopRecordingButton]="features().canRecordRoom"
|
||||||
[recordingActivityViewRecordingsButton]="features().showRecordings"
|
[recordingActivityViewRecordingsButton]="features().canRetrieveRecordings && hasRecordings"
|
||||||
[recordingActivityShowRecordingsList]="false"
|
[recordingActivityShowRecordingsList]="false"
|
||||||
[activitiesPanelBroadcastingActivity]="false"
|
[activitiesPanelBroadcastingActivity]="false"
|
||||||
[showDisconnectionDialog]="false"
|
[showDisconnectionDialog]="false"
|
||||||
@ -37,8 +36,7 @@
|
|||||||
(onParticipantLeft)="onParticipantLeft($event)"
|
(onParticipantLeft)="onParticipantLeft($event)"
|
||||||
(onRecordingStartRequested)="onRecordingStartRequested($event)"
|
(onRecordingStartRequested)="onRecordingStartRequested($event)"
|
||||||
(onRecordingStopRequested)="onRecordingStopRequested($event)"
|
(onRecordingStopRequested)="onRecordingStopRequested($event)"
|
||||||
(onViewRecordingsClicked)="onViewRecordingsClicked(undefined)"
|
(onViewRecordingsClicked)="onViewRecordingsClicked()"
|
||||||
(onViewRecordingClicked)="onViewRecordingsClicked($event)"
|
|
||||||
>
|
>
|
||||||
@if (features().canModerateRoom) {
|
@if (features().canModerateRoom) {
|
||||||
<div *ovToolbarAdditionalButtons>
|
<div *ovToolbarAdditionalButtons>
|
||||||
|
|||||||
@ -84,6 +84,8 @@ export class MeetingComponent implements OnInit {
|
|||||||
participantForm = new FormGroup({
|
participantForm = new FormGroup({
|
||||||
name: new FormControl('', [Validators.required, Validators.minLength(4)])
|
name: new FormControl('', [Validators.required, Validators.minLength(4)])
|
||||||
});
|
});
|
||||||
|
|
||||||
|
hasRecordings = false;
|
||||||
showRecordingCard = false;
|
showRecordingCard = false;
|
||||||
|
|
||||||
showBackButton = true;
|
showBackButton = true;
|
||||||
@ -175,13 +177,23 @@ export class MeetingComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
private async checkForRecordings() {
|
private async checkForRecordings() {
|
||||||
try {
|
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({
|
const { recordings } = await this.recordingService.listRecordings({
|
||||||
maxItems: 1,
|
maxItems: 1,
|
||||||
roomId: this.roomId,
|
roomId: this.roomId,
|
||||||
fields: 'recordingId'
|
fields: 'recordingId'
|
||||||
});
|
});
|
||||||
this.showRecordingCard = recordings.length > 0;
|
this.hasRecordings = recordings.length > 0;
|
||||||
|
this.showRecordingCard = this.hasRecordings;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking for recordings:', error);
|
console.error('Error checking for recordings:', error);
|
||||||
this.showRecordingCard = false;
|
this.showRecordingCard = false;
|
||||||
@ -326,10 +338,34 @@ export class MeetingComponent implements OnInit {
|
|||||||
const event = JSON.parse(new TextDecoder().decode(payload));
|
const event = JSON.parse(new TextDecoder().decode(payload));
|
||||||
|
|
||||||
switch (topic) {
|
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: {
|
case MeetSignalType.MEET_ROOM_PREFERENCES_UPDATED: {
|
||||||
// Update room preferences
|
// Update room preferences
|
||||||
const { preferences } = event as MeetRoomPreferencesUpdatedPayload;
|
const { preferences } = event as MeetRoomPreferencesUpdatedPayload;
|
||||||
this.featureConfService.setRoomPreferences(preferences);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case MeetSignalType.MEET_PARTICIPANT_ROLE_UPDATED: {
|
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) {
|
async onViewRecordingsClicked() {
|
||||||
if (recordingId) {
|
window.open(`/room/${this.roomId}/recordings?secret=${this.roomSecret}`, '_blank');
|
||||||
await this.recordingService.playRecording(recordingId);
|
|
||||||
} else {
|
|
||||||
window.open(`/room/${this.roomId}/recordings?secret=${this.roomSecret}`, '_blank');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
import { computed, Injectable, signal } from '@angular/core';
|
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';
|
import { LoggerService } from 'openvidu-components-angular';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,7 +20,7 @@ export interface ApplicationFeatures {
|
|||||||
showScreenShare: boolean;
|
showScreenShare: boolean;
|
||||||
|
|
||||||
// UI Features
|
// UI Features
|
||||||
showRecordings: boolean;
|
showRecordingPanel: boolean;
|
||||||
showChat: boolean;
|
showChat: boolean;
|
||||||
showBackgrounds: boolean;
|
showBackgrounds: boolean;
|
||||||
showParticipantList: boolean;
|
showParticipantList: boolean;
|
||||||
@ -24,6 +30,7 @@ export interface ApplicationFeatures {
|
|||||||
// Permissions
|
// Permissions
|
||||||
canModerateRoom: boolean;
|
canModerateRoom: boolean;
|
||||||
canRecordRoom: boolean;
|
canRecordRoom: boolean;
|
||||||
|
canRetrieveRecordings: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,7 +43,7 @@ const DEFAULT_FEATURES: ApplicationFeatures = {
|
|||||||
showMicrophone: true,
|
showMicrophone: true,
|
||||||
showScreenShare: true,
|
showScreenShare: true,
|
||||||
|
|
||||||
showRecordings: true,
|
showRecordingPanel: true,
|
||||||
showChat: true,
|
showChat: true,
|
||||||
showBackgrounds: true,
|
showBackgrounds: true,
|
||||||
showParticipantList: true,
|
showParticipantList: true,
|
||||||
@ -44,7 +51,8 @@ const DEFAULT_FEATURES: ApplicationFeatures = {
|
|||||||
showFullscreen: true,
|
showFullscreen: true,
|
||||||
|
|
||||||
canModerateRoom: false,
|
canModerateRoom: false,
|
||||||
canRecordRoom: false
|
canRecordRoom: false,
|
||||||
|
canRetrieveRecordings: false
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,10 +69,16 @@ export class FeatureConfigurationService {
|
|||||||
protected roomPreferences = signal<MeetRoomPreferences | undefined>(undefined);
|
protected roomPreferences = signal<MeetRoomPreferences | undefined>(undefined);
|
||||||
protected participantPermissions = signal<ParticipantPermissions | undefined>(undefined);
|
protected participantPermissions = signal<ParticipantPermissions | undefined>(undefined);
|
||||||
protected participantRole = signal<ParticipantRole | undefined>(undefined);
|
protected participantRole = signal<ParticipantRole | undefined>(undefined);
|
||||||
|
protected recordingPermissions = signal<RecordingPermissions | undefined>(undefined);
|
||||||
|
|
||||||
// Computed signal to derive features based on current configurations
|
// Computed signal to derive features based on current configurations
|
||||||
public readonly features = computed<ApplicationFeatures>(() =>
|
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) {
|
constructor(protected loggerService: LoggerService) {
|
||||||
@ -95,6 +109,14 @@ export class FeatureConfigurationService {
|
|||||||
this.participantRole.set(role);
|
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
|
* Checks if a specific feature is enabled
|
||||||
*/
|
*/
|
||||||
@ -108,14 +130,15 @@ export class FeatureConfigurationService {
|
|||||||
protected calculateFeatures(
|
protected calculateFeatures(
|
||||||
roomPrefs?: MeetRoomPreferences,
|
roomPrefs?: MeetRoomPreferences,
|
||||||
participantPerms?: ParticipantPermissions,
|
participantPerms?: ParticipantPermissions,
|
||||||
role?: ParticipantRole
|
role?: ParticipantRole,
|
||||||
|
recordingPerms?: RecordingPermissions
|
||||||
): ApplicationFeatures {
|
): ApplicationFeatures {
|
||||||
// Start with default configuration
|
// Start with default configuration
|
||||||
const features: ApplicationFeatures = { ...DEFAULT_FEATURES };
|
const features: ApplicationFeatures = { ...DEFAULT_FEATURES };
|
||||||
|
|
||||||
// Apply room configurations
|
// Apply room configurations
|
||||||
if (roomPrefs) {
|
if (roomPrefs) {
|
||||||
features.showRecordings = roomPrefs.recordingPreferences.enabled;
|
features.showRecordingPanel = roomPrefs.recordingPreferences.enabled;
|
||||||
features.showChat = roomPrefs.chatPreferences.enabled;
|
features.showChat = roomPrefs.chatPreferences.enabled;
|
||||||
features.showBackgrounds = roomPrefs.virtualBackgroundPreferences.enabled;
|
features.showBackgrounds = roomPrefs.virtualBackgroundPreferences.enabled;
|
||||||
}
|
}
|
||||||
@ -123,8 +146,7 @@ export class FeatureConfigurationService {
|
|||||||
// Apply participant permissions (these can restrict enabled features)
|
// Apply participant permissions (these can restrict enabled features)
|
||||||
if (participantPerms) {
|
if (participantPerms) {
|
||||||
// Only restrict if the feature is already enabled
|
// Only restrict if the feature is already enabled
|
||||||
if (features.showRecordings) {
|
if (features.showRecordingPanel) {
|
||||||
// features.showRecordings = !!recordingRole;
|
|
||||||
features.canRecordRoom = participantPerms.openvidu.canRecord;
|
features.canRecordRoom = participantPerms.openvidu.canRecord;
|
||||||
}
|
}
|
||||||
if (features.showChat) {
|
if (features.showChat) {
|
||||||
@ -149,6 +171,11 @@ export class FeatureConfigurationService {
|
|||||||
features.canModerateRoom = role === ParticipantRole.MODERATOR;
|
features.canModerateRoom = role === ParticipantRole.MODERATOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply recording permissions
|
||||||
|
if (recordingPerms) {
|
||||||
|
features.canRetrieveRecordings = recordingPerms.canRetrieveRecordings;
|
||||||
|
}
|
||||||
|
|
||||||
this.log.d('Calculated features', features);
|
this.log.d('Calculated features', features);
|
||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ShareRecordingDialogComponent } from '@lib/components';
|
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 { MeetRecordingFilters, MeetRecordingInfo, RecordingPermissions } from '@lib/typings/ce';
|
||||||
import { getValidDecodedToken } from '@lib/utils';
|
import { getValidDecodedToken } from '@lib/utils';
|
||||||
import { LoggerService } from 'openvidu-components-angular';
|
import { LoggerService } from 'openvidu-components-angular';
|
||||||
@ -25,6 +25,7 @@ export class RecordingService {
|
|||||||
private httpService: HttpService,
|
private httpService: HttpService,
|
||||||
protected participantService: ParticipantService,
|
protected participantService: ParticipantService,
|
||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
|
protected featureConfService: FeatureConfigurationService,
|
||||||
protected dialog: MatDialog
|
protected dialog: MatDialog
|
||||||
) {
|
) {
|
||||||
this.log = this.loggerService.get('OpenVidu Meet - RecordingManagerService');
|
this.log = this.loggerService.get('OpenVidu Meet - RecordingManagerService');
|
||||||
@ -159,10 +160,18 @@ export class RecordingService {
|
|||||||
*/
|
*/
|
||||||
async generateRecordingToken(roomId: string, secret: string): Promise<RecordingPermissions> {
|
async generateRecordingToken(roomId: string, secret: string): Promise<RecordingPermissions> {
|
||||||
const path = `${HttpService.INTERNAL_API_PATH_PREFIX}/rooms/${roomId}/recording-token`;
|
const path = `${HttpService.INTERNAL_API_PATH_PREFIX}/rooms/${roomId}/recording-token`;
|
||||||
const { token } = await this.httpService.postRequest<{ token: string }>(path, { secret });
|
|
||||||
|
|
||||||
this.setRecordingPermissionsFromToken(token);
|
try {
|
||||||
return this.recordingPermissions;
|
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 {
|
try {
|
||||||
const decodedToken = getValidDecodedToken(token);
|
const decodedToken = getValidDecodedToken(token);
|
||||||
this.recordingPermissions = decodedToken.metadata.recordingPermissions;
|
this.recordingPermissions = decodedToken.metadata.recordingPermissions;
|
||||||
|
|
||||||
|
// Update feature configuration
|
||||||
|
this.featureConfService.setRecordingPermissions(this.recordingPermissions);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log.e('Error setting recording permissions from token', error);
|
this.log.e('Error setting recording permissions from token', error);
|
||||||
throw new Error('Error setting recording permissions from token');
|
throw new Error('Error setting recording permissions from token');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user