refactor: optimize HeroBannerComponent for better performance and readability
This commit is contained in:
parent
300cb97b2f
commit
77960560d3
4 changed files with 126 additions and 95 deletions
|
|
@ -1,15 +1,6 @@
|
||||||
<section
|
<section [class.fullVh]="!isWideScreen">
|
||||||
[ngClass]="{
|
<div class="video-banner" [class.fullVhBanner]="!isWideScreen">
|
||||||
fullVh: !isWideScreen
|
<!-- Fallback image -->
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="video-banner"
|
|
||||||
[ngClass]="{
|
|
||||||
fullVhBanner: !isWideScreen
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<!-- Fallback image is displayed before the video is loaded -->
|
|
||||||
@if (!isVideoLoaded) {
|
@if (!isVideoLoaded) {
|
||||||
<img [src]="thumbnailUrl" class="fallback-image" />
|
<img [src]="thumbnailUrl" class="fallback-image" />
|
||||||
}
|
}
|
||||||
|
|
@ -19,31 +10,23 @@
|
||||||
<video
|
<video
|
||||||
#videoElement
|
#videoElement
|
||||||
[src]="previewClipUrl"
|
[src]="previewClipUrl"
|
||||||
poster="{{ thumbnailUrl }}"
|
[poster]="thumbnailUrl"
|
||||||
autoplay
|
autoplay
|
||||||
muted
|
muted
|
||||||
loop
|
loop
|
||||||
playsinline
|
playsinline
|
||||||
class="video-banner"
|
|
||||||
(canplay)="onVideoLoad()"
|
(canplay)="onVideoLoad()"
|
||||||
(error)="onVideoError()"
|
(error)="onVideoError()"
|
||||||
></video>
|
></video>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (currentVideo !== null) {
|
@if (currentVideo) {
|
||||||
<div
|
<div class="content" [class.fullVhContent]="!isWideScreen">
|
||||||
class="content"
|
|
||||||
[ngClass]="{
|
|
||||||
fullVhContent: !isWideScreen
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="title">{{ currentVideo.title }}</div>
|
<div class="title">{{ currentVideo.title }}</div>
|
||||||
<div
|
<div
|
||||||
class="description hide-scrollbar"
|
class="description hide-scrollbar"
|
||||||
[ngClass]="{
|
[class.fullVhDescription]="!isWideScreen"
|
||||||
fullVhDescription: !isWideScreen
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
{{ currentVideo.description }}
|
{{ currentVideo.description }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -59,25 +42,30 @@
|
||||||
[value]="'Play'"
|
[value]="'Play'"
|
||||||
[imgPath]="'play'"
|
[imgPath]="'play'"
|
||||||
[imgReverse]="true"
|
[imgReverse]="true"
|
||||||
[disabled]="!isAnyResolutionAvailable()"
|
[disabled]="!isAnyResolutionAvailable"
|
||||||
(click)="playVideoId(videoUrl, currentVideo)"
|
(click)="playVideoId(videoUrl, currentVideo)"
|
||||||
></app-btn-large>
|
></app-btn-large>
|
||||||
<div class="favorite-img" (click)="toggleLikeVideo(currentVideo)">
|
|
||||||
@if(checkLikeVideos(currentVideo)) {
|
<!-- Favorite button -->
|
||||||
|
@let isLiked = checkLikeVideos(currentVideo);
|
||||||
|
<div
|
||||||
|
class="favorite-img"
|
||||||
|
(click)="toggleVideoStatus(currentVideo.id, 'favorite')"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
class="filled"
|
[src]="
|
||||||
src="./../../../../assets/img/favorite-filled.svg"
|
isLiked
|
||||||
alt=""
|
? './assets/img/favorite-filled.svg'
|
||||||
|
: './assets/img/favorite-outlined.svg'
|
||||||
|
"
|
||||||
|
[class.filled]="isLiked"
|
||||||
|
[class.outlined]="!isLiked"
|
||||||
|
alt="favorite"
|
||||||
/>
|
/>
|
||||||
} @else {
|
|
||||||
<img
|
|
||||||
class="outlined"
|
|
||||||
src="./../../../../assets/img/favorite-outlined.svg"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
@if (!isAnyResolutionAvailable()) {
|
|
||||||
|
<!-- Loading indicator -->
|
||||||
|
@if (!isAnyResolutionAvailable) {
|
||||||
<p>
|
<p>
|
||||||
(The video is being converted)
|
(The video is being converted)
|
||||||
<a (click)="refreshPage()">Refresh</a>
|
<a (click)="refreshPage()">Refresh</a>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@ section {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
&::after {
|
&::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -31,12 +36,6 @@ section {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-banner {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 60%;
|
width: 60%;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { BtnSmallComponent } from '../../../shared/components/buttons/btn-small/
|
||||||
import { UserService } from '../../../services/user.service';
|
import { UserService } from '../../../services/user.service';
|
||||||
import { ResolutionService } from '../../../services/resolution.service';
|
import { ResolutionService } from '../../../services/resolution.service';
|
||||||
import { Video } from '../../../interfaces/video.interface';
|
import { Video } from '../../../interfaces/video.interface';
|
||||||
|
import { firstValueFrom, timeInterval } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-hero-banner',
|
selector: 'app-hero-banner',
|
||||||
|
|
@ -51,6 +52,8 @@ export class HeroBannerComponent implements OnChanges {
|
||||||
availableResolutions: string[];
|
availableResolutions: string[];
|
||||||
videoIsUploaded: { [resolution: string]: boolean };
|
videoIsUploaded: { [resolution: string]: boolean };
|
||||||
|
|
||||||
|
private readonly PLAYBACK_SPEED = 0.5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the HeroBannerComponent with the VideoService, ResolutionService and UserService.
|
* Initializes the HeroBannerComponent with the VideoService, ResolutionService and UserService.
|
||||||
* Gets the available resolutions and video upload status.
|
* Gets the available resolutions and video upload status.
|
||||||
|
|
@ -74,30 +77,27 @@ export class HeroBannerComponent implements OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the resolution status of the video, sets the video speed and updates the video URLs.
|
* Lifecycle hook called when the component's input properties have changed.
|
||||||
* @param changes SimpleChanges object containing the changed input properties.
|
* Updates the video details if the current video has changed.
|
||||||
|
* @param changes The changes to the component's input properties.
|
||||||
*/
|
*/
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes['currentVideo'] && this.currentVideo !== null) {
|
if (changes['currentVideo']) {
|
||||||
const videoId = this.currentVideo.id;
|
this.updateVideoDetails(this.currentVideo);
|
||||||
if (videoId) {
|
|
||||||
this.loadVideoResolutionStatus(videoId);
|
|
||||||
setTimeout(() => this.setVideoSpeed(), 0);
|
|
||||||
this.updateVideoUrls();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the URLs for the video, thumbnail, and video thumbnail.
|
* Updates the video details, resolution status, and playback speed
|
||||||
|
* for the hero banner component based on the given video.
|
||||||
|
* @param video The video to update the hero banner component with.
|
||||||
*/
|
*/
|
||||||
private updateVideoUrls(): void {
|
private updateVideoDetails(video: Video | null): void {
|
||||||
if (this.currentVideo) {
|
if (video) {
|
||||||
const videoId = this.currentVideo.id;
|
const videoId = video.id;
|
||||||
const fileName = this.currentVideo.file_name;
|
this.loadVideoResolutionStatus(videoId);
|
||||||
this.videoUrl = `${this.environmentBaseUrl}/media/videos/${videoId}/${fileName}`;
|
this.updateVideoUrls();
|
||||||
this.thumbnailUrl = `${this.environmentBaseUrl}/media/thumbnails/${videoId}/${fileName}_1080p.jpg`;
|
this.setVideoSpeed();
|
||||||
this.previewClipUrl = `${this.environmentBaseUrl}/media/thumbnails/${videoId}/${fileName}_preview-clip.mp4`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,22 +105,62 @@ export class HeroBannerComponent implements OnChanges {
|
||||||
* Loads the resolution status of the video.
|
* Loads the resolution status of the video.
|
||||||
*/
|
*/
|
||||||
private loadVideoResolutionStatus(videoId: number): void {
|
private loadVideoResolutionStatus(videoId: number): void {
|
||||||
this.videoService
|
firstValueFrom(this.videoService.isVideoResolutionUploaded(videoId))
|
||||||
.isVideoResolutionUploaded(videoId)
|
.then((resolutions) => {
|
||||||
.subscribe((resolutions) => {
|
|
||||||
this.videoIsUploaded = resolutions;
|
this.videoIsUploaded = resolutions;
|
||||||
this.videoIsUploadedChange.emit(this.videoIsUploaded);
|
this.videoIsUploadedChange.emit(this.videoIsUploaded);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error loading the video resolution:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the URLs for the video, thumbnail, and preview clip
|
||||||
|
* based on the current video. Sets the appropriate media paths
|
||||||
|
* for the video player and displays.
|
||||||
|
*/
|
||||||
|
private updateVideoUrls(): void {
|
||||||
|
if (this.currentVideo) {
|
||||||
|
const { id, file_name } = this.currentVideo;
|
||||||
|
this.videoUrl = this.getVideoMediaPath('video', id, file_name);
|
||||||
|
this.thumbnailUrl = this.getVideoMediaPath('thumbnail', id, file_name);
|
||||||
|
this.previewClipUrl = this.getVideoMediaPath('preview', id, file_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a URL for a video, thumbnail, or preview clip given the type, id, and file name.
|
||||||
|
* @param type The type of media to generate the URL for.
|
||||||
|
* @param id The id of the video.
|
||||||
|
* @param file The file name of the video.
|
||||||
|
* @returns A URL pointing to the desired media.
|
||||||
|
*/
|
||||||
|
private getVideoMediaPath(
|
||||||
|
type: 'video' | 'thumbnail' | 'preview',
|
||||||
|
id: number,
|
||||||
|
file: string
|
||||||
|
): string {
|
||||||
|
switch (type) {
|
||||||
|
case 'video':
|
||||||
|
return `${this.environmentBaseUrl}/media/videos/${id}/${file}`;
|
||||||
|
case 'thumbnail':
|
||||||
|
return `${this.environmentBaseUrl}/media/thumbnails/${id}/${file}_1080p.jpg`;
|
||||||
|
case 'preview':
|
||||||
|
return `${this.environmentBaseUrl}/media/thumbnails/${id}/${file}_preview-clip.mp4`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the playback rate of the video.
|
* Sets the playback rate of the video.
|
||||||
*/
|
*/
|
||||||
private setVideoSpeed(): void {
|
private setVideoSpeed(): void {
|
||||||
if (this.videoElementRef) {
|
setInterval(() => {
|
||||||
const videoElement = this.videoElementRef.nativeElement;
|
const videoElement = this.videoElementRef?.nativeElement;
|
||||||
videoElement.playbackRate = 0.5;
|
if (videoElement) {
|
||||||
}
|
videoElement.playbackRate = this.PLAYBACK_SPEED;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,26 +178,40 @@ export class HeroBannerComponent implements OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the like status of the video.
|
* Plays the video and marks it as watched.
|
||||||
*/
|
*/
|
||||||
toggleLikeVideo(video: Video): void {
|
playVideoId(videoPath: string, video: Video): void {
|
||||||
if (video.id !== undefined) {
|
this.playVideo.emit(videoPath);
|
||||||
const videoId = video.id;
|
this.toggleVideoStatus(video.id, 'watched');
|
||||||
this.favoriteVideos = this.favoriteVideos.includes(videoId)
|
}
|
||||||
? this.favoriteVideos.filter((id) => id !== videoId)
|
|
||||||
: [...this.favoriteVideos, videoId];
|
/**
|
||||||
|
* Toggles the status of a video (favorite or watched) and emits the updated
|
||||||
|
* favoriteVideos or watchedVideos array.
|
||||||
|
* @param videoId The id of the video to toggle the status for.
|
||||||
|
* @param statusType The type of status to toggle ('favorite' or 'watched').
|
||||||
|
*/
|
||||||
|
toggleVideoStatus(videoId: number, statusType: 'favorite' | 'watched'): void {
|
||||||
|
if (statusType === 'favorite') {
|
||||||
|
this.favoriteVideos = this.toggleArrayItem(this.favoriteVideos, videoId);
|
||||||
this.favoriteVideoChange.emit(this.favoriteVideos);
|
this.favoriteVideoChange.emit(this.favoriteVideos);
|
||||||
|
} else if (statusType === 'watched') {
|
||||||
|
this.watchedVideos = this.toggleArrayItem(this.watchedVideos, videoId);
|
||||||
|
this.watchedVideoChange.emit(this.watchedVideos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the watched status of the video.
|
* Toggles the presence of an item in an array, returning a new array.
|
||||||
|
*
|
||||||
|
* @param array The array to modify.
|
||||||
|
* @param id The item to toggle.
|
||||||
|
* @returns A new array with the item toggled.
|
||||||
*/
|
*/
|
||||||
toggleWatchedVideo(videoId: number): void {
|
private toggleArrayItem(array: number[], id: number): number[] {
|
||||||
this.watchedVideos = this.watchedVideos.includes(videoId)
|
return array.includes(id)
|
||||||
? this.watchedVideos.filter((id) => id !== videoId)
|
? array.filter((item) => item !== id)
|
||||||
: [...this.watchedVideos, videoId];
|
: [...array, id];
|
||||||
this.watchedVideoChange.emit(this.watchedVideos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -167,13 +221,6 @@ export class HeroBannerComponent implements OnChanges {
|
||||||
return video.id !== undefined && this.favoriteVideos.includes(video.id);
|
return video.id !== undefined && this.favoriteVideos.includes(video.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if any resolution is available.
|
|
||||||
*/
|
|
||||||
isAnyResolutionAvailable(): boolean {
|
|
||||||
return Object.values(this.videoIsUploaded).some((available) => available);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the page with the current video.
|
* Refreshes the page with the current video.
|
||||||
*/
|
*/
|
||||||
|
|
@ -189,12 +236,9 @@ export class HeroBannerComponent implements OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays the video and marks it as watched.
|
* Checks if any of the video resolutions are available to watch.
|
||||||
*/
|
*/
|
||||||
playVideoId(videoPath: string, video: Video): void {
|
get isAnyResolutionAvailable(): boolean {
|
||||||
if (video.id !== undefined) {
|
return Object.values(this.videoIsUploaded).some((available) => available);
|
||||||
this.playVideo.emit(videoPath);
|
|
||||||
this.toggleWatchedVideo(video.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
export interface Video {
|
export interface Video {
|
||||||
id?: number;
|
id: number;
|
||||||
creator: number;
|
creator: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue