diff --git a/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.html b/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.html index 962e731..3771830 100644 --- a/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.html +++ b/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.html @@ -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) {
diff --git a/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.ts b/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.ts index 4b26c6f..36547a4 100644 --- a/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.ts +++ b/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.ts @@ -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'); } } diff --git a/frontend/projects/shared-meet-components/src/lib/services/feature-configuration.service.ts b/frontend/projects/shared-meet-components/src/lib/services/feature-configuration.service.ts index 5215821..2506766 100644 --- a/frontend/projects/shared-meet-components/src/lib/services/feature-configuration.service.ts +++ b/frontend/projects/shared-meet-components/src/lib/services/feature-configuration.service.ts @@ -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(undefined); protected participantPermissions = signal(undefined); protected participantRole = signal(undefined); + protected recordingPermissions = signal(undefined); // Computed signal to derive features based on current configurations public readonly features = computed(() => - 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; } diff --git a/frontend/projects/shared-meet-components/src/lib/services/recording.service.ts b/frontend/projects/shared-meet-components/src/lib/services/recording.service.ts index 9a95318..913d2db 100644 --- a/frontend/projects/shared-meet-components/src/lib/services/recording.service.ts +++ b/frontend/projects/shared-meet-components/src/lib/services/recording.service.ts @@ -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 { 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');