Carlos Santos a636ad485f backend: Implement participant name reservation system
- Added ParticipantNameService to manage unique participant name reservations.
- Integrated name reservation in ParticipantService during token generation.
- Implemented cleanup of expired name reservations in LivekitWebhookService.
- Enhanced RedisService with atomic operations for name reservation.
- Updated internal configuration for participant name reservation limits.
- Added tests for participant name reservation and release functionality.
- Updated frontend dependencies to use the latest version of openvidu-components-angular.
2025-08-18 18:49:46 +02:00

291 lines
9.6 KiB
HTML

@if (showMeeting) {
<ov-videoconference
[token]="participantToken"
[prejoin]="true"
[prejoinDisplayParticipantName]="false"
[videoEnabled]="features().videoEnabled"
[audioEnabled]="features().audioEnabled"
[toolbarRoomName]="roomName"
[toolbarCameraButton]="features().showCamera"
[toolbarMicrophoneButton]="features().showMicrophone"
[toolbarScreenshareButton]="features().showScreenShare"
[toolbarLeaveButton]="!features().canModerateRoom"
[toolbarRecordingButton]="features().canRecordRoom"
[toolbarViewRecordingsButton]="features().canRetrieveRecordings && hasRecordings"
[toolbarBroadcastingButton]="false"
[toolbarChatPanelButton]="features().showChat"
[toolbarBackgroundEffectsButton]="features().showBackgrounds"
[toolbarParticipantsPanelButton]="features().showParticipantList"
[toolbarSettingsButton]="features().showSettings"
[toolbarFullscreenButton]="features().showFullscreen"
[toolbarActivitiesPanelButton]="features().showRecordingPanel"
[activitiesPanelRecordingActivity]="features().showRecordingPanel"
[recordingActivityShowControls]="{
play: false,
download: false,
delete: false,
externalView: true
}"
[recordingActivityStartStopRecordingButton]="features().canRecordRoom"
[recordingActivityViewRecordingsButton]="features().canRetrieveRecordings && hasRecordings"
[recordingActivityShowRecordingsList]="false"
[activitiesPanelBroadcastingActivity]="false"
[showDisconnectionDialog]="false"
(onRoomCreated)="onRoomCreated($event)"
(onParticipantConnected)="onParticipantConnected($event)"
(onParticipantLeft)="onParticipantLeft($event)"
(onRecordingStartRequested)="onRecordingStartRequested($event)"
(onRecordingStopRequested)="onRecordingStopRequested($event)"
(onViewRecordingsClicked)="onViewRecordingsClicked()"
>
@if (features().canModerateRoom) {
<div *ovToolbarAdditionalButtons>
<!-- Copy Link Button -->
<button
id="copy-speaker-link"
mat-icon-button
(click)="copySpeakerLink()"
[disableRipple]="true"
matTooltip="Copy the meeting link"
>
<mat-icon>link</mat-icon>
</button>
<!-- Leave Button -->
<button
id="leave-btn"
mat-icon-button
[matMenuTriggerFor]="leaveMenu"
matTooltip="Leave options"
[disableRipple]="true"
class="custom-leave-btn"
>
<mat-icon>call_end</mat-icon>
</button>
<mat-menu #leaveMenu="matMenu">
<button mat-menu-item (click)="leaveMeeting()" id="leave-option">
<mat-icon>logout</mat-icon>
<span>Leave meeting</span>
</button>
<mat-divider></mat-divider>
<button mat-menu-item (click)="endMeeting()" id="end-meeting-option">
<mat-icon>no_meeting_room</mat-icon>
<span>End meeting for all</span>
</button>
</mat-menu>
</div>
<div *ovParticipantPanelAfterLocalParticipant>
<div class="share-meeting-link-container">
<ov-share-meeting-link
[meetingUrl]="hostname + '/room/' + roomId"
(copyClicked)="copySpeakerLink()"
></ov-share-meeting-link>
</div>
</div>
<ng-container *ovLayoutAdditionalElements>
@if (remoteParticipants.length === 0) {
<div class="main-share-meeting-link-container fade-in-delayed-more">
<ov-share-meeting-link
[title]="'Start collaborating'"
[subtitle]="'Share this link to bring others into the meeting'"
[titleSize]="'xl'"
[titleWeight]="'bold'"
[meetingUrl]="hostname + '/room/' + roomId"
(copyClicked)="copySpeakerLink()"
></ov-share-meeting-link>
</div>
}
</ng-container>
}
<ng-container *ovParticipantPanelItem="let participant">
<!-- If Meet participant is moderator -->
@if (features().canModerateRoom) {
<div class="participant-item-container">
<!-- Local participant -->
@if (participant.isLocal) {
<ov-participant-panel-item [participant]="participant">
<ng-container *ovParticipantPanelParticipantBadge>
<span class="moderator-badge">
<mat-icon matTooltip="Moderator" class="material-symbols-outlined"
>shield_person</mat-icon
>
</span>
</ng-container>
</ov-participant-panel-item>
} @else {
<!-- Remote participant -->
<ov-participant-panel-item [participant]="participant">
@if (participant.isModerator()) {
<ng-container *ovParticipantPanelParticipantBadge>
<span class="moderator-badge">
<mat-icon matTooltip="Moderator" class="material-symbols-outlined"
>shield_person</mat-icon
>
</span>
</ng-container>
}
<div *ovParticipantPanelItemElements>
<!-- Button to make moderator if not -->
@if (!participant.isModerator()) {
<button
mat-icon-button
(click)="makeModerator(participant)"
matTooltip="Make participant moderator"
class="make-moderator-btn"
>
<mat-icon class="material-symbols-outlined">add_moderator</mat-icon>
</button>
}
<!-- Button to kick participant -->
<button
mat-icon-button
(click)="kickParticipant(participant)"
matTooltip="Kick participant"
class="force-disconnect-btn"
>
<mat-icon>call_end</mat-icon>
</button>
</div>
</ov-participant-panel-item>
}
</div>
} @else {
<!-- If I can't moderate the room -->
<div class="participant-item-container">
<ov-participant-panel-item [participant]="participant">
@if (participant.isModerator()) {
<ng-container *ovParticipantPanelParticipantBadge>
<span class="moderator-badge">
<mat-icon matTooltip="Moderator" class="material-symbols-outlined">
shield_person
</mat-icon>
</span>
</ng-container>
}
</ov-participant-panel-item>
</div>
}
</ng-container>
</ov-videoconference>
} @else {
<!-- Move this logic to prejoin meeting page -->
<div class="ov-page-container">
<div class="room-access-container fade-in">
<!-- Header Section -->
<div class="room-header">
<mat-icon class="ov-room-icon room-icon">video_chat</mat-icon>
<div class="room-info">
<h1 class="room-title">{{ roomName }}</h1>
<p class="room-subtitle">Choose how you want to proceed</p>
</div>
</div>
<!-- Action Cards Grid -->
<div class="action-cards-grid">
<!-- Join Room Card -->
<mat-card class="action-card primary-card fade-in">
<mat-card-header class="card-header">
<mat-icon class="ov-room-icon card-icon">meeting_room</mat-icon>
<div class="card-title-group">
<mat-card-title>Join Meeting</mat-card-title>
<mat-card-subtitle>Enter the room and start connecting</mat-card-subtitle>
</div>
</mat-card-header>
<mat-card-content class="card-content">
<form [formGroup]="participantForm" (ngSubmit)="submitAccessMeeting()" class="join-form">
<mat-form-field appearance="outline" class="name-field">
<mat-label>Your display name</mat-label>
<input
id="participant-name-input"
matInput
placeholder="Enter your name"
formControlName="name"
required
/>
<mat-icon matSuffix class="ov-action-icon">person</mat-icon>
@if (participantForm.get('name')?.hasError('minlength')) {
<mat-error> The name must be at least <strong>4 characters</strong> </mat-error>
}
@if (participantForm.get('name')?.hasError('required')) {
<mat-error> The name is <strong>required</strong> </mat-error>
}
@if (participantForm.get('name')?.hasError('participantExists')) {
<mat-error>
The name is already taken. <strong> Please choose another name </strong>
</mat-error>
}
</mat-form-field>
<button
mat-raised-button
color="primary"
id="participant-name-submit"
type="submit"
class="join-button"
[disabled]="participantForm.invalid"
>
<span>Join Meeting</span>
</button>
</form>
</mat-card-content>
</mat-card>
<!-- View Recordings Card -->
@if (showRecordingCard) {
<mat-card class="action-card secondary-card fade-in-delayed">
<mat-card-header class="card-header">
<mat-icon class="ov-recording-icon card-icon">video_library</mat-icon>
<div class="card-title-group">
<mat-card-title>View Recordings</mat-card-title>
<mat-card-subtitle>Browse and manage past recordings</mat-card-subtitle>
</div>
</mat-card-header>
<mat-card-content class="card-content">
<div class="recordings-info">
<p class="recordings-description">
Access previously recorded meetings from this room. You can watch, download, or
manage existing recordings.
</p>
</div>
<button
id="view-recordings-btn"
mat-stroked-button
color="accent"
(click)="goToRecordings()"
class="recordings-button"
>
<span>Browse Recordings</span>
</button>
</mat-card-content>
</mat-card>
}
</div>
<!-- Room URL Badge -->
@if (features().canModerateRoom) {
<ov-share-meeting-link
[meetingUrl]="hostname + '/room/' + roomId"
(copyClicked)="copySpeakerLink()"
></ov-share-meeting-link>
}
<!-- Quick Actions -->
@if (showBackButton) {
<div class="quick-actions fade-in-delayed-more">
<button mat-button class="quick-action-button" (click)="goBack()">
<mat-icon>arrow_back</mat-icon>
<span>{{ backButtonText }}</span>
</button>
</div>
}
</div>
</div>
}