Merge pull request #245 from 4gray/feature/mpv-integration

Feature/mpv integration
This commit is contained in:
4gray authored and GitHub committed 2023-03-24 07:31:07 +01:00
commit eb569e2252
15 files changed
+132 -26

No files matched your search

+37 -11
View File
@@ -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)).on('crash', () => (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)
);
+1 -13
View File
@@ -1,5 +1,5 @@
/* eslint-disable no-useless-catch */
import { app, BrowserWindow, globalShortcut, Menu } from 'electron';
import { app, BrowserWindow, Menu } from 'electron';
import * as path from 'path';
import * as url from 'url';
import { Api } from './api';
@@ -78,21 +78,9 @@ function createWindow(): BrowserWindow {
}
});
registerGlobalShortcuts();
return win;
}
/**
* Registers common keyboard shortcuts
*/
function registerGlobalShortcuts() {
if (process.platform === 'darwin') {
globalShortcut.register('Command+Q', () => {
app.quit();
});
}
}
/**
* Creates hidden window for EPG worker
* Hidden window is used as an additional thread to avoid blocking of the UI by long operations
+1
View File
@@ -72,6 +72,7 @@ export class AppMenu {
{
label: 'Exit',
click: () => app.quit(),
accelerator: 'CmdOrCtrl+Q',
},
],
});
+13 -2
View File
@@ -1,12 +1,12 @@
{
"name": "iptvnator",
"version": "0.11.1",
"version": "0.12.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "iptvnator",
"version": "0.11.1",
"version": "0.12.0",
"hasInstallScript": true,
"dependencies": {
"@angular/animations": "15.1.1",
@@ -36,6 +36,7 @@
"ngx-indexed-db": "9.4.2",
"ngx-skeleton-loader": "7.0.0",
"ngx-whats-new": "0.4.0",
"node-mpv": "2.0.0-beta.2",
"rxjs": "7.8.0",
"semver": "7.3.8",
"uuid": "9.0.0",
@@ -18653,6 +18654,11 @@
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
"dev": true
},
"node_modules/node-mpv": {
"version": "2.0.0-beta.2",
"resolved": "https://registry.npmjs.org/node-mpv/-/node-mpv-2.0.0-beta.2.tgz",
"integrity": "sha512-jf1InAB6tSXYlLs53DSw7ZEGCAhuWibILMF8GU6FmX6jXvkScLfGa9B7nmrIidG8euDpi3hdnUht0PqrHlAbXA=="
},
"node_modules/node-polyfill-webpack-plugin": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/node-polyfill-webpack-plugin/-/node-polyfill-webpack-plugin-1.1.4.tgz",
@@ -41633,6 +41639,11 @@
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
"dev": true
},
"node-mpv": {
"version": "2.0.0-beta.2",
"resolved": "https://registry.npmjs.org/node-mpv/-/node-mpv-2.0.0-beta.2.tgz",
"integrity": "sha512-jf1InAB6tSXYlLs53DSw7ZEGCAhuWibILMF8GU6FmX6jXvkScLfGa9B7nmrIidG8euDpi3hdnUht0PqrHlAbXA=="
},
"node-polyfill-webpack-plugin": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/node-polyfill-webpack-plugin/-/node-polyfill-webpack-plugin-1.1.4.tgz",
+1
View File
@@ -78,6 +78,7 @@
"ngx-indexed-db": "9.4.2",
"ngx-skeleton-loader": "7.0.0",
"ngx-whats-new": "0.4.0",
"node-mpv": "2.0.0-beta.2",
"rxjs": "7.8.0",
"semver": "7.3.8",
"uuid": "9.0.0",
+3
View File
@@ -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';
@@ -25,6 +25,7 @@
[class.hide-captions]="!playerSettings.showCaptions"
></app-vjs-player>
</ng-container>
<!-- default html player component -->
<app-html-video-player
*ngIf="playerSettings.player === 'html5'"
@@ -33,6 +34,13 @@
>
</app-html-video-player>
<ng-container *ngIf="(activeChannel$ | async) && playerSettings.player === 'mpv'">
<div class="mpv-player">
<img src="./assets/images/custom-player.png" />
<p>The channel is played in a separate window in the MPV player. (<a [routerLink] style="cursor: pointer;" (click)="openUrl('https://github.com/4gray/iptvnator/wiki/What-is-mpv-video-player-and-how-to-install-it-on-different-operating-systems%3F')">Installation instructions</a>)</p>
</div>
</ng-container>
<!-- channel overlay -->
<app-info-overlay
[channel]="activeChannel$ | async"
@@ -22,3 +22,17 @@
min-width: 200px;
}
}
@media (prefers-color-scheme: dark) {
.mpv-player {
display: flex;
margin-top: 50px;
flex-direction: column;
align-items: center;
img {
filter: invert(100%);
width: 400px;
}
}
}
@@ -8,6 +8,7 @@ import {
OnDestroy,
OnInit,
} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import { StorageMap } from '@ngx-pwa/local-storage';
@@ -20,6 +21,7 @@ import {
} from 'rxjs';
import { Channel } from '../../../../../shared/channel.interface';
import {
ERROR,
PLAYLIST_PARSE_BY_URL,
PLAYLIST_PARSE_RESPONSE,
} from '../../../../../shared/ipc-commands';
@@ -73,6 +75,14 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
/** IPC Renderer commands list with callbacks */
commandsList = [
{
id: ERROR,
execute: (response: { message: string }): void => {
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');
}
}
+14
View File
@@ -245,6 +245,20 @@ export class WhatsNewService {
},
},
],
'0.13.0': [
{
imageHeight: 400,
imageBgColor: '#333',
imageSrc: './assets/updates/0130/mpv-player.png',
title: '🚀 MPV player integration',
html: 'Long-awaited mpv player support, which can now be selected from the settings. Before activating the player from the settings page, make sure it is installed on your system. For more details about mvp integration, check the <a target="_blank" href="https://github.com/4gray/iptvnator/wiki/What-is-mpv-video-player-and-how-to-install-it-on-different-operating-systems%3F">wiki page</a>.',
button: {
text: 'YAY',
textColor: '#ccc',
bgColor: '#111',
},
},
],
};
/** Options for the "what is new" modal dialogs */
+4
View File
@@ -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 */
+1
View File
@@ -8,6 +8,7 @@ import { Theme } from './theme.enum';
export enum VideoPlayer {
VideoJs = 'videojs',
Html5Player = 'html5',
MPV = 'mpv',
}
/**
+20
View File
@@ -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
) {}
Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB