- 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.
291 lines
9.6 KiB
HTML
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>
|
|
}
|