feat: create ResolutionService to centralize and manage video resolutions
This commit is contained in:
parent
df8305806f
commit
3d4ad193a8
6 changed files with 124 additions and 63 deletions
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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[]) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
33
frontend/src/app/services/resolution.service.ts
Normal file
33
frontend/src/app/services/resolution.service.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue