frontend: enhance view recording component
This commit is contained in:
parent
76093b9f42
commit
4d76101d1e
@ -1,56 +1,161 @@
|
||||
@if (recording) {
|
||||
<mat-card class="recording-container">
|
||||
@if (recordingUrl) {
|
||||
@if (!videoError) {
|
||||
<div class="video-container">
|
||||
<video
|
||||
autoplay
|
||||
controls
|
||||
playsinline
|
||||
class="video-player"
|
||||
[src]="recordingUrl"
|
||||
(error)="videoError = true"
|
||||
></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>
|
||||
}
|
||||
}
|
||||
<!-- Loading State -->
|
||||
@if (isLoading) {
|
||||
<div class="ov-page-container">
|
||||
<div class="loading-container fade-in">
|
||||
<mat-spinner diameter="48"></mat-spinner>
|
||||
<p class="loading-text">Loading recording...</p>
|
||||
</div>
|
||||
</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>
|
||||
<!-- Error State -->
|
||||
@else if (hasError) {
|
||||
<div class="ov-page-container">
|
||||
<div class="error-container fade-in">
|
||||
<div class="error-content">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
<h2 class="error-title">Recording Not Found</h2>
|
||||
<p class="error-message">The recording you're looking for doesn't exist or is no longer available.</p>
|
||||
<div class="error-actions">
|
||||
<button mat-raised-button color="primary" (click)="retryLoad()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
<span>Try Again</span>
|
||||
</button>
|
||||
<button mat-button (click)="goBack()">
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
<span>Go Back</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Recording Content -->
|
||||
@else if (recording) {
|
||||
<div class="ov-page-container">
|
||||
<div class="recording-page-content fade-in">
|
||||
<!-- Header Section -->
|
||||
<div class="recording-header">
|
||||
<div class="header-content">
|
||||
<div class="header-actions">
|
||||
<button mat-icon-button (click)="goBack()" matTooltip="Go back" class="back-btn">
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="header-info">
|
||||
<div class="recording-info">
|
||||
<h1 class="recording-title">{{ recording.roomId }}</h1>
|
||||
<div class="recording-metadata">
|
||||
<span class="recording-date">
|
||||
<mat-icon class="ov-action-icon">schedule</mat-icon>
|
||||
{{ recording.startDate | date: 'MMM d, y - h:mm a' }}
|
||||
</span>
|
||||
@if (recording.duration) {
|
||||
<span class="recording-duration">
|
||||
<mat-icon class="ov-action-icon">timer</mat-icon>
|
||||
{{ formatDuration(recording.duration) }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="recording-status">
|
||||
<mat-icon [class]="'status-icon ' + recording.status">{{ getStatusIcon() }}</mat-icon>
|
||||
<span class="status-text">{{ getStatusMessage() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Actions Section -->
|
||||
@if (recordingUrl && !videoError) {
|
||||
<div class="actions-section">
|
||||
<div class="primary-actions">
|
||||
<button
|
||||
mat-button
|
||||
(click)="downloadRecording()"
|
||||
matTooltip="Download recording"
|
||||
class="action-btn primary-button"
|
||||
>
|
||||
<mat-icon>download</mat-icon>
|
||||
<span>Download</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
(click)="openShareDialog()"
|
||||
matTooltip="Share recording"
|
||||
class="action-btn"
|
||||
>
|
||||
<mat-icon>share</mat-icon>
|
||||
<span>Share</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Share Link Component -->
|
||||
<!-- <ov-share-meeting-link
|
||||
[meetingUrl]="recordingUrl"
|
||||
title="Share this recording"
|
||||
titleSize="md"
|
||||
subtitle="Anyone with this link can view the recording"
|
||||
(copyClicked)="copyRecordingLink()"
|
||||
></ov-share-meeting-link> -->
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Video Player Section -->
|
||||
<div class="video-section">
|
||||
@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 class="video-container">
|
||||
<video
|
||||
#videoPlayer
|
||||
controls
|
||||
controlsList="nodownload"
|
||||
playsinline
|
||||
class="video-player"
|
||||
(loadeddata)="onVideoLoaded()"
|
||||
(error)="onVideoError()"
|
||||
>
|
||||
<source [src]="recordingUrl" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
@if (!isVideoLoaded) {
|
||||
<div class="video-loading-overlay">
|
||||
<mat-spinner diameter="40"></mat-spinner>
|
||||
<span>Loading video...</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else if (videoError) {
|
||||
<div class="video-error-container">
|
||||
<div class="video-error-content">
|
||||
<mat-icon class="error-icon">error</mat-icon>
|
||||
<h3>Video Playback Error</h3>
|
||||
<p>There was an error loading the video. Please check your connection and try again.</p>
|
||||
<button mat-raised-button color="primary" (click)="retryLoad()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
<span>Retry</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="video-unavailable-container">
|
||||
<div class="video-unavailable-content">
|
||||
<mat-icon class="status-icon">{{ getStatusIcon() }}</mat-icon>
|
||||
<h3>{{ getStatusMessage() }}</h3>
|
||||
@if (['STARTING', 'ACTIVE', 'ENDING'].includes(recording.status)) {
|
||||
<p>The recording is still being processed. Please check back in a few minutes.</p>
|
||||
<button mat-raised-button color="primary" (click)="retryLoad()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
<span>Refresh</span>
|
||||
</button>
|
||||
} @else {
|
||||
<p>This recording is not available for playback.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
} @else {
|
||||
<div class="loader-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -1,82 +1,337 @@
|
||||
.loader-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 60vh;
|
||||
@import '../../../../../../src/assets/styles/design-tokens';
|
||||
|
||||
// === MAIN PAGE LAYOUT ===
|
||||
|
||||
.recording-page-content {
|
||||
@include ov-container;
|
||||
@include ov-page-content;
|
||||
max-width: 1000px;
|
||||
padding-top: var(--ov-meet-spacing-xl);
|
||||
}
|
||||
|
||||
.recording-container {
|
||||
max-width: 900px;
|
||||
margin: 20px auto;
|
||||
padding: 0;
|
||||
}
|
||||
// === LOADING STATES ===
|
||||
|
||||
.video-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 56.25%; /* 16:9 aspect ratio */
|
||||
}
|
||||
|
||||
.video-player {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
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;
|
||||
margin: 16px 0 8px;
|
||||
}
|
||||
|
||||
.info-actions {
|
||||
display: flex;
|
||||
.loading-container {
|
||||
@include ov-flex-center;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
gap: var(--ov-meet-spacing-md);
|
||||
height: 60vh;
|
||||
color: var(--ov-meet-text-secondary);
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.info-actions {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.loading-text {
|
||||
margin: 0;
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
font-weight: var(--ov-meet-font-weight-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
// === ERROR STATES ===
|
||||
|
||||
.error-container {
|
||||
@include ov-flex-center;
|
||||
min-height: 60vh;
|
||||
padding: var(--ov-meet-spacing-xl);
|
||||
|
||||
.error-content {
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
|
||||
.error-icon {
|
||||
@include ov-icon(xl);
|
||||
color: var(--ov-meet-color-error);
|
||||
margin-bottom: var(--ov-meet-spacing-md);
|
||||
}
|
||||
|
||||
.error-title {
|
||||
margin: 0 0 var(--ov-meet-spacing-sm) 0;
|
||||
font-size: var(--ov-meet-font-size-xxl);
|
||||
font-weight: var(--ov-meet-font-weight-semibold);
|
||||
color: var(--ov-meet-text-primary);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin: 0 0 var(--ov-meet-spacing-lg) 0;
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
color: var(--ov-meet-text-secondary);
|
||||
line-height: var(--ov-meet-line-height-relaxed);
|
||||
}
|
||||
|
||||
.error-actions {
|
||||
display: flex;
|
||||
gap: var(--ov-meet-spacing-md);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
button {
|
||||
@include ov-button-base;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ov-meet-spacing-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
// === HEADER SECTION ===
|
||||
|
||||
.recording-header {
|
||||
margin-bottom: var(--ov-meet-spacing-xl);
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: var(--ov-meet-spacing-lg);
|
||||
|
||||
@include ov-tablet-down {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.header-info {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
.recording-info {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.recording-title {
|
||||
margin: 0 0 var(--ov-meet-spacing-sm) 0;
|
||||
font-size: var(--ov-meet-font-size-hero);
|
||||
font-weight: var(--ov-meet-font-weight-light);
|
||||
color: var(--ov-meet-text-primary);
|
||||
line-height: var(--ov-meet-line-height-tight);
|
||||
|
||||
@include ov-mobile-down {
|
||||
font-size: var(--ov-meet-font-size-xxl);
|
||||
}
|
||||
}
|
||||
|
||||
.recording-metadata {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--ov-meet-spacing-lg);
|
||||
margin-bottom: var(--ov-meet-spacing-sm);
|
||||
|
||||
@include ov-mobile-down {
|
||||
flex-direction: column;
|
||||
gap: var(--ov-meet-spacing-xs);
|
||||
}
|
||||
|
||||
.recording-date,
|
||||
.recording-duration {
|
||||
@include ov-flex-center;
|
||||
gap: var(--ov-meet-spacing-xs);
|
||||
font-size: var(--ov-meet-font-size-sm);
|
||||
color: var(--ov-meet-text-secondary);
|
||||
|
||||
mat-icon {
|
||||
@include ov-icon(sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recording-status {
|
||||
@include ov-flex-center;
|
||||
gap: var(--ov-meet-spacing-xs);
|
||||
justify-content: flex-start;
|
||||
|
||||
.status-icon {
|
||||
@include ov-icon(sm);
|
||||
|
||||
&.COMPLETE {
|
||||
color: var(--ov-meet-color-success);
|
||||
}
|
||||
|
||||
&.STARTING,
|
||||
&.ACTIVE,
|
||||
&.ENDING {
|
||||
color: var(--ov-meet-color-warning);
|
||||
}
|
||||
|
||||
&.FAILED {
|
||||
color: var(--ov-meet-color-error);
|
||||
}
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: var(--ov-meet-font-size-sm);
|
||||
color: var(--ov-meet-text-secondary);
|
||||
font-weight: var(--ov-meet-font-weight-medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: var(--ov-meet-spacing-sm);
|
||||
|
||||
@include ov-tablet-down {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
@include ov-button-base;
|
||||
@include ov-theme-transition;
|
||||
color: var(--ov-meet-text-secondary);
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--ov-meet-text-primary);
|
||||
background-color: var(--ov-meet-surface-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === VIDEO SECTION ===
|
||||
|
||||
.video-section {
|
||||
margin-bottom: var(--ov-meet-spacing-xl);
|
||||
|
||||
.video-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 56.25%; // 16:9 aspect ratio
|
||||
background: var(--ov-meet-surface-color);
|
||||
border-radius: var(--ov-meet-radius-lg);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--ov-meet-shadow-md);
|
||||
|
||||
.video-player {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--ov-meet-background-color);
|
||||
}
|
||||
|
||||
.video-loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@include ov-flex-center;
|
||||
flex-direction: column;
|
||||
gap: var(--ov-meet-spacing-md);
|
||||
background: var(--ov-meet-surface-color);
|
||||
color: var(--ov-meet-text-secondary);
|
||||
font-size: var(--ov-meet-font-size-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.video-error-container,
|
||||
.video-unavailable-container {
|
||||
@include ov-flex-center;
|
||||
min-height: 300px;
|
||||
background: var(--ov-meet-surface-color);
|
||||
border-radius: var(--ov-meet-radius-lg);
|
||||
border: 1px solid var(--ov-meet-border-color-light);
|
||||
|
||||
.video-error-content,
|
||||
.video-unavailable-content {
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
padding: var(--ov-meet-spacing-xl);
|
||||
|
||||
.error-icon,
|
||||
.status-icon {
|
||||
@include ov-icon(xl);
|
||||
margin-bottom: var(--ov-meet-spacing-md);
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
color: var(--ov-meet-color-error);
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
color: var(--ov-meet-text-secondary);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0 0 var(--ov-meet-spacing-sm) 0;
|
||||
font-size: var(--ov-meet-font-size-xl);
|
||||
font-weight: var(--ov-meet-font-weight-semibold);
|
||||
color: var(--ov-meet-text-primary);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 var(--ov-meet-spacing-lg) 0;
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
color: var(--ov-meet-text-secondary);
|
||||
line-height: var(--ov-meet-line-height-relaxed);
|
||||
}
|
||||
|
||||
button {
|
||||
@include ov-button-base;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--ov-meet-spacing-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === ACTIONS SECTION ===
|
||||
|
||||
.actions-section {
|
||||
flex: 2;
|
||||
align-self: self-end;
|
||||
|
||||
.primary-actions {
|
||||
@include ov-flex-center;
|
||||
gap: var(--ov-meet-spacing-md);
|
||||
flex-wrap: wrap;
|
||||
justify-content: end;
|
||||
|
||||
.action-btn {
|
||||
@include ov-button-base;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ov-meet-spacing-sm);
|
||||
min-width: 120px;
|
||||
height: 48px;
|
||||
|
||||
mat-icon {
|
||||
@include ov-icon(sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === RESPONSIVE ADJUSTMENTS ===
|
||||
|
||||
@include ov-mobile-down {
|
||||
.recording-page-content {
|
||||
padding: var(--ov-meet-spacing-lg);
|
||||
padding-top: var(--ov-meet-spacing-xl);
|
||||
}
|
||||
|
||||
.actions-section .primary-actions {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.action-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include ov-tablet-down {
|
||||
.video-section {
|
||||
.video-error-container,
|
||||
.video-unavailable-container {
|
||||
min-height: 250px;
|
||||
|
||||
.video-error-content,
|
||||
.video-unavailable-content {
|
||||
padding: var(--ov-meet-spacing-lg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,59 +1,164 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RecordingManagerService } from '@lib/services';
|
||||
import { MeetRecordingInfo, MeetRecordingStatus } from '@lib/typings/ce';
|
||||
import { ActionService } from 'openvidu-components-angular';
|
||||
import { ShareMeetingLinkComponent } from '@lib/components/share-meeting-link/share-meeting-link.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-view-recording',
|
||||
selector: 'ov-view-recording',
|
||||
templateUrl: './view-recording.component.html',
|
||||
styleUrls: ['./view-recording.component.scss'],
|
||||
standalone: true,
|
||||
imports: [MatCardModule, MatButtonModule, MatIconModule, DatePipe, MatProgressSpinnerModule]
|
||||
imports: [
|
||||
MatCardModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
DatePipe,
|
||||
MatProgressSpinnerModule,
|
||||
MatTooltipModule,
|
||||
MatSnackBarModule,
|
||||
ShareMeetingLinkComponent
|
||||
]
|
||||
})
|
||||
export class ViewRecordingComponent implements OnInit {
|
||||
export class ViewRecordingComponent implements OnInit, OnDestroy {
|
||||
recording?: MeetRecordingInfo;
|
||||
recordingUrl?: string;
|
||||
|
||||
videoError = false;
|
||||
isLoading = true;
|
||||
hasError = false;
|
||||
isVideoLoaded = false;
|
||||
|
||||
constructor(
|
||||
protected recordingService: RecordingManagerService,
|
||||
protected actionService: ActionService,
|
||||
protected route: ActivatedRoute
|
||||
protected route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
private snackBar: MatSnackBar
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.loadRecording();
|
||||
}
|
||||
|
||||
ngOnDestroy() {}
|
||||
|
||||
private async loadRecording() {
|
||||
const recordingId = this.route.snapshot.paramMap.get('recording-id');
|
||||
const secret = this.route.snapshot.queryParams['secret'];
|
||||
|
||||
if (!recordingId) {
|
||||
this.hasError = true;
|
||||
this.isLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.recording = await this.recordingService.getRecording(recordingId!, secret);
|
||||
this.recording = await this.recordingService.getRecording(recordingId, secret);
|
||||
|
||||
if (this.recording.status === MeetRecordingStatus.COMPLETE) {
|
||||
this.recordingUrl = this.recordingService.getRecordingMediaUrl(recordingId!, secret);
|
||||
this.recordingUrl = this.recordingService.getRecordingMediaUrl(recordingId, secret);
|
||||
}
|
||||
console.warn('Recording loaded:', this.recordingUrl);
|
||||
} catch (error) {
|
||||
console.error('Error fetching recording:', error);
|
||||
this.hasError = true;
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
onVideoLoaded = () => {
|
||||
this.isVideoLoaded = true;
|
||||
this.videoError = false;
|
||||
};
|
||||
|
||||
onVideoError = () => {
|
||||
this.videoError = true;
|
||||
this.isVideoLoaded = false;
|
||||
};
|
||||
|
||||
downloadRecording() {
|
||||
if (!this.recording || !this.recordingUrl) {
|
||||
console.error('Recording is not available for download');
|
||||
this.snackBar.open('Recording is not available for download', 'Close', { duration: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
this.recordingService.downloadRecording(this.recording);
|
||||
this.snackBar.open('Download started', 'Close', { duration: 2000 });
|
||||
}
|
||||
|
||||
openShareDialog() {
|
||||
const url = window.location.href;
|
||||
this.recordingService.openShareRecordingDialog(this.recording!.recordingId, url);
|
||||
}
|
||||
|
||||
// copyRecordingLink() {
|
||||
// const url = window.location.href;
|
||||
// navigator.clipboard.writeText(url).then(() => {
|
||||
// this.snackBar.open('Link copied to clipboard', 'Close', { duration: 2000 });
|
||||
// }).catch(() => {
|
||||
// this.snackBar.open('Failed to copy link', 'Close', { duration: 3000 });
|
||||
// });
|
||||
// }
|
||||
|
||||
goBack() {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
} else {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
|
||||
retryLoad() {
|
||||
this.isLoading = true;
|
||||
this.hasError = false;
|
||||
this.videoError = false;
|
||||
this.loadRecording();
|
||||
}
|
||||
|
||||
getStatusIcon(): string {
|
||||
if (!this.recording) return 'error_outline';
|
||||
|
||||
switch (this.recording.status) {
|
||||
case MeetRecordingStatus.STARTING:
|
||||
case MeetRecordingStatus.ACTIVE:
|
||||
case MeetRecordingStatus.ENDING:
|
||||
return 'hourglass_empty';
|
||||
case MeetRecordingStatus.COMPLETE:
|
||||
return 'check_circle';
|
||||
default:
|
||||
return 'error_outline';
|
||||
}
|
||||
}
|
||||
|
||||
getStatusMessage(): string {
|
||||
if (!this.recording) return 'Recording not found';
|
||||
|
||||
switch (this.recording.status) {
|
||||
case MeetRecordingStatus.STARTING:
|
||||
return 'Recording is starting...';
|
||||
case MeetRecordingStatus.ACTIVE:
|
||||
return 'Recording is in progress...';
|
||||
case MeetRecordingStatus.ENDING:
|
||||
return 'Recording is finalizing...';
|
||||
case MeetRecordingStatus.COMPLETE:
|
||||
return 'Recording is ready to watch';
|
||||
default:
|
||||
return 'Recording has failed';
|
||||
}
|
||||
}
|
||||
|
||||
formatDuration(durationSeconds: number): string {
|
||||
const minutes = Math.floor(durationSeconds / 60);
|
||||
const seconds = Math.floor(durationSeconds % 60);
|
||||
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user