From 2a9123781f781a33676a2f00e004d237834f2ce5 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sat, 29 Jul 2023 19:14:56 +0200 Subject: [PATCH 01/27] feat: add experimental vlc player support --- electron/api.ts | 48 ++++++++++++++++++++++++ shared/ipc-commands.ts | 2 + src/app/settings/settings.component.html | 20 ++++++++++ src/app/settings/settings.component.ts | 29 +++++++++----- src/app/settings/settings.interface.ts | 2 + src/assets/i18n/en.json | 8 +++- 6 files changed, 98 insertions(+), 11 deletions(-) diff --git a/electron/api.ts b/electron/api.ts index 2b56d57cb..4002108f8 100644 --- a/electron/api.ts +++ b/electron/api.ts @@ -25,11 +25,13 @@ import { MIGRATE_PLAYLISTS_RESPONSE, OPEN_FILE, OPEN_MPV_PLAYER, + OPEN_VLC_PLAYER, PLAYLIST_PARSE_BY_URL, PLAYLIST_PARSE_RESPONSE, PLAYLIST_UPDATE, PLAYLIST_UPDATE_RESPONSE, SET_MPV_PLAYER_PATH, + SET_VLC_PLAYER_PATH, XTREAM_REQUEST, XTREAM_RESPONSE, } from '../shared/ipc-commands'; @@ -39,6 +41,8 @@ import { ParsedPlaylist } from '../src/typings.d'; const fs = require('fs'); const https = require('https'); +const child_process = require('child_process'); +const path = require('path'); const mpvAPI = require('node-mpv'); @@ -63,6 +67,7 @@ const agent = new https.Agent({ }); const MPV_PLAYER_PATH = 'MPV_PLAYER_PATH'; +const VLC_PLAYER_PATH = 'VLC_PLAYER_PATH'; export class Api { /** Instance of the main application window */ @@ -254,6 +259,24 @@ export class Api { // recreate mpv player instance with new binary path if it was changed if (store.get(MPV_PLAYER_PATH, mpvPlayerPath) !== mpvPlayerPath) this.mpv = this.createMpvInstance(); + }) + .on(OPEN_VLC_PLAYER, (event, { url }) => { + const proc = child_process.spawn( + this.getVlcPath(), + [`"${url as string}"`], + { + shell: true, + } + ); + + proc.on('exit', (code) => { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + console.log(`VLC exited with code ${code}`); + }); + }) + .on(SET_VLC_PLAYER_PATH, (_event, vlcPlayerPath) => { + console.log('... setting vlc player path', vlcPlayerPath); + store.set(VLC_PLAYER_PATH, vlcPlayerPath); }); // listeners for EPG events @@ -503,6 +526,31 @@ export class Api { return parse(m3uString); } + getDefaultVlcPath() { + if (process.platform === 'win32') { + return path.join( + 'C:', + 'Program Files (x86)', + 'VideoLAN', + 'VLC', + 'vlc.exe' + ); + } else if (process.platform === 'linux') { + return '/usr/bin/vlc'; + } else if (process.platform === 'darwin') { + return '/Applications/VLC.app/Contents/MacOS/VLC'; + } + } + + getVlcPath() { + const customVlcPath = this.store.get(VLC_PLAYER_PATH); + if (customVlcPath) { + return customVlcPath; + } else { + return this.getDefaultVlcPath(); + } + } + /** @deprecated - used only for migration */ getAllPlaylists() { return db diff --git a/shared/ipc-commands.ts b/shared/ipc-commands.ts index 1a57b142b..f3a063276 100644 --- a/shared/ipc-commands.ts +++ b/shared/ipc-commands.ts @@ -44,6 +44,8 @@ export const AUTO_UPDATE_PLAYLISTS_RESPONSE = 'AUTO_UPDATE_RESPONSE'; // Experimental export const OPEN_MPV_PLAYER = 'OPEN_MPV_PLAYER'; export const SET_MPV_PLAYER_PATH = 'SET_MPV_PLAYER_PATH'; +export const OPEN_VLC_PLAYER = 'OPEN_VLC_PLAYER'; +export const SET_VLC_PLAYER_PATH = 'SET_VLC_PLAYER_PATH'; // Xtream export const XTREAM_REQUEST = 'XTREAM_REQUEST'; diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 9a3704d21..d899cbc38 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -110,6 +110,26 @@ +
+
+ {{ 'SETTINGS.VLC_PLAYER_PATH_LABEL' | translate }} +

{{ 'SETTINGS.VLC_PLAYER_PATH_DESCRIPTION' | translate }}

+
+
+ + {{ + 'SETTINGS.VLC_PLAYER_PATH' | translate + }} + + +
+
+
{{ 'SETTINGS.LANGUAGE' | translate }} diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index f133b9ac5..6f5193297 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -15,6 +15,7 @@ import * as semver from 'semver'; import { EPG_FORCE_FETCH, SET_MPV_PLAYER_PATH, + SET_VLC_PLAYER_PATH, } from '../../../shared/ipc-commands'; import { Playlist } from '../../../shared/playlist.interface'; import { DataService } from '../services/data.service'; @@ -43,6 +44,17 @@ export class SettingsComponent implements OnInit { /** Flag that indicates whether the app runs in electron environment */ isElectron = this.electronService.isElectron; + electronPlayers = [ + { + id: VideoPlayer.MPV, + label: 'MPV Player', + }, + { + id: VideoPlayer.VLC, + label: 'VLC', + }, + ]; + /** Player options */ players = [ { @@ -53,15 +65,7 @@ export class SettingsComponent implements OnInit { id: VideoPlayer.VideoJs, label: 'VideoJs Player', }, - ...(this.isElectron - ? - [ - { - id: VideoPlayer.MPV, - label: 'MPV Player', - }, - ] - : []), + ...(this.isElectron ? this.electronPlayers : []), ]; /** Current version of the app */ @@ -84,6 +88,7 @@ export class SettingsComponent implements OnInit { showCaptions: false, theme: Theme.LightTheme, mpvPlayerPath: '', + vlcPlayerPath: '', }); /** Form array with epg sources */ @@ -138,6 +143,7 @@ export class SettingsComponent implements OnInit { ? settings.theme : Theme.LightTheme, mpvPlayerPath: settings.mpvPlayerPath, + vlcPlayerPath: settings.vlcPlayerPath, }); if (this.isElectron) { @@ -230,6 +236,11 @@ export class SettingsComponent implements OnInit { SET_MPV_PLAYER_PATH, this.settingsForm.value.mpvPlayerPath ); + + this.electronService.sendIpcEvent( + SET_VLC_PLAYER_PATH, + this.settingsForm.value.mpvPlayerPath + ); } /** diff --git a/src/app/settings/settings.interface.ts b/src/app/settings/settings.interface.ts index 605cf6507..f31a3f981 100644 --- a/src/app/settings/settings.interface.ts +++ b/src/app/settings/settings.interface.ts @@ -9,6 +9,7 @@ export enum VideoPlayer { VideoJs = 'videojs', Html5Player = 'html5', MPV = 'mpv', + VLC = 'vlc', } /** @@ -21,4 +22,5 @@ export interface Settings { showCaptions: boolean; theme: Theme; mpvPlayerPath: string; + vlcPlayerPath: string; } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index b5f4c3d38..98760d648 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -8,7 +8,8 @@ "FILE_UPLOAD": "Add via file upload", "URL_UPLOAD": "Add via URL", "TEXT_IMPORT": "Import as text", - "XTREME_IMPORT": "Import xtreme code" + "XTREME_IMPORT": "Import xtreme code", + "STALKER_PORTAL_IMPORT": "Add Stalker Portal" }, "PLAYLISTS": { "NO_PLAYLISTS": "No playlists were added", @@ -119,7 +120,10 @@ "PLAYLISTS_REMOVED": "All playlists were removed.", "MPV_PLAYER_PATH_LABEL": "mpv path", "MPV_PLAYER_PATH_DESCRIPTION": "Set path to mpv player in your system. It will use the provided path to a mpv binary instead of using the one found in $PATH.", - "MPV_PLAYER_PATH": "mpv player path" + "MPV_PLAYER_PATH": "mpv player path", + "VLC_PLAYER_PATH_LABEL": "VLC path", + "VLC_PLAYER_PATH_DESCRIPTION": "Set path to VLC player in your system. It will use the provided path to a VLC binary instead of the default path for our system.", + "VLC_PLAYER_PATH": "VLC player path" }, "THEMES": { "DARK_THEME": "Dark theme", From ef80b6fe4f9b4cee79242d120031eb010efd46c8 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sat, 5 Aug 2023 13:29:48 +0200 Subject: [PATCH 02/27] chore: update iptv-playlist-parser and prettier --- package-lock.json | 128 +++++++++++++++++++++++++++++++++++++++------- package.json | 4 +- 2 files changed, 112 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 317fec713..7f36521ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "iptvnator", - "version": "0.13.0", + "version": "0.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "iptvnator", - "version": "0.13.0", + "version": "0.14.0", "hasInstallScript": true, "dependencies": { "@angular/animations": "16.1.5", @@ -30,7 +30,7 @@ "electron-store": "8.1.0", "epg-parser": "0.1.6", "hls.js": "1.2.7", - "iptv-playlist-parser": "0.8.0", + "iptv-playlist-parser": "github:4gray/iptv-playlist-parser", "lodash": "4.17.21", "moment": "2.29.4", "nedb-promises": "6.0.3", @@ -94,7 +94,7 @@ "npm-run-all": "4.1.5", "playwright": "1.31.1", "playwright-core": "1.31.1", - "prettier": "2.8.8", + "prettier": "3.0.0", "semantic-release": "19.0.3", "stylelint": "14.2.0", "stylelint-config-standard": "24.0.0", @@ -15746,9 +15746,13 @@ } }, "node_modules/iptv-playlist-parser": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/iptv-playlist-parser/-/iptv-playlist-parser-0.8.0.tgz", - "integrity": "sha512-83+6Fl248rxm6tyzgPi2rqHQU1IBZnZvbWXyGAcBxveitckc+sh6b4+aDqQa+u2IJP2HVZilRd2C229kqJtLuQ==" + "version": "0.12.2", + "resolved": "git+ssh://git@github.com/4gray/iptv-playlist-parser.git#2b48764180edf25821dae3a4b1c9a6a7d0a3bf84", + "license": "MIT", + "dependencies": { + "is-valid-path": "^0.1.1", + "validator": "^13.7.0" + } }, "node_modules/is-arguments": { "version": "1.1.1", @@ -15959,6 +15963,36 @@ "node": ">=8" } }, + "node_modules/is-invalid-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz", + "integrity": "sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ==", + "dependencies": { + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-invalid-path/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-invalid-path/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -16212,6 +16246,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-valid-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz", + "integrity": "sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A==", + "dependencies": { + "is-invalid-path": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -24898,15 +24943,15 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -29130,6 +29175,14 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/validator": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", + "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -41965,9 +42018,12 @@ "dev": true }, "iptv-playlist-parser": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/iptv-playlist-parser/-/iptv-playlist-parser-0.8.0.tgz", - "integrity": "sha512-83+6Fl248rxm6tyzgPi2rqHQU1IBZnZvbWXyGAcBxveitckc+sh6b4+aDqQa+u2IJP2HVZilRd2C229kqJtLuQ==" + "version": "git+ssh://git@github.com/4gray/iptv-playlist-parser.git#2b48764180edf25821dae3a4b1c9a6a7d0a3bf84", + "from": "iptv-playlist-parser@github:4gray/iptv-playlist-parser", + "requires": { + "is-valid-path": "^0.1.1", + "validator": "^13.7.0" + } }, "is-arguments": { "version": "1.1.1", @@ -42109,6 +42165,29 @@ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true }, + "is-invalid-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz", + "integrity": "sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ==", + "requires": { + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, "is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -42275,6 +42354,14 @@ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, + "is-valid-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz", + "integrity": "sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A==", + "requires": { + "is-invalid-path": "^0.1.0" + } + }, "is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -48660,9 +48747,9 @@ "dev": true }, "prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", "dev": true }, "pretty-bytes": { @@ -51901,6 +51988,11 @@ "builtins": "^5.0.0" } }, + "validator": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", + "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index c81dce98c..221529480 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "electron-store": "8.1.0", "epg-parser": "0.1.6", "hls.js": "1.2.7", - "iptv-playlist-parser": "0.8.0", + "iptv-playlist-parser": "github:4gray/iptv-playlist-parser", "lodash": "4.17.21", "moment": "2.29.4", "nedb-promises": "6.0.3", @@ -136,7 +136,7 @@ "npm-run-all": "4.1.5", "playwright": "1.31.1", "playwright-core": "1.31.1", - "prettier": "2.8.8", + "prettier": "3.0.0", "semantic-release": "19.0.3", "stylelint": "14.2.0", "stylelint-config-standard": "24.0.0", From 011204aca12f7f6601886bafcf32055d08c15d6d Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sat, 5 Aug 2023 13:34:16 +0200 Subject: [PATCH 03/27] refactor: update interfaces according to updates --- shared/channel.interface.ts | 3 +-- shared/playlist.interface.ts | 2 ++ src/app/shared/playlist-meta.type.ts | 2 ++ src/typings.d.ts | 2 -- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/shared/channel.interface.ts b/shared/channel.interface.ts index f907e59c6..67d18d629 100644 --- a/shared/channel.interface.ts +++ b/shared/channel.interface.ts @@ -12,8 +12,6 @@ export interface Channel { tvg: { id: string; name: string; - language: string; - country: string; url: string; logo: string; rec: string; @@ -29,4 +27,5 @@ export interface Channel { referrer: string; 'user-agent': string; }; + radio: string; } diff --git a/shared/playlist.interface.ts b/shared/playlist.interface.ts index 374c4877c..9afc1011b 100644 --- a/shared/playlist.interface.ts +++ b/shared/playlist.interface.ts @@ -32,4 +32,6 @@ export interface Playlist { serverUrl?: string; username?: string; password?: string; + macAddress?: string; + portalUrl?: string; } diff --git a/src/app/shared/playlist-meta.type.ts b/src/app/shared/playlist-meta.type.ts index bc9e4d424..29e09a2b5 100644 --- a/src/app/shared/playlist-meta.type.ts +++ b/src/app/shared/playlist-meta.type.ts @@ -18,4 +18,6 @@ export type PlaylistMeta = Pick< | 'serverUrl' | 'username' | 'password' + | 'macAddress' + | 'portalUrl' >; diff --git a/src/typings.d.ts b/src/typings.d.ts index b3c5bb6fd..dcec9ae7e 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -23,8 +23,6 @@ export interface ParsedPlaylistItem { tvg: { id: string; name: string; - language: string; - country: string; url: string; logo: string; rec: string; From 1e2ab0b8ed799eeff3d88958e30d60d8eb36eb84 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sun, 6 Aug 2023 21:53:45 +0200 Subject: [PATCH 04/27] feat(stalker): new interfaces and ipc commands --- shared/ipc-commands.ts | 4 ++++ shared/stalker-portal-actions.enum.ts | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 shared/stalker-portal-actions.enum.ts diff --git a/shared/ipc-commands.ts b/shared/ipc-commands.ts index f3a063276..a8ab6408c 100644 --- a/shared/ipc-commands.ts +++ b/shared/ipc-commands.ts @@ -50,3 +50,7 @@ export const SET_VLC_PLAYER_PATH = 'SET_VLC_PLAYER_PATH'; // Xtream export const XTREAM_REQUEST = 'XTREAM_REQUEST'; export const XTREAM_RESPONSE = 'XTREAM_RESPONSE'; + +// Stalker +export const STALKER_REQUEST = 'STALKER_REQUEST'; +export const STALKER_RESPONSE = 'STALKER_RESPONSE'; diff --git a/shared/stalker-portal-actions.enum.ts b/shared/stalker-portal-actions.enum.ts new file mode 100644 index 000000000..60d10ce80 --- /dev/null +++ b/shared/stalker-portal-actions.enum.ts @@ -0,0 +1,6 @@ +export enum StalkerPortalActions { + GetCategories = 'get_categories', + GetGenres = 'get_genres', + CreateLink = 'create_link', + GetOrderedList = 'get_ordered_list', +} From a749bec69059aa915dfbad39e62bb3e9c9703d97 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sun, 6 Aug 2023 21:54:52 +0200 Subject: [PATCH 05/27] refactor(xtream): update navigation-bar component --- .../content-type-navigation-item.interface.ts | 6 ++++ src/app/xtream/content-type.enum.ts | 6 ++-- .../navigation-bar.component.html | 16 +++++----- .../navigation-bar.component.ts | 6 ++++ src/app/xtream/portal-card-item.interface.ts | 10 ++++++ .../xtream-main-container.component.html | 1 + .../xtream/xtream-main-container.component.ts | 31 ++++++++++++++----- 7 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 src/app/xtream/content-type-navigation-item.interface.ts create mode 100644 src/app/xtream/portal-card-item.interface.ts diff --git a/src/app/xtream/content-type-navigation-item.interface.ts b/src/app/xtream/content-type-navigation-item.interface.ts new file mode 100644 index 000000000..70d976686 --- /dev/null +++ b/src/app/xtream/content-type-navigation-item.interface.ts @@ -0,0 +1,6 @@ +import { ContentType } from './content-type.enum'; + +export interface ContentTypeNavigationItem { + contentType: ContentType; + label: string; +} \ No newline at end of file diff --git a/src/app/xtream/content-type.enum.ts b/src/app/xtream/content-type.enum.ts index 7125355a3..abd1672fa 100644 --- a/src/app/xtream/content-type.enum.ts +++ b/src/app/xtream/content-type.enum.ts @@ -1,5 +1,5 @@ export enum ContentType { - LIVE_STREAMS = 'LIVE_STREAMS', - VODS = 'VODS', - SERIES = 'SERIES', + VODS = 'vod', + SERIES = 'series', + ITV = 'itv', } diff --git a/src/app/xtream/navigation-bar/navigation-bar.component.html b/src/app/xtream/navigation-bar/navigation-bar.component.html index c7798b890..ea876fb6d 100644 --- a/src/app/xtream/navigation-bar/navigation-bar.component.html +++ b/src/app/xtream/navigation-bar/navigation-bar.component.html @@ -10,15 +10,15 @@ [value]="contentType" (change)="contentTypeChanged.emit($event.value)" > - Live Streams - VOD Streams - Series + {{ item.label }} +
diff --git a/src/app/xtream/navigation-bar/navigation-bar.component.ts b/src/app/xtream/navigation-bar/navigation-bar.component.ts index 3d00134c9..5cf63b6d3 100644 --- a/src/app/xtream/navigation-bar/navigation-bar.component.ts +++ b/src/app/xtream/navigation-bar/navigation-bar.component.ts @@ -6,6 +6,7 @@ import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatIconModule } from '@angular/material/icon'; import { RouterLink } from '@angular/router'; import { Breadcrumb } from '../breadcrumb.interface'; +import { ContentTypeNavigationItem } from '../content-type-navigation-item.interface'; import { ContentType } from '../content-type.enum'; import { PortalStore } from '../portal.store'; @@ -28,6 +29,7 @@ export class NavigationBarComponent { @Input({ required: true }) breadcrumbs: Breadcrumb[]; @Input({ required: true }) contentType: ContentType; @Input() searchVisible = true; + @Input() contentTypeNavigationItems: ContentTypeNavigationItem[]; @Output() contentTypeChanged = new EventEmitter(); @Output() breadcrumbClicked = new EventEmitter(); @@ -45,4 +47,8 @@ export class NavigationBarComponent { setSearchText(text: string) { this.portalStore.setSearchPhrase(text); } + + trackByValue(_i: number, value: ContentTypeNavigationItem) { + return value.contentType; + } } diff --git a/src/app/xtream/portal-card-item.interface.ts b/src/app/xtream/portal-card-item.interface.ts new file mode 100644 index 000000000..8812636d0 --- /dev/null +++ b/src/app/xtream/portal-card-item.interface.ts @@ -0,0 +1,10 @@ +// should represent the content of a card element - xtream category or VOD or any item from stalker, vportal etc +export interface PortalCardItem { + // TODO + id: string; + type: 'category' | 'vod' | 'serial' | 'live'; // TODO: enum + name: string; + coverUrl?: string; + streamType?: 'live' | 'movie'; // TODO: maybe not needed, since type is there + categoryId?: string; +} diff --git a/src/app/xtream/xtream-main-container.component.html b/src/app/xtream/xtream-main-container.component.html index e48a0c633..27114e920 100644 --- a/src/app/xtream/xtream-main-container.component.html +++ b/src/app/xtream/xtream-main-container.component.html @@ -8,6 +8,7 @@ [searchVisible]=" currentLayout === 'category' || currentLayout === 'category_content' " + [contentTypeNavigationItems]="navigationContentTypes" />
diff --git a/src/app/xtream/xtream-main-container.component.ts b/src/app/xtream/xtream-main-container.component.ts index 8edb4343e..d40787372 100644 --- a/src/app/xtream/xtream-main-container.component.ts +++ b/src/app/xtream/xtream-main-container.component.ts @@ -25,6 +25,7 @@ import { IpcCommand } from '../../../shared/ipc-command.class'; import { ERROR, OPEN_MPV_PLAYER, + OPEN_VLC_PLAYER, XTREAM_REQUEST, XTREAM_RESPONSE, } from '../../../shared/ipc-commands'; @@ -55,24 +56,22 @@ import { PlaylistsService } from '../services/playlists.service'; import { Settings, VideoPlayer } from '../settings/settings.interface'; import { STORE_KEY } from '../shared/enums/store-keys.enum'; import { Breadcrumb } from './breadcrumb.interface'; +import { ContentTypeNavigationItem } from './content-type-navigation-item.interface'; import { PlayerDialogComponent } from './player-dialog/player-dialog.component'; import { PortalStore } from './portal.store'; import { SerialDetailsComponent } from './serial-details/serial-details.component'; const ContentTypes = { - LIVE_STREAMS: { - title: 'Live streams', + [ContentType.ITV]: { getContentAction: XtreamCodeActions.GetLiveStreams, getCategoryAction: XtreamCodeActions.GetLiveCategories, }, - VODS: { - title: 'VOD streams', + [ContentType.VODS]: { getContentAction: XtreamCodeActions.GetVodStreams, getCategoryAction: XtreamCodeActions.GetVodCategories, getDetailsAction: XtreamCodeActions.GetVodInfo, }, - SERIES: { - title: 'Series', + [ContentType.SERIES]: { getContentAction: XtreamCodeActions.GetSeries, getCategoryAction: XtreamCodeActions.GetSeriesCategories, getDetailsAction: XtreamCodeActions.GetSeriesInfo, @@ -123,6 +122,20 @@ export class XtreamMainContainerComponent implements OnInit { storage = inject(StorageMap); store = inject(Store); currentPlaylist = this.store.selectSignal(selectCurrentPlaylist); + navigationContentTypes: ContentTypeNavigationItem[] = [ + { + contentType: ContentType.ITV, + label: 'Live Streams', + }, + { + contentType: ContentType.VODS, + label: 'VOD Streams', + }, + { + contentType: ContentType.SERIES, + label: 'Series', + }, + ]; favorites$: Observable; breadcrumbs: Breadcrumb[] = []; @@ -157,7 +170,6 @@ export class XtreamMainContainerComponent implements OnInit { ngOnInit() { this.setInitialBreadcrumb(); - console.log(this.currentPlaylist()._id); this.favorites$ = this.playlistService.getPortalFavorites( this.currentPlaylist()._id ); @@ -276,6 +288,10 @@ export class XtreamMainContainerComponent implements OnInit { this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, { url: streamUrl, }); + } else if (player === VideoPlayer.VLC) { + this.dataService.sendIpcEvent(OPEN_VLC_PLAYER, { + url: streamUrl, + }); } else { this.dialog.open(PlayerDialogComponent, { data: { streamUrl, player, title }, @@ -385,7 +401,6 @@ export class XtreamMainContainerComponent implements OnInit { } setSearchPhrase(searchPhrase: string) { - console.log(searchPhrase); this.searchPhrase = searchPhrase; } From 91568c07050121bfa9a959991766a95b76776c13 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sun, 6 Aug 2023 22:11:37 +0200 Subject: [PATCH 06/27] feat(stalker): add stalker portal related translations --- src/assets/i18n/en.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 98760d648..798c9d414 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -8,7 +8,7 @@ "FILE_UPLOAD": "Add via file upload", "URL_UPLOAD": "Add via URL", "TEXT_IMPORT": "Import as text", - "XTREME_IMPORT": "Import xtreme code", + "XTREME_IMPORT": "Add Xtreme Code", "STALKER_PORTAL_IMPORT": "Add Stalker Portal" }, "PLAYLISTS": { @@ -17,6 +17,7 @@ "ADDED_VIA_URL": "Added via URL upload. URL:", "ADDED_VIA_FILE": "Added via file upload", "XTREAM_PLAYLIST": "Xtream playlist", + "STALKER_PORTAL": "Stalker portal", "CHANNELS": "Channels", "ADDED": "Added", "SHOW_DETAILS": "Show playlist details", @@ -39,7 +40,8 @@ "EXPORT_PLAYLIST": "Export playlist as m3u", "SERVER_URL": "Server URL", "USERNAME": "Username", - "PASSWORD": "Password" + "PASSWORD": "Password", + "MAC_ADDRESS": "Mac Address" }, "REMOVE_DIALOG": { "TITLE": "Remove playlist", @@ -76,6 +78,13 @@ "ADD": "Add", "TEST_CREDENTIALS": "Test credentials", "URL_VALIDATION_ERROR": "Should be a valid url with protocol (e.g. http://example.com or https://example.com:4324)" + }, + "STALKER_PORTAL": { + "TITLE": "Title", + "MAC_ADDRESS": "Mac Address", + "SERVER_URL": "Server URL", + "URL_VALIDATION_ERROR": "Should be a valid url with protocol (e.g. http://example.com/portal.php or https://example.com:4324/portal.php)", + "ADD": "Add" } }, "SETTINGS": { From e06e0d558ac97e898225046ed2c7b2a88fe907b6 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sun, 6 Aug 2023 22:50:11 +0200 Subject: [PATCH 07/27] fix(xtream): reset search text after navigation --- src/app/xtream/navigation-bar/navigation-bar.component.html | 2 +- src/app/xtream/navigation-bar/navigation-bar.component.ts | 5 +++++ src/app/xtream/xtream-main-container.component.ts | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/xtream/navigation-bar/navigation-bar.component.html b/src/app/xtream/navigation-bar/navigation-bar.component.html index ea876fb6d..6671345cb 100644 --- a/src/app/xtream/navigation-bar/navigation-bar.component.html +++ b/src/app/xtream/navigation-bar/navigation-bar.component.html @@ -8,7 +8,7 @@ name="contentType" aria-label="Content Type" [value]="contentType" - (change)="contentTypeChanged.emit($event.value)" + (change)="changeContentType($event.value)" >
From fd001c26ced7e40896634ca6ff41854e4f0a2eed Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sun, 6 Aug 2023 23:52:38 +0200 Subject: [PATCH 11/27] feat(stalker): add import stalker portal component --- src/app/app-routing.module.ts | 7 ++ src/app/home/home.component.html | 21 ++++-- src/app/home/home.module.ts | 2 + .../stalker-portal-import.component.html | 54 +++++++++++++++ .../stalker-portal-import.component.ts | 69 +++++++++++++++++++ 5 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 src/app/home/stalker-portal-import/stalker-portal-import.component.html create mode 100644 src/app/home/stalker-portal-import/stalker-portal-import.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index e7f084c48..135e5eb64 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -36,6 +36,13 @@ const routes: Routes = [ (c) => c.XtreamMainContainerComponent ), }, + { + path: 'portals/:id', + loadComponent: () => + import('./stalker/stalker-main-container.component').then( + (c) => c.StalkerMainContainerComponent + ), + }, { path: '**', redirectTo: '', diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 72da861a7..9d1690d05 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -46,9 +46,7 @@
- + @@ -59,14 +57,12 @@
- + - text_format + video_library
{{ 'HOME.TABS.XTREME_IMPORT' | translate }}
@@ -75,6 +71,17 @@
+ + + dashboard +
+ {{ 'HOME.TABS.STALKER_PORTAL_IMPORT' | translate }} +
+
+ + + +
diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts index f645965e6..585e654cb 100644 --- a/src/app/home/home.module.ts +++ b/src/app/home/home.module.ts @@ -5,6 +5,7 @@ import { DragDropFileUploadDirective } from './file-upload/drag-drop-file-upload import { FileUploadComponent } from './file-upload/file-upload.component'; import { HomeComponent } from './home.component'; import { HomeRoutingModule } from './home.routing'; +import { StalkerPortalImportComponent } from './stalker-portal-import/stalker-portal-import.component'; import { TextImportComponent } from './text-import/text-import.component'; import { UrlUploadComponent } from './url-upload/url-upload.component'; import { XtreamCodeImportComponent } from './xtream-code-import/xtream-code-import.component'; @@ -15,6 +16,7 @@ import { XtreamCodeImportComponent } from './xtream-code-import/xtream-code-impo HomeRoutingModule, SharedModule, XtreamCodeImportComponent, + StalkerPortalImportComponent, ], declarations: [ DragDropFileUploadDirective, diff --git a/src/app/home/stalker-portal-import/stalker-portal-import.component.html b/src/app/home/stalker-portal-import/stalker-portal-import.component.html new file mode 100644 index 000000000..5336ca695 --- /dev/null +++ b/src/app/home/stalker-portal-import/stalker-portal-import.component.html @@ -0,0 +1,54 @@ +
+ + {{ + 'HOME.STALKER_PORTAL.TITLE' | translate + }} + + + + {{ + 'HOME.STALKER_PORTAL.SERVER_URL' | translate + }} + + {{ + 'HOME.STALKER_PORTAL.URL_VALIDATION_ERROR' | translate + }} + {{ 'SETTINGS.EPG_URL_ERROR' | translate }} + + + {{ + 'HOME.STALKER_PORTAL.MAC_ADDRESS' | translate + }} + + + + +
diff --git a/src/app/home/stalker-portal-import/stalker-portal-import.component.ts b/src/app/home/stalker-portal-import/stalker-portal-import.component.ts new file mode 100644 index 000000000..0a21fa798 --- /dev/null +++ b/src/app/home/stalker-portal-import/stalker-portal-import.component.ts @@ -0,0 +1,69 @@ +import { Component, inject } from '@angular/core'; +import { + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { Store } from '@ngrx/store'; +import { TranslateModule } from '@ngx-translate/core'; +import { v4 as uuid } from 'uuid'; +import { Playlist } from '../../../../shared/playlist.interface'; +import { DataService } from '../../services/data.service'; +import { addPlaylist } from '../../state/actions'; + +@Component({ + standalone: true, + imports: [ + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + TranslateModule, + ], + selector: 'app-stalker-portal-import', + templateUrl: './stalker-portal-import.component.html', + styles: [ + ` + :host { + display: flex; + margin: 10px; + justify-content: center; + } + + form { + width: 100%; + } + `, + ], +}) +export class StalkerPortalImportComponent { + URL_REGEX = /^(http|https|file):\/\/[^ "]+$/; + + form = new FormGroup({ + _id: new FormControl(uuid()), + title: new FormControl('', [Validators.required]), + macAddress: new FormControl(''), + password: new FormControl(''), + username: new FormControl(''), + portalUrl: new FormControl('', [ + Validators.required, + Validators.pattern(this.URL_REGEX), + ]), + importDate: new FormControl(new Date().toISOString()), + }); + + dataService = inject(DataService); + store = inject(Store); + + addPlaylist() { + this.store.dispatch( + addPlaylist({ playlist: this.form.value as Playlist }) + ); + } +} From 1ac733cfa8b7311e851520db8f053ab515566a51 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Mon, 7 Aug 2023 09:56:45 +0200 Subject: [PATCH 12/27] feat(stalker): add stalker related inputs to playlist-info dialog --- .../playlist-info/playlist-info.component.html | 14 +++++++++++++- .../playlist-info/playlist-info.component.ts | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/app/home/recent-playlists/playlist-info/playlist-info.component.html b/src/app/home/recent-playlists/playlist-info/playlist-info.component.html index 24ae9072e..944161283 100644 --- a/src/app/home/recent-playlists/playlist-info/playlist-info.component.html +++ b/src/app/home/recent-playlists/playlist-info/playlist-info.component.html @@ -12,6 +12,18 @@

}} + + {{ + 'HOME.PLAYLISTS.INFO_DIALOG.SERVER_URL' | translate + }} + + + + {{ + 'HOME.PLAYLISTS.INFO_DIALOG.MAC_ADDRESS' | translate + }} + + {{ 'HOME.PLAYLISTS.INFO_DIALOG.SERVER_URL' | translate @@ -89,7 +101,7 @@

mat-flat-button color="accent" (click)="exportPlaylist()" - *ngIf="!playlist.serverUrl" + *ngIf="!playlist.serverUrl && !playlist.portalUrl" > {{ 'HOME.PLAYLISTS.INFO_DIALOG.EXPORT_PLAYLIST' | translate }} diff --git a/src/app/home/recent-playlists/playlist-info/playlist-info.component.ts b/src/app/home/recent-playlists/playlist-info/playlist-info.component.ts index 8f89a1b74..ae8901ad5 100644 --- a/src/app/home/recent-playlists/playlist-info/playlist-info.component.ts +++ b/src/app/home/recent-playlists/playlist-info/playlist-info.component.ts @@ -98,6 +98,8 @@ export class PlaylistInfoComponent { serverUrl: new FormControl(this.playlist.serverUrl), username: new FormControl(this.playlist.username), password: new FormControl(this.playlist.password), + macAddress: new FormControl(this.playlist.macAddress), + portalUrl: new FormControl(this.playlist.portalUrl), }); } From 94decafda1ca184a1a3c4f2e37606e283613dd65 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:00:01 +0200 Subject: [PATCH 13/27] refactor: support stalker portal in playlist-item cmp --- .../playlist-item/playlist-item.component.html | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/app/home/recent-playlists/playlist-item/playlist-item.component.html b/src/app/home/recent-playlists/playlist-item/playlist-item.component.html index 6933b5b7f..e2a785b91 100644 --- a/src/app/home/recent-playlists/playlist-item/playlist-item.component.html +++ b/src/app/home/recent-playlists/playlist-item/playlist-item.component.html @@ -14,7 +14,7 @@ folder @@ -25,9 +25,18 @@ [matTooltip]="'HOME.PLAYLISTS.XTREAM_PLAYLIST' | translate" >public + dashboard
{{ item.title || item.filename }}
- + {{ 'HOME.PLAYLISTS.CHANNELS' | translate }}: {{ item.count }} | {{ 'HOME.PLAYLISTS.ADDED' | translate }}: @@ -35,7 +44,7 @@ | {{ 'HOME.PLAYLISTS.UPDATED' | translate }}: - {{ item.updateDate | date : 'MMMM d, yyyy, HH:mm' }} + {{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }} Xtream Code Playlist + >Xtream/Stalker Playlist | {{ 'HOME.PLAYLISTS.ADDED' | translate }}: {{ item.importDate | date }} From 9e24b1f7368c82d1b2a2d8227c4d9993d505293d Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Wed, 9 Aug 2023 22:53:48 +0200 Subject: [PATCH 14/27] feat: stalker portal support in the main process --- electron/api.ts | 52 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/electron/api.ts b/electron/api.ts index 4002108f8..c70bc57d9 100644 --- a/electron/api.ts +++ b/electron/api.ts @@ -32,6 +32,8 @@ import { PLAYLIST_UPDATE_RESPONSE, SET_MPV_PLAYER_PATH, SET_VLC_PLAYER_PATH, + STALKER_REQUEST, + STALKER_RESPONSE, XTREAM_REQUEST, XTREAM_RESPONSE, } from '../shared/ipc-commands'; @@ -318,17 +320,52 @@ export class Api { this.workerWindow.webContents.send(EPG_FORCE_FETCH, arg) ); - ipcMain.on( - XTREAM_REQUEST, - (event, arg: { url: string; params: Record }) => { - const xtreamApiPath = '/player_api.php'; + ipcMain + .on( + XTREAM_REQUEST, + ( + event, + arg: { url: string; params: Record } + ) => { + const xtreamApiPath = '/player_api.php'; + axios + .get(arg.url + xtreamApiPath, { + params: arg.params ?? {}, + }) + .then((result) => { + event.sender.send(XTREAM_RESPONSE, { + payload: result.data, + action: arg.params.action, + }); + }) + .catch((err) => { + event.sender.send(ERROR, { + message: + err.response?.statusText ?? + 'Error: not found', + status: err.response?.status ?? 404, + }); + }); + } + ) + .on(STALKER_REQUEST, (event, arg: any) => { axios - .get(arg.url + xtreamApiPath, { + .get(arg.url, { params: arg.params ?? {}, + headers: { + Cookie: `mac=${arg.macAddress as string}`, + ...(arg.params.token + ? { + Authorization: `Bearer ${ + arg.params.token as string + }`, + } + : {}), + }, }) .then((result) => { - event.sender.send(XTREAM_RESPONSE, { + event.sender.send(STALKER_RESPONSE, { payload: result.data, action: arg.params.action, }); @@ -340,8 +377,7 @@ export class Api { status: err.response?.status ?? 404, }); }); - } - ); + }); } createMpvInstance() { From 40f781cbb8e5af49a40298895198e7a5d4d35032 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Wed, 9 Aug 2023 22:54:12 +0200 Subject: [PATCH 15/27] feat: stalker portal support for the pwa --- src/app/services/pwa.service.ts | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/app/services/pwa.service.ts b/src/app/services/pwa.service.ts index ca7665e20..d9bbebf6a 100644 --- a/src/app/services/pwa.service.ts +++ b/src/app/services/pwa.service.ts @@ -10,6 +10,8 @@ import { PLAYLIST_PARSE_BY_URL, PLAYLIST_PARSE_RESPONSE, PLAYLIST_UPDATE, + STALKER_REQUEST, + STALKER_RESPONSE, XTREAM_REQUEST, XTREAM_RESPONSE, } from '../../../shared/ipc-commands'; @@ -71,6 +73,14 @@ export class PwaService extends DataService { this.forwardXtreamRequest( payload as { url: string; params: Record } ); + } else if (type === STALKER_REQUEST) { + this.forwardStalkerRequest( + payload as { + url: string; + macAddress: string; + params: Record; + } + ); } } @@ -145,13 +155,22 @@ export class PwaService extends DataService { forwardXtreamRequest(payload: { url: string; params: Record; + macAddress?: string; }) { + const headers = payload.macAddress + ? { + headers: { + Cookie: `mac=${payload.macAddress}`, + }, + } + : {}; return this.http .get(`${this.corsProxyUrl}/xtream`, { params: { url: payload.url, ...payload.params, }, + ...headers, }) .subscribe((response) => { window.postMessage({ @@ -162,6 +181,28 @@ export class PwaService extends DataService { }); } + forwardStalkerRequest(payload: { + url: string; + params: Record; + macAddress: string; + }) { + return this.http + .get(`${this.corsProxyUrl}/stalker`, { + params: { + url: payload.url, + ...payload.params, + macAddress: payload.macAddress, + }, + }) + .subscribe((response) => { + window.postMessage({ + type: STALKER_RESPONSE, + payload: (response as any).payload, + action: payload.params.action, + }); + }); + } + getPlaylistFromUrl(url: string) { return this.http.get(`${this.corsProxyUrl}/parse`, { params: { url }, From ff90a66c32fe6ffc8353322927e2ed5ca2898cc1 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Wed, 9 Aug 2023 22:57:15 +0200 Subject: [PATCH 16/27] refactor: fix navigation to stalker portal playlist --- src/app/home/recent-playlists/recent-playlists.component.ts | 2 ++ src/app/state/effects.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/app/home/recent-playlists/recent-playlists.component.ts b/src/app/home/recent-playlists/recent-playlists.component.ts index 19d0ef55d..89d656624 100644 --- a/src/app/home/recent-playlists/recent-playlists.component.ts +++ b/src/app/home/recent-playlists/recent-playlists.component.ts @@ -169,6 +169,8 @@ export class RecentPlaylistsComponent implements OnDestroy { getPlaylist(playlistMeta: PlaylistMeta): void { if (playlistMeta.serverUrl) { this.router.navigate(['xtreams', playlistMeta._id]); + } else if (playlistMeta.macAddress) { + this.router.navigate(['portals', playlistMeta._id]); } else { this.router.navigate(['playlists', playlistMeta._id]); this.playlistClicked.emit(playlistMeta._id); diff --git a/src/app/state/effects.ts b/src/app/state/effects.ts index be251e84a..eea33d041 100644 --- a/src/app/state/effects.ts +++ b/src/app/state/effects.ts @@ -169,6 +169,8 @@ export class PlaylistEffects { tap((playlist) => { if (playlist.serverUrl) { this.router.navigate(['/xtreams/', playlist._id]); + } else if (playlist.macAddress) { + this.router.navigate(['portals', playlist._id]); } else { this.router.navigate(['/playlists/', playlist._id]); } From 9044fe4b61ff3240400de3bca3674b0bd0696447 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Wed, 9 Aug 2023 22:58:25 +0200 Subject: [PATCH 17/27] refactor: some improvements in interfaces --- src/app/xtream/breadcrumb.interface.ts | 5 ++++- src/app/xtream/xtream-main-container.component.ts | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/app/xtream/breadcrumb.interface.ts b/src/app/xtream/breadcrumb.interface.ts index dfb2d38bf..f619a3c3a 100644 --- a/src/app/xtream/breadcrumb.interface.ts +++ b/src/app/xtream/breadcrumb.interface.ts @@ -1,7 +1,10 @@ +import { StalkerPortalActions } from '../../../shared/stalker-portal-actions.enum'; import { XtreamCodeActions } from '../../../shared/xtream-code-actions'; +export type PortalActions = StalkerPortalActions | XtreamCodeActions; + export interface Breadcrumb { - action: XtreamCodeActions; + action: PortalActions; title: string; category_id?: string; } diff --git a/src/app/xtream/xtream-main-container.component.ts b/src/app/xtream/xtream-main-container.component.ts index 1b9298bed..629b1d5bb 100644 --- a/src/app/xtream/xtream-main-container.component.ts +++ b/src/app/xtream/xtream-main-container.component.ts @@ -55,7 +55,7 @@ import { import { PlaylistsService } from '../services/playlists.service'; import { Settings, VideoPlayer } from '../settings/settings.interface'; import { STORE_KEY } from '../shared/enums/store-keys.enum'; -import { Breadcrumb } from './breadcrumb.interface'; +import { Breadcrumb, PortalActions } from './breadcrumb.interface'; import { ContentTypeNavigationItem } from './content-type-navigation-item.interface'; import { PlayerDialogComponent } from './player-dialog/player-dialog.component'; import { PortalStore } from './portal.store'; @@ -164,15 +164,15 @@ export class XtreamMainContainerComponent implements OnInit { effect(() => { if (this.currentPlaylist()) { this.getCategories(this.selectedContentType); + this.favorites$ = this.playlistService.getPortalFavorites( + this.currentPlaylist()._id + ); } }); } ngOnInit() { this.setInitialBreadcrumb(); - this.favorites$ = this.playlistService.getPortalFavorites( - this.currentPlaylist()._id - ); this.commandsList.forEach((command) => { if (this.dataService.isElectron) { @@ -363,7 +363,7 @@ export class XtreamMainContainerComponent implements OnInit { }); } - getLayoutViewBasedOnAction(action: XtreamCodeActions) { + getLayoutViewBasedOnAction(action: PortalActions) { let result: LayoutView = 'category'; switch (action) { case XtreamCodeActions.GetLiveCategories: From bb0e832b6b1da4d667e5ffb981b911f5e64e615a Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sat, 12 Aug 2023 17:30:03 +0200 Subject: [PATCH 18/27] refactor(portals): some improvements in nav-bar --- .../navigation-bar.component.html | 12 ++++++---- .../navigation-bar.component.scss | 22 +++++++++++-------- .../navigation-bar.component.ts | 15 ++++++++++++- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/app/xtream/navigation-bar/navigation-bar.component.html b/src/app/xtream/navigation-bar/navigation-bar.component.html index 6671345cb..1cbfe90e7 100644 --- a/src/app/xtream/navigation-bar/navigation-bar.component.html +++ b/src/app/xtream/navigation-bar/navigation-bar.component.html @@ -28,11 +28,15 @@
diff --git a/src/app/xtream/navigation-bar/navigation-bar.component.scss b/src/app/xtream/navigation-bar/navigation-bar.component.scss index ae09b0113..5acc450b9 100644 --- a/src/app/xtream/navigation-bar/navigation-bar.component.scss +++ b/src/app/xtream/navigation-bar/navigation-bar.component.scss @@ -16,25 +16,29 @@ #sub-panel { background: white; border-bottom: 1px solid #ccc; - padding: 0 10px 5px; + padding: 0 1em; display: flex; justify-content: space-between; align-items: center; - height: 30px; + height: 40px; .breadcrumbs { - span { - cursor: pointer; + display: flex; + + div { + display: flex; + align-items: center; &:not(:last-child)::after { - content: '>'; - margin: 0 10px; + display: none; } - } - span:hover { - text-decoration: underline; + span:hover { + text-decoration: underline; + cursor: pointer; + } } + } .search { diff --git a/src/app/xtream/navigation-bar/navigation-bar.component.ts b/src/app/xtream/navigation-bar/navigation-bar.component.ts index ad7a570af..9c7477d88 100644 --- a/src/app/xtream/navigation-bar/navigation-bar.component.ts +++ b/src/app/xtream/navigation-bar/navigation-bar.component.ts @@ -5,6 +5,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatIconModule } from '@angular/material/icon'; import { RouterLink } from '@angular/router'; +import { Subject, debounceTime, distinctUntilChanged } from 'rxjs'; import { Breadcrumb } from '../breadcrumb.interface'; import { ContentTypeNavigationItem } from '../content-type-navigation-item.interface'; import { ContentType } from '../content-type.enum'; @@ -30,14 +31,25 @@ export class NavigationBarComponent { @Input({ required: true }) contentType: ContentType; @Input() searchVisible = true; @Input() contentTypeNavigationItems: ContentTypeNavigationItem[]; + @Input() clientSideSearch = true; @Output() contentTypeChanged = new EventEmitter(); @Output() breadcrumbClicked = new EventEmitter(); @Output() favoritesClicked = new EventEmitter(); + @Output() searchTextChanged = new EventEmitter(); ContentTypeEnum = ContentType; portalStore = inject(PortalStore); searchPhrase = this.portalStore.searchPhrase; + searchPhraseUpdate = new Subject(); + + constructor() { + this.searchPhraseUpdate + .pipe(debounceTime(600), distinctUntilChanged()) + .subscribe((value) => { + this.setSearchText(value); + }); + } processBreadcrumbClick(item: Breadcrumb) { this.setSearchText(''); @@ -45,7 +57,8 @@ export class NavigationBarComponent { } setSearchText(text: string) { - this.portalStore.setSearchPhrase(text); + if (this.clientSideSearch) this.portalStore.setSearchPhrase(text); + else this.searchTextChanged.emit(text); } changeContentType(type: ContentType) { From adea3cf0d144e49cfcd832ec52b063d42b1f4e77 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Mon, 14 Aug 2023 08:49:20 +0200 Subject: [PATCH 19/27] refactor(stalker): extend portal related store --- src/app/xtream/portal.store.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/app/xtream/portal.store.ts b/src/app/xtream/portal.store.ts index 10c311a7c..299e2a908 100644 --- a/src/app/xtream/portal.store.ts +++ b/src/app/xtream/portal.store.ts @@ -3,6 +3,7 @@ import { ComponentStore } from '@ngrx/component-store'; export interface PortalState { searchPhrase: string; + content: any[]; } @Injectable({ providedIn: 'root' }) @@ -10,12 +11,16 @@ export class PortalStore extends ComponentStore { constructor() { super({ searchPhrase: '', + content: [], }); } // selectors readonly searchPhrase = this.selectSignal((state) => state.searchPhrase); + readonly getContentById = (id: string) => + this.selectSignal((state) => state.content.find((i) => i.id === id)); + // reducers readonly setSearchPhrase = this.updater( (state, searchPhrase: string): PortalState => ({ @@ -23,4 +28,11 @@ export class PortalStore extends ComponentStore { searchPhrase, }) ); + + readonly setContent = this.updater( + (state, content: any[]): PortalState => ({ + ...state, + content, + }) + ); } From 01d1cc51fd1ffbc30069d8d791ef5ed999ffff6b Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Thu, 17 Aug 2023 23:49:05 +0200 Subject: [PATCH 20/27] refactor: add store logic for navigation to adjacent channels --- src/app/state/actions.ts | 7 ++++++ src/app/state/effects.ts | 46 +++++++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/app/state/actions.ts b/src/app/state/actions.ts index cfb0ca544..e9d146084 100644 --- a/src/app/state/actions.ts +++ b/src/app/state/actions.ts @@ -119,3 +119,10 @@ export const updatePlaylistPositions = createAction( export const removeAllPlaylists = createAction( `${STORE_KEY} Remove all playlists` ); + +export const setAdjacentChannelAsActive = createAction( + `${STORE_KEY} Set adjacent channel as active`, + props<{ + direction: 'next' | 'previous'; + }>() +); diff --git a/src/app/state/effects.ts b/src/app/state/effects.ts index eea33d041..a8b1eb33e 100644 --- a/src/app/state/effects.ts +++ b/src/app/state/effects.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Router } from '@angular/router'; @@ -6,8 +5,14 @@ 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 { + combineLatestWith, + firstValueFrom, + map, + switchMap, + tap, + withLatestFrom, +} from 'rxjs'; import { CHANNEL_SET_USER_AGENT, EPG_GET_PROGRAM, @@ -19,7 +24,9 @@ import { Settings, VideoPlayer } from '../settings/settings.interface'; import { STORE_KEY } from '../shared/enums/store-keys.enum'; import * as PlaylistActions from './actions'; import { + selectActive, selectActivePlaylistId, + selectChannels, selectFavorites, selectIsEpgAvailable, } from './selectors'; @@ -34,7 +41,7 @@ export class PlaylistEffects { this.store.select(selectFavorites), this.store.select(selectActivePlaylistId) ), - switchMap(([action, favorites, playlistId]) => + switchMap(([, favorites, playlistId]) => this.playlistsService.updateFavorites(playlistId, favorites) ) ); @@ -83,7 +90,8 @@ export class PlaylistEffects { if ( settings && Object.keys(settings).length > 0 && - settings.player === VideoPlayer.MPV + settings.player === VideoPlayer.MPV && + channel.radio !== 'true' ) this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, { url: channel.url, @@ -249,6 +257,34 @@ export class PlaylistEffects { { dispatch: false } ); + setAdjacentChannelAsActive$ = createEffect(() => { + return this.actions$.pipe( + ofType(PlaylistActions.setAdjacentChannelAsActive), + withLatestFrom( + this.store.select(selectChannels), + this.store.select(selectActive) + ), + map(([action, channels, activeChannel]) => { + let adjacentChannel; + const index = channels.findIndex( + (channel) => channel.id === activeChannel.id + ); + if (action.direction === 'next') { + if (index === channels.length - 1) + adjacentChannel = activeChannel; + adjacentChannel = channels[index + 1]; + } else if (action.direction === 'previous') { + if (index === -1 || index === 0) + adjacentChannel = activeChannel; + adjacentChannel = channels[index - 1]; + } + return PlaylistActions.setActiveChannelSuccess({ + channel: adjacentChannel, + }); + }) + ); + }); + constructor( private actions$: Actions, private playlistsService: PlaylistsService, From 3bec3564bd134a60cc88d37b2facc4a4fbb85dee Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Thu, 17 Aug 2023 23:51:02 +0200 Subject: [PATCH 21/27] feat(portals): extend models and interfaces --- shared/stalker-portal-actions.enum.ts | 1 + src/app/stalker/stalker-content-types.ts | 20 ++++++++++++++++++++ src/app/xtream/content-type.enum.ts | 1 + 3 files changed, 22 insertions(+) create mode 100644 src/app/stalker/stalker-content-types.ts diff --git a/shared/stalker-portal-actions.enum.ts b/shared/stalker-portal-actions.enum.ts index 60d10ce80..372167685 100644 --- a/shared/stalker-portal-actions.enum.ts +++ b/shared/stalker-portal-actions.enum.ts @@ -3,4 +3,5 @@ export enum StalkerPortalActions { GetGenres = 'get_genres', CreateLink = 'create_link', GetOrderedList = 'get_ordered_list', + Favorites = 'favorites', } diff --git a/src/app/stalker/stalker-content-types.ts b/src/app/stalker/stalker-content-types.ts new file mode 100644 index 000000000..efb42ebe7 --- /dev/null +++ b/src/app/stalker/stalker-content-types.ts @@ -0,0 +1,20 @@ +import { StalkerPortalActions } from '../../../shared/stalker-portal-actions.enum'; + +export const StalkerContentTypes = { + stb: { + doAuth: 'do_auth', + handshake: 'handshake', + }, + itv: { + title: 'Live streams', + getContentAction: StalkerPortalActions.GetOrderedList, + getCategoryAction: StalkerPortalActions.GetGenres, + getLink: StalkerPortalActions.CreateLink, + }, + vod: { + title: 'VOD streams', + getContentAction: StalkerPortalActions.GetOrderedList, + getCategoryAction: StalkerPortalActions.GetCategories, + getLink: StalkerPortalActions.CreateLink, + }, +}; diff --git a/src/app/xtream/content-type.enum.ts b/src/app/xtream/content-type.enum.ts index abd1672fa..af7a8ef05 100644 --- a/src/app/xtream/content-type.enum.ts +++ b/src/app/xtream/content-type.enum.ts @@ -2,4 +2,5 @@ export enum ContentType { VODS = 'vod', SERIES = 'series', ITV = 'itv', + FAVORITES = 'favorites', } From 53d4f9bc0fbe36520fa01fb1fa9311adeae5cef3 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Thu, 17 Aug 2023 23:52:08 +0200 Subject: [PATCH 22/27] style(portals): card style improvements --- .../category-content-view.component.scss | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/xtream/category-content-view/category-content-view.component.scss b/src/app/xtream/category-content-view/category-content-view.component.scss index 0df8317a9..c68e4a407 100644 --- a/src/app/xtream/category-content-view/category-content-view.component.scss +++ b/src/app/xtream/category-content-view/category-content-view.component.scss @@ -11,7 +11,7 @@ mat-card { cursor: pointer; - width: 200px; + width: 170px; mat-card-content { padding: 0; @@ -26,13 +26,14 @@ .stream-icon-placeholder { width: 100%; background: #999; - height: 300px; + max-height: 300px; + min-height: 130px; + display: flex; + justify-content: center; + align-items: center; &::after { content: 'No cover'; - position: relative; - top: 140px; - left: 70px; } } From c9f7735a3d377238d2cd8fefe5b167be86cef763 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Thu, 17 Aug 2023 23:54:19 +0200 Subject: [PATCH 23/27] chore: update material icons font --- src/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.html b/src/index.html index e1460df5b..d2475cdec 100644 --- a/src/index.html +++ b/src/index.html @@ -1,4 +1,4 @@ - + @@ -26,7 +26,7 @@ rel="stylesheet" />