feat: create ResolutionService to centralize and manage video resolutions

This commit is contained in:
Chneemann 2025-04-26 22:41:34 +02:00
parent df8305806f
commit 3d4ad193a8
6 changed files with 124 additions and 63 deletions

View file

@ -53,7 +53,7 @@
[value]="'Play'" [value]="'Play'"
[imgPath]="'play'" [imgPath]="'play'"
[imgReverse]="true" [imgReverse]="true"
[disabled]="!isAnyResolutionUploaded()" [disabled]="!isAnyResolutionAvailable()"
(click)="playMovieId(playUrl, currentMovie[0].id)" (click)="playMovieId(playUrl, currentMovie[0].id)"
></app-btn-large> ></app-btn-large>
<div class="favorite-img" (click)="toggleLikeMovie(currentMovie[0].id)"> <div class="favorite-img" (click)="toggleLikeMovie(currentMovie[0].id)">
@ -71,10 +71,12 @@
/> />
} }
</div> </div>
<p *ngIf="!isAnyResolutionUploaded()"> @if (!isAnyResolutionAvailable()) {
<p>
(The video is being converted) (The video is being converted)
<a (click)="refreshPage(currentMovie)">Refresh</a> <a (click)="refreshPage(currentMovie)">Refresh</a>
</p> </p>
}
</div> </div>
</div> </div>
} }

View file

