mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:47:41 -08:00
feat: integrate epg feature
This commit is contained in:
16 files changed
+380
-14
No files matched your search
@@ -4,6 +4,8 @@ import { AppComponent } from './app.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ElectronService } from './services/electron.service';
|
||||
import { ElectronServiceStub } from './home/home.component.spec';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MockModule } from 'ng-mocks';
|
||||
|
||||
jest.mock('custom-electron-titlebar', () => {
|
||||
return {
|
||||
@@ -16,14 +18,23 @@ jest.mock('custom-electron-titlebar', () => {
|
||||
};
|
||||
});
|
||||
|
||||
describe('AppComponen t', () => {
|
||||
class MatSnackBarStub {
|
||||
open(): void {}
|
||||
}
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
providers: [
|
||||
{ provide: ElectronService, useClass: ElectronServiceStub },
|
||||
{ provide: MatSnackBar, useClass: MatSnackBarStub },
|
||||
],
|
||||
imports: [
|
||||
MockModule(MatSnackBarModule),
|
||||
RouterTestingModule,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
imports: [RouterTestingModule, TranslateModule.forRoot()],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@ import { akitaDevtools } from '@datorama/akita';
|
||||
import { Titlebar, Color } from 'custom-electron-titlebar';
|
||||
import { ElectronService } from './services/electron.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ChannelStore } from './state';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { Settings } from './settings/settings.interface';
|
||||
import { EPG_ERROR, EPG_FETCH, EPG_FETCH_DONE } from './shared/ipc-commands';
|
||||
|
||||
// create custom title bar
|
||||
new Titlebar({
|
||||
@@ -20,10 +25,13 @@ new Titlebar({
|
||||
})
|
||||
export class AppComponent {
|
||||
constructor(
|
||||
private channelStore: ChannelStore,
|
||||
private electronService: ElectronService,
|
||||
private ngZone: NgZone,
|
||||
private router: Router,
|
||||
private translate: TranslateService
|
||||
private translate: TranslateService,
|
||||
private snackBar: MatSnackBar,
|
||||
private storage: StorageMap
|
||||
) {
|
||||
if (!AppConfig.production) {
|
||||
akitaDevtools(ngZone);
|
||||
@@ -51,6 +59,32 @@ export class AppComponent {
|
||||
});
|
||||
});
|
||||
|
||||
this.electronService.ipcRenderer
|
||||
.on(EPG_FETCH_DONE, () => {
|
||||
this.ngZone.run(() => {
|
||||
this.channelStore.update((store) => ({
|
||||
...store,
|
||||
epgAvailable: true,
|
||||
}));
|
||||
this.snackBar.open(
|
||||
'EPG was successfully downloaded!',
|
||||
null,
|
||||
{
|
||||
duration: 2000,
|
||||
verticalPosition: 'bottom',
|
||||
horizontalPosition: 'right',
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
.on(EPG_ERROR, () => {
|
||||
this.snackBar.open('EPG Error: something went wrong...', null, {
|
||||
duration: 2000,
|
||||
verticalPosition: 'bottom',
|
||||
horizontalPosition: 'right',
|
||||
});
|
||||
});
|
||||
|
||||
if (
|
||||
(this.electronService.remote.process.platform === 'linux' ||
|
||||
this.electronService.remote.process.platform === 'win32') &&
|
||||
@@ -70,4 +104,36 @@ export class AppComponent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes for app settings on component init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.storage.get('settings').subscribe((settings: Settings) => {
|
||||
if (
|
||||
settings &&
|
||||
Object.keys(settings).length > 0 &&
|
||||
settings.epgUrl
|
||||
) {
|
||||
this.electronService.ipcRenderer.send(EPG_FETCH, {
|
||||
url: settings.epgUrl,
|
||||
});
|
||||
this.snackBar.open('Fetch EPG data...', 'Close', {
|
||||
verticalPosition: 'bottom',
|
||||
horizontalPosition: 'right',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all ipc command listeners on component destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.electronService.ipcRenderer.removeAllListeners(EPG_FETCH_DONE);
|
||||
this.electronService.ipcRenderer.removeAllListeners(EPG_ERROR);
|
||||
this.electronService.ipcRenderer.removeAllListeners(
|
||||
'add-playlist-view'
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
@@ -16,6 +16,8 @@ import { FilterPipeModule } from 'ngx-filter-pipe';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { createChannel } from '../../../state';
|
||||
import * as MOCKED_PLAYLIST from '../../../../mocks/playlist.json';
|
||||
import { ElectronService } from '../../../services/electron.service';
|
||||
import { ElectronServiceStub } from '../../../home/home.component.spec';
|
||||
|
||||
class MatSnackBarStub {
|
||||
open(): void {}
|
||||
@@ -32,6 +34,7 @@ describe('ChannelListContainerComponent', () => {
|
||||
providers: [
|
||||
ChannelQuery,
|
||||
{ provide: MatSnackBar, useClass: MatSnackBarStub },
|
||||
{ provide: ElectronService, useClass: ElectronServiceStub },
|
||||
],
|
||||
imports: [
|
||||
MockModule(MatSnackBarModule),
|
||||
|
||||
+1
-4
@@ -79,10 +79,7 @@ export class ChannelListContainerComponent {
|
||||
*/
|
||||
selectChannel(channel: Channel): void {
|
||||
this.selected = channel;
|
||||
this.channelStore.update((store) => ({
|
||||
...store,
|
||||
active: channel,
|
||||
}));
|
||||
this.channelStore.setActiveChannel(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<ng-container *ngIf="channel; else noEpg">
|
||||
<div id="channel-info" fxLayoutAlign="row">
|
||||
<img
|
||||
fxFlex="48px"
|
||||
*ngIf="channel?.icon"
|
||||
[src]="channel.icon"
|
||||
width="48"
|
||||
/>
|
||||
<div fxFlex="100" fxLayoutAlign="start center" class="channel-name">
|
||||
{{ channel?.name[0]?.value }}
|
||||
</div>
|
||||
</div>
|
||||
<mat-list>
|
||||
<mat-list-item
|
||||
*ngFor="let item of items"
|
||||
[class.active]="timeNow >= item.start && timeNow <= item.stop"
|
||||
>
|
||||
<div matLine>
|
||||
<span class="time">{{ item.start }} - {{ item.stop }}</span>
|
||||
</div>
|
||||
<p matLine>{{ item?.title[0]?.value }}</p>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</ng-container>
|
||||
<ng-template #noEpg>
|
||||
<mat-list>
|
||||
<mat-list-item>
|
||||
Ooops, EPG is not available for this channel.<br />
|
||||
Please add/change the url of EPG list the settings of the app.
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,25 @@
|
||||
.active {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: #666;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-size: 0.8em;
|
||||
display: inline-block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#channel-info {
|
||||
padding:10px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.channel-name {
|
||||
padding-left: 10px
|
||||
}
|
||||
|
||||
mat-list {
|
||||
height: calc(100vh - 110px);
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { EpgListComponent } from './epg-list.component';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MockModule } from 'ng-mocks';
|
||||
import { ElectronService } from 'app/services/electron.service';
|
||||
import { ElectronServiceStub } from 'app/home/home.component.spec';
|
||||
|
||||
describe('EpgListComponent', () => {
|
||||
let component: EpgListComponent;
|
||||
let fixture: ComponentFixture<EpgListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [EpgListComponent],
|
||||
imports: [MockModule(MatListModule)],
|
||||
providers: [
|
||||
{ provide: ElectronService, useClass: ElectronServiceStub },
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EpgListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Component, NgZone } from '@angular/core';
|
||||
import { EpgChannel } from '../../models/epg-channel.model';
|
||||
import { EpgProgram } from '../../models/epg-program.model';
|
||||
import * as moment from 'moment';
|
||||
import { ElectronService } from '../../../services/electron.service';
|
||||
import { EPG_GET_PROGRAM_DONE } from '../../../shared/ipc-commands';
|
||||
|
||||
interface EpgData {
|
||||
channel: EpgChannel;
|
||||
items: EpgProgram[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-epg-list',
|
||||
templateUrl: './epg-list.component.html',
|
||||
styleUrls: ['./epg-list.component.scss'],
|
||||
})
|
||||
export class EpgListComponent {
|
||||
/** Channel info in EPG format */
|
||||
channel: EpgChannel;
|
||||
|
||||
/** Today as formatted date string */
|
||||
dateToday: string;
|
||||
|
||||
/** Array with EPG programs */
|
||||
items: EpgProgram[] = [];
|
||||
|
||||
/** Object with epg programs for the active channel */
|
||||
programs: {
|
||||
payload: EpgData;
|
||||
};
|
||||
|
||||
/** Current time as formatted string */
|
||||
timeNow: string;
|
||||
|
||||
/**
|
||||
* Creates an instance of EpgListComponent
|
||||
* @param electronService
|
||||
* @param ngZone
|
||||
*/
|
||||
constructor(
|
||||
private electronService: ElectronService,
|
||||
private ngZone: NgZone
|
||||
) {
|
||||
this.electronService.ipcRenderer.on(
|
||||
EPG_GET_PROGRAM_DONE,
|
||||
(event, response) => {
|
||||
this.ngZone.run(() => this.handleEpgData(response));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.timeNow = moment(Date.now()).format('HH:mm');
|
||||
this.dateToday = moment(Date.now()).format('YYYYMMDD');
|
||||
this.channel = programs.payload?.channel;
|
||||
this.items = programs.payload?.items
|
||||
.filter((item) =>
|
||||
item.start.includes(this.dateToday.toString())
|
||||
)
|
||||
.map((program) => ({
|
||||
...program,
|
||||
start: moment(program.start, 'YYYYMMDDHHmm ZZ').format(
|
||||
'HH:mm'
|
||||
),
|
||||
stop: moment(program.stop, 'YYYYMMDDHHmm ZZ').format(
|
||||
'HH:mm'
|
||||
),
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
return a.start.localeCompare(b.start);
|
||||
});
|
||||
} else {
|
||||
this.items = [];
|
||||
this.channel = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all ipc renderer listeners after destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.electronService.ipcRenderer.removeAllListeners(
|
||||
EPG_GET_PROGRAM_DONE
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@
|
||||
<mat-toolbar color="primary">
|
||||
<button
|
||||
mat-icon-button
|
||||
mat-toolbar="Add to favorites"
|
||||
(click)="drawer.toggle()"
|
||||
matTooltip="Open channels list"
|
||||
>
|
||||
@@ -40,8 +39,16 @@
|
||||
</button>
|
||||
{{ activeChannel.name }}
|
||||
<div class="spacer"></div>
|
||||
<button mat-icon-button (click)="openAbout()">
|
||||
<!-- <button mat-icon-button (click)="openAbout()">
|
||||
<mat-icon>info</mat-icon>
|
||||
</button> -->
|
||||
<button
|
||||
*ngIf="epgAvailable$ | async"
|
||||
mat-button
|
||||
(click)="drawerRight.toggle()"
|
||||
matTooltip="Open EPG list"
|
||||
>
|
||||
EPG
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
@@ -65,4 +72,14 @@
|
||||
>
|
||||
</app-html-video-player>
|
||||
</mat-drawer-content>
|
||||
<!-- right sidebar content -->
|
||||
<mat-drawer
|
||||
position="end"
|
||||
#drawerRight
|
||||
mode="side"
|
||||
(keydown.escape)="close()"
|
||||
disableClose
|
||||
>
|
||||
<app-epg-list></app-epg-list>
|
||||
</mat-drawer>
|
||||
</mat-drawer-container>
|
||||
@@ -12,6 +12,7 @@ import { ChannelStore } from '../../../state/channel.store';
|
||||
import * as MOCKED_PLAYLIST from '../../../../mocks/playlist.json';
|
||||
import { createChannel } from '../../../state';
|
||||
import { HtmlVideoPlayerComponent } from '../html-video-player/html-video-player.component';
|
||||
import { EpgListComponent } from '../epg-list/epg-list.component';
|
||||
|
||||
class MatSnackBarStub {
|
||||
open(): void {}
|
||||
@@ -26,6 +27,7 @@ describe('VideoPlayerComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
MockComponent(EpgListComponent),
|
||||
MockComponent(HtmlVideoPlayerComponent),
|
||||
MockComponent(VjsPlayerComponent),
|
||||
MockComponent(VideoPlayerComponent),
|
||||
|
||||
@@ -31,6 +31,11 @@ export class VideoPlayerComponent implements OnInit {
|
||||
/** Name of the selected channel */
|
||||
channelTitle: string;
|
||||
|
||||
/** EPG availability flag */
|
||||
epgAvailable$: Observable<boolean> = this.channelQuery.select(
|
||||
(store) => store.epgAvailable
|
||||
);
|
||||
|
||||
/** Favorites list */
|
||||
favorites$: Observable<string[]> = this.channelQuery.select(
|
||||
(store) => store.favorites
|
||||
@@ -42,6 +47,9 @@ export class VideoPlayerComponent implements OnInit {
|
||||
/** Sidebar object */
|
||||
@ViewChild('sidenav') sideNav: MatSidenav;
|
||||
|
||||
// DEV
|
||||
epgPrograms = [];
|
||||
|
||||
/**
|
||||
* Creates an instance of VideoPlayerComponent
|
||||
* @param channelQuery akita's channel query
|
||||
|
||||
@@ -6,6 +6,7 @@ import { VideoPlayerComponent } from './components/video-player/video-player.com
|
||||
import { VjsPlayerComponent } from './components/vjs-player/vjs-player.component';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
import { EpgListComponent } from './components/epg-list/epg-list.component';
|
||||
|
||||
const routes: Routes = [{ path: '', component: VideoPlayerComponent }];
|
||||
|
||||
@@ -13,6 +14,7 @@ const routes: Routes = [{ path: '', component: VideoPlayerComponent }];
|
||||
imports: [CommonModule, RouterModule.forChild(routes), SharedModule],
|
||||
declarations: [
|
||||
ChannelListContainerComponent,
|
||||
EpgListComponent,
|
||||
HtmlVideoPlayerComponent,
|
||||
VideoPlayerComponent,
|
||||
VjsPlayerComponent,
|
||||
|
||||
@@ -12,6 +12,35 @@
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<mat-list>
|
||||
<mat-list-item>
|
||||
<div matLine fxLayout="row">
|
||||
<p fxFlex>EPG Url (*.xml or *.xml.gz)</p>
|
||||
<p fxFlex fxLayoutAlign="start center">
|
||||
<mat-form-field class="full-width">
|
||||
<input
|
||||
placeholder="Enter a URL"
|
||||
matInput
|
||||
type="url"
|
||||
formControlName="epgUrl"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button
|
||||
*ngIf="
|
||||
(epgAvailable$ | async) &&
|
||||
settingsForm.value.epgUrl
|
||||
"
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
matTooltip="Refresh EPG"
|
||||
type="button"
|
||||
(click)="fetchEpg()"
|
||||
>
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
<p matLine></p>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<div matLine fxLayout="row">
|
||||
<p fxFlex>Video player</p>
|
||||
|
||||
@@ -3,11 +3,13 @@ import { FormGroup, FormBuilder } from '@angular/forms';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Settings } from './settings.interface';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import * as semver from 'semver';
|
||||
import { ElectronService } from 'app/services/electron.service';
|
||||
import { ChannelQuery } from 'app/state';
|
||||
import { EPG_FETCH } from 'app/shared/ipc-commands';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
@@ -43,14 +45,23 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
/** Update message to show */
|
||||
updateMessage: string;
|
||||
|
||||
/** EPG availability flag */
|
||||
epgAvailable$: Observable<boolean> = this.channelQuery.select(
|
||||
(store) => store.epgAvailable
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates an instance of SettingsComponent and injects some dependencies into the component
|
||||
* @param channelQuery
|
||||
* @param electronService
|
||||
* @param formBuilder
|
||||
* @param http
|
||||
* @param router
|
||||
* @param snackBar
|
||||
* @param storage
|
||||
*/
|
||||
constructor(
|
||||
private channelQuery: ChannelQuery,
|
||||
private electronService: ElectronService,
|
||||
private formBuilder: FormBuilder,
|
||||
private http: HttpClient,
|
||||
@@ -60,6 +71,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
) {
|
||||
this.settingsForm = this.formBuilder.group({
|
||||
player: ['html5'], // default value
|
||||
epgUrl: '',
|
||||
});
|
||||
|
||||
this.subscription.add(
|
||||
@@ -88,7 +100,10 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
this.subscription.add(
|
||||
this.storage.get('settings').subscribe((settings: Settings) => {
|
||||
if (settings) {
|
||||
this.settingsForm.setValue(settings);
|
||||
this.settingsForm.setValue({
|
||||
player: settings.player ? settings.player : 'html5',
|
||||
epgUrl: settings.epgUrl ? settings.epgUrl : '',
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -110,6 +125,10 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
duration: 2000,
|
||||
}
|
||||
);
|
||||
// check whether the epg url was changed or not
|
||||
if (this.settingsForm.value.epgUrl) {
|
||||
this.fetchEpg();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -127,4 +146,17 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
ngOnDestroy(): void {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and updates EPG from the given URL
|
||||
*/
|
||||
fetchEpg(): void {
|
||||
this.electronService.ipcRenderer.send(EPG_FETCH, {
|
||||
url: this.settingsForm.value.epgUrl,
|
||||
});
|
||||
this.snackBar.open('Fetch EPG data...', 'Close', {
|
||||
verticalPosition: 'bottom',
|
||||
horizontalPosition: 'right',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,5 @@ export type VideoPlayerType = 'html5' | 'videojs';
|
||||
*/
|
||||
export interface Settings {
|
||||
player: VideoPlayerType;
|
||||
epgUrl: string;
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { EntityState, EntityStore, StoreConfig } from '@datorama/akita';
|
||||
import { EPG_GET_PROGRAM } from '../shared/ipc-commands';
|
||||
import { ElectronService } from '../services/electron.service';
|
||||
import { Channel } from './channel.model';
|
||||
|
||||
export interface ChannelState extends EntityState<Channel> {
|
||||
active: Channel;
|
||||
epgAvailable: boolean;
|
||||
favorites: string[];
|
||||
playlistId: string;
|
||||
active: Channel;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -14,13 +16,14 @@ export interface ChannelState extends EntityState<Channel> {
|
||||
export class ChannelStore extends EntityStore<ChannelState> {
|
||||
/**
|
||||
* Creates an instance of ChannelStore
|
||||
* @param channelStore channels store
|
||||
* @param electronService electron service
|
||||
*/
|
||||
constructor(private electronService: ElectronService) {
|
||||
super({
|
||||
active: undefined,
|
||||
epgAvailable: false,
|
||||
favorites: [],
|
||||
playlistId: '',
|
||||
active: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,4 +48,18 @@ export class ChannelStore extends EntityStore<ChannelState> {
|
||||
return { favorites };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates selected/active channel in store
|
||||
* @param channel selected channel
|
||||
*/
|
||||
setActiveChannel(channel: Channel): void {
|
||||
this.update((store) => ({
|
||||
...store,
|
||||
active: channel,
|
||||
}));
|
||||
this.electronService.ipcRenderer.send(EPG_GET_PROGRAM, {
|
||||
channelName: channel.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user