feat(stalker): refactor epg handling to use get_epg_info

- Implemented unit tests for StalkerCollectionChannelsListComponent to verify EPG program loading and state management.
- Created unit tests for StalkerLiveStreamLayoutComponent to ensure proper rendering of EPG lists and handling of channel playback.
- Refactored EPG loading logic in StalkerLiveStreamLayoutComponent to improve performance and maintainability.
- Updated HTML template in StalkerLiveStreamLayoutComponent to replace deprecated components with new EPG list component.
- Enhanced EPG program handling by introducing methods for timestamp parsing and current program detection.
This commit is contained in:
4gray committed 2026-04-11 10:43:50 +02:00
1 parent bf427f7226
commit 545dfd8c67
14 files changed
+1542 -255

No files matched your search

@@ -135,15 +135,6 @@ function logoUrl(seed: string): string {
return `https://picsum.photos/seed/logo-${seed}/100/100`;
}
function isoNow(offsetMinutes: number): string {
const d = new Date(Date.now() + offsetMinutes * 60 * 1000);
return d.toISOString();
}
function unixNow(offsetMinutes: number): number {
return Math.floor((Date.now() + offsetMinutes * 60 * 1000) / 1000);
}
// ---------------------------------------------------------------------------
// Generator
// ---------------------------------------------------------------------------
@@ -390,29 +381,44 @@ export function generateSeasons(
// EPG generator
// ---------------------------------------------------------------------------
const EPG_PROGRAM_TYPES = ['News', 'Movie', 'Documentary', 'Entertainment', 'Sports', 'Kids', 'Series'];
const EPG_PROGRAM_TYPES = [
'News',
'Movie',
'Documentary',
'Entertainment',
'Sports',
'Kids',
'Series',
];
export function generateEpg(channelName: string): RawEpgProgram[] {
const programs: RawEpgProgram[] = [];
// Generate 12 programs: 6 past, current, 5 future (30-min slots)
const SLOT_MINUTES = 30;
const startOffset = -6 * SLOT_MINUTES; // start 3 hours ago
const SLOT_MINUTES = 120;
const SLOTS_PER_DAY = (24 * 60) / SLOT_MINUTES;
const TOTAL_DAYS = 7;
const dayStart = new Date();
dayStart.setUTCHours(0, 0, 0, 0);
for (let i = 0; i < 12; i++) {
const startMin = startOffset + i * SLOT_MINUTES;
const stopMin = startMin + SLOT_MINUTES;
for (let i = 0; i < SLOTS_PER_DAY * TOTAL_DAYS; i++) {
const startDate = new Date(
dayStart.getTime() + i * SLOT_MINUTES * 60 * 1000
);
const stopDate = new Date(
startDate.getTime() + SLOT_MINUTES * 60 * 1000
);
const category = EPG_PROGRAM_TYPES[i % EPG_PROGRAM_TYPES.length];
programs.push({
id: String(i + 1),
name: `${channelName}: ${faker.company.catchPhrase()}`,
start: isoNow(startMin),
stop: isoNow(stopMin),
start_timestamp: unixNow(startMin),
stop_timestamp: unixNow(stopMin),
start: startDate.toISOString(),
stop: stopDate.toISOString(),
start_timestamp: Math.floor(startDate.getTime() / 1000),
stop_timestamp: Math.floor(stopDate.getTime() / 1000),
descr: faker.lorem.sentence(),
category,
});
}
return programs;
}
@@ -0,0 +1,37 @@
import { Request, Response } from 'express';
import { getPortalData } from '../data-store.js';
import { extractMac } from './get-categories.handler.js';
/**
* Stalker get_epg_info — returns bulk EPG keyed by channel id.
*
* Query params:
* period: number of future hours to include (default 168)
*/
export function handleGetEpgInfo(req: Request, res: Response): void {
const mac = extractMac(req);
const period = parseInt((req.query['period'] as string) ?? '168', 10);
const data = getPortalData(mac);
const now = Math.floor(Date.now() / 1000);
const currentDayStart = new Date();
currentDayStart.setUTCHours(0, 0, 0, 0);
const startTimestamp = Math.floor(currentDayStart.getTime() / 1000);
const endTimestamp = now + Math.max(period, 1) * 60 * 60;
const epgByChannel = Object.fromEntries(
[...data.epg.entries()].map(([channelId, programs]) => [
channelId,
programs.filter(
(program) =>
program.stop_timestamp >= startTimestamp &&
program.start_timestamp <= endTimestamp
),
])
);
res.json({
js: {
data: epgByChannel,
},
});
}
@@ -24,9 +24,15 @@ export function handleGetShortEpg(req: Request, res: Response): void {
data.epg.set(channelId, programs);
}
const now = Math.floor(Date.now() / 1000);
const currentIndex = programs.findIndex(
(program) => program.stop_timestamp > now
);
const startIndex = currentIndex >= 0 ? currentIndex : 0;
res.json({
js: {
data: programs.slice(0, size),
data: programs.slice(startIndex, startIndex + size),
},
});
}
@@ -6,6 +6,7 @@ import { handleGetOrderedList } from '../handlers/get-ordered-list.handler.js';
import { handleGetSeasons } from '../handlers/get-seasons.handler.js';
import { handleCreateLink } from '../handlers/create-link.handler.js';
import { handleFavorites } from '../handlers/favorites.handler.js';
import { handleGetEpgInfo } from '../handlers/get-epg-info.handler.js';
import { handleGetShortEpg } from '../handlers/get-short-epg.handler.js';
import { handleGetGenres } from '../handlers/get-genres.handler.js';
@@ -45,9 +46,11 @@ export default function dispatchPortalAction(req: Request, res: Response): void
handleFavorites(req, res);
break;
case 'get_short_epg':
case 'get_epg_info':
handleGetShortEpg(req, res);
break;
case 'get_epg_info':
handleGetEpgInfo(req, res);
break;
default:
console.warn(`[portal] Unknown action: ${action}`);
res.json({ js: { error: `Unknown action: ${action}` } });