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.versionUrl = options.versionUrl ?? null;
this.version = null;
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 ?? '
Keine Daten gefunden.
';
this.items = new Map();
this.initialized = false;
this.refreshTimer = null;
this.tickTimer = null;
}
getGrid() {
return document.querySelector(this.gridSelector);
}
async hasChanged() {
if (!this.versionUrl) {
return true;
}
const data = await TVGuideLive.fetchJson(this.versionUrl);
const version = String(data.version ?? '0');
if (this.version === null) {
this.version = version;
return true;
}
if (this.version !== version) {
this.version = version;
return true;
}
return false;
}
async refresh() {
const grid = this.getGrid();
if (!grid) return;
if (!(await this.hasChanged())) {
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);
}
};