From 2e8a7494b40a02e9ff33d2347c15645d2ff704f4 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 15 Dec 2024 21:02:14 +0100 Subject: [PATCH] feat(player): add ArtPlayer and DPlayer components with integration in video player --- .../art-player/art-player.component.ts | 122 ++++++++++++++++++ .../components/d-player/d-player.component.ts | 89 +++++++++++++ .../video-player/video-player.component.html | 17 +++ .../video-player/video-player.component.ts | 4 + .../web-player-view.component.html | 4 + .../web-player-view.component.ts | 10 +- src/app/settings/settings.interface.ts | 3 +- 7 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 src/app/player/components/art-player/art-player.component.ts create mode 100644 src/app/player/components/d-player/d-player.component.ts diff --git a/src/app/player/components/art-player/art-player.component.ts b/src/app/player/components/art-player/art-player.component.ts new file mode 100644 index 000000000..40b511ba4 --- /dev/null +++ b/src/app/player/components/art-player/art-player.component.ts @@ -0,0 +1,122 @@ +import { CommonModule } from '@angular/common'; +import { + Component, + ElementRef, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from '@angular/core'; +import Artplayer from 'artplayer'; +import Hls from 'hls.js'; +import { Channel } from '../../../../../shared/channel.interface'; + +@Component({ + selector: 'app-art-player', + standalone: true, + imports: [CommonModule], + template: `
`, + styles: [ + ` + :host { + display: block; + width: 100%; + height: 100%; + } + .artplayer-container { + width: 100%; + height: 100%; + } + `, + ], +}) +export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges { + @Input() channel: Channel; + @Input() volume = 1; + @Input() showCaptions = false; + + private player: Artplayer; + + constructor(private elementRef: ElementRef) {} + + ngOnInit(): void { + this.initPlayer(); + } + + ngOnDestroy(): void { + if (this.player) { + this.player.destroy(); + } + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['channel'] && !changes['channel'].firstChange) { + if (this.player) { + this.player.destroy(); + } + this.initPlayer(); + } + } + + private initPlayer(): void { + const el = this.elementRef.nativeElement.querySelector( + '.artplayer-container' + ); + const isLive = this.channel.url.toLowerCase().includes('m3u8'); + + this.player = new Artplayer({ + container: el, + url: this.channel.url + (this.channel.epgParams || ''), + volume: this.volume, + isLive: isLive, + autoplay: true, + type: this.getVideoType(this.channel.url), + pip: true, + autoSize: true, + autoMini: true, + screenshot: true, + setting: true, + playbackRate: true, + aspectRatio: true, + fullscreen: true, + fullscreenWeb: true, + customType: { + m3u8: function (video: HTMLVideoElement, url: string) { + if (Hls.isSupported()) { + const hls = new Hls(); + hls.loadSource(url); + hls.attachMedia(video); + } else if ( + video.canPlayType('application/vnd.apple.mpegurl') + ) { + video.src = url; + } + }, + mkv: function (video: HTMLVideoElement, url: string) { + video.src = url; + // Add error handling + video.onerror = () => { + console.error('Error loading MKV file:', video.error); + // Fallback to treating it as a regular video + video.src = url; + }; + }, + }, + }); + } + + private getVideoType(url: string): string { + const extension = url.split('.').pop()?.toLowerCase(); + switch (extension) { + case 'mkv': + return 'video/matroska'; // Changed from 'mkv' + case 'm3u8': + return 'm3u8'; + case 'mp4': + return 'mp4'; + default: + return 'auto'; + } + } +} diff --git a/src/app/player/components/d-player/d-player.component.ts b/src/app/player/components/d-player/d-player.component.ts new file mode 100644 index 000000000..d9bc8048d --- /dev/null +++ b/src/app/player/components/d-player/d-player.component.ts @@ -0,0 +1,89 @@ +import { CommonModule } from '@angular/common'; +import { + Component, + ElementRef, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from '@angular/core'; +import DPlayer from 'dplayer'; +import { Channel } from '../../../../../shared/channel.interface'; + +@Component({ + selector: 'app-d-player', + standalone: true, + imports: [CommonModule], + template: `
`, + styles: [ + ` + .dplayer-container { + width: 100%; + height: calc(100vh - 64px); + } + `, + ], +}) +export class DPlayerComponent implements OnInit, OnDestroy, OnChanges { + @Input() channel: Channel; + @Input() volume = 1; + @Input() showCaptions = false; + + private player: any; + + constructor(private elementRef: ElementRef) {} + + ngOnInit(): void { + this.initPlayer(); + } + + ngOnDestroy(): void { + if (this.player) { + this.player.destroy(); + } + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['channel'] && !changes['channel'].firstChange) { + // Destroy existing player + if (this.player) { + this.player.destroy(); + } + // Reinitialize with new channel + this.initPlayer(); + } + } + + private initPlayer(): void { + const el = + this.elementRef.nativeElement.querySelector('.dplayer-container'); + const isLive = this.channel.url.toLowerCase().includes('m3u8'); + + this.player = new DPlayer({ + container: el, + live: isLive, + volume: this.volume, + autoplay: true, + video: { + url: this.channel.url + (this.channel.epgParams || ''), + type: this.getVideoType(this.channel.url), + }, + logo: this.channel.tvg?.logo, + }); + } + + private getVideoType(url: string): string { + const extension = url.split('.').pop()?.toLowerCase(); + switch (extension) { + case 'mkv': + return 'video/matroska'; + case 'm3u8': + return 'hls'; + case 'mp4': + return 'auto'; + default: + return 'auto'; + } + } +} diff --git a/src/app/player/components/video-player/video-player.component.html b/src/app/player/components/video-player/video-player.component.html index 8bbc053e6..b201edfcb 100644 --- a/src/app/player/components/video-player/video-player.component.html +++ b/src/app/player/components/video-player/video-player.component.html @@ -49,6 +49,23 @@ [volume]="volume" [showCaptions]="playerSettings.showCaptions" /> + + + + + + +
} @else if (player === 'html5') { +} @else if (player === 'dplayer') { + +} @else if (player === 'artplayer') { + } diff --git a/src/app/portals/web-player-view/web-player-view.component.ts b/src/app/portals/web-player-view/web-player-view.component.ts index 3c7c196ee..2bba89644 100644 --- a/src/app/portals/web-player-view/web-player-view.component.ts +++ b/src/app/portals/web-player-view/web-player-view.component.ts @@ -10,6 +10,8 @@ import { import { toSignal } from '@angular/core/rxjs-interop'; import { StorageMap } from '@ngx-pwa/local-storage'; import { getExtensionFromUrl } from '../../../../shared/playlist.utils'; +import { ArtPlayerComponent } from '../../player/components/art-player/art-player.component'; +import { DPlayerComponent } from '../../player/components/d-player/d-player.component'; import { HtmlVideoPlayerComponent } from '../../player/components/html-video-player/html-video-player.component'; import { VjsPlayerComponent } from '../../player/components/vjs-player/vjs-player.component'; import { Settings, VideoPlayer } from '../../settings/settings.interface'; @@ -20,7 +22,13 @@ import { STORE_KEY } from '../../shared/enums/store-keys.enum'; selector: 'app-web-player-view', templateUrl: './web-player-view.component.html', styleUrls: ['./web-player-view.component.scss'], - imports: [HtmlVideoPlayerComponent, NgIf, VjsPlayerComponent], + imports: [ + HtmlVideoPlayerComponent, + NgIf, + VjsPlayerComponent, + DPlayerComponent, + ArtPlayerComponent, + ], encapsulation: ViewEncapsulation.None, }) export class WebPlayerViewComponent { diff --git a/src/app/settings/settings.interface.ts b/src/app/settings/settings.interface.ts index e521ae7c6..ddf8176d0 100644 --- a/src/app/settings/settings.interface.ts +++ b/src/app/settings/settings.interface.ts @@ -3,13 +3,14 @@ import { Theme } from './theme.enum'; /** * Contains all types of supported video players - * TODO: extract to separate file */ export enum VideoPlayer { VideoJs = 'videojs', Html5Player = 'html5', MPV = 'mpv', VLC = 'vlc', + DPlayer = 'dplayer', + ArtPlayer = 'artplayer', } /**