166 lines
3.9 KiB
JavaScript
166 lines
3.9 KiB
JavaScript
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 ?? '<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 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);
|
|
}
|
|
};
|