From 400f97baf4e0b3bc3963cefa21437d4a01119cd6 Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 27 Nov 2024 20:11:01 +0100 Subject: [PATCH] fix: update EPG item description and refactor component Ref the EPG item to directly display the category and description values instead of accessing the first element of the arrays. Update the EpgListComponent to improve the handling of EPG data and selected date filtering. Change references from Electron to Tauri in the component to reflect the current framework usage. --- src/app/app.component.ts | 4 - .../channel-list-container.component.ts | 10 +- .../epg-item-description.component.html | 6 +- .../epg-list-item.component.html | 2 +- .../epg-list/epg-list.component.html | 187 ++++++++---------- .../epg-list/epg-list.component.scss | 1 + .../components/epg-list/epg-list.component.ts | 138 +++++++------ .../html-video-player.component.ts | 79 +++++--- .../info-overlay/info-overlay.component.html | 6 +- .../multi-epg-container.component.html | 14 +- .../multi-epg-container.component.scss | 3 +- .../toolbar/toolbar.component.html | 5 +- .../video-player/video-player.component.html | 2 +- .../video-player/video-player.component.ts | 6 +- src/app/player/models/epg-channel.model.ts | 2 +- src/app/services/epg.service.ts | 137 +++++++++---- src/app/services/tauri.service.ts | 11 +- src/app/settings/settings.component.html | 18 +- src/app/settings/settings.component.ts | 22 +-- 19 files changed, 377 insertions(+), 276 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a2a9c59e8..d9123d77d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,8 +12,6 @@ import * as semver from 'semver'; import { IpcCommand } from '../../shared/ipc-command.class'; import { AUTO_UPDATE_PLAYLISTS, - EPG_ERROR, - EPG_FETCH_DONE, ERROR, OPEN_FILE, SETTINGS_UPDATE, @@ -50,8 +48,6 @@ export class AppComponent { commandsList = [ new IpcCommand(VIEW_ADD_PLAYLIST, () => this.navigateToRoute('/')), new IpcCommand(VIEW_SETTINGS, () => this.navigateToRoute('/settings')), - new IpcCommand(EPG_FETCH_DONE, () => this.epgService.onEpgFetchDone()), - new IpcCommand(EPG_ERROR, () => this.epgService.onEpgError()), new IpcCommand(SHOW_WHATS_NEW, () => this.showWhatsNewDialog()), new IpcCommand(ERROR, (response: { message: string; status: number }) => this.showErrorAsNotification(response) 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 7bfba83c2..bd9540891 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 @@ -22,6 +22,7 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core'; import * as _ from 'lodash'; import { map, skipWhile } from 'rxjs'; import { Channel } from '../../../../../shared/channel.interface'; +import { EpgService } from '../../../services/epg.service'; import { FilterPipe } from '../../../shared/pipes/filter.pipe'; import * as PlaylistActions from '../../../state/actions'; import { @@ -116,7 +117,8 @@ export class ChannelListContainerComponent { constructor( private readonly store: Store, private snackBar: MatSnackBar, - private translateService: TranslateService + private translateService: TranslateService, + private epgService: EpgService ) {} /** @@ -126,6 +128,12 @@ export class ChannelListContainerComponent { selectChannel(channel: Channel): void { this.selected = channel; this.store.dispatch(PlaylistActions.setActiveChannel({ channel })); + + const epgChannelId = channel?.tvg?.id || channel?.name; + + if (epgChannelId) { + this.epgService.getChannelPrograms(epgChannelId); + } } /** diff --git a/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html index 297a15d4f..73fbe21b2 100644 --- a/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html +++ b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html @@ -6,7 +6,7 @@

{{ 'EPG.PROGRAM_DIALOG.TITLE' | translate }}
-

{{ epgProgram.title[0].value }}

+

{{ epgProgram.title }}

{{ 'EPG.PROGRAM_DIALOG.LANGUAGE' | translate }}
@@ -16,13 +16,13 @@

{{ 'EPG.PROGRAM_DIALOG.CATEGORY' | translate }}
-

{{ epgProgram.category[0].value }}

+

{{ epgProgram.category }}

{{ 'EPG.PROGRAM_DIALOG.DESCRIPTION' | translate }}
-

{{ epgProgram.desc[0].value }}

+

{{ epgProgram.desc }}

diff --git a/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html index 2f30b200e..d06100f57 100644 --- a/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html +++ b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html @@ -17,7 +17,7 @@ > } -@if (item.desc.length > 0) { +@if (item.desc?.length > 0) { -
-
-
- @if (channel?.icon) { - - } +@let items = filteredItems$ | async; +@let timeshiftUntil = timeshiftUntil$ | async; + + +
+
+
+ @if (channel?.icon) { + + } +
+
+
+ {{ channel?.name }}
-
-
- {{ channel?.name[0]?.value }} -
-
- {{ - playingNow - ? playingNow.title[0]?.value - : ('EPG.LIVE_STREAM' | translate) - }} -
+
+ {{ + playingNow + ? playingNow.title + : ('EPG.LIVE_STREAM' | translate) + }}
- -
- - - {{ dateToday | momentDate: 'YYYYMMDD' : 'MMMM Do, dddd' }} - -
- - @let timeshiftUntil = timeshiftUntil$ | async; - @if (timeshiftUntil) { - - + + + {{ dateToday | momentDate: 'YYYY-MM-DD' : 'MMMM Do, dddd' }} + +
+
+ + +@if (timeshiftUntil) { + + + @if (items?.length > 0) { + @for (program of items; track program) { + @if ( + program.start < timeshiftUntil || program.start >= timeNow + ) { + +
+ +
+

{{ program?.title }}

+
+ } @else {
-

{{ item?.title[0]?.value }}

+

- - -
- -
-

-
-
- - - } @else { -

- {{ 'EPG.EPG_NOT_AVAILABLE_DATE' | translate }} -

+ } + } -
- } -} @else { - - - {{ 'EPG.EPG_NOT_AVAILABLE_CHANNEL_TITLE' | translate }}
- {{ 'EPG.EPG_NOT_AVAILABLE_CHANNEL_DESCRIPTION' | translate }} -
-
+ } @else { +

+ {{ 'EPG.EPG_NOT_AVAILABLE_DATE' | translate }} +

+ } + } diff --git a/src/app/player/components/epg-list/epg-list.component.scss b/src/app/player/components/epg-list/epg-list.component.scss index 6a89462c1..23848830d 100644 --- a/src/app/player/components/epg-list/epg-list.component.scss +++ b/src/app/player/components/epg-list/epg-list.component.scss @@ -7,6 +7,7 @@ left: 0; bottom: 0; height: 100%; + overflow-x: hidden; } .active { diff --git a/src/app/player/components/epg-list/epg-list.component.ts b/src/app/player/components/epg-list/epg-list.component.ts index 69111be17..9dbe62359 100644 --- a/src/app/player/components/epg-list/epg-list.component.ts +++ b/src/app/player/components/epg-list/epg-list.component.ts @@ -1,5 +1,5 @@ import { AsyncPipe } from '@angular/common'; -import { Component, NgZone } from '@angular/core'; +import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatDividerModule } from '@angular/material/divider'; @@ -9,10 +9,11 @@ import { MatTooltipModule } from '@angular/material/tooltip'; import { Store } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; import moment from 'moment'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { EPG_GET_PROGRAM_DONE } from '../../../../../shared/ipc-commands'; import { DataService } from '../../../services/data.service'; +import { EpgService } from '../../../services/epg.service'; import { MomentDatePipe } from '../../../shared/pipes/moment-date.pipe'; import { resetActiveEpgProgram, @@ -29,8 +30,7 @@ export interface EpgData { items: EpgProgram[]; } -const DATE_FORMAT = 'YYYYMMDD'; -const DATE_TIME_FORMAT = 'YYYYMMDDHHmm ZZ'; +const DATE_FORMAT = 'YYYY-MM-DD'; @Component({ standalone: true, @@ -58,7 +58,7 @@ export class EpgListComponent { dateToday: string; /** Array with EPG programs */ - items: EpgProgram[] = []; + items$ = this.epgService.currentEpgPrograms$; /** Object with epg programs for the active channel */ programs: { @@ -77,61 +77,65 @@ export class EpgListComponent { /** Timeshift availability date, based on tvg-rec value from the channel */ timeshiftUntil$: Observable; - /** - * Creates an instance of EpgListComponent - * @param store - * @param electronService - * @param ngZone - */ + private readonly selectedDate$ = new BehaviorSubject( + moment().format(DATE_FORMAT) + ); + + /** Filtered EPG programs based on selected date */ + filteredItems$ = combineLatest([this.items$, this.selectedDate$]).pipe( + map(([items, selectedDate]) => + items + .filter( + (item) => + moment(item.start).format('YYYY-MM-DD') === selectedDate + ) + .sort((a, b) => moment(a.start).diff(moment(b.start))) + ) + ); + constructor( private readonly store: Store, - private electronService: DataService, - private ngZone: NgZone - ) { - this.electronService.listenOn( - EPG_GET_PROGRAM_DONE, - (event, response) => { - this.ngZone.run(() => this.handleEpgData(response)); - } - ); - } + private dataService: DataService, + private readonly epgService: EpgService + ) {} /** * Subscribe for values from the store on component init */ ngOnInit(): void { this.timeshiftUntil$ = this.store.select(selectActive).pipe( - // eslint-disable-next-line @ngrx/avoid-mapping-selectors map((active) => { + console.log(active); + this.channel = { + id: active?.tvg?.id, + name: active?.name, + url: [active?.url], + icon: [active?.tvg?.logo], + }; return ( active?.tvg?.rec || active?.timeshift || active?.catchup?.days ); }), - map((value) => - moment(Date.now()) - .subtract(value, 'days') - .format(DATE_TIME_FORMAT) - ) + map((value) => moment().subtract(value, 'days').toISOString()) ); + + this.items$.subscribe((programs) => this.handleEpgData(programs)); + this.dateToday = moment().format(DATE_FORMAT); + this.selectedDate$.next(this.dateToday); } /** * Handles incoming epg programs for the active channel from the main process * @param programs */ - handleEpgData(programs: { payload: EpgData }): void { - if (programs?.payload?.items?.length > 0) { - this.programs = programs; - this.timeNow = moment(Date.now()).format(DATE_TIME_FORMAT); - this.dateToday = moment(Date.now()).format(DATE_FORMAT); - this.channel = programs.payload?.channel; - this.items = this.selectPrograms(programs); - + handleEpgData(programs: EpgProgram[]): void { + this.timeNow = new Date().toISOString(); + this.dateToday = moment().format(DATE_FORMAT); + if (programs.length > 0) { this.setPlayingNow(); } else { - this.items = []; this.channel = null; this.store.dispatch(setCurrentEpgProgram(undefined)); } @@ -139,23 +143,20 @@ export class EpgListComponent { /** * Selects the program based on the active date - * @param programs object with all available epg programs for the active channel */ selectPrograms(programs: { payload: EpgData }): EpgProgram[] { + const selectedDate = moment(this.dateToday).format('YYYY-MM-DD'); return programs.payload?.items - .filter((item) => item.start.includes(this.dateToday.toString())) + .filter( + (item) => + moment(item.start).format('YYYY-MM-DD') === selectedDate + ) .map((program) => ({ ...program, - start: moment(program.start, DATE_TIME_FORMAT).format( - DATE_TIME_FORMAT - ), - stop: moment(program.stop, DATE_TIME_FORMAT).format( - DATE_TIME_FORMAT - ), + start: program.start, // Keep ISO format + stop: program.stop, // Keep ISO format })) - .sort((a, b) => { - return a.start.localeCompare(b.start); - }); + .sort((a, b) => moment(a.start).diff(moment(b.start))); } /** @@ -163,28 +164,37 @@ export class EpgListComponent { * @param direction direction to switch */ changeDate(direction: 'next' | 'prev'): void { - let dateToSwitch; - if (direction === 'next') { - dateToSwitch = moment(this.dateToday, DATE_FORMAT) - .add(1, 'days') - .format(DATE_FORMAT); - } else if (direction === 'prev') { - dateToSwitch = moment(this.dateToday, DATE_FORMAT) - .subtract(1, 'days') - .format(DATE_FORMAT); - } - this.dateToday = dateToSwitch; - this.items = this.selectPrograms(this.programs); + const newDate = moment(this.selectedDate$.value) + [direction === 'next' ? 'add' : 'subtract'](1, 'days') + .format(DATE_FORMAT); + + this.dateToday = newDate; + this.selectedDate$.next(newDate); } /** * Sets the playing now variable based on the current time */ setPlayingNow(): void { - this.playingNow = this.items.find( - (item) => this.timeNow >= item.start && this.timeNow <= item.stop - ); - this.store.dispatch(setCurrentEpgProgram({ program: this.playingNow })); + this.items$ + .pipe( + map((items) => + items.find((item) => { + const now = new Date().toISOString(); + const start = new Date(item.start).toISOString(); + const stop = new Date(item.stop).toISOString(); + return now >= start && now <= stop; + }) + ) + ) + .subscribe((playingNow) => { + this.playingNow = playingNow; + if (playingNow) { + this.store.dispatch( + setCurrentEpgProgram({ program: playingNow }) + ); + } + }); } /** @@ -211,6 +221,6 @@ export class EpgListComponent { * Removes all ipc renderer listeners after destroy */ ngOnDestroy(): void { - this.electronService.removeAllListeners(EPG_GET_PROGRAM_DONE); + this.dataService.removeAllListeners(EPG_GET_PROGRAM_DONE); } } 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 9624d94d3..d32dda43f 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 @@ -75,32 +75,61 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy { if (channel.url) { const url = channel.url + (channel.epgParams ?? ''); const extension = getExtensionFromUrl(channel.url); - this.dataService.sendIpcEvent(CHANNEL_SET_USER_AGENT, { - userAgent: channel.http?.['user-agent'] ?? '', - referer: channel.http?.referrer ?? '', - }); - if ( - extension !== 'mp4' && - extension !== 'mpv' && - Hls && - Hls.isSupported() - ) { - console.log('... switching channel to ', channel.name, url); - this.hls = new Hls(); - this.hls.attachMedia(this.videoPlayer.nativeElement); - this.hls.loadSource(url); - - this.handlePlayOperation(); - } else { - console.error('something wrong with hls.js init...'); - this.addSourceToVideo( - this.videoPlayer.nativeElement, - url, - 'video/mp4' - ); - this.videoPlayer.nativeElement.play(); - } + // Send IPC event and handle the response + this.dataService + .sendIpcEvent(CHANNEL_SET_USER_AGENT, { + userAgent: channel.http?.['user-agent'] ?? '', + referer: channel.http?.referrer ?? '', + }) + .then(() => { + if ( + extension !== 'mp4' && + extension !== 'mpv' && + Hls && + Hls.isSupported() + ) { + console.log( + '... switching channel to ', + channel.name, + url + ); + this.hls = new Hls(); + this.hls.attachMedia(this.videoPlayer.nativeElement); + this.hls.loadSource(url); + this.handlePlayOperation(); + } else { + console.log('Using native video player...'); + this.addSourceToVideo( + this.videoPlayer.nativeElement, + url, + 'video/mp4' + ); + this.videoPlayer.nativeElement.play(); + } + }) + .catch((error) => { + console.error('Error setting user agent:', error); + // Continue playback even if setting user agent fails + if ( + extension !== 'mp4' && + extension !== 'mpv' && + Hls && + Hls.isSupported() + ) { + this.hls = new Hls(); + this.hls.attachMedia(this.videoPlayer.nativeElement); + this.hls.loadSource(url); + this.handlePlayOperation(); + } else { + this.addSourceToVideo( + this.videoPlayer.nativeElement, + url, + 'video/mp4' + ); + this.videoPlayer.nativeElement.play(); + } + }); } } diff --git a/src/app/player/components/info-overlay/info-overlay.component.html b/src/app/player/components/info-overlay/info-overlay.component.html index 224bdc013..9794878ea 100644 --- a/src/app/player/components/info-overlay/info-overlay.component.html +++ b/src/app/player/components/info-overlay/info-overlay.component.html @@ -6,17 +6,17 @@ >
- {{ epgProgram?.title[0]?.value }} | {{ channel.name }} + {{ epgProgram?.title }} | {{ channel.name }}
- {{ epgProgram?.desc[0]?.value }} + {{ epgProgram?.desc }}
- @if (item.icon[0]) { + @if (item.icon) { } - @if (!item.icon[0]) { + @if (!item.icon) {
- {{ item.name[0].value }} + {{ item.name }}
} @@ -99,7 +99,7 @@ @@ -118,7 +118,7 @@ [attr.x]="program.startPosition" >
diff --git a/src/app/player/components/multi-epg/multi-epg-container.component.scss b/src/app/player/components/multi-epg/multi-epg-container.component.scss index 17e51e4a6..e787f143a 100644 --- a/src/app/player/components/multi-epg/multi-epg-container.component.scss +++ b/src/app/player/components/multi-epg/multi-epg-container.component.scss @@ -28,7 +28,7 @@ rect { #epg-container { width: calc(100vw - 100px); - height: calc(100vh - 73px); + height: calc(100vh - 42px); overflow-x: scroll; overflow-y: hidden; } @@ -53,7 +53,6 @@ rect { #epg-navigation { display: flex; background-color: white; - margin-top: 30px; border-bottom: 2px solid #fff; align-items: center; } diff --git a/src/app/player/components/video-player/toolbar/toolbar.component.html b/src/app/player/components/video-player/toolbar/toolbar.component.html index 937ce8b4b..4ba9644bf 100644 --- a/src/app/player/components/video-player/toolbar/toolbar.component.html +++ b/src/app/player/components/video-player/toolbar/toolbar.component.html @@ -23,7 +23,8 @@ {{ activeChannel?.name }}
- + @let isEpgAvailable = isEpgAvailable$ | async; + @if (isEpgAvailable) { - + } 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 327a10371..8bbc053e6 100644 --- a/src/app/player/components/video-player/video-player.component.html +++ b/src/app/player/components/video-player/video-player.component.html @@ -90,6 +90,6 @@ - + diff --git a/src/app/player/components/video-player/video-player.component.ts b/src/app/player/components/video-player/video-player.component.ts index 482869683..19da38575 100644 --- a/src/app/player/components/video-player/video-player.component.ts +++ b/src/app/player/components/video-player/video-player.component.ts @@ -118,7 +118,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { listeners = []; - isElectron = this.dataService.isElectron; + isTauri = this.dataService.getAppEnvironment() === 'tauri'; sidebarView: SidebarView = 'CHANNELS'; @@ -210,7 +210,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { */ setRendererListeners(): void { this.commandsList.forEach((command) => { - if (this.isElectron) { + if (this.isTauri) { this.dataService.listenOn(command.id, (event, response) => this.ngZone.run(() => command.execute(response)) ); @@ -243,7 +243,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if (this.isElectron) { + if (this.isTauri) { this.dataService.removeAllListeners(PLAYLIST_PARSE_RESPONSE); } else { this.listeners.forEach((listener) => diff --git a/src/app/player/models/epg-channel.model.ts b/src/app/player/models/epg-channel.model.ts index 1f3fbcb28..f0c47e4d1 100644 --- a/src/app/player/models/epg-channel.model.ts +++ b/src/app/player/models/epg-channel.model.ts @@ -1,6 +1,6 @@ export interface EpgChannel { id: string; - name: { lang: string; value: string }[]; + name: string; icon: string[]; url: string[]; } diff --git a/src/app/services/epg.service.ts b/src/app/services/epg.service.ts index a4b0c6d45..1164c2d7e 100644 --- a/src/app/services/epg.service.ts +++ b/src/app/services/epg.service.ts @@ -1,66 +1,125 @@ import { Injectable } from '@angular/core'; -import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; -import { EPG_FETCH } from '../../../shared/ipc-commands'; -import { setEpgAvailableFlag } from '../state/actions'; -import { DataService } from './data.service'; +import { invoke } from '@tauri-apps/api/core'; +import { BehaviorSubject, from } from 'rxjs'; +import { catchError, map, tap } from 'rxjs/operators'; +import { EpgProgram } from '../player/models/epg-program.model'; +import * as PlaylistActions from '../state/actions'; @Injectable({ providedIn: 'root', }) export class EpgService { - /** Default options for epg snackbar notifications */ - epgSnackBarOptions: MatSnackBarConfig = { - verticalPosition: 'bottom', - horizontalPosition: 'start', - }; + private epgAvailable = new BehaviorSubject(false); + private currentEpgPrograms = new BehaviorSubject([]); + + epgAvailable$ = this.epgAvailable.asObservable(); + currentEpgPrograms$ = this.currentEpgPrograms.asObservable(); constructor( - private store: Store, - private electronService: DataService, private snackBar: MatSnackBar, - private translate: TranslateService + private translate: TranslateService, + private store: Store ) {} /** - * Fetches and updates EPG from the given sources - * @param urls epg source urls + * Fetches EPG from the given URLs */ - fetchEpg(urls: string | string[]) { - const urlsArray = Array.isArray(urls) ? urls : [urls]; - urlsArray.forEach((url) => - this.electronService.sendIpcEvent(EPG_FETCH, { - url, - }) - ); + fetchEpg(urls: string[]): void { this.showFetchSnackbar(); + + // Filter out empty URLs and send all URLs at once + const validUrls = urls.filter((url) => url?.trim()); + if (validUrls.length === 0) return; + + from(invoke('fetch_epg', { url: validUrls })) + .pipe( + tap(() => { + this.epgAvailable.next(true); + this.showSuccessSnackbar(); + }), + catchError((err) => { + console.error('EPG fetch error:', err); + this.epgAvailable.next(false); + this.showErrorSnackbar(); + throw err; + }) + ) + .subscribe(); } - showFetchSnackbar() { - this.snackBar.open( - this.translate.instant('EPG.FETCH_EPG'), - this.translate.instant('CLOSE'), - this.epgSnackBarOptions - ); + /** + * Gets EPG programs for a specific channel + */ + getChannelPrograms(channelId: string): void { + console.log('Fetching EPG for channel ID:', channelId); + from(invoke('get_channel_programs', { channelId })) + .pipe( + tap((programs) => { + console.log('Received programs:', programs); + }), + map((programs) => + programs.map((program) => ({ + ...program, + start: new Date(program.start).toISOString(), + stop: new Date(program.stop).toISOString(), + })) + ), + catchError((err) => { + console.error('EPG get programs error:', err); + this.showErrorSnackbar(); + this.currentEpgPrograms.next([]); + throw err; + }) + ) + .subscribe((programs) => { + if (programs.length === 0) { + this.store.dispatch( + PlaylistActions.setEpgAvailableFlag({ value: false }) + ); + } else { + this.store.dispatch( + PlaylistActions.setEpgAvailableFlag({ value: true }) + ); + } + this.currentEpgPrograms.next(programs); + console.log('Updated programs:', programs); // Debug log + }); + } + + /** + * Shows fetch in progress snackbar + */ + showFetchSnackbar(): void { + this.snackBar.open(this.translate.instant('EPG.FETCH_EPG'), null, { + duration: 2000, + horizontalPosition: 'start', + }); + } + + /** + * Shows success snackbar + */ + private showSuccessSnackbar(): void { + this.snackBar.open(this.translate.instant('EPG.FETCH_SUCCESS'), null, { + duration: 2000, + horizontalPosition: 'start', + }); } - onEpgFetchDone() { - this.store.dispatch(setEpgAvailableFlag({ value: true })); + /** + * Shows error snackbar + */ + private showErrorSnackbar(): void { this.snackBar.open( - this.translate.instant('EPG.DOWNLOAD_SUCCESS'), - null, + this.translate.instant('EPG.ERROR'), + this.translate.instant('CLOSE'), { - ...this.epgSnackBarOptions, duration: 2000, + horizontalPosition: 'start', } ); } - - onEpgError() { - this.snackBar.open(this.translate.instant('EPG.ERROR'), null, { - ...this.epgSnackBarOptions, - duration: 2000, - }); - } } diff --git a/src/app/services/tauri.service.ts b/src/app/services/tauri.service.ts index 8727e1734..e83f28725 100644 --- a/src/app/services/tauri.service.ts +++ b/src/app/services/tauri.service.ts @@ -3,6 +3,7 @@ import { invoke } from '@tauri-apps/api/core'; import { fetch } from '@tauri-apps/plugin-http'; import { parse } from 'iptv-playlist-parser'; import { + EPG_GET_PROGRAM_DONE, ERROR, PLAYLIST_PARSE_BY_URL, PLAYLIST_PARSE_RESPONSE, @@ -66,6 +67,11 @@ export class TauriService extends DataService { }); throw error; }); + } else if (type === 'EPG_FETCH_DONE') { + window.postMessage({ + type: EPG_GET_PROGRAM_DONE, + payload, + }); } else { console.log('Unknown type', type); } @@ -184,7 +190,10 @@ export class TauriService extends DataService { } removeAllListeners(type: string): void { - throw new Error('Method not implemented.'); + console.error( + 'Method not implemented. Following type was provided:', + type + ); } listenOn(command: string, callback: (...args: any[]) => void): void { diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 79c3a0ab5..842b07155 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -9,7 +9,7 @@
- +
{{ 'SETTINGS.EPG_URL_LABEL' | translate }} @@ -205,7 +205,7 @@ >
- @if (isPwa || isElectron) { + @if (isPwa || isTauri) {
@@ -311,12 +311,14 @@
-
- {{ 'SETTINGS.EPG_NOTE' | translate }} -  {{ - 'SETTINGS.EPG_NOTE_URL_TEXT' | translate - }} -
+ @if (isPwa) { +
+ {{ 'SETTINGS.EPG_NOTE' | translate }} +  {{ + 'SETTINGS.EPG_NOTE_URL_TEXT' | translate + }} +
+ }
diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index 8e31459dd..fe8bb3fe9 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -22,7 +22,6 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { take } from 'rxjs'; import * as semver from 'semver'; import { - EPG_FORCE_FETCH, SET_MPV_PLAYER_PATH, SET_VLC_PLAYER_PATH, SETTINGS_UPDATE, @@ -65,7 +64,7 @@ export class SettingsComponent implements OnInit { languageEnum = Language; /** Flag that indicates whether the app runs in electron environment */ - isElectron = this.electronService.isElectron; + isTauri = this.electronService.getAppEnvironment() === 'tauri'; isPwa = this.electronService.getAppEnvironment() === 'pwa'; @@ -91,7 +90,7 @@ export class SettingsComponent implements OnInit { label: 'VideoJs Player', }, ...this.electronPlayers, - /* ...(this.isElectron ? this.electronPlayers : []), */ + /* ...(this.isTauri ? this.electronPlayers : []), */ ]; /** Current version of the app */ @@ -109,7 +108,7 @@ export class SettingsComponent implements OnInit { /** Settings form object */ settingsForm = this.formBuilder.group({ player: [VideoPlayer.VideoJs], - ...(this.isElectron ? { epgUrl: new FormArray([]) } : {}), + ...(this.isTauri ? { epgUrl: new FormArray([]) } : {}), language: Language.ENGLISH, showCaptions: false, theme: Theme.LightTheme, @@ -161,7 +160,7 @@ export class SettingsComponent implements OnInit { player: settings.player ? settings.player : VideoPlayer.VideoJs, - ...(this.isElectron ? { epgUrl: [] } : {}), + ...(this.isTauri ? { epgUrl: [] } : {}), language: settings.language ?? Language.ENGLISH, showCaptions: settings.showCaptions ?? false, theme: settings.theme ?? Theme.LightTheme, @@ -175,7 +174,7 @@ export class SettingsComponent implements OnInit { throw new Error(error); } - if (this.isElectron) { + if (this.isTauri) { this.setEpgUrls(settings.epgUrl); } } @@ -280,15 +279,17 @@ export class SettingsComponent implements OnInit { */ applyChangedSettings(): void { this.settingsForm.markAsPristine(); - // check whether the epg url was changed or not - if (this.isElectron) { + if (this.isTauri) { let epgUrls = this.settingsForm.value.epgUrl; if (epgUrls) { if (!Array.isArray(epgUrls)) { epgUrls = [epgUrls]; } epgUrls = epgUrls.filter((url) => url !== ''); - this.epgService.fetchEpg(epgUrls); + if (epgUrls.length > 0) { + // Fetch all EPG URLs at once + this.epgService.fetchEpg(epgUrls); + } } } this.translate.use(this.settingsForm.value.language); @@ -315,8 +316,7 @@ export class SettingsComponent implements OnInit { * @param url epg source url */ refreshEpg(url: string): void { - this.electronService.sendIpcEvent(EPG_FORCE_FETCH, url); - this.epgService.showFetchSnackbar(); + this.epgService.fetchEpg([url]); } /**