From 9cbccfcfb2916eb944d0f752829f2f2000e4ff3e Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 29 Jan 2022 23:25:39 +0100 Subject: [PATCH 1/4] chore: add compiled js files from shared to gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index fbb6bf316..16cbd8b86 100644 --- a/.gitignore +++ b/.gitignore @@ -8,15 +8,14 @@ /release api.js epg-worker.js -ipc-commands.js main.js menu.js -playlist.interface.js src/**/*.js !src/karma.conf.js *.js.map data.db preload.js +shared/*.js # dependencies /node_modules From e59b3c451c25808ed8eda4884ba48f8141eef2c2 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 29 Jan 2022 23:27:09 +0100 Subject: [PATCH 2/4] refactor: extract shared functions and interfaces from angular app --- shared/channel.interface.ts | 32 +++++++++++++++++++++++++++++ shared/constants.ts | 4 ++++ shared/playlist.utils.ts | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 shared/channel.interface.ts create mode 100644 shared/constants.ts create mode 100644 shared/playlist.utils.ts diff --git a/shared/channel.interface.ts b/shared/channel.interface.ts new file mode 100644 index 000000000..f907e59c6 --- /dev/null +++ b/shared/channel.interface.ts @@ -0,0 +1,32 @@ +/** + * Represents channel object + * TODO: define channel interface in iptv-parser library + */ +export interface Channel { + id: string; + url: string; + name: string; + group: { + title: string; + }; + tvg: { + id: string; + name: string; + language: string; + country: string; + url: string; + logo: string; + rec: string; + }; + epgParams?: string; + timeshift?: string; + catchup?: { + type?: string; + source?: string; + days?: string; + }; + http: { + referrer: string; + 'user-agent': string; + }; +} diff --git a/shared/constants.ts b/shared/constants.ts new file mode 100644 index 000000000..781bb1861 --- /dev/null +++ b/shared/constants.ts @@ -0,0 +1,4 @@ +/** + * Id of the channel with favorite channels aggregated from all added playlists + */ +export const GLOBAL_FAVORITES_PLAYLIST_ID = 'GLOBAL_FAVORITES'; diff --git a/shared/playlist.utils.ts b/shared/playlist.utils.ts new file mode 100644 index 000000000..f486725d9 --- /dev/null +++ b/shared/playlist.utils.ts @@ -0,0 +1,40 @@ +import { Channel } from './channel.interface'; +import { GLOBAL_FAVORITES_PLAYLIST_ID } from './constants'; +import { Playlist } from './playlist.interface'; + +/** + * Aggregates favorite channels as objects from all available playlists + * @param playlists all available playlists + * @returns an array with favorite channels from all playlists + */ +export function aggregateFavoriteChannels(playlists: Playlist[]): Channel[] { + const favorites = []; + playlists.forEach((playlist) => { + if (playlist.favorites?.length > 0) { + playlist.playlist.items.forEach((channel) => { + if (playlist.favorites.includes(channel.id)) { + favorites.push(channel); + } + }); + } + }); + return favorites; +} + +/** + * Creates a simplified playlist object which is used for global favorites + * @param channels channels list + * @returns simplified playlist object + */ +export function createFavoritesPlaylist( + channels: Channel[] +): Partial { + return { + id: GLOBAL_FAVORITES_PLAYLIST_ID, + _id: GLOBAL_FAVORITES_PLAYLIST_ID, + count: channels.length, + playlist: { + items: channels, + }, + }; +} From c7d5310ef709a052aa81c8ef4642fb90b96eef5b Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 29 Jan 2022 23:28:18 +0100 Subject: [PATCH 3/4] style: set vertical alignment of channel logos --- .../channel-list-container.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/player/components/channel-list-container/channel-list-container.component.scss b/src/app/player/components/channel-list-container/channel-list-container.component.scss index b4fcacd72..cb9ff0ec6 100644 --- a/src/app/player/components/channel-list-container/channel-list-container.component.scss +++ b/src/app/player/components/channel-list-container/channel-list-container.component.scss @@ -22,7 +22,7 @@ } .channel-logo { - height: 48px; + max-height: 48px; overflow: hidden; } From 764201a0afa03b0ed3c075e3c3cfcf6fba5c105a Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 29 Jan 2022 23:33:06 +0100 Subject: [PATCH 4/4] feat: generate global playlist with all favorites this commit closes #97 --- api.ts | 35 +++++++++++-- .../channel-list-container.component.ts | 3 +- .../epg-list/epg-list.component.spec.ts | 19 +++---- .../html-video-player.component.ts | 2 +- .../info-overlay/info-overlay.component.ts | 4 +- .../video-player/video-player.component.html | 1 + .../video-player.component.spec.ts | 28 +++++------ .../video-player/video-player.component.ts | 14 ++++-- src/app/services/pwa.service.ts | 50 +++---------------- src/app/state/channel.model.ts | 33 +----------- src/app/state/channel.store.ts | 2 +- 11 files changed, 78 insertions(+), 113 deletions(-) diff --git a/api.ts b/api.ts index d2c58dc46..e635dbf98 100644 --- a/api.ts +++ b/api.ts @@ -3,6 +3,7 @@ import axios from 'axios'; import { app, BrowserWindow, ipcMain, session } from 'electron'; import { parse } from 'iptv-playlist-parser'; import Nedb, { Cursor } from 'nedb-promises-ts'; +import { GLOBAL_FAVORITES_PLAYLIST_ID } from './shared/constants'; import { CHANNEL_SET_USER_AGENT, EPG_ERROR, @@ -27,6 +28,10 @@ import { PLAYLIST_UPDATE_RESPONSE, } from './shared/ipc-commands'; import { Playlist, PlaylistUpdateState } from './shared/playlist.interface'; +import { + aggregateFavoriteChannels, + createFavoritesPlaylist, +} from './shared/playlist.utils'; import { ParsedPlaylist } from './src/typings.d'; const fs = require('fs'); @@ -97,12 +102,16 @@ export class Api { ipcMain.on(PLAYLIST_GET_ALL, (event) => this.sendAllPlaylists(event)); ipcMain.on(PLAYLIST_GET_BY_ID, (event, args) => { - db.findOne({ _id: args.id }).then((playlist) => { - this.setUserAgent(playlist.userAgent); - event.sender.send(PLAYLIST_PARSE_RESPONSE, { - payload: playlist, + if (args.id === GLOBAL_FAVORITES_PLAYLIST_ID) { + this.sendPlaylistWithGlobalFavorites(event); + } else { + db.findOne({ _id: args.id }).then((playlist) => { + this.setUserAgent(playlist?.userAgent); + event.sender.send(PLAYLIST_PARSE_RESPONSE, { + payload: playlist, + }); }); - }); + } }); ipcMain.on(PLAYLIST_REMOVE_BY_ID, (event, args) => { @@ -234,6 +243,22 @@ export class Api { this.refreshPlaylists(); } + /** + * Sends a message with playlist that contains favorite channels from all available playlists + * @param event ipc main event + */ + sendPlaylistWithGlobalFavorites(event: Electron.IpcMainEvent) { + db.find({ type: { $exists: false } }).then((playlists: Playlist[]) => { + const favoriteChannels = aggregateFavoriteChannels(playlists); + const favPlaylist = createFavoritesPlaylist(favoriteChannels); + + event.sender.send(PLAYLIST_PARSE_RESPONSE, { + type: PLAYLIST_PARSE_RESPONSE, + payload: favPlaylist, + }); + }); + } + /** * Set default listeners for custom-titlebar */ diff --git a/src/app/player/components/channel-list-container/channel-list-container.component.ts b/src/app/player/components/channel-list-container/channel-list-container.component.ts index 7dee5961e..8de54395a 100644 --- a/src/app/player/components/channel-list-container/channel-list-container.component.ts +++ b/src/app/player/components/channel-list-container/channel-list-container.component.ts @@ -8,7 +8,8 @@ import { import { MatSnackBar } from '@angular/material/snack-bar'; import * as _ from 'lodash'; import { Observable } from 'rxjs'; -import { Channel, ChannelQuery, ChannelStore } from '../../../state'; +import { Channel } from '../../../../../shared/channel.interface'; +import { ChannelQuery, ChannelStore } from '../../../state'; @Component({ selector: 'app-channel-list-container', diff --git a/src/app/player/components/epg-list/epg-list.component.spec.ts b/src/app/player/components/epg-list/epg-list.component.spec.ts index 1455791c6..a9c4d6446 100644 --- a/src/app/player/components/epg-list/epg-list.component.spec.ts +++ b/src/app/player/components/epg-list/epg-list.component.spec.ts @@ -1,19 +1,20 @@ -import { MockComponent, MockProvider, MockModule, MockPipe } from 'ng-mocks'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; /* eslint-disable @typescript-eslint/unbound-method */ import { MatDialog, MatDialogModule } from '@angular/material/dialog'; -import { TranslatePipe } from '@ngx-translate/core'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { EpgListComponent, EpgData } from './epg-list.component'; +import { MatIconModule } from '@angular/material/icon'; import { MatListModule } from '@angular/material/list'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { TranslatePipe } from '@ngx-translate/core'; +import * as moment from 'moment'; +import { MockComponent, MockModule, MockPipe, MockProvider } from 'ng-mocks'; +import { Channel } from '../../../../../shared/channel.interface'; +import { EPG_GET_PROGRAM_DONE } from '../../../../../shared/ipc-commands'; import { DataService } from '../../../services/data.service'; import { ElectronServiceStub } from '../../../services/electron.service.stub'; -import * as moment from 'moment'; -import { EPG_GET_PROGRAM_DONE } from '../../../../../shared/ipc-commands'; -import { Channel, ChannelStore } from '../../../state'; import { MomentDatePipe } from '../../../shared/pipes/moment-date.pipe'; -import { MatIconModule } from '@angular/material/icon'; +import { ChannelStore } from '../../../state'; import { EpgListItemComponent } from './epg-list-item/epg-list-item.component'; +import { EpgData, EpgListComponent } from './epg-list.component'; describe('EpgListComponent', () => { let component: EpgListComponent; diff --git a/src/app/player/components/html-video-player/html-video-player.component.ts b/src/app/player/components/html-video-player/html-video-player.component.ts index 48f6ee572..199ca965b 100644 --- a/src/app/player/components/html-video-player/html-video-player.component.ts +++ b/src/app/player/components/html-video-player/html-video-player.component.ts @@ -7,8 +7,8 @@ import { SimpleChanges, ViewChild, } from '@angular/core'; -import { Channel } from '../../../state'; import Hls from 'hls.js'; +import { Channel } from '../../../../../shared/channel.interface'; /** * This component contains the implementation of HTML5 based video player diff --git a/src/app/player/components/info-overlay/info-overlay.component.ts b/src/app/player/components/info-overlay/info-overlay.component.ts index 5cd9d4575..83803d640 100644 --- a/src/app/player/components/info-overlay/info-overlay.component.ts +++ b/src/app/player/components/info-overlay/info-overlay.component.ts @@ -1,7 +1,7 @@ -import { Channel } from './../../../state/channel.model'; import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; -import { EpgProgram } from '../../models/epg-program.model'; import * as moment from 'moment'; +import { Channel } from '../../../../../shared/channel.interface'; +import { EpgProgram } from '../../models/epg-program.model'; @Component({ selector: 'app-info-overlay', 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 80c2c96c6..9fce54e64 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 @@ menu