From 21b9c8f804a11eb15eadfdeba1bfb894a31509b3 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Thu, 23 Mar 2023 23:41:13 +0100 Subject: [PATCH] feat: mpv player integration --- electron/api.ts | 48 ++++++++++++++----- shared/ipc-commands.ts | 3 ++ .../video-player/video-player.component.html | 8 ++++ .../video-player/video-player.component.scss | 14 ++++++ .../video-player/video-player.component.ts | 15 ++++++ src/app/settings/settings.component.ts | 4 ++ src/app/settings/settings.interface.ts | 1 + src/app/state/effects.ts | 20 ++++++++ 8 files changed, 102 insertions(+), 11 deletions(-) diff --git a/electron/api.ts b/electron/api.ts index 502d0511f..f798d5b84 100644 --- a/electron/api.ts +++ b/electron/api.ts @@ -24,6 +24,7 @@ import { MIGRATE_PLAYLISTS, MIGRATE_PLAYLISTS_RESPONSE, OPEN_FILE, + OPEN_MPV_PLAYER, PLAYLIST_PARSE_BY_URL, PLAYLIST_PARSE_RESPONSE, PLAYLIST_UPDATE, @@ -36,6 +37,11 @@ import { ParsedPlaylist } from '../src/typings.d'; const fs = require('fs'); const https = require('https'); +const mpvAPI = require('node-mpv'); +const createMpvInstance = () => new mpvAPI({}, ['--autofit=70%']); +let mpv = createMpvInstance(); +mpv.on('quit', () => (mpv = null)); + /** @deprecated - used only for migration */ const Nedb = require('nedb-promises'); @@ -143,7 +149,7 @@ export class Api { ) .on( CHANNEL_SET_USER_AGENT, - (event, args: { userAgent: string; referer?: string }) => { + (_event, args: { userAgent: string; referer?: string }) => { if (args.userAgent && args.referer) { this.setUserAgent(args.userAgent, args.referer); } else { @@ -205,44 +211,64 @@ export class Api { }) .on(DELETE_ALL_PLAYLISTS, (event) => { this.removeAllPlaylists(event); + }) + // eslint-disable-next-line @typescript-eslint/no-misused-promises + .on(OPEN_MPV_PLAYER, async (event, { url }) => { + try { + if (mpv === null) { + mpv = createMpvInstance(); + } + if (mpv.isRunning()) { + await mpv.load(url); + } else { + await mpv.start(); + await mpv.load(url); + } + } catch (error) { + console.log(error); + event.sender.send(ERROR, { + message: + 'Error: Something went wrong. Make sure that mpv player is installed on your system.', + }); + } }); // listeners for EPG events ipcMain - .on(EPG_GET_PROGRAM, (event, arg) => + .on(EPG_GET_PROGRAM, (_event, arg) => this.workerWindow.webContents.send(EPG_GET_PROGRAM, arg) ) - .on(EPG_GET_CHANNELS, (event, arg) => + .on(EPG_GET_CHANNELS, (_event, arg) => this.workerWindow.webContents.send(EPG_GET_CHANNELS, arg) ) - .on(EPG_GET_CHANNELS_DONE, (event, arg) => + .on(EPG_GET_CHANNELS_DONE, (_event, arg) => this.mainWindow.webContents.send(EPG_GET_CHANNELS_DONE, arg) ) - .on(EPG_GET_PROGRAM_DONE, (event, arg) => { + .on(EPG_GET_PROGRAM_DONE, (_event, arg) => { this.mainWindow.webContents.send(EPG_GET_PROGRAM_DONE, arg); }) - .on(EPG_FETCH, (event, arg) => + .on(EPG_FETCH, (_event, arg) => this.workerWindow.webContents.send(EPG_FETCH, arg?.url) ) - .on(EPG_FETCH_DONE, (event, arg) => + .on(EPG_FETCH_DONE, (_event, arg) => this.mainWindow.webContents.send(EPG_FETCH_DONE, arg) ) - .on(EPG_ERROR, (event, arg) => + .on(EPG_ERROR, (_event, arg) => this.mainWindow.webContents.send(EPG_ERROR, arg) ) - .on(EPG_GET_CHANNELS_BY_RANGE, (event, arg) => { + .on(EPG_GET_CHANNELS_BY_RANGE, (_event, arg) => { this.workerWindow.webContents.send( EPG_GET_CHANNELS_BY_RANGE, arg ); }) - .on(EPG_GET_CHANNELS_BY_RANGE_RESPONSE, (event, arg) => + .on(EPG_GET_CHANNELS_BY_RANGE_RESPONSE, (_event, arg) => this.mainWindow.webContents.send( EPG_GET_CHANNELS_BY_RANGE_RESPONSE, arg ) ) - .on(EPG_FORCE_FETCH, (event, arg) => + .on(EPG_FORCE_FETCH, (_event, arg) => this.workerWindow.webContents.send(EPG_FORCE_FETCH, arg) ); diff --git a/shared/ipc-commands.ts b/shared/ipc-commands.ts index b227bc0f5..02a230682 100644 --- a/shared/ipc-commands.ts +++ b/shared/ipc-commands.ts @@ -40,3 +40,6 @@ export const DELETE_ALL_PLAYLISTS = 'DELETE_ALL_PLAYLISTS'; // Auto-update export const AUTO_UPDATE_PLAYLISTS = 'AUTO_UPDATE'; export const AUTO_UPDATE_PLAYLISTS_RESPONSE = 'AUTO_UPDATE_RESPONSE'; + +// Experimental +export const OPEN_MPV_PLAYER = 'OPEN_MPV_PLAYER'; 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 92a789537..ff78276e8 100644 --- a/src/app/player/components/video-player/video-player.component.html +++ b/src/app/player/components/video-player/video-player.component.html @@ -25,6 +25,7 @@ [class.hide-captions]="!playerSettings.showCaptions" > + + + + + The channel is played in a separate window in the MPV player. (Installation instructions) + + + { + this.snackBar.open(response.message, '', { + duration: 3100, + }); + }, + }, { id: PLAYLIST_PARSE_RESPONSE, execute: (response: { payload: Playlist }): void => { @@ -109,6 +119,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { private ngZone: NgZone, private overlay: Overlay, private playlistsService: PlaylistsService, + private snackBar: MatSnackBar, private storage: StorageMap, private store: Store ) {} @@ -217,4 +228,8 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { this.overlayRef.addPanelClass('epg-overlay'); this.overlayRef.attach(componentPortal); } + + openUrl(url: string) { + window.open(url, '_blank'); + } } diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index 766b5902f..fbbbc521f 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -47,6 +47,10 @@ export class SettingsComponent implements OnInit { id: VideoPlayer.VideoJs, label: 'VideoJs Player', }, + { + id: VideoPlayer.MPV, + label: 'MPV Player', + }, ]; /** Current version of the app */ diff --git a/src/app/settings/settings.interface.ts b/src/app/settings/settings.interface.ts index c55e83e7b..544b45ff3 100644 --- a/src/app/settings/settings.interface.ts +++ b/src/app/settings/settings.interface.ts @@ -8,6 +8,7 @@ import { Theme } from './theme.enum'; export enum VideoPlayer { VideoJs = 'videojs', Html5Player = 'html5', + MPV = 'mpv', } /** diff --git a/src/app/state/effects.ts b/src/app/state/effects.ts index fcc0845a2..b1ae3b1ac 100644 --- a/src/app/state/effects.ts +++ b/src/app/state/effects.ts @@ -4,14 +4,19 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { Router } from '@angular/router'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; +import { StorageMap } from '@ngx-pwa/local-storage'; import { TranslateService } from '@ngx-translate/core'; +import { firstValueFrom } from 'rxjs'; import { combineLatestWith, map, switchMap, tap } from 'rxjs/operators'; import { CHANNEL_SET_USER_AGENT, EPG_GET_PROGRAM, + OPEN_MPV_PLAYER, } from '../../../shared/ipc-commands'; import { DataService } from '../services/data.service'; import { PlaylistsService } from '../services/playlists.service'; +import { Settings, VideoPlayer } from '../settings/settings.interface'; +import { STORE_KEY } from '../shared/enums/store-keys.enum'; import * as PlaylistActions from './actions'; import { selectActivePlaylistId, @@ -72,6 +77,20 @@ export class PlaylistEffects { userAgent: channel.http['user-agent'], }); } + + firstValueFrom(this.storage.get(STORE_KEY.Settings)).then( + (settings: Settings) => { + if ( + settings && + Object.keys(settings).length > 0 && + settings.player === VideoPlayer.MPV + ) + this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, { + url: channel.url, + }); + } + ); + return PlaylistActions.setActiveChannelSuccess({ channel: action.channel, }); @@ -230,6 +249,7 @@ export class PlaylistEffects { private dataService: DataService, private router: Router, private snackBar: MatSnackBar, + private storage: StorageMap, private store: Store, private translate: TranslateService ) {}
The channel is played in a separate window in the MPV player. (Installation instructions)