Skip to content

Commit ee5b669

Browse files
committed
chore: EPG generate optimizations
1 parent 9425b41 commit ee5b669

File tree

1 file changed

+61
-50
lines changed

1 file changed

+61
-50
lines changed

app/Http/Controllers/EpgGenerateController.php

Lines changed: 61 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -249,49 +249,57 @@ private function generate($playlist)
249249
// Get programmes from cache for date range
250250
$cachedProgrammes = $cacheService->getCachedProgrammesRange($epg, $startDate, $endDate, $epgChannelIds);
251251

252-
// Output programmes from cache
253-
foreach ($cachedProgrammes as $channelId => $programmes) {
254-
foreach ($programmes as $programme) {
255-
// Find matching channels for this EPG channel ID
256-
$filtered = array_filter($channels, fn($ch) => array_key_exists($channelId, $ch));
257-
if (!count($filtered)) {
258-
continue;
252+
// Pre-build channel mapping index for O(1) lookups
253+
$channelMapping = [];
254+
foreach ($channels as $channelMap) {
255+
foreach ($channelMap as $epgChannelId => $mappedId) {
256+
if (!isset($channelMapping[$epgChannelId])) {
257+
$channelMapping[$epgChannelId] = [];
259258
}
259+
$channelMapping[$epgChannelId][] = $mappedId;
260+
}
261+
}
260262

261-
foreach ($filtered as $ch) {
262-
$mappedChannelId = $ch[$channelId];
263+
// Output programmes from cache
264+
foreach ($cachedProgrammes as $channelId => $programmes) {
265+
// Skip if no mapping for this channel
266+
if (!isset($channelMapping[$channelId])) {
267+
continue;
268+
}
263269

264-
// Format times for XMLTV
265-
$start = $this->formatXmltvDateTime($programme['start']);
266-
$stop = $this->formatXmltvDateTime($programme['stop']);
270+
foreach ($programmes as $programme) {
271+
// Format times once per programme (not per channel mapping)
272+
$start = $this->formatXmltvDateTime($programme['start']);
273+
$stop = $this->formatXmltvDateTime($programme['stop']);
267274

268-
// Output programme tag
269-
echo ' <programme channel="' . htmlspecialchars($mappedChannelId) . '"';
270-
if ($start) echo ' start="' . $start . '"';
271-
if ($stop) echo ' stop="' . $stop . '"';
272-
echo '>' . PHP_EOL;
275+
foreach ($channelMapping[$channelId] as $mappedChannelId) {
276+
// Build programme XML in buffer for batch output
277+
$progXml = ' <programme channel="' . htmlspecialchars($mappedChannelId) . '"';
278+
if ($start) $progXml .= ' start="' . $start . '"';
279+
if ($stop) $progXml .= ' stop="' . $stop . '"';
280+
$progXml .= '>' . PHP_EOL;
273281

274282
if ($programme['title']) {
275-
echo ' <title>' . htmlspecialchars($programme['title']) . '</title>' . PHP_EOL;
283+
$progXml .= ' <title>' . htmlspecialchars($programme['title']) . '</title>' . PHP_EOL;
276284
}
277285
if ($programme['subtitle']) {
278-
echo ' <sub-title>' . htmlspecialchars($programme['subtitle']) . '</sub-title>' . PHP_EOL;
286+
$progXml .= ' <sub-title>' . htmlspecialchars($programme['subtitle']) . '</sub-title>' . PHP_EOL;
279287
}
280288
if ($programme['desc']) {
281-
echo ' <desc>' . htmlspecialchars($programme['desc']) . '</desc>' . PHP_EOL;
289+
$progXml .= ' <desc>' . htmlspecialchars($programme['desc']) . '</desc>' . PHP_EOL;
282290
}
283291
if ($programme['category']) {
284-
echo ' <category>' . htmlspecialchars($programme['category']) . '</category>' . PHP_EOL;
292+
$progXml .= ' <category>' . htmlspecialchars($programme['category']) . '</category>' . PHP_EOL;
285293
}
286294
if ($programme['episode_num']) {
287-
echo ' <episode-num system="xmltv_ns">' . htmlspecialchars($programme['episode_num']) . '</episode-num>' . PHP_EOL;
295+
$progXml .= ' <episode-num system="xmltv_ns">' . htmlspecialchars($programme['episode_num']) . '</episode-num>' . PHP_EOL;
288296
}
289297
if ($programme['icon']) {
290298
$icon = htmlspecialchars($programme['icon']);
291299
if ($logoProxyEnabled) {
292300
$icon = LogoProxyController::generateProxyUrl($icon);
293301
}
294-
echo ' <icon src="' . $icon . '"/>' . PHP_EOL;
302+
$progXml .= ' <icon src="' . $icon . '"/>' . PHP_EOL;
295303
}
296304
// Program artwork images (NEW)
297305
if (!empty($programme['images'] ?? null) && is_array($programme['images'])) {
@@ -308,17 +316,18 @@ private function generate($playlist)
308316
$orient = htmlspecialchars($image['orient'], ENT_XML1);
309317
$size = htmlspecialchars($image['size'], ENT_XML1);
310318

311-
echo " <icon src=\"{$url}\" type=\"{$type}\" width=\"{$width}\" height=\"{$height}\" orient=\"{$orient}\" size=\"{$size}\" />\n";
319+
$progXml .= " <icon src=\"{$url}\" type=\"{$type}\" width=\"{$width}\" height=\"{$height}\" orient=\"{$orient}\" size=\"{$size}\" />\n";
312320
}
313321
}
314322
if ($programme['rating']) {
315-
echo ' <rating><value>' . htmlspecialchars($programme['rating']) . '</value></rating>' . PHP_EOL;
323+
$progXml .= ' <rating><value>' . htmlspecialchars($programme['rating']) . '</value></rating>' . PHP_EOL;
316324
}
317325
if (!empty($programme['new']) && $programme['new']) {
318-
echo ' <new />' . PHP_EOL;
326+
$progXml .= ' <new />' . PHP_EOL;
319327
}
320328

321-
echo ' </programme>' . PHP_EOL;
329+
$progXml .= ' </programme>' . PHP_EOL;
330+
echo $progXml;
322331
}
323332
}
324333
}
@@ -334,41 +343,43 @@ private function generate($playlist)
334343

335344
// If dummy EPG channels, generate dummy programmes
336345
if (count($dummyEpgChannels) > 0) {
346+
// Pre-calculate all time slots once (major optimization)
347+
$timeSlots = [];
348+
$startTime = Carbon::now()->startOf('day')->subMinutes($dummyEpgLength);
349+
$iterations = (int)((5 * 24 * 60) / $dummyEpgLength);
350+
351+
for ($i = 0; $i < $iterations; $i++) {
352+
$startTime->addMinutes($dummyEpgLength);
353+
$timeSlots[] = [
354+
'start' => str_replace(':', '', $startTime->format('YmdHis P')),
355+
'end' => str_replace(':', '', $startTime->copy()->addMinutes($dummyEpgLength)->format('YmdHis P'))
356+
];
357+
}
358+
359+
// Generate programmes for each channel using pre-calculated time slots
337360
foreach ($dummyEpgChannels as $dummyEpgChannel) {
338361
$tvgId = $dummyEpgChannel['tvg_id'];
339362
$title = $dummyEpgChannel['title'];
340363
$icon = $dummyEpgChannel['icon'];
341-
$channelNo = $dummyEpgChannel['channel_no'];
342364
$group = $dummyEpgChannel['group'];
343365
$includeCategory = $dummyEpgChannel['include_category'];
344366

345-
// Generate dummy programmes for the specified length
346-
$startTime = Carbon::now()
347-
->startOf('day')
348-
->subMinutes($dummyEpgLength);
349-
350-
// Generate 5 days worth of EPG data, based on the `$dummyEpgLength`, which is how long the programme should last in minutes
351-
for ($i = 0; $i < (5 * 24 * 60) / $dummyEpgLength; $i++) {
352-
$startTime->addMinutes($dummyEpgLength);
353-
$endTime = clone $startTime;
354-
$endTime->addMinutes($dummyEpgLength);
355-
356-
// Format the start and end times
357-
$start = str_replace(':', '', $startTime->format('YmdHis P'));
358-
$end = str_replace(':', '', $endTime->format('YmdHis P'));
359-
360-
// Output the <programme> tag
361-
echo ' <programme channel="' . $tvgId . '" start="' . $start . '" stop="' . $end . '">' . PHP_EOL;
362-
echo ' <title>' . $title . '</title>' . PHP_EOL;
367+
// Build all programmes for this channel in one string buffer
368+
$buffer = '';
369+
foreach ($timeSlots as $slot) {
370+
$buffer .= ' <programme channel="' . $tvgId . '" start="' . $slot['start'] . '" stop="' . $slot['end'] . '">' . PHP_EOL;
371+
$buffer .= ' <title>' . $title . '</title>' . PHP_EOL;
363372
if ($icon) {
364-
echo ' <icon src="' . $icon . '"/>' . PHP_EOL;
373+
$buffer .= ' <icon src="' . $icon . '"/>' . PHP_EOL;
365374
}
366-
echo ' <desc>' . $title . '</desc>' . PHP_EOL;
375+
$buffer .= ' <desc>' . $title . '</desc>' . PHP_EOL;
367376
if ($includeCategory) {
368-
echo ' <category lang="en">' . $group . '</category>' . PHP_EOL;
377+
$buffer .= ' <category lang="en">' . $group . '</category>' . PHP_EOL;
369378
}
370-
echo ' </programme>' . PHP_EOL;
379+
$buffer .= ' </programme>' . PHP_EOL;
371380
}
381+
// Single echo per channel instead of 600+ echoes
382+
echo $buffer;
372383
}
373384
}
374385

0 commit comments

Comments
 (0)