From 3d4ad193a8b327ecf036a0b6df001495c90642f1 Mon Sep 17 00:00:00 2001 From: Chneemann Date: Sat, 26 Apr 2025 22:41:34 +0200 Subject: [PATCH] feat: create ResolutionService to centralize and manage video resolutions --- .../hero-banner/hero-banner.component.html | 6 +- .../home/hero-banner/hero-banner.component.ts | 25 +++--- .../src/app/components/home/home.component.ts | 23 ++--- .../video-player/video-player.component.ts | 89 +++++++++++-------- frontend/src/app/services/movie.service.ts | 11 ++- .../src/app/services/resolution.service.ts | 33 +++++++ 6 files changed, 124 insertions(+), 63 deletions(-) create mode 100644 frontend/src/app/services/resolution.service.ts diff --git a/frontend/src/app/components/home/hero-banner/hero-banner.component.html b/frontend/src/app/components/home/hero-banner/hero-banner.component.html index d829c44..00133eb 100644 --- a/frontend/src/app/components/home/hero-banner/hero-banner.component.html +++ b/frontend/src/app/components/home/hero-banner/hero-banner.component.html @@ -53,7 +53,7 @@ [value]="'Play'" [imgPath]="'play'" [imgReverse]="true" - [disabled]="!isAnyResolutionUploaded()" + [disabled]="!isAnyResolutionAvailable()" (click)="playMovieId(playUrl, currentMovie[0].id)" >
@@ -71,10 +71,12 @@ /> }
-

