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]); } /**