Merge pull request #619 from 4gray/feat/add-artplayer

feat/add-artplayer
This commit is contained in:
4gray authored and GitHub committed 2025-09-27 20:18:32 +02:00
commit f5b3a15b83
11 files changed
+31 -173

No files matched your search

+1 -39
View File
@@ -37,7 +37,6 @@
"artplayer": "5.3.0",
"axios": "1.12.2",
"date-fns": "2.29.3",
"dplayer": "1.27.1",
"epg-parser": "0.1.6",
"hls.js": "1.6.13",
"iptv-playlist-parser": "github:4gray/iptv-playlist-parser",
@@ -85,7 +84,6 @@
"@semantic-release/git": "10.0.1",
"@semantic-release/npm": "12.0.1",
"@tauri-apps/cli": "2.3.1",
"@types/dplayer": "1.25.5",
"@types/jest": "29.5.14",
"@types/mocha": "9.0.0",
"@types/node": "20.10.0",
@@ -115,7 +113,7 @@
"wait-on": "6.0.0"
},
"engines": {
"node": ">=20.0.0"
"node": ">=20.8.1"
}
},
"node_modules/@algolia/abtesting": {
@@ -8374,12 +8372,6 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/dplayer": {
"version": "1.25.5",
"resolved": "https://registry.npmjs.org/@types/dplayer/-/dplayer-1.25.5.tgz",
"integrity": "sha512-p/7O94dHDo0Irn2KWIqFE+fBCA4DS7QL3jfCOjCUPBAOgppyyTjmNZjKEfiJa1M3n1oVQqG7xnPwhiIuCqOzkQ==",
"dev": true
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -9786,11 +9778,6 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/balloon-css": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/balloon-css/-/balloon-css-1.2.0.tgz",
"integrity": "sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A=="
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -11973,26 +11960,6 @@
"node": ">=8"
}
},
"node_modules/dplayer": {
"version": "1.27.1",
"resolved": "https://registry.npmjs.org/dplayer/-/dplayer-1.27.1.tgz",
"integrity": "sha512-2laBMXs5V1B9zPwJ7eAIw/OBo+Xjvy03i4GHTk3Cg+IWbrq8rKMFO0fFr6ClAYotYOCcFGOvaJDkOZcgKllsCA==",
"dependencies": {
"axios": "1.2.3",
"balloon-css": "^1.0.3",
"promise-polyfill": "8.3.0"
}
},
"node_modules/dplayer/node_modules/axios": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.3.tgz",
"integrity": "sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -23420,11 +23387,6 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
"node_modules/promise-polyfill": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz",
"integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg=="
},
"node_modules/promise-retry": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
-2
View File
@@ -64,7 +64,6 @@
"artplayer": "5.3.0",
"axios": "1.12.2",
"date-fns": "2.29.3",
"dplayer": "1.27.1",
"epg-parser": "0.1.6",
"hls.js": "1.6.13",
"iptv-playlist-parser": "github:4gray/iptv-playlist-parser",
@@ -112,7 +111,6 @@
"@semantic-release/git": "10.0.1",
"@semantic-release/npm": "12.0.1",
"@tauri-apps/cli": "2.3.1",
"@types/dplayer": "1.25.5",
"@types/jest": "29.5.14",
"@types/mocha": "9.0.0",
"@types/node": "20.10.0",
@@ -1,4 +1,3 @@
import {
Component,
ElementRef,
@@ -12,6 +11,8 @@ import Artplayer from 'artplayer';
import Hls from 'hls.js';
import { Channel } from '../../../../../shared/channel.interface';
Artplayer.AUTO_PLAYBACK_TIMEOUT = 10000;
@Component({
selector: 'app-art-player',
imports: [],
@@ -28,7 +29,7 @@ import { Channel } from '../../../../../shared/channel.interface';
height: 100%;
}
`,
]
],
})
export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
@Input() channel: Channel;
@@ -62,7 +63,7 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
const el = this.elementRef.nativeElement.querySelector(
'.artplayer-container'
);
const isLive = this.channel.url.toLowerCase().includes('m3u8');
const isLive = this.channel?.url?.toLowerCase().includes('m3u8');
this.player = new Artplayer({
container: el,
@@ -72,6 +73,7 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
autoplay: true,
type: this.getVideoType(this.channel.url),
pip: true,
autoPlayback: true,
autoSize: true,
autoMini: true,
screenshot: true,
@@ -80,6 +82,11 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
aspectRatio: true,
fullscreen: true,
fullscreenWeb: true,
playsInline: true,
airplay: true,
backdrop: true,
mutex: true,
theme: '#ff0000',
customType: {
m3u8: function (video: HTMLVideoElement, url: string) {
if (Hls.isSupported()) {
@@ -1,88 +0,0 @@
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',
imports: [],
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,14 +49,6 @@
[showCaptions]="playerSettings.showCaptions"
/>
}
<!-- Add DPlayer component -->
@if (playerSettings.player === 'dplayer') {
<app-d-player
[channel]="activeChannel"
[volume]="volume"
[showCaptions]="playerSettings.showCaptions"
/>
}
<!-- Add ArtPlayer component -->
@if (playerSettings.player === 'artplayer') {
<app-art-player
@@ -38,7 +38,6 @@ import {
} 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';
@@ -67,7 +66,6 @@ export const COMPONENT_OVERLAY_REF = new InjectionToken(
SidebarComponent,
ToolbarComponent,
VjsPlayerComponent,
DPlayerComponent,
ArtPlayerComponent,
],
templateUrl: './video-player.component.html',
@@ -1,11 +1,9 @@
@if (player === 'videojs') {
<app-vjs-player [options]="vjsOptions" />
<app-vjs-player [options]="vjsOptions" />
} @else if (player === 'html5') {
@if (player === 'html5') {
<app-html-video-player [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" />
<app-art-player [channel]="channel" [volume]="1" [showCaptions]="false" />
} @else {
<app-vjs-player [options]="vjsOptions" />
}
@@ -2,6 +2,11 @@ app-vjs-player > .video-js {
height: 50vh !important;
}
app-html-video-player > #video-player {
app-html-video-player > #video-player {
height: 50vh !important;
}
app-art-player > .artplayer-container {
width: 100% !important;
height: 300px !important;
}
@@ -1,4 +1,3 @@
import {
Component,
Signal,
@@ -11,7 +10,6 @@ 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';
@@ -21,13 +19,8 @@ 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,
VjsPlayerComponent,
DPlayerComponent,
ArtPlayerComponent
],
encapsulation: ViewEncapsulation.None
imports: [ArtPlayerComponent, HtmlVideoPlayerComponent, VjsPlayerComponent],
encapsulation: ViewEncapsulation.None,
})
export class WebPlayerViewComponent {
storage = inject(StorageMap);
@@ -43,15 +36,13 @@ export class WebPlayerViewComponent {
vjsOptions: { sources: { src: string; type: string }[] };
constructor() {
effect(
() => {
this.player = this.settings()?.player ?? VideoPlayer.VideoJs;
effect(() => {
this.player = this.settings()?.player;
this.setChannel(this.streamUrl());
this.setChannel(this.streamUrl());
if (this.player === VideoPlayer.VideoJs)
this.setVjsOptions(this.streamUrl());
},
{ allowSignalWrites: true }
);
});
}
setVjsOptions(streamUrl: string) {
+2 -6
View File
@@ -49,9 +49,9 @@ import { HeaderComponent } from '../shared/components/header/header.component';
import * as PlaylistActions from '../state/actions';
import { selectIsEpgAvailable } from '../state/selectors';
import { SettingsService } from './../services/settings.service';
import { StreamFormat } from './stream-format.enum';
import { Language } from './language.enum';
import { VideoPlayer } from './settings.interface';
import { StreamFormat } from './stream-format.enum';
import { Theme } from './theme.enum';
@Component({
@@ -107,14 +107,10 @@ export class SettingsComponent implements OnInit {
id: VideoPlayer.VideoJs,
label: 'VideoJs Player',
},
/* {
id: VideoPlayer.DPlayer,
label: 'DPlayer',
},
{
id: VideoPlayer.ArtPlayer,
label: 'ArtPlayer',
}, */
},
...(this.isTauri ? this.osPlayers : []),
];
+1 -2
View File
@@ -1,5 +1,5 @@
import { StreamFormat } from './stream-format.enum';
import { Language } from './language.enum';
import { StreamFormat } from './stream-format.enum';
import { Theme } from './theme.enum';
/**
@@ -10,7 +10,6 @@ export enum VideoPlayer {
Html5Player = 'html5',
MPV = 'mpv',
VLC = 'vlc',
DPlayer = 'dplayer',
ArtPlayer = 'artplayer',
}