- Parse #EXTVLCOPT:program=N from M3U playlists (both full and streaming parsers, per channel, strict mode) and store it on playlist items - Transcode multi-program MPEG-TS streams by mapping only the selected program's first video and audio stream (DVB teletext/subtitles in the program otherwise make FFmpeg fail to produce HLS) - Optional per-source "zap before streaming" with configurable delay: POST /api/openwebif/zap takes sourceId + streamId, requires an enabled, zap-enabled M3U source containing that channel, derives the receiver from the configured source URL (never from the client), validates the Enigma2 service reference, uses a timeout without redirects and does not echo the receiver response - Live TV: Stop button, and stop playback when leaving the page - Tests for ordinary and program-tagged playlists and the zap endpoint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
112 lines
3.6 KiB
JavaScript
112 lines
3.6 KiB
JavaScript
/**
|
|
* Home Page Controller
|
|
*/
|
|
|
|
class LivePage {
|
|
constructor(app) {
|
|
this.app = app;
|
|
this.handleKeydown = this.handleKeydown.bind(this);
|
|
}
|
|
|
|
async init() {
|
|
// Load sources and channels on initial page load
|
|
await this.app.channelList.loadSources();
|
|
await this.app.channelList.loadChannels();
|
|
|
|
// Silently fetch EPG data for sidebar info
|
|
try {
|
|
await this.app.epgGuide.fetchEpgData();
|
|
|
|
// Clear cache so we don't get stale "null" results from initial render
|
|
this.app.channelList.clearProgramInfoCache();
|
|
|
|
// Update program info in existing DOM elements without re-rendering
|
|
this.updateProgramInfo();
|
|
} catch (err) {
|
|
console.warn('Background EPG fetch failed:', err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update "Now Playing" info in existing channel elements without blocking UI
|
|
*/
|
|
updateProgramInfo() {
|
|
const channelItems = Array.from(document.querySelectorAll('.channel-item'));
|
|
if (channelItems.length === 0) return;
|
|
|
|
// Build a map for O(1) channel lookups
|
|
const channelMap = new Map();
|
|
this.app.channelList.channels.forEach(c => channelMap.set(c.id, c));
|
|
|
|
// Process in small batches to avoid blocking UI
|
|
const BATCH_SIZE = 50;
|
|
let index = 0;
|
|
|
|
const processBatch = () => {
|
|
const end = Math.min(index + BATCH_SIZE, channelItems.length);
|
|
|
|
for (let i = index; i < end; i++) {
|
|
const item = channelItems[i];
|
|
const channelId = item.dataset.channelId;
|
|
const channel = channelMap.get(channelId);
|
|
|
|
if (channel) {
|
|
const programDiv = item.querySelector('.channel-program');
|
|
if (programDiv) {
|
|
const programTitle = this.app.channelList.getProgramInfo(channel);
|
|
programDiv.textContent = programTitle || '';
|
|
}
|
|
}
|
|
}
|
|
|
|
index = end;
|
|
if (index < channelItems.length) {
|
|
// Yield to browser before next batch
|
|
requestAnimationFrame(processBatch);
|
|
}
|
|
};
|
|
|
|
// Start processing
|
|
requestAnimationFrame(processBatch);
|
|
}
|
|
|
|
handleKeydown(e) {
|
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
|
|
switch (e.key) {
|
|
case 'ArrowUp':
|
|
// Check if player handles arrows for volume
|
|
if (this.app.player && !this.app.player.settings.arrowKeysChangeChannel) return;
|
|
|
|
e.preventDefault();
|
|
this.app.channelList.selectPrevChannel();
|
|
break;
|
|
case 'ArrowDown':
|
|
// Check if player handles arrows for volume
|
|
if (this.app.player && !this.app.player.settings.arrowKeysChangeChannel) return;
|
|
|
|
e.preventDefault();
|
|
this.app.channelList.selectNextChannel();
|
|
break;
|
|
}
|
|
}
|
|
|
|
async show() {
|
|
document.addEventListener('keydown', this.handleKeydown);
|
|
|
|
// Only reload if channels aren't already loaded
|
|
if (this.app.channelList.channels.length === 0) {
|
|
await this.app.channelList.loadSources();
|
|
await this.app.channelList.loadChannels();
|
|
}
|
|
}
|
|
|
|
hide() {
|
|
document.removeEventListener('keydown', this.handleKeydown);
|
|
// Stop the stream when leaving Live TV so the transcode session is released
|
|
this.app.player?.stop();
|
|
}
|
|
}
|
|
|
|
window.LivePage = LivePage;
|