frontend: enhance view recording component with error handling and status messages
This commit is contained in:
parent
253c7cb37b
commit
fdc93e4f19
@ -108,7 +108,9 @@ export class RoomRecordingsComponent implements OnInit {
|
||||
openShareDialog(recording: MeetRecordingInfo) {
|
||||
this.dialog.open(ShareRecordingDialogComponent, {
|
||||
width: '400px',
|
||||
data: { recordingId: recording.recordingId }
|
||||
data: {
|
||||
recordingId: recording.recordingId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,25 +1,58 @@
|
||||
@if (recording && recordingUrl) {
|
||||
@if (recording) {
|
||||
<mat-card class="recording-container">
|
||||
<div class="video-container">
|
||||
<video controls class="video-player">
|
||||
<source [src]="recordingUrl" type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</div>
|
||||
@if (recordingUrl) {
|
||||
@if (!videoError) {
|
||||
<div class="video-container">
|
||||
<video
|
||||
autoplay
|
||||
controls
|
||||
muted
|
||||
class="video-player"
|
||||
[src]="recordingUrl"
|
||||
(error)="videoError = true"
|
||||
>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="video-error">
|
||||
<mat-icon color="warn">error</mat-icon>
|
||||
<span>Error loading video. Please try again later.</span>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
@if (['STARTING', 'ACTIVE', 'ENDING'].includes(recording.status)) {
|
||||
<div class="status-message">
|
||||
<mat-icon color="accent">hourglass_empty</mat-icon>
|
||||
<span>Recording is still in progress...</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="status-message error">
|
||||
<mat-icon color="warn">error_outline</mat-icon>
|
||||
<span>Recording has failed</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<mat-card-content>
|
||||
<h2 class="title">{{ recording.roomId }}</h2>
|
||||
<div class="info-actions">
|
||||
<span class="date">{{ recording.startDate | date: 'M/d/yy, H:mm' }}</span>
|
||||
<div class="actions">
|
||||
<button mat-icon-button color="primary" (click)="downloadRecording()" aria-label="Download">
|
||||
<mat-icon>download</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="accent" (click)="openShareDialog()" aria-label="Share">
|
||||
<mat-icon>share</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
@if (recordingUrl && !videoError) {
|
||||
<div class="actions">
|
||||
<button mat-icon-button color="primary" (click)="downloadRecording()" aria-label="Download">
|
||||
<mat-icon>download</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="accent" (click)="openShareDialog()" aria-label="Share">
|
||||
<mat-icon>share</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
} @else {
|
||||
<div class="loader-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
.loader-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 60vh;
|
||||
}
|
||||
|
||||
.recording-container {
|
||||
max-width: 900px;
|
||||
margin: 20px auto;
|
||||
@ -18,6 +25,31 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #555;
|
||||
font-size: 1.1rem;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.status-message.error {
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
.video-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
background-color: #fbe9e7;
|
||||
border: 1px solid #ef9a9a;
|
||||
color: #c62828;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 600;
|
||||
|
||||
@ -4,25 +4,29 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ShareRecordingDialogComponent } from '@lib/components';
|
||||
import { HttpService } from '@lib/services';
|
||||
import { ActionService, MeetRecordingInfo } from 'shared-meet-components';
|
||||
import { ActionService, MeetRecordingInfo, MeetRecordingStatus } from 'shared-meet-components';
|
||||
|
||||
@Component({
|
||||
selector: 'app-view-recording',
|
||||
templateUrl: './view-recording.component.html',
|
||||
styleUrls: ['./view-recording.component.scss'],
|
||||
standalone: true,
|
||||
imports: [MatCardModule, MatButtonModule, MatIconModule, DatePipe]
|
||||
imports: [MatCardModule, MatButtonModule, MatIconModule, DatePipe, MatProgressSpinnerModule]
|
||||
})
|
||||
export class ViewRecordingComponent implements OnInit {
|
||||
recording: MeetRecordingInfo | undefined;
|
||||
recordingUrl: string | undefined;
|
||||
recording?: MeetRecordingInfo;
|
||||
recordingUrl?: string;
|
||||
|
||||
videoError = false;
|
||||
|
||||
constructor(
|
||||
protected httpService: HttpService,
|
||||
protected actionService: ActionService,
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected dialog: MatDialog
|
||||
) {}
|
||||
@ -31,18 +35,26 @@ export class ViewRecordingComponent implements OnInit {
|
||||
const recordingId = this.route.snapshot.paramMap.get('recording-id');
|
||||
const secret = this.route.snapshot.queryParams['secret'];
|
||||
|
||||
this.recording = await this.httpService.getRecording(recordingId!, secret!);
|
||||
this.playRecording();
|
||||
}
|
||||
try {
|
||||
this.recording = await this.httpService.getRecording(recordingId!, secret!);
|
||||
|
||||
playRecording() {
|
||||
this.recordingUrl = this.httpService.getRecordingMediaUrl(this.recording!.recordingId);
|
||||
if (this.recording.status === MeetRecordingStatus.COMPLETE) {
|
||||
this.recordingUrl = this.httpService.getRecordingMediaUrl(this.recording!.recordingId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching recording:', error);
|
||||
}
|
||||
}
|
||||
|
||||
downloadRecording() {
|
||||
if (!this.recording || !this.recordingUrl) {
|
||||
console.error('Recording is not available for download');
|
||||
return;
|
||||
}
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = this.recordingUrl!;
|
||||
link.download = this.recording!.filename || 'openvidu-recording.mp4';
|
||||
link.href = this.recordingUrl;
|
||||
link.download = this.recording.filename || 'openvidu-recording.mp4';
|
||||
link.dispatchEvent(
|
||||
new MouseEvent('click', {
|
||||
bubbles: true,
|
||||
@ -58,7 +70,10 @@ export class ViewRecordingComponent implements OnInit {
|
||||
openShareDialog() {
|
||||
this.dialog.open(ShareRecordingDialogComponent, {
|
||||
width: '400px',
|
||||
data: { recordingId: this.recording!.recordingId }
|
||||
data: {
|
||||
recordingId: this.recording!.recordingId,
|
||||
recordingUrl: window.location.href
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user