Live Ansicht

This commit is contained in:
Gogs
2026-07-07 12:37:28 +02:00
parent c2da564dd6
commit 5abbe9220e
5 changed files with 247 additions and 1 deletions
+44
View File
@@ -0,0 +1,44 @@
<?php
require_once '/var/www/src/Config.php';
require_once '/var/www/src/Services/ProgrammeService.php';
header('Content-Type: application/json; charset=utf-8');
$config = new Config(
'/var/www/config/config.json',
'/var/www/config/channels.json'
);
$timezone = $config->get('timezone', 'Europe/Vienna');
$cacheFile = '/var/www/cache/guide.json';
if (!file_exists($cacheFile)) {
http_response_code(404);
echo json_encode([
'error' => 'guide.json not found',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
$data = json_decode(file_get_contents($cacheFile), true);
if (!is_array($data)) {
http_response_code(500);
echo json_encode([
'error' => 'guide.json is invalid',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
$service = new ProgrammeService($timezone);
$items = $service->getCurrentProgrammes($data, $config);
$now = new DateTimeImmutable('now', new DateTimeZone($timezone));
echo json_encode([
'now' => $now->format(DateTimeInterface::ATOM),
'timestamp' => $now->getTimestamp(),
'items' => $items,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+61
View File
@@ -0,0 +1,61 @@
function nowCreateCard(item) {
const article = document.createElement('article');
article.className = 'now-card';
article.innerHTML = `
<div class="now-channel">
<span data-live-channel-name></span>
<span data-live-favorite></span>
</div>
<h3 data-live-title></h3>
<div class="now-subtitle" data-live-subtitle></div>
<div class="now-time" data-live-time></div>
<div class="progress">
<div class="progress-bar" data-live-progress></div>
</div>
`;
nowUpdateCard(article, item);
return article;
}
function nowUpdateCard(card, item) {
card.querySelector('[data-live-channel-name]').textContent = item.channel_name;
card.querySelector('[data-live-favorite]').textContent = item.favorite ? '⭐' : '';
card.querySelector('[data-live-title]').textContent = item.title;
const subtitle = card.querySelector('[data-live-subtitle]');
subtitle.textContent = item.subtitle || '';
subtitle.style.display = item.subtitle ? '' : 'none';
nowTickCard(card, item);
}
function nowTickCard(card, item) {
card.querySelector('[data-live-time]').textContent =
`${TVGuideLive.formatTime(item.start)} ${TVGuideLive.formatTime(item.stop)} · noch ${TVGuideLive.calcRemainingMinutes(item.stop)} Min.`;
card.querySelector('[data-live-progress]').style.width =
`${TVGuideLive.calcPercent(item.start, item.stop)}%`;
}
document.addEventListener('DOMContentLoaded', () => {
if (!window.LiveEngine) return;
const liveNow = new LiveEngine({
url: '/api/live-now.php',
interval: 30000,
gridSelector: '[data-live-now-grid]',
itemKey: item => item.channel_id,
createItem: nowCreateCard,
updateItem: nowUpdateCard,
tickItem: nowTickCard,
emptyHtml: '<div class="empty">Aktuell wurden keine laufenden Sendungen gefunden.</div>'
});
liveNow.start();
});
+138
View File
@@ -0,0 +1,138 @@
window.TVGuideLive = {
formatTime(timestamp) {
const date = new Date(timestamp * 1000);
return String(date.getHours()).padStart(2, '0') + ':' +
String(date.getMinutes()).padStart(2, '0');
},
calcPercent(start, stop) {
const now = Math.floor(Date.now() / 1000);
const duration = Math.max(1, stop - start);
const elapsed = Math.max(0, now - start);
return Math.min(100, Math.max(0, Math.round((elapsed / duration) * 100)));
},
calcRemainingMinutes(stop) {
const now = Math.floor(Date.now() / 1000);
return Math.max(0, Math.ceil((stop - now) / 60));
},
async fetchJson(url) {
const response = await fetch(url, { cache: 'no-store' });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
};
window.LiveEngine = class LiveEngine {
constructor(options) {
this.url = options.url;
this.interval = options.interval ?? 30000;
this.gridSelector = options.gridSelector;
this.itemKey = options.itemKey;
this.createItem = options.createItem;
this.updateItem = options.updateItem;
this.tickItem = options.tickItem;
this.emptyHtml = options.emptyHtml ?? '<div class="empty">Keine Daten gefunden.</div>';
this.items = new Map();
this.initialized = false;
this.refreshTimer = null;
this.tickTimer = null;
}
getGrid() {
return document.querySelector(this.gridSelector);
}
async refresh() {
const grid = this.getGrid();
if (!grid) return;
try {
const data = await TVGuideLive.fetchJson(this.url);
const items = data.items ?? [];
if (items.length === 0) {
grid.innerHTML = this.emptyHtml;
this.items.clear();
this.initialized = true;
return;
}
if (!this.initialized) {
grid.innerHTML = '';
this.initialized = true;
}
const empty = grid.querySelector('.empty');
if (empty) empty.remove();
this.renderDiff(grid, items);
} catch (error) {
console.error('Live-EPG konnte nicht aktualisiert werden:', error);
}
}
renderDiff(grid, items) {
const seen = new Set();
for (const item of items) {
const key = String(this.itemKey(item));
seen.add(key);
this.items.set(key, item);
let element = grid.querySelector(`[data-live-key="${CSS.escape(key)}"]`);
if (!element) {
element = this.createItem(item);
element.dataset.liveKey = key;
grid.appendChild(element);
} else {
this.updateItem(element, item);
}
}
grid.querySelectorAll('[data-live-key]').forEach(element => {
const key = element.dataset.liveKey;
if (!seen.has(key)) {
element.remove();
this.items.delete(key);
}
});
}
tick() {
const grid = this.getGrid();
if (!grid || !this.tickItem) return;
grid.querySelectorAll('[data-live-key]').forEach(element => {
const key = element.dataset.liveKey;
const item = this.items.get(key);
if (item) {
this.tickItem(element, item);
}
});
}
start() {
this.refresh();
this.refreshTimer = setInterval(() => this.refresh(), this.interval);
if (this.tickItem) {
this.tickTimer = setInterval(() => this.tick(), 1000);
}
}
stop() {
if (this.refreshTimer) clearInterval(this.refreshTimer);
if (this.tickTimer) clearInterval(this.tickTimer);
}
};
+3
View File
@@ -20,5 +20,8 @@
</div>
<script type="module" src="/assets/js/app.js"></script>
<script src="/assets/js/live.js"></script>
<script src="/assets/js/live-now.js"></script>
</body>
</html>
+1 -1
View File
@@ -17,7 +17,7 @@
</div>
<?php endif; ?>
<div class="now-grid">
<div class="now-grid" data-live-now-grid>
<?php foreach ($currentProgrammes as $item): ?>
<article class="now-card">
<div class="now-channel">