diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 0fb45dadd..3a1201377 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -120,15 +120,21 @@ export class AppComponent implements OnInit, OnDestroy { .getValueFromLocalStorage(STORE_KEY.Settings) .subscribe((settings: Settings) => { if (settings && Object.keys(settings).length > 0) { - this.dataService.sendIpcEvent(SETTINGS_UPDATE, settings); + // Send settings to Electron main process + if (window.electron) { + window.electron.updateSettings(settings); + } + this.translate.use(settings.language ?? this.DEFAULT_LANG); - /* if ( + + // Fetch EPG if URLs are configured + if ( + window.electron && settings.epgUrl?.length > 0 && - settings.epgUrl?.some((u) => u !== '') && - isTauri() + settings.epgUrl?.some((u) => u !== '') ) { this.epgService.fetchEpg(settings.epgUrl); - } */ + } if (settings.theme) { this.settingsService.changeTheme(settings.theme); diff --git a/apps/web/src/app/home/video-player/video-player.component.ts b/apps/web/src/app/home/video-player/video-player.component.ts index 6017108f1..8b9e635f5 100644 --- a/apps/web/src/app/home/video-player/video-player.component.ts +++ b/apps/web/src/app/home/video-player/video-player.component.ts @@ -35,7 +35,6 @@ import { import { Observable, combineLatestWith, filter, map, switchMap } from 'rxjs'; import { DataService, PlaylistsService } from 'services'; import { - CHANNEL_SET_USER_AGENT, COMPONENT_OVERLAY_REF, Channel, ERROR, @@ -44,14 +43,12 @@ import { Playlist, STORE_KEY, Settings, + SidebarView, VideoPlayer, } from 'shared-interfaces'; import { SettingsStore } from '../../services/settings-store.service'; import { SettingsComponent } from '../../settings/settings.component'; -/** Possible sidebar view options */ -export type SidebarView = 'CHANNELS' | 'PLAYLISTS'; - @Component({ imports: [ AsyncPipe, @@ -175,15 +172,13 @@ export class VideoPlayerComponent implements OnInit { ); return this.playlistsService.getPlaylist(params['id']).pipe( map((playlist) => { - this.dataService.sendIpcEvent( - CHANNEL_SET_USER_AGENT, - playlist.userAgent - ? { - referer: 'localhost', - userAgent: playlist.userAgent, - } - : {} - ); + // Set user agent if specified on playlist level + if (playlist.userAgent) { + window.electron?.setUserAgent( + playlist.userAgent, + 'localhost' + ); + } this.store.dispatch( PlaylistActions.setChannels({ diff --git a/apps/web/src/app/services/electron.service.ts b/apps/web/src/app/services/electron.service.ts index 53a567e08..64f475e56 100644 --- a/apps/web/src/app/services/electron.service.ts +++ b/apps/web/src/app/services/electron.service.ts @@ -5,7 +5,6 @@ import { DataService } from 'services'; import { AUTO_UPDATE_PLAYLISTS, AUTO_UPDATE_PLAYLISTS_RESPONSE, - EPG_GET_PROGRAM_DONE, ERROR, Playlist, PLAYLIST_PARSE_BY_URL, @@ -92,11 +91,6 @@ export class ElectronService extends DataService { console.error('VLC launch error:', error); throw error; } - } else if (type === 'EPG_FETCH_DONE') { - window.postMessage({ - type: EPG_GET_PROGRAM_DONE, - payload, - }); } else if (type === AUTO_UPDATE_PLAYLISTS) { const data = payload as Playlist[]; const playlists = await window.electron.autoUpdatePlaylists(data); diff --git a/apps/web/src/app/services/settings-store.service.ts b/apps/web/src/app/services/settings-store.service.ts index e9735cb01..6215f28dc 100644 --- a/apps/web/src/app/services/settings-store.service.ts +++ b/apps/web/src/app/services/settings-store.service.ts @@ -36,14 +36,19 @@ export const SettingsStore = signalStore( withState(DEFAULT_SETTINGS), withMethods((store, storage = inject(StorageMap)) => ({ async loadSettings() { - const stored = await firstValueFrom( - storage.get(STORE_KEY.Settings) - ); - if (stored) { - patchState(store, { - ...DEFAULT_SETTINGS, - ...(stored as Settings), - }); + try { + const stored = await firstValueFrom( + storage.get(STORE_KEY.Settings) + ); + if (stored) { + patchState(store, { + ...DEFAULT_SETTINGS, + ...(stored as Settings), + }); + } + } catch (error) { + console.error('Failed to load settings:', error); + // Keep default settings if loading fails } }, @@ -51,9 +56,14 @@ export const SettingsStore = signalStore( patchState(store, settings); // Save the complete settings object, not just the partial update const completeSettings = this.getSettings(); - await firstValueFrom( - storage.set(STORE_KEY.Settings, completeSettings) - ); + try { + await firstValueFrom( + storage.set(STORE_KEY.Settings, completeSettings) + ); + } catch (error) { + console.error('Failed to save settings:', error); + throw error; + } }, getSettings() { diff --git a/apps/web/src/app/settings/settings.component.ts b/apps/web/src/app/settings/settings.component.ts index 2f69ea6ed..136bc4ce4 100644 --- a/apps/web/src/app/settings/settings.component.ts +++ b/apps/web/src/app/settings/settings.component.ts @@ -41,8 +41,6 @@ import { DataService, EpgService, PlaylistsService } from 'services'; import { Language, Playlist, - SET_MPV_PLAYER_PATH, - SET_VLC_PLAYER_PATH, StreamFormat, Theme, VideoPlayer, @@ -165,7 +163,9 @@ export class SettingsComponent implements OnInit { * Reads the config object from the browsers * storage (indexed db) */ - ngOnInit(): void { + async ngOnInit(): Promise { + // Wait for settings to load before setting the form + await this.settingsStore.loadSettings(); this.setSettings(); this.checkAppVersion(); } @@ -252,21 +252,18 @@ export class SettingsComponent implements OnInit { onSubmit(): void { this.settingsStore.updateSettings(this.settingsForm.value).then(() => { this.applyChangedSettings(); - /* this.dataService.sendIpcEvent( - SETTINGS_UPDATE, - this.settingsForm.value - ); */ - window.electron?.updateSettings(this.settingsForm.value); - - this.dataService.sendIpcEvent( - SET_MPV_PLAYER_PATH, - this.settingsForm.value.mpvPlayerPath - ); - - this.dataService.sendIpcEvent( - SET_VLC_PLAYER_PATH, - this.settingsForm.value.mpvPlayerPath - ); + + if (window.electron) { + window.electron.updateSettings(this.settingsForm.value); + + // Set player paths if using external players + if (this.settingsForm.value.mpvPlayerPath) { + window.electron.setMpvPlayerPath(this.settingsForm.value.mpvPlayerPath); + } + if (this.settingsForm.value.vlcPlayerPath) { + window.electron.setVlcPlayerPath(this.settingsForm.value.vlcPlayerPath); + } + } }); if (this.isDialog) { this.matDialog.closeAll(); diff --git a/libs/m3u-state/src/lib/effects.ts b/libs/m3u-state/src/lib/effects.ts index 8860543ba..b15fdd71d 100644 --- a/libs/m3u-state/src/lib/effects.ts +++ b/libs/m3u-state/src/lib/effects.ts @@ -13,10 +13,8 @@ import { tap, withLatestFrom, } from 'rxjs'; -import { DataService, PlaylistsService } from 'services'; +import { DataService, EpgService, PlaylistsService } from 'services'; import { - CHANNEL_SET_USER_AGENT, - EPG_GET_PROGRAM, OPEN_MPV_PLAYER, OPEN_VLC_PLAYER, Playlist, @@ -36,6 +34,7 @@ export class PlaylistEffects { private actions$ = inject(Actions); private playlistsService = inject(PlaylistsService); private dataService = inject(DataService); + private epgService = inject(EpgService); private router = inject(Router); private snackBar = inject(MatSnackBar); private storage = inject(StorageMap); @@ -126,15 +125,19 @@ export class PlaylistEffects { ofType(PlaylistActions.setActiveChannel), map((action) => { const { channel } = action; - this.dataService.sendIpcEvent(EPG_GET_PROGRAM, { - channel, - }); + + // Use modern EPG service to get channel programs + const channelId = channel.tvg?.id || channel.name; + if (channelId) { + this.epgService.getChannelPrograms(channelId); + } + + // Set user agent if specified on channel if (channel.http['user-agent']) { - this.dataService.sendIpcEvent(CHANNEL_SET_USER_AGENT, { - referer: channel.http.referrer, - userAgent: channel.http['user-agent'], - origin: channel.http.origin, - }); + window.electron?.setUserAgent( + channel.http['user-agent'], + channel.http.referrer + ); } firstValueFrom(this.storage.get(STORE_KEY.Settings)).then( diff --git a/libs/shared/interfaces/src/lib/channel.model.ts b/libs/shared/interfaces/src/lib/channel.model.ts index fe11cb104..9ef7f065f 100755 --- a/libs/shared/interfaces/src/lib/channel.model.ts +++ b/libs/shared/interfaces/src/lib/channel.model.ts @@ -1,4 +1,4 @@ -import { Channel } from 'shared-interfaces'; +import { Channel } from './channel.interface'; /** * Creates new channel object based on the given fields @@ -15,3 +15,6 @@ export function createChannel(params: Partial): Channel { http: params.http, } as Channel; } + +/** Possible sidebar view options */ +export type SidebarView = 'CHANNELS' | 'PLAYLISTS'; diff --git a/libs/shared/interfaces/src/lib/ipc-commands.ts b/libs/shared/interfaces/src/lib/ipc-commands.ts index 84783f203..871747f70 100644 --- a/libs/shared/interfaces/src/lib/ipc-commands.ts +++ b/libs/shared/interfaces/src/lib/ipc-commands.ts @@ -2,8 +2,6 @@ export const EPG_FETCH = 'EPG:FETCH'; export const EPG_FETCH_DONE = 'EPG:FETCH_DONE'; export const EPG_ERROR = 'EPG:ERROR'; -export const EPG_GET_PROGRAM = 'EPG:GET_PROGRAM'; -export const EPG_GET_PROGRAM_DONE = 'EPG:GET_PROGRAM_DONE'; export const EPG_GET_CHANNELS = 'EPG:GET_CHANNELS'; export const EPG_GET_CHANNELS_DONE = 'EPG:GET_CHANNELS_DONE'; export const EPG_GET_CHANNELS_BY_RANGE = 'EPG:EPG_GET_CHANNELS_BY_RANGE'; @@ -21,9 +19,6 @@ export const PLAYLIST_UPDATE_RESPONSE = 'PLAYLIST:UPDATE_RESPONSE'; export const ERROR = 'ERROR'; export const OPEN_FILE = 'OPEN_FILE'; -// Channels -export const CHANNEL_SET_USER_AGENT = 'CHANNEL:SET_USER_AGENT'; - // Views export const VIEW_SETTINGS = 'VIEW:SETTINGS'; export const VIEW_ADD_PLAYLIST = 'VIEW:PLAYLISTS'; diff --git a/libs/ui/components/src/lib/html-video-player/html-video-player.component.ts b/libs/ui/components/src/lib/html-video-player/html-video-player.component.ts index 256482b03..27270b909 100644 --- a/libs/ui/components/src/lib/html-video-player/html-video-player.component.ts +++ b/libs/ui/components/src/lib/html-video-player/html-video-player.component.ts @@ -12,7 +12,7 @@ import { import Hls from 'hls.js'; import { getExtensionFromUrl } from 'm3u-utils'; import { DataService } from 'services'; -import { Channel, CHANNEL_SET_USER_AGENT } from 'shared-interfaces'; +import { Channel } from 'shared-interfaces'; /** * This component contains the implementation of HTML5 based video player @@ -74,61 +74,38 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy { const url = channel.url + (channel.epgParams ?? ''); const extension = getExtensionFromUrl(channel.url); - // Send IPC event and handle the response - this.dataService - .sendIpcEvent(CHANNEL_SET_USER_AGENT, { - userAgent: channel.http?.['user-agent'] ?? '', - referer: channel.http?.referrer ?? '', - origin: channel.http?.origin ?? '', - }) - .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: any) => { - 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(); - } - }); + // Set user agent if specified on channel + if (channel.http?.['user-agent']) { + window.electron?.setUserAgent( + channel.http['user-agent'], + 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.log('Using native video player...'); + this.addSourceToVideo( + this.videoPlayer.nativeElement, + url, + 'video/mp4' + ); + this.videoPlayer.nativeElement.play(); + } } } diff --git a/libs/ui/components/src/lib/vjs-player/vjs-player.component.ts b/libs/ui/components/src/lib/vjs-player/vjs-player.component.ts index e6dc50074..172c4fecc 100644 --- a/libs/ui/components/src/lib/vjs-player/vjs-player.component.ts +++ b/libs/ui/components/src/lib/vjs-player/vjs-player.component.ts @@ -12,7 +12,7 @@ import { import '@yangkghjh/videojs-aspect-ratio-panel'; import videoJs from 'video.js'; import 'videojs-contrib-quality-levels'; -import 'videojs-hls-quality-selector'; +//import 'videojs-hls-quality-selector'; /** * This component contains the implementation of video player that is based on video.js library @@ -28,9 +28,9 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy { /** DOM-element reference */ @ViewChild('target', { static: true }) target!: ElementRef; /** Options of VideoJs player */ - @Input() options!: videoJs.PlayerOptions; + @Input() options!: any; /** VideoJs object */ - player!: videoJs.Player; + player!: any; @Input() volume = 1; /** @@ -56,9 +56,11 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy { }); } ); - this.player.hlsQualitySelector({ + /* this.player.hlsQualitySelector({ displayCurrentQuality: true, }); + // this one produces warning in console + */ this.player['aspectRatioPanel'](); }