+ @if (!isAnyResolutionAvailable()) { +

(The video is being converted) Refresh

+ } } diff --git a/frontend/src/app/components/home/hero-banner/hero-banner.component.ts b/frontend/src/app/components/home/hero-banner/hero-banner.component.ts index 9fedb71..2dae029 100644 --- a/frontend/src/app/components/home/hero-banner/hero-banner.component.ts +++ b/frontend/src/app/components/home/hero-banner/hero-banner.component.ts @@ -14,6 +14,7 @@ import { MovieService } from '../../../services/movie.service'; import { environment } from '../../../../environments/environment'; import { BtnSmallComponent } from '../../../shared/components/buttons/btn-small/btn-small.component'; import { UserService } from '../../../services/user.service'; +import { ResolutionService } from '../../../services/resolution.service'; @Component({ selector: 'app-hero-banner', @@ -41,15 +42,19 @@ export class HeroBannerComponent implements OnChanges { thumbnailUrl: string = ''; playUrl: string = ''; environmentBaseUrl: string = environment.baseUrl; - movieIsUploaded: { [resolution: string]: boolean } = { - '320p': true, - '720p': true, - '1080p': true, - }; + + availableResolutions: string[]; + movieIsUploaded: { [resolution: string]: boolean }; + constructor( private movieService: MovieService, + private resolutionService: ResolutionService, public userService: UserService - ) {} + ) { + this.availableResolutions = + this.resolutionService.getAvailableResolutions(); + this.movieIsUploaded = this.resolutionService.initMovieIsUploaded(); + } ngAfterViewInit() { this.videoSpeed(); @@ -121,12 +126,8 @@ export class HeroBannerComponent implements OnChanges { return this.favoriteMovies.includes(videoId); } - isAnyResolutionUploaded(): boolean { - return ( - this.movieIsUploaded['320p'] || - this.movieIsUploaded['720p'] || - this.movieIsUploaded['1080p'] - ); + isAnyResolutionAvailable(): boolean { + return Object.values(this.movieIsUploaded).some((available) => available); } refreshPage(newMovies: any[]) { diff --git a/frontend/src/app/components/home/home.component.ts b/frontend/src/app/components/home/home.component.ts index cadcd5f..d8934d0 100644 --- a/frontend/src/app/components/home/home.component.ts +++ b/frontend/src/app/components/home/home.component.ts @@ -8,6 +8,7 @@ import { VideoPlayerComponent } from './video-player/video-player.component'; import { BtnSmallComponent } from '../../shared/components/buttons/btn-small/btn-small.component'; import { UploadMovieComponent } from './upload-movie/upload-movie.component'; import { UserService } from '../../services/user.service'; +import { ResolutionService } from '../../services/resolution.service'; @Component({ selector: 'app-home', @@ -33,18 +34,21 @@ export class HomeComponent implements OnInit { playMovie: string = ''; isLoading: boolean = true; uploadMovieOverview: boolean = false; - availableResolutions: string[] = ['360p', '720p', '1080p']; - currentResolution = '720p'; - movieIsUploaded: { [resolution: string]: boolean } = { - '360p': false, - '720p': false, - '1080p': false, - }; + + availableResolutions: string[]; + currentResolution: string; + movieIsUploaded: { [resolution: string]: boolean }; constructor( private movieService: MovieService, - public userService: UserService - ) {} + public userService: UserService, + private resolutionService: ResolutionService + ) { + this.availableResolutions = + this.resolutionService.getAvailableResolutions(); + this.currentResolution = this.resolutionService.getDefaultResolution(); + this.movieIsUploaded = this.resolutionService.initMovieIsUploaded(); + } async ngOnInit() { this.loadLikedAndWatchedMovies(); @@ -110,7 +114,6 @@ export class HomeComponent implements OnInit { this.isLoading = true; try { this.movies = await this.movieService.getAllMovies(); - console.log(this.movies); } finally { this.isLoading = false; } diff --git a/frontend/src/app/components/home/video-player/video-player.component.ts b/frontend/src/app/components/home/video-player/video-player.component.ts index af58c70..026f23a 100644 --- a/frontend/src/app/components/home/video-player/video-player.component.ts +++ b/frontend/src/app/components/home/video-player/video-player.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit, OnDestroy, ElementRef, Input } from '@angular/core'; +import { ResolutionService } from '../../../services/resolution.service'; import Hls from 'hls.js'; @Component({ @@ -9,44 +10,62 @@ import Hls from 'hls.js'; }) export class VideoPlayerComponent implements OnInit, OnDestroy { @Input() playMovie: string = ''; + private hls: Hls | null = null; private videoElement: HTMLVideoElement | null = null; private resolutionUrls: { [key: string]: string } = {}; + private defaultResolution: string; - constructor(private elementRef: ElementRef) {} + constructor( + private elementRef: ElementRef, + private resolutionService: ResolutionService + ) { + this.defaultResolution = this.resolutionService.getDefaultResolution(); + } ngOnInit(): void { + this.initializePlayer(); + } + + ngOnDestroy(): void { + if (this.hls) { + this.hls.destroy(); + } + } + + private initializePlayer(): void { this.videoElement = this.elementRef.nativeElement.querySelector( 'video' ) as HTMLVideoElement; - if (this.playMovie) { - this.resolutionUrls = { - '360p': `${this.playMovie}_360p.m3u8`, - '720p': `${this.playMovie}_720p.m3u8`, - '1080p': `${this.playMovie}_1080p.m3u8`, - }; - - if (Hls.isSupported() && this.videoElement) { - this.hls = new Hls(); - this.hls.loadSource(this.resolutionUrls['720p']); // Default resolution - this.hls.attachMedia(this.videoElement); - this.hls.on(Hls.Events.MANIFEST_PARSED, () => { - this.videoElement?.play(); - }); - } else if ( - this.videoElement.canPlayType('application/vnd.apple.mpegurl') - ) { - this.videoElement.src = this.resolutionUrls['720p']; // Default resolution - this.videoElement.addEventListener('canplay', () => { - this.videoElement?.play(); - }); - } - - this.updateScreenDimensions(); - } else { + if (!this.playMovie) { console.error('playMovie is not set.'); + return; } + + this.resolutionUrls = Object.fromEntries( + this.resolutionService + .getAvailableResolutions() + .map((res) => [res, `${this.playMovie}_${res}.m3u8`]) + ); + + const defaultUrl = this.resolutionUrls[this.defaultResolution]; + + if (Hls.isSupported() && this.videoElement) { + this.hls = new Hls(); + this.hls.loadSource(defaultUrl); + this.hls.attachMedia(this.videoElement); + this.hls.on(Hls.Events.MANIFEST_PARSED, () => { + this.videoElement?.play(); + }); + } else if (this.videoElement.canPlayType('application/vnd.apple.mpegurl')) { + this.videoElement.src = defaultUrl; + this.videoElement.addEventListener('canplay', () => { + this.videoElement?.play(); + }); + } + + this.updateScreenDimensions(); } private updateScreenDimensions() { @@ -56,22 +75,20 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { } } - ngOnDestroy(): void { - if (this.hls) { - this.hls.destroy(); - } - } - public switchResolution(resolution: string) { - if (this.resolutionUrls[resolution]) { + const targetUrl = + this.resolutionUrls[resolution] || + this.resolutionUrls[this.defaultResolution]; + + if (targetUrl) { if (this.hls) { - this.hls.loadSource(this.resolutionUrls[resolution]); + this.hls.loadSource(targetUrl); this.hls.attachMedia(this.videoElement!); } else if (this.videoElement) { - this.videoElement.src = this.resolutionUrls[resolution]; + this.videoElement.src = targetUrl; } } else { - console.error(`Resolution URL for ${resolution} not found.`); + console.error(`No URL found for resolution '${resolution}' or fallback.`); } } } diff --git a/frontend/src/app/services/movie.service.ts b/frontend/src/app/services/movie.service.ts index 10f82cc..429229d 100644 --- a/frontend/src/app/services/movie.service.ts +++ b/frontend/src/app/services/movie.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { catchError, firstValueFrom, map, Observable, of } from 'rxjs'; import { ApiService } from './api.service'; import { AuthService } from './auth.service'; +import { ResolutionService } from './resolution.service'; @Injectable({ providedIn: 'root', @@ -10,12 +11,16 @@ export class MovieService { private movieCache: { [key: number]: { [resolution: string]: boolean }; } = {}; - private readonly availableResolutions = ['360p', '720p', '1080p']; + private availableResolutions: string[]; constructor( private apiService: ApiService, - private authService: AuthService - ) {} + private authService: AuthService, + private resolutionService: ResolutionService + ) { + this.availableResolutions = + this.resolutionService.getAvailableResolutions(); + } /** * Fetch all movies available on the server diff --git a/frontend/src/app/services/resolution.service.ts b/frontend/src/app/services/resolution.service.ts new file mode 100644 index 0000000..814a8ea --- /dev/null +++ b/frontend/src/app/services/resolution.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class ResolutionService { + private readonly AVAILABLE_RESOLUTIONS = ['360p', '720p', '1080p']; + + /** + * Gibt die zentral definierten verfügbaren Auflösungen zurück + */ + getAvailableResolutions(): string[] { + return this.AVAILABLE_RESOLUTIONS; + } + + /** + * Initialisiert ein Objekt, das alle Auflösungen auf `false` setzt + */ + initMovieIsUploaded(): { [resolution: string]: boolean } { + return Object.fromEntries( + this.AVAILABLE_RESOLUTIONS.map((resolution) => [resolution, false]) + ); + } + + /** + * Gibt eine Standardauflösung zurück (z.B. '720p'), oder die erste verfügbare + */ + getDefaultResolution(): string { + return this.AVAILABLE_RESOLUTIONS.includes('720p') + ? '720p' + : this.AVAILABLE_RESOLUTIONS[0]; + } +}