feat(player): add ArtPlayer and DPlayer components with integration in video player

This commit is contained in:
4gray committed 2024-12-15 21:02:14 +01:00
1 parent b1f11a57bc
commit 2e8a7494b4
7 files changed
+247 -2

No files matched your search

@@ -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: `<div #artplayer class="artplayer-container"></div>`,
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';
}
}
}
@@ -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: `<div #dplayer class="dplayer-container"></div>`,
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';
}
}
}
@@ -49,6 +49,23 @@
[volume]="volume"
[showCaptions]="playerSettings.showCaptions"
/>
<!-- Add DPlayer component -->
<app-d-player
*ngIf="playerSettings.player === 'dplayer'"
[channel]="activeChannel"
[volume]="volume"
[showCaptions]="playerSettings.showCaptions"
/>
<!-- Add ArtPlayer component -->
<app-art-player
*ngIf="playerSettings.player === 'artplayer'"
[channel]="activeChannel"
[volume]="volume"
[showCaptions]="playerSettings.showCaptions"
/>
<div
class="custom-player"
*ngIf="playerSettings.player === 'mpv'"
@@ -35,7 +35,9 @@ import {
selectChannels,
selectCurrentEpgProgram,
} from '../../../state/selectors';
import { ArtPlayerComponent } from '../art-player/art-player.component';
import { AudioPlayerComponent } from '../audio-player/audio-player.component';
import { DPlayerComponent } from '../d-player/d-player.component';
import { EpgListComponent } from '../epg-list/epg-list.component';
import { HtmlVideoPlayerComponent } from '../html-video-player/html-video-player.component';
import { InfoOverlayComponent } from '../info-overlay/info-overlay.component';
@@ -65,6 +67,8 @@ export const COMPONENT_OVERLAY_REF = new InjectionToken(
SidebarComponent,
ToolbarComponent,
VjsPlayerComponent,
DPlayerComponent,
ArtPlayerComponent,
],
templateUrl: './video-player.component.html',
styleUrls: ['./video-player.component.scss'],
@@ -2,4 +2,8 @@
<app-vjs-player [options]="vjsOptions" />
} @else if (player === 'html5') {
<app-html-video-player *ngIf="player === 'html5'" [channel]="channel" />
} @else if (player === 'dplayer') {
<app-d-player [channel]="channel" [volume]="1" [showCaptions]="false" />
} @else if (player === 'artplayer') {
<app-art-player [channel]="channel" [volume]="1" [showCaptions]="false" />
}
@@ -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 {
+2 -1
View File
@@ -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',
}
/**