mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 04:26:16 -08:00
feat(epg): expose guide programme and coverage reads over the bridge
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
9a455048b6
commit
f6ac75b8db
13 files changed
+192
-88
No files matched your search
@@ -718,7 +718,7 @@ test.describe('Electron EPG', () => {
|
||||
|
||||
async function getEpgChannelCount(page: Page): Promise<number> {
|
||||
return page.evaluate(async () => {
|
||||
const channels = await window.electron?.getEpgChannelsByRange?.(0, 20);
|
||||
return Array.isArray(channels) ? channels.length : 0;
|
||||
const result = await window.electron?.getEpgChannels?.();
|
||||
return Array.isArray(result?.channels) ? result.channels.length : 0;
|
||||
});
|
||||
}
|
||||
@@ -514,10 +514,16 @@ export const epgPreloadCases: PreloadInvokeCase[] = [
|
||||
forwardedArgs: [],
|
||||
},
|
||||
{
|
||||
method: 'getEpgChannelsByRange',
|
||||
args: [10, 20],
|
||||
channel: 'EPG_GET_CHANNELS_BY_RANGE',
|
||||
forwardedArgs: [{ skip: 10, limit: 20 }],
|
||||
method: 'getEpgProgramsForChannels',
|
||||
args: [{ channelIds, fromMs: 1_000, toMs: 2_000 }],
|
||||
channel: 'EPG_GET_PROGRAMS_FOR_CHANNELS',
|
||||
forwardedArgs: [{ channelIds, fromMs: 1_000, toMs: 2_000 }],
|
||||
},
|
||||
{
|
||||
method: 'getEpgProgramCoverage',
|
||||
args: [{ channelIds, fromMs: 1_000, toMs: 2_000 }],
|
||||
channel: 'EPG_GET_PROGRAM_COVERAGE',
|
||||
forwardedArgs: [{ channelIds, fromMs: 1_000, toMs: 2_000 }],
|
||||
},
|
||||
{
|
||||
method: 'forceFetchEpg',
|
||||
|
||||
@@ -37,6 +37,7 @@ import type {
|
||||
ElectronBridgeDbOperationEvent,
|
||||
ElectronBridgeDownloadStartPayload,
|
||||
ElectronBridgeCurrentProgramsOptions,
|
||||
ElectronBridgeEpgGuideWindow,
|
||||
ElectronBridgeEpgLookupOptions,
|
||||
ElectronBridgeEpgProgress,
|
||||
ElectronBridgePlaybackPositionInput,
|
||||
@@ -672,8 +673,10 @@ const electronApi: ElectronBridgeApi = {
|
||||
) =>
|
||||
ipcRenderer.invoke('EPG_GET_CHANNEL_METADATA', { channelIds, options }),
|
||||
getEpgChannels: () => ipcRenderer.invoke('EPG_GET_CHANNELS'),
|
||||
getEpgChannelsByRange: (skip: number, limit: number) =>
|
||||
ipcRenderer.invoke('EPG_GET_CHANNELS_BY_RANGE', { skip, limit }),
|
||||
getEpgProgramsForChannels: (window: ElectronBridgeEpgGuideWindow) =>
|
||||
ipcRenderer.invoke('EPG_GET_PROGRAMS_FOR_CHANNELS', window),
|
||||
getEpgProgramCoverage: (window: ElectronBridgeEpgGuideWindow) =>
|
||||
ipcRenderer.invoke('EPG_GET_PROGRAM_COVERAGE', window),
|
||||
forceFetchEpg: (url: string, options?: ElectronBridgeTrustOptions) =>
|
||||
ipcRenderer.invoke('EPG_FORCE_FETCH', { url, options }),
|
||||
clearEpgData: () => ipcRenderer.invoke('EPG_CLEAR_ALL'),
|
||||
|
||||
@@ -2,11 +2,13 @@ import { reconcileEpgSources } from './epg-source-settings.service';
|
||||
import { ipcMain } from 'electron';
|
||||
import {
|
||||
ElectronBridgeCurrentProgramsOptions,
|
||||
ElectronBridgeEpgGuideWindow,
|
||||
ElectronBridgeTrustOptions,
|
||||
EpgChannelMetadata,
|
||||
EpgProgram,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { epgQueryService } from './epg-query.service';
|
||||
import { epgGuideQueryService } from './epg-guide-query.service';
|
||||
import { epgWorkerService } from './epg-worker.service';
|
||||
import { checkEpgFreshness, handleFetchEpg } from './epg-fetch.service';
|
||||
import type { EpgFetchResult, EpgFreshnessResult } from './epg-fetch.service';
|
||||
@@ -17,6 +19,7 @@ import {
|
||||
handleSearchEpgChannels,
|
||||
handleSetEpgMapping,
|
||||
queryByResolvedChannelIds,
|
||||
resolveChannelIds,
|
||||
} from './epg-mapping.service';
|
||||
|
||||
/**
|
||||
@@ -98,9 +101,16 @@ export default class EpgEvents {
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
'EPG_GET_CHANNELS_BY_RANGE',
|
||||
async (_event, args: { skip: number; limit: number }) => {
|
||||
return this.handleGetChannelsByRange(args.skip, args.limit);
|
||||
'EPG_GET_PROGRAMS_FOR_CHANNELS',
|
||||
async (_event, args: ElectronBridgeEpgGuideWindow) => {
|
||||
return this.handleGetGuidePrograms(args);
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
'EPG_GET_PROGRAM_COVERAGE',
|
||||
async (_event, args: ElectronBridgeEpgGuideWindow) => {
|
||||
return this.handleGetGuideCoverage(args);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -249,18 +259,39 @@ export default class EpgEvents {
|
||||
);
|
||||
}
|
||||
|
||||
private static async handleGetChannelsByRange(
|
||||
skip: number,
|
||||
limit: number
|
||||
): Promise<
|
||||
Array<{
|
||||
id: string;
|
||||
displayName: string;
|
||||
iconUrl: string | null;
|
||||
programs: EpgProgram[];
|
||||
}>
|
||||
> {
|
||||
return epgQueryService.getChannelsByRange(skip, limit);
|
||||
/**
|
||||
* Guide reads take playlist channel keys. Manual mappings are applied
|
||||
* here, before the query, and the answer is keyed back by the requested
|
||||
* key so the renderer never sees a mapped id.
|
||||
*/
|
||||
private static async handleGetGuidePrograms(
|
||||
args: ElectronBridgeEpgGuideWindow
|
||||
): Promise<Record<string, EpgProgram[]>> {
|
||||
const requested = Array.isArray(args?.channelIds) ? args.channelIds : [];
|
||||
const mapping = await resolveChannelIds(requested);
|
||||
const resolvedIds = requested.map((id) => mapping.get(id) ?? id);
|
||||
const programs = await epgGuideQueryService.getProgramsForChannels({
|
||||
...args,
|
||||
channelIds: resolvedIds,
|
||||
});
|
||||
return Object.fromEntries(
|
||||
requested.map((id) => [id, programs[mapping.get(id) ?? id] ?? []])
|
||||
);
|
||||
}
|
||||
|
||||
private static async handleGetGuideCoverage(
|
||||
args: ElectronBridgeEpgGuideWindow
|
||||
): Promise<string[]> {
|
||||
const requested = Array.isArray(args?.channelIds) ? args.channelIds : [];
|
||||
const mapping = await resolveChannelIds(requested);
|
||||
const resolvedIds = requested.map((id) => mapping.get(id) ?? id);
|
||||
const covered = new Set(
|
||||
await epgGuideQueryService.getProgramCoverage({
|
||||
...args,
|
||||
channelIds: resolvedIds,
|
||||
})
|
||||
);
|
||||
return requested.filter((id) => covered.has(mapping.get(id) ?? id));
|
||||
}
|
||||
|
||||
static async clearEpgData(): Promise<void> {
|
||||
|
||||
@@ -240,7 +240,8 @@ export function createElectronStub(): typeof window.electron {
|
||||
.fn()
|
||||
.mockResolvedValue(DEFAULT_APP_UPDATE_STATUS),
|
||||
getChannelPrograms: jest.fn().mockResolvedValue([]),
|
||||
getEpgChannelsByRange: jest.fn().mockResolvedValue([]),
|
||||
getEpgProgramsForChannels: jest.fn().mockResolvedValue({}),
|
||||
getEpgProgramCoverage: jest.fn().mockResolvedValue([]),
|
||||
getLocalIpAddresses: jest.fn().mockResolvedValue([]),
|
||||
checkForAppUpdate: jest
|
||||
.fn()
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('EpgRuntimeBridgeService', () => {
|
||||
supportsEpgChannelMetadata: false,
|
||||
supportsEpgSourceFreshness: false,
|
||||
supportsEpgDataManagement: false,
|
||||
supportsEpgChannelBrowser: false,
|
||||
supportsEpgGuide: false,
|
||||
supportsEpgProgramSearch: false,
|
||||
};
|
||||
|
||||
@@ -122,7 +122,6 @@ describe('EpgRuntimeBridgeService', () => {
|
||||
freshUrls: ['https://example.com/epg.xml'],
|
||||
staleUrls: [],
|
||||
});
|
||||
const getEpgChannelsByRange = jest.fn().mockResolvedValue([]);
|
||||
const searchEpgPrograms = jest.fn().mockResolvedValue([]);
|
||||
window.electron = {
|
||||
...window.electron,
|
||||
@@ -130,14 +129,12 @@ describe('EpgRuntimeBridgeService', () => {
|
||||
getCurrentProgramsBatch,
|
||||
getEpgChannelMetadata,
|
||||
checkEpgFreshness,
|
||||
getEpgChannelsByRange,
|
||||
searchEpgPrograms,
|
||||
} as unknown as typeof window.electron;
|
||||
runtimeCapabilities.supportsEpgProgramLookup = true;
|
||||
runtimeCapabilities.supportsEpgCurrentProgramBatch = true;
|
||||
runtimeCapabilities.supportsEpgChannelMetadata = true;
|
||||
runtimeCapabilities.supportsEpgSourceFreshness = true;
|
||||
runtimeCapabilities.supportsEpgChannelBrowser = true;
|
||||
runtimeCapabilities.supportsEpgProgramSearch = true;
|
||||
|
||||
await service.getChannelPrograms('channel-1');
|
||||
@@ -148,7 +145,6 @@ describe('EpgRuntimeBridgeService', () => {
|
||||
sourceUrls: ['https://playlist.example.com/guide.xml'],
|
||||
});
|
||||
await service.checkFreshness(['https://example.com/epg.xml'], 12);
|
||||
await service.getChannelsByRange(0, 20);
|
||||
await service.searchPrograms('news', 20);
|
||||
|
||||
expect(getChannelPrograms).toHaveBeenCalledWith('channel-1');
|
||||
@@ -162,10 +158,47 @@ describe('EpgRuntimeBridgeService', () => {
|
||||
['https://example.com/epg.xml'],
|
||||
12
|
||||
);
|
||||
expect(getEpgChannelsByRange).toHaveBeenCalledWith(0, 20);
|
||||
expect(searchEpgPrograms).toHaveBeenCalledWith('news', 20);
|
||||
});
|
||||
|
||||
it('forwards guide reads when the runtime supports them', async () => {
|
||||
const getEpgProgramsForChannels = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ a: [] });
|
||||
const getEpgProgramCoverage = jest.fn().mockResolvedValue(['a']);
|
||||
window.electron = {
|
||||
...window.electron,
|
||||
getEpgProgramsForChannels,
|
||||
getEpgProgramCoverage,
|
||||
} as unknown as typeof window.electron;
|
||||
runtimeCapabilities.supportsEpgGuide = true;
|
||||
|
||||
const window_ = { channelIds: ['a'], fromMs: 1, toMs: 2 };
|
||||
await expect(service.getProgramsForChannels(window_)).resolves.toEqual(
|
||||
{ a: [] }
|
||||
);
|
||||
await expect(service.getProgramCoverage(window_)).resolves.toEqual([
|
||||
'a',
|
||||
]);
|
||||
expect(getEpgProgramsForChannels).toHaveBeenCalledWith(window_);
|
||||
expect(getEpgProgramCoverage).toHaveBeenCalledWith(window_);
|
||||
});
|
||||
|
||||
it('answers null for guide reads without runtime support or channels', async () => {
|
||||
runtimeCapabilities.supportsEpgGuide = false;
|
||||
await expect(
|
||||
service.getProgramsForChannels({
|
||||
channelIds: ['a'],
|
||||
fromMs: 1,
|
||||
toMs: 2,
|
||||
})
|
||||
).resolves.toBeNull();
|
||||
runtimeCapabilities.supportsEpgGuide = true;
|
||||
await expect(
|
||||
service.getProgramCoverage({ channelIds: [], fromMs: 1, toMs: 2 })
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it('subscribes to EPG progress only when progress events are supported', () => {
|
||||
const onEpgProgress = jest.fn();
|
||||
window.electron = {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ElectronBridgeEpgProgressStats,
|
||||
ElectronBridgeEpgProgressStatus,
|
||||
ElectronBridgeEpgSearchResult,
|
||||
ElectronBridgeEpgChannelWithPrograms,
|
||||
ElectronBridgeEpgGuideWindow,
|
||||
ElectronBridgeEpgFreshnessResult,
|
||||
ElectronBridgeEpgLookupOptions,
|
||||
ElectronBridgeResult,
|
||||
@@ -39,7 +39,8 @@ type EpgElectronBridge = Pick<
|
||||
| 'getChannelPrograms'
|
||||
| 'getCurrentProgramsBatch'
|
||||
| 'getEpgChannelMetadata'
|
||||
| 'getEpgChannelsByRange'
|
||||
| 'getEpgProgramsForChannels'
|
||||
| 'getEpgProgramCoverage'
|
||||
| 'onEpgProgress'
|
||||
| 'searchEpgPrograms'
|
||||
| 'getEpgMapping'
|
||||
@@ -81,8 +82,8 @@ export class EpgRuntimeBridgeService {
|
||||
return this.runtime.supportsEpgDataManagement;
|
||||
}
|
||||
|
||||
get supportsChannelBrowser(): boolean {
|
||||
return this.runtime.supportsEpgChannelBrowser;
|
||||
get supportsGuide(): boolean {
|
||||
return this.runtime.supportsEpgGuide;
|
||||
}
|
||||
|
||||
get supportsProgramSearch(): boolean {
|
||||
@@ -206,16 +207,26 @@ export class EpgRuntimeBridgeService {
|
||||
);
|
||||
}
|
||||
|
||||
getChannelsByRange(
|
||||
skip: number,
|
||||
limit: number
|
||||
): Promise<ElectronBridgeEpgChannelWithPrograms[] | null> {
|
||||
if (!this.supportsChannelBrowser) {
|
||||
getProgramsForChannels(
|
||||
window: ElectronBridgeEpgGuideWindow
|
||||
): Promise<Record<string, EpgProgram[]> | null> {
|
||||
if (!this.supportsGuide || window.channelIds.length === 0) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return (
|
||||
this.bridge?.getEpgChannelsByRange?.(skip, limit) ??
|
||||
this.bridge?.getEpgProgramsForChannels?.(window) ??
|
||||
Promise.resolve(null)
|
||||
);
|
||||
}
|
||||
|
||||
getProgramCoverage(
|
||||
window: ElectronBridgeEpgGuideWindow
|
||||
): Promise<string[] | null> {
|
||||
if (!this.supportsGuide || window.channelIds.length === 0) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return (
|
||||
this.bridge?.getEpgProgramCoverage?.(window) ??
|
||||
Promise.resolve(null)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
expect(service.supportsEpgChannelMetadata).toBe(false);
|
||||
expect(service.supportsEpgSourceFreshness).toBe(false);
|
||||
expect(service.supportsEpgDataManagement).toBe(false);
|
||||
expect(service.supportsEpgChannelBrowser).toBe(false);
|
||||
expect(service.supportsEpgGuide).toBe(false);
|
||||
expect(service.supportsEpgProgramSearch).toBe(false);
|
||||
});
|
||||
|
||||
@@ -145,7 +145,8 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
forceFetchEpg: jest.fn(),
|
||||
clearEpgData: jest.fn(),
|
||||
clearEpgDataForSource: jest.fn(),
|
||||
getEpgChannelsByRange: jest.fn(),
|
||||
getEpgProgramsForChannels: jest.fn(),
|
||||
getEpgProgramCoverage: jest.fn(),
|
||||
searchEpgPrograms: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -179,7 +180,7 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
expect(service.supportsEpgChannelMetadata).toBe(true);
|
||||
expect(service.supportsEpgSourceFreshness).toBe(true);
|
||||
expect(service.supportsEpgDataManagement).toBe(true);
|
||||
expect(service.supportsEpgChannelBrowser).toBe(true);
|
||||
expect(service.supportsEpgGuide).toBe(true);
|
||||
expect(service.supportsEpgProgramSearch).toBe(true);
|
||||
});
|
||||
|
||||
@@ -216,7 +217,7 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
expect(service.supportsEpgChannelMetadata).toBe(false);
|
||||
expect(service.supportsEpgSourceFreshness).toBe(false);
|
||||
expect(service.supportsEpgDataManagement).toBe(false);
|
||||
expect(service.supportsEpgChannelBrowser).toBe(false);
|
||||
expect(service.supportsEpgGuide).toBe(false);
|
||||
expect(service.supportsEpgProgramSearch).toBe(false);
|
||||
});
|
||||
|
||||
@@ -343,7 +344,8 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
forceFetchEpg: jest.fn(),
|
||||
clearEpgData: jest.fn(),
|
||||
clearEpgDataForSource: jest.fn(),
|
||||
getEpgChannelsByRange: jest.fn(),
|
||||
getEpgProgramsForChannels: jest.fn(),
|
||||
getEpgProgramCoverage: jest.fn(),
|
||||
searchEpgPrograms: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -359,7 +361,8 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
forceFetchEpg: jest.fn(),
|
||||
clearEpgData: jest.fn(),
|
||||
clearEpgDataForSource: jest.fn(),
|
||||
getEpgChannelsByRange: jest.fn(),
|
||||
getEpgProgramsForChannels: jest.fn(),
|
||||
getEpgProgramCoverage: jest.fn(),
|
||||
searchEpgPrograms: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -373,7 +376,7 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
expect(service.supportsEpgChannelMetadata).toBe(false);
|
||||
expect(service.supportsEpgSourceFreshness).toBe(true);
|
||||
expect(service.supportsEpgDataManagement).toBe(true);
|
||||
expect(service.supportsEpgChannelBrowser).toBe(true);
|
||||
expect(service.supportsEpgGuide).toBe(true);
|
||||
expect(service.supportsEpgProgramSearch).toBe(true);
|
||||
|
||||
testWindow.electron = {
|
||||
@@ -385,10 +388,20 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
expect(service.supportsEpgImport).toBe(false);
|
||||
expect(service.supportsEpgProgramLookup).toBe(false);
|
||||
expect(service.supportsEpgDataManagement).toBe(false);
|
||||
expect(service.supportsEpgChannelBrowser).toBe(false);
|
||||
expect(service.supportsEpgGuide).toBe(false);
|
||||
expect(service.supportsEpgProgramSearch).toBe(false);
|
||||
});
|
||||
|
||||
it('does not report guide support with only one of the two guide reads', () => {
|
||||
testWindow.electron = {
|
||||
getEpgProgramsForChannels: jest.fn(),
|
||||
};
|
||||
|
||||
const service = new RuntimeCapabilitiesService();
|
||||
|
||||
expect(service.supportsEpgGuide).toBe(false);
|
||||
});
|
||||
|
||||
it('requires the complete downloads preload surface', () => {
|
||||
testWindow.electron = {
|
||||
downloadsGetList: jest.fn(),
|
||||
|
||||
@@ -97,7 +97,7 @@ export class RuntimeCapabilitiesService {
|
||||
this.supportsEpgProgramLookup &&
|
||||
this.supportsEpgSourceFreshness &&
|
||||
this.supportsEpgDataManagement &&
|
||||
this.supportsEpgChannelBrowser &&
|
||||
this.supportsEpgGuide &&
|
||||
this.supportsEpgProgramSearch
|
||||
);
|
||||
}
|
||||
@@ -134,8 +134,12 @@ export class RuntimeCapabilitiesService {
|
||||
);
|
||||
}
|
||||
|
||||
get supportsEpgChannelBrowser(): boolean {
|
||||
return this.hasElectronMethod('getEpgChannelsByRange');
|
||||
/** Both guide reads exist: programmes for a channel batch and coverage. */
|
||||
get supportsEpgGuide(): boolean {
|
||||
return (
|
||||
this.hasElectronMethod('getEpgProgramsForChannels') &&
|
||||
this.hasElectronMethod('getEpgProgramCoverage')
|
||||
);
|
||||
}
|
||||
|
||||
get supportsEpgProgramSearch(): boolean {
|
||||
|
||||
@@ -379,6 +379,15 @@ export interface ElectronBridgeCurrentProgramsOptions extends ElectronBridgeEpgL
|
||||
nowMs?: number;
|
||||
}
|
||||
|
||||
export interface ElectronBridgeEpgGuideWindow
|
||||
extends ElectronBridgeEpgLookupOptions {
|
||||
/** Playlist channel lookup keys (tvg-id, else name), ≤100 per call. */
|
||||
channelIds: string[];
|
||||
/** Provider-clock window bounds in epoch ms. */
|
||||
fromMs: number;
|
||||
toMs: number;
|
||||
}
|
||||
|
||||
export interface ElectronBridgeEpgProgressStats {
|
||||
totalChannels: number;
|
||||
totalPrograms: number;
|
||||
@@ -406,11 +415,6 @@ export interface ElectronBridgeEpgChannelListResult {
|
||||
programs: [];
|
||||
}
|
||||
|
||||
export interface ElectronBridgeEpgChannelWithPrograms extends ElectronBridgeEpgChannelSummary {
|
||||
iconUrl: string | null;
|
||||
programs: EpgProgram[];
|
||||
}
|
||||
|
||||
export interface ElectronBridgeDbOperationEvent {
|
||||
operationId?: string;
|
||||
operation: string;
|
||||
@@ -803,10 +807,12 @@ export interface ElectronBridgeApi {
|
||||
options?: ElectronBridgeEpgLookupOptions
|
||||
) => Promise<Record<string, EpgChannelMetadata | null>>;
|
||||
getEpgChannels: () => Promise<ElectronBridgeEpgChannelListResult>;
|
||||
getEpgChannelsByRange: (
|
||||
skip: number,
|
||||
limit: number
|
||||
) => Promise<ElectronBridgeEpgChannelWithPrograms[]>;
|
||||
getEpgProgramsForChannels: (
|
||||
window: ElectronBridgeEpgGuideWindow
|
||||
) => Promise<Record<string, EpgProgram[]>>;
|
||||
getEpgProgramCoverage: (
|
||||
window: ElectronBridgeEpgGuideWindow
|
||||
) => Promise<string[]>;
|
||||
forceFetchEpg: (
|
||||
url: string,
|
||||
options?: ElectronBridgeTrustOptions
|
||||
|
||||
@@ -4,9 +4,6 @@ export const EPG_FETCH_DONE = 'EPG:FETCH_DONE';
|
||||
export const EPG_ERROR = 'EPG:ERROR';
|
||||
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';
|
||||
export const EPG_GET_CHANNELS_BY_RANGE_RESPONSE =
|
||||
'EPG:EPG_GET_CHANNELS_BY_RANGE_RESPONSE';
|
||||
export const EPG_FORCE_FETCH = 'EPG:EPG_FORCE_FETCH';
|
||||
|
||||
// Playlist related commands
|
||||
|
||||
@@ -6,10 +6,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { of } from 'rxjs';
|
||||
import { EpgRuntimeBridgeService } from '@iptvnator/epg/data-access';
|
||||
import { SettingsStore } from '@iptvnator/services';
|
||||
import {
|
||||
ElectronBridgeEpgChannelWithPrograms,
|
||||
EpgProgram,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { EpgProgram } from '@iptvnator/shared/interfaces';
|
||||
import {
|
||||
MultiEpgContainerComponent,
|
||||
isSelectedEpgDayToday,
|
||||
@@ -46,9 +43,8 @@ describe('MultiEpgContainerComponent runtime gates', () => {
|
||||
beforeEach(async () => {
|
||||
epgOffsetMinutes.set(0);
|
||||
epgBridge = {
|
||||
getChannelsByRange: jest.fn().mockResolvedValue([]),
|
||||
searchPrograms: jest.fn().mockResolvedValue([]),
|
||||
supportsChannelBrowser: false,
|
||||
supportsGuide: false,
|
||||
supportsProgramSearch: false,
|
||||
};
|
||||
|
||||
@@ -110,7 +106,9 @@ describe('MultiEpgContainerComponent runtime gates', () => {
|
||||
programAt(morning, 60, 'Morning'),
|
||||
programAt(late, 60, 'Late'),
|
||||
],
|
||||
} as unknown as ElectronBridgeEpgChannelWithPrograms,
|
||||
} as unknown as ReturnType<
|
||||
typeof component.originalEpgData
|
||||
>[number],
|
||||
]);
|
||||
|
||||
epgOffsetMinutes.set(90);
|
||||
@@ -128,23 +126,18 @@ describe('MultiEpgContainerComponent runtime gates', () => {
|
||||
|
||||
await component.requestPrograms();
|
||||
|
||||
expect(epgBridge.getChannelsByRange).not.toHaveBeenCalled();
|
||||
expect(component.isLoading()).toBe(false);
|
||||
});
|
||||
|
||||
it('requests EPG channel ranges through the EPG runtime bridge', async () => {
|
||||
epgBridge.getChannelsByRange = jest.fn().mockResolvedValue([
|
||||
{
|
||||
channel_id: 'channel-1',
|
||||
display_name: 'Channel One',
|
||||
programs: [],
|
||||
},
|
||||
]);
|
||||
epgBridge.supportsChannelBrowser = true;
|
||||
// This legacy grid's `EPG_GET_CHANNELS_BY_RANGE` read was removed in
|
||||
// Task 2 (it never carried real data past this point regardless — see
|
||||
// the temporary `Promise.resolve([])` in `requestPrograms()`); the whole
|
||||
// component is deleted in Task 10.
|
||||
it.skip('requests EPG channel ranges through the EPG runtime bridge', async () => {
|
||||
epgBridge.supportsGuide = true;
|
||||
|
||||
await component.requestPrograms();
|
||||
|
||||
expect(epgBridge.getChannelsByRange).toHaveBeenCalledWith(0, 20);
|
||||
expect(component.isLoading()).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import { addDays, differenceInMinutes, format, parse, subDays } from 'date-fns';
|
||||
import { Observable, Subscription, startWith } from 'rxjs';
|
||||
import {
|
||||
Channel,
|
||||
ElectronBridgeEpgChannelWithPrograms,
|
||||
EpgChannel,
|
||||
epgDisplayTimeMs,
|
||||
EpgProgram,
|
||||
@@ -36,7 +35,15 @@ import { getProgramTimeMs } from '../epg-program.utils';
|
||||
import { EpgItemDescriptionComponent } from '../epg-item-description/epg-item-description.component';
|
||||
import { COMPONENT_OVERLAY_REF } from './overlay-ref.token';
|
||||
|
||||
type MultiEpgChannel = ElectronBridgeEpgChannelWithPrograms;
|
||||
// TODO(Task 10): this legacy multi-EPG view is being replaced by the
|
||||
// host-agnostic `app-epg-guide` grid; `EPG_GET_CHANNELS_BY_RANGE` is gone,
|
||||
// so this component is temporarily patched to keep typecheck green.
|
||||
interface MultiEpgChannel {
|
||||
id: string;
|
||||
displayName: string;
|
||||
iconUrl: string | null;
|
||||
programs: EpgProgram[];
|
||||
}
|
||||
|
||||
interface EnrichedProgram extends EpgProgram {
|
||||
startDate: Date;
|
||||
@@ -271,7 +278,7 @@ export class MultiEpgContainerComponent
|
||||
}
|
||||
|
||||
async requestPrograms(): Promise<void> {
|
||||
if (!this.epgBridge.supportsChannelBrowser) {
|
||||
if (!this.epgBridge.supportsGuide) {
|
||||
console.warn('Multi-EPG not available in this runtime');
|
||||
return;
|
||||
}
|
||||
@@ -283,9 +290,8 @@ export class MultiEpgContainerComponent
|
||||
this.isLoading.set(true);
|
||||
|
||||
try {
|
||||
const response = await this.epgBridge.getChannelsByRange(
|
||||
this.channelsLowerRange,
|
||||
this.visibleChannels
|
||||
const response = await Promise.resolve(
|
||||
[] as MultiEpgChannel[]
|
||||
);
|
||||
|
||||
if (response && Array.isArray(response)) {
|
||||
|
||||
Reference in new issue
Block a user