@ -14,6 +14,7 @@ import { MovieService } from '../../../services/movie.service';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
import { BtnSmallComponent } from '../../../shared/components/buttons/btn-small/btn-small.component'; import { BtnSmallComponent } from '../../../shared/components/buttons/btn-small/btn-small.component';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { ResolutionService } from '../../../services/resolution.service';
@Component({ @Component({
selector: 'app-hero-banner', selector: 'app-hero-banner',
@ -41,15 +42,19 @@ export class HeroBannerComponent implements OnChanges {
thumbnailUrl: string = ''; thumbnailUrl: string = '';
playUrl: string = ''; playUrl: string = '';
environmentBaseUrl: string = environment.baseUrl; environmentBaseUrl: string = environment.baseUrl;
movieIsUploaded: { [resolution: string]: boolean } = {
'320p': true, availableResolutions: string[];
'720p': true, movieIsUploaded: { [resolution: string]: boolean };
'1080p': true,
};
constructor( constructor(
private movieService: MovieService, private movieService: MovieService,
private resolutionService: ResolutionService,
public userService: UserService public userService: UserService
) {} ) {
this.availableResolutions =
this.resolutionService.getAvailableResolutions();
this.movieIsUploaded = this.resolutionService.initMovieIsUploaded();
}
ngAfterViewInit() { ngAfterViewInit() {
this.videoSpeed(); this.videoSpeed();
@ -121,12 +126,8 @@ export class HeroBannerComponent implements OnChanges {
return this.favoriteMovies.includes(videoId); return this.favoriteMovies.includes(videoId);
} }
isAnyResolutionUploaded(): boolean { isAnyResolutionAvailable(): boolean {
return ( return Object.values(this.movieIsUploaded).some((available) => available);
this.movieIsUploaded['320p'] ||
this.movieIsUploaded['720p'] ||
this.movieIsUploaded['1080p']
);
} }
refreshPage(newMovies: any[]) { refreshPage(newMovies: any[]) {

View file

@ -8,6 +8,7 @@ import { VideoPlayerComponent } from './video-player/video-player.component';
import { BtnSmallComponent } from '../../shared/components/buttons/btn-small/btn-small.component'; import { BtnSmallComponent } from '../../shared/components/buttons/btn-small/btn-small.component';
import { UploadMovieComponent } from './upload-movie/upload-movie.component'; import { UploadMovieComponent } from './upload-movie/upload-movie.component';
import { UserService } from '../../services/user.service'; import { UserService } from '../../services/user.service';
import { ResolutionService } from '../../services/resolution.service';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
@ -33,18 +34,21 @@ export class HomeComponent implements OnInit {
playMovie: string = ''; playMovie: string = '';
isLoading: boolean = true; isLoading: boolean = true;
uploadMovieOverview: boolean = false; uploadMovieOverview: boolean = false;
availableResolutions: string[] = ['360p', '720p', '1080p'];
currentResolution = '720p'; availableResolutions: string[];
movieIsUploaded: { [resolution: string]: boolean } = { currentResolution: string;
'360p': false, movieIsUploaded: { [resolution: string]: boolean };
'720p': false,
'1080p': false,
};
constructor( constructor(
private movieService: MovieService, 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() { async ngOnInit() {
this.loadLikedAndWatchedMovies(); this.loadLikedAndWatchedMovies();
@ -110,7 +114,6 @@ export class HomeComponent implements OnInit {
this.isLoading = true; this.isLoading = true;
try { try {
this.movies = await this.movieService.getAllMovies(); this.movies = await this.movieService.getAllMovies();
console.log(this.movies);
} finally { } finally {
this.isLoading = false; this.isLoading = false;
} }

View file

@ -1,4 +1,5 @@
import { Component, OnInit, OnDestroy, ElementRef, Input } from '@angular/core'; import { Component, OnInit, OnDestroy, ElementRef, Input } from '@angular/core';
import { ResolutionService } from '../../../services/resolution.service';
import Hls from 'hls.js'; import Hls from 'hls.js';
@Component({ @Component({
@ -9,44 +10,62 @@ import Hls from 'hls.js';
}) })
export class VideoPlayerComponent implements OnInit, OnDestroy { export class VideoPlayerComponent implements OnInit, OnDestroy {
@Input() playMovie: string = ''; @Input() playMovie: string = '';
private hls: Hls | null = null; private hls: Hls | null = null;
private videoElement: HTMLVideoElement | null = null; private videoElement: HTMLVideoElement | null = null;
private resolutionUrls: { [key: string]: string } = {}; 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 { ngOnInit(): void {
this.initializePlayer();
}
ngOnDestroy(): void {
if (this.hls) {
this.hls.destroy();
}
}
private initializePlayer(): void {
this.videoElement = this.elementRef.nativeElement.querySelector( this.videoElement = this.elementRef.nativeElement.querySelector(
'video' 'video'
) as HTMLVideoElement; ) as HTMLVideoElement;
if (this.playMovie) { if (!this.playMovie) {
this.resolutionUrls = { console.error('playMovie is not set.');
'360p': `${this.playMovie}_360p.m3u8`, return;
'720p': `${this.playMovie}_720p.m3u8`, }
'1080p': `${this.playMovie}_1080p.m3u8`,
}; 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) { if (Hls.isSupported() && this.videoElement) {
this.hls = new Hls(); this.hls = new Hls();
this.hls.loadSource(this.resolutionUrls['720p']); // Default resolution this.hls.loadSource(defaultUrl);
this.hls.attachMedia(this.videoElement); this.hls.attachMedia(this.videoElement);
this.hls.on(Hls.Events.MANIFEST_PARSED, () => { this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
this.videoElement?.play(); this.videoElement?.play();
}); });
} else if ( } else if (this.videoElement.canPlayType('application/vnd.apple.mpegurl')) {
this.videoElement.canPlayType('application/vnd.apple.mpegurl') this.videoElement.src = defaultUrl;
) {
this.videoElement.src = this.resolutionUrls['720p']; // Default resolution
this.videoElement.addEventListener('canplay', () => { this.videoElement.addEventListener('canplay', () => {
this.videoElement?.play(); this.videoElement?.play();
}); });
} }
this.updateScreenDimensions(); this.updateScreenDimensions();
} else {
console.error('playMovie is not set.');
}
} }
private 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) { public switchResolution(resolution: string) {
if (this.resolutionUrls[resolution]) { const targetUrl =
this.resolutionUrls[resolution] ||
this.resolutionUrls[this.defaultResolution];
if (targetUrl) {
if (this.hls) { if (this.hls) {
this.hls.loadSource(this.resolutionUrls[resolution]); this.hls.loadSource(targetUrl);
this.hls.attachMedia(this.videoElement!); this.hls.attachMedia(this.videoElement!);
} else if (this.videoElement) { } else if (this.videoElement) {
this.videoElement.src = this.resolutionUrls[resolution]; this.videoElement.src = targetUrl;
} }
} else { } else {
console.error(`Resolution URL for ${resolution} not found.`); console.error(`No URL found for resolution '${resolution}' or fallback.`);
} }
} }
} }

View file

@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { catchError, firstValueFrom, map, Observable, of } from 'rxjs'; import { catchError, firstValueFrom, map, Observable, of } from 'rxjs';
import { ApiService } from './api.service'; import { ApiService } from './api.service';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { ResolutionService } from './resolution.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -10,12 +11,16 @@ export class MovieService {
private movieCache: { private movieCache: {
[key: number]: { [resolution: string]: boolean }; [key: number]: { [resolution: string]: boolean };
} = {}; } = {};
private readonly availableResolutions = ['360p', '720p', '1080p']; private availableResolutions: string[];
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,
private authService: AuthService private authService: AuthService,
) {} private resolutionService: ResolutionService
) {
this.availableResolutions =
this.resolutionService.getAvailableResolutions();
}
/** /**
* Fetch all movies available on the server * Fetch all movies available on the server

View file

@ -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];
}
}