refactor: optimize code in VideoPlayerComponent for better readability and performance

This commit is contained in:
Chneemann 2025-05-15 04:21:15 +02:00
parent 765c3f3000
commit 92bf86bca0

View file

@ -1,8 +1,8 @@
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 { ResolutionService } from '../../../services/resolution.service';
import Hls from 'hls.js';
import { VideoProgressService } from '../../../services/video-progress.service'; import { VideoProgressService } from '../../../services/video-progress.service';
import { Video } from '../../../interfaces/video.interface'; import { Video } from '../../../interfaces/video.interface';
import Hls from 'hls.js';
@Component({ @Component({
selector: 'app-video-player', selector: 'app-video-player',
@ -16,13 +16,14 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
@Input() currentVideo: Video | null = null; @Input() currentVideo: Video | null = null;
private hls: Hls | null = null; private hls: Hls | null = null;
private resizeObserver: ResizeObserver | null = null;
private videoElement: HTMLVideoElement | null = null; private videoElement: HTMLVideoElement | null = null;
private resolutionUrls: { [key: string]: string } = {};
private availableResolutions: { [key: string]: string } = {};
private defaultResolution: string; private defaultResolution: string;
/** /**
* Initializes the VideoPlayerComponent with the ElementRef, ResolutionService and VideoProgressService. * Initializes the VideoPlayerComponent with the ElementRef, ResolutionService and VideoProgressService.
* Get the default resolution.
*/ */
constructor( constructor(
private elementRef: ElementRef, private elementRef: ElementRef,
@ -42,36 +43,50 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
/** /**
* Angular lifecycle hook: Called once the component is about to be destroyed. * Angular lifecycle hook: Called once the component is about to be destroyed.
* Cleans up HLS resources and stops tracking video progress. * Cleans up the player, stops tracking video progress and disconnects the resize observer.
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.hls?.destroy(); this.hls?.destroy();
this.videoProgressService.stopTracking(); this.videoProgressService.stopTracking();
this.resizeObserver?.disconnect();
} }
/** /**
* Sets up the video player by initializing the video element, retrieving resolution URLs, * Sets up the video player by initializing the video element,
* and obtaining the initial playback position for the current video. It also starts tracking * generating resolution URLs, loading initial playback progress,
* the video's playback progress and updates the video's screen dimensions. * and setting up resize observation.
*/ */
private setupPlayer(): void { private setupPlayer(): void {
this.videoElement = this.elementRef.nativeElement.querySelector('video'); this.videoElement = this.elementRef.nativeElement.querySelector('video');
if (!this.playVideo || !this.videoElement) return; if (!this.playVideo || !this.videoElement) return;
this.resolutionUrls = this.getResolutionUrls(); this.availableResolutions = this.generateResolutions();
const defaultUrl = this.resolutionUrls[this.defaultResolution]; const defaultUrl = this.availableResolutions[this.defaultResolution];
this.loadInitialProgress(defaultUrl);
this.observeResize();
}
/**
* Loads the initial playback progress for the current video and initializes the player.
* Starts tracking the playback progress. Falls back to start at 0 on error.
*
* @param url - The video URL to initialize the player with.
*/
private loadInitialProgress(url: string): void {
this.videoProgressService this.videoProgressService
.getInitialProgress(this.currentVideo!.id) .getInitialProgress(this.currentVideo!.id)
.then((startTime) => { .then((startTime) => {
this.initPlayer(defaultUrl, startTime); this.initPlayer(url, startTime);
this.videoProgressService.startTracking( this.videoProgressService.startTracking(
this.videoElement!, this.videoElement!,
this.currentVideo!.id this.currentVideo!.id
); );
})
.catch((error) => {
console.error('Error loading the progress:', error);
this.initPlayer(url, 0);
}); });
this.updateScreenDimensions();
} }
/** /**
@ -89,61 +104,43 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
this.hls.on(Hls.Events.MANIFEST_PARSED, () => { this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
if (this.videoElement) { if (this.videoElement) {
this.videoElement.currentTime = startTime; this.videoElement.currentTime = startTime;
this.videoElement.play(); this.videoElement.play().catch(console.error);
} }
}); });
} else if (this.videoElement.canPlayType('application/vnd.apple.mpegurl')) { } else if (this.videoElement.canPlayType('application/vnd.apple.mpegurl')) {
this.videoElement.src = url; this.videoElement.src = url;
this.videoElement.addEventListener('canplay', () => { this.videoElement.addEventListener('canplay', () => {
this.videoElement!.currentTime = startTime; this.videoElement!.currentTime = startTime;
this.videoElement!.play(); this.videoElement!.play().catch(console.error);
}); });
} }
} }
/** /**
* Generates a mapping of available resolutions to their respective URLs. * Generates a mapping of available video resolutions to their stream URLs.
* @returns An object containing resolution-URL pairs. * @returns An object like { '720p': 'video_720p.m3u8', '1080p': 'video_1080p.m3u8' }.
*/ */
private getResolutionUrls(): { [key: string]: string } { private generateResolutions(): { [key: string]: string } {
return Object.fromEntries( return this.resolutionService
this.resolutionService .getAvailableResolutions()
.getAvailableResolutions() .reduce((acc, res) => {
.map((res) => [res, `${this.playVideo}_${res}.m3u8`]) acc[res] = `${this.playVideo}_${res}.m3u8`;
); return acc;
} }, {} as { [key: string]: string });
/**
* Initializes the video player using HLS.js if supported.
* @param url - The URL of the video stream to load.
*/
private initHlsPlayer(url: string): void {
if (!this.videoElement) return;
this.hls = new Hls();
this.hls.loadSource(url);
this.hls.attachMedia(this.videoElement);
this.hls.on(Hls.Events.MANIFEST_PARSED, () => this.videoElement?.play());
}
/**
* Initializes the native HTML5 video player if HLS is natively supported.
* @param url - The URL of the video stream to load.
*/
private initNativePlayer(url: string): void {
if (!this.videoElement) return;
this.videoElement.src = url;
this.videoElement.addEventListener('canplay', () =>
this.videoElement?.play()
);
} }
/** /**
* Updates the video element's dimensions to match the window size. * Updates the video element's dimensions to match the window size.
*/ */
private updateScreenDimensions(): void { private observeResize(): void {
if (!this.videoElement) return; if (!this.videoElement) return;
this.videoElement.style.width = `${window.innerWidth}px`; this.resizeObserver = new ResizeObserver(() => {
this.videoElement.style.height = `${window.innerHeight}px`; if (!this.videoElement) return;
const { clientWidth, clientHeight } = document.documentElement;
this.videoElement.style.width = `${clientWidth}px`;
this.videoElement.style.height = `${clientHeight}px`;
});
this.resizeObserver.observe(document.body);
} }
/** /**
@ -152,15 +149,20 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
*/ */
public switchResolution(resolution: string): void { public switchResolution(resolution: string): void {
const targetUrl = const targetUrl =
this.resolutionUrls[resolution] || this.availableResolutions[resolution] ||
this.resolutionUrls[this.defaultResolution]; this.availableResolutions[this.defaultResolution];
if (targetUrl) { if (!targetUrl) {
this.hls
? this.loadHlsSource(targetUrl)
: this.loadNativeSource(targetUrl);
} else {
console.error(`No URL found for resolution '${resolution}'`); console.error(`No URL found for resolution '${resolution}'`);
return;
} }
if (this.hls) {
this.loadHlsSource(targetUrl);
} else {
this.loadNativeSource(targetUrl);
}
this.videoElement?.play().catch(console.error);
} }
/** /**
@ -168,8 +170,9 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
* @param url - The URL of the new video stream. * @param url - The URL of the new video stream.
*/ */
private loadHlsSource(url: string): void { private loadHlsSource(url: string): void {
this.hls?.loadSource(url); if (!this.hls || !this.videoElement) return;
this.hls?.attachMedia(this.videoElement!); this.hls.loadSource(url);
this.hls.attachMedia(this.videoElement);
} }
/** /**