32e0434785
- Add /api/transcode endpoint for audio-only transcoding - Video passthrough (no re-encoding) for fast performance - Audio transcode to AAC for browser compatibility - Add 'Force Audio Transcode' setting toggle in UI - Add ffmpeg-static as optional dependency - Handle Electron asar.unpacked path for packaged apps
457 lines
21 KiB
HTML
457 lines
21 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>NodeCast TV | IPTV Player</title>
|
|
<meta name="description" content="Stream live TV, movies, and series with EPG support">
|
|
<link rel="stylesheet" href="css/main.css">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<!-- HLS.js for video playback -->
|
|
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.7/dist/hls.min.js"></script>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="app">
|
|
<!-- Navigation -->
|
|
<nav class="navbar">
|
|
<div class="navbar-brand">
|
|
<span class="logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
|
|
<path
|
|
d="M21 6h-7.59l3.29-3.29L16 2l-4 4-4-4-.71.71L10.59 6H3a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8a2 2 0 0 0-2-2zm0 14H3V8h18v12zM9 10v8l7-4z" />
|
|
</svg></span>
|
|
<span class="brand-text">NodeCast <span class="brand-accent">TV</span></span>
|
|
</div>
|
|
<div class="navbar-menu">
|
|
<a href="#" class="nav-link active" data-page="home">
|
|
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
|
class="icon">
|
|
<path
|
|
d="M21 6h-7.59l3.29-3.29L16 2l-4 4-4-4-.71.71L10.59 6H3a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8a2 2 0 0 0-2-2zm0 14H3V8h18v12zM9 10v8l7-4z" />
|
|
</svg></span>
|
|
<span>Live TV</span>
|
|
</a>
|
|
<a href="#" class="nav-link" data-page="guide">
|
|
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
|
class="icon">
|
|
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z" />
|
|
</svg></span>
|
|
<span>TV Guide</span>
|
|
</a>
|
|
<a href="#" class="nav-link" data-page="movies">
|
|
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
|
class="icon">
|
|
<path
|
|
d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2V4h-4z" />
|
|
</svg></span>
|
|
<span>Movies</span>
|
|
</a>
|
|
<a href="#" class="nav-link" data-page="series">
|
|
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
|
class="icon">
|
|
<path
|
|
d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z" />
|
|
</svg></span>
|
|
<span>Series</span>
|
|
</a>
|
|
<a href="#" class="nav-link" data-page="settings">
|
|
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
|
class="icon">
|
|
<path
|
|
d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L3.16 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.04.64.09.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.58 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" />
|
|
</svg></span>
|
|
<span>Settings</span>
|
|
</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Main Content Area -->
|
|
<main class="main-content">
|
|
<!-- Home Page -->
|
|
<div id="page-home" class="page active">
|
|
<div class="home-layout">
|
|
<!-- Channel Sidebar -->
|
|
<aside class="channel-sidebar">
|
|
<div class="sidebar-header">
|
|
<input type="text" id="channel-search" placeholder="Search channels..." class="search-input">
|
|
<div class="sidebar-controls">
|
|
<select id="source-select" class="source-select">
|
|
<option value="">All Sources</option>
|
|
</select>
|
|
<button id="toggle-groups" class="btn btn-sm btn-ghost" title="Expand/Collapse All">
|
|
<span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
|
|
<path
|
|
d="M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z" />
|
|
</svg></span>
|
|
</button>
|
|
|
|
</div>
|
|
</div>
|
|
<div id="channel-list" class="channel-list">
|
|
<div class="empty-state">
|
|
<p>No channels loaded</p>
|
|
<p class="hint">Add a source in Settings to get started</p>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Video Player Area -->
|
|
<section class="player-section">
|
|
<div class="video-container">
|
|
<video id="video-player" controls></video>
|
|
<div class="player-overlay" id="player-overlay">
|
|
<div class="overlay-content">
|
|
<p>Select a channel to start watching</p>
|
|
</div>
|
|
</div>
|
|
<!-- Now Playing Overlay -->
|
|
<div class="now-playing-overlay hidden" id="now-playing">
|
|
<div class="now-playing-info">
|
|
<h3 class="channel-name">No channel selected</h3>
|
|
<p class="program-title"></p>
|
|
<p class="program-time"></p>
|
|
</div>
|
|
<div class="up-next">
|
|
<h4>Up Next</h4>
|
|
<ul id="up-next-list"></ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Guide Page -->
|
|
<div id="page-guide" class="page">
|
|
<div class="guide-header">
|
|
<h2>TV Guide</h2>
|
|
<div class="guide-controls">
|
|
<input type="text" id="epg-search" placeholder="Search channels..." class="search-input"
|
|
style="min-width: 200px;">
|
|
<select id="epg-group-select" class="source-select">
|
|
<option value="">All Groups</option>
|
|
</select>
|
|
<button id="guide-prev" class="btn btn-ghost">← Earlier</button>
|
|
<span id="guide-date" class="guide-date">Today</span>
|
|
<button id="guide-next" class="btn btn-ghost">Later →</button>
|
|
</div>
|
|
</div>
|
|
<div id="epg-grid" class="epg-grid">
|
|
<div class="empty-state">
|
|
<p>No EPG data available</p>
|
|
<p class="hint">Add an EPG source in Settings</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Movies Page -->
|
|
<div id="page-movies" class="page">
|
|
<div class="movies-header">
|
|
<h2>Movies</h2>
|
|
<div class="movies-controls">
|
|
<select id="movies-source-select" class="source-select">
|
|
<option value="">All Sources</option>
|
|
</select>
|
|
<select id="movies-category-select" class="source-select">
|
|
<option value="">All Categories</option>
|
|
</select>
|
|
<input type="text" id="movies-search" placeholder="Search movies..." class="search-input">
|
|
<button id="movies-favorites-btn" class="btn btn-icon btn-ghost" title="Show Favorites Only">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
|
|
<path
|
|
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
|
|
</svg> Favorites
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div id="movies-grid" class="movies-grid">
|
|
<div class="empty-state">
|
|
<p>Loading movies...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Series Page -->
|
|
<div id="page-series" class="page">
|
|
<div class="series-header">
|
|
<h2>Series</h2>
|
|
<div class="series-controls">
|
|
<select id="series-source-select" class="source-select">
|
|
<option value="">All Sources</option>
|
|
</select>
|
|
<select id="series-category-select" class="source-select">
|
|
<option value="">All Categories</option>
|
|
</select>
|
|
<input type="text" id="series-search" placeholder="Search series..." class="search-input">
|
|
<button id="series-favorites-btn" class="btn btn-icon btn-ghost" title="Show Favorites Only">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
|
|
<path
|
|
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
|
|
</svg> Favorites
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="series-content">
|
|
<div id="series-grid" class="series-grid">
|
|
<div class="empty-state">
|
|
<p>Loading series...</p>
|
|
</div>
|
|
</div>
|
|
<!-- Series details panel (seasons/episodes) -->
|
|
<div id="series-details" class="series-details hidden">
|
|
<button class="series-back-btn">← Back to Series</button>
|
|
<div class="series-details-header">
|
|
<img id="series-poster" src="" alt="" class="series-poster">
|
|
<div class="series-info">
|
|
<h3 id="series-title"></h3>
|
|
<p id="series-plot"></p>
|
|
</div>
|
|
</div>
|
|
<div id="series-seasons" class="series-seasons"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Page -->
|
|
<div id="page-settings" class="page">
|
|
<div class="settings-container">
|
|
<h2>Settings</h2>
|
|
|
|
<!-- Tabs -->
|
|
<div class="tabs">
|
|
<button class="tab active" data-tab="sources">Sources</button>
|
|
<button class="tab" data-tab="player">Player</button>
|
|
<button class="tab" data-tab="content">Manage Content</button>
|
|
</div>
|
|
|
|
<!-- Sources Tab -->
|
|
<div id="tab-sources" class="tab-content active">
|
|
<div class="source-section">
|
|
<div class="section-header">
|
|
<h3>Xtream Connections</h3>
|
|
<button class="btn btn-primary" id="add-xtream">+ Add Xtream</button>
|
|
</div>
|
|
<div id="xtream-list" class="source-list"></div>
|
|
</div>
|
|
|
|
<div class="source-section">
|
|
<div class="section-header">
|
|
<h3>M3U Playlists</h3>
|
|
<button class="btn btn-primary" id="add-m3u">+ Add M3U</button>
|
|
</div>
|
|
<div id="m3u-list" class="source-list"></div>
|
|
</div>
|
|
|
|
<div class="source-section">
|
|
<div class="section-header">
|
|
<h3>EPG Sources</h3>
|
|
<button class="btn btn-primary" id="add-epg">+ Add EPG</button>
|
|
</div>
|
|
<div id="epg-list" class="source-list"></div>
|
|
</div>
|
|
|
|
<div class="source-section">
|
|
<h3>EPG Data Settings</h3>
|
|
<div class="setting-item">
|
|
<label for="epg-refresh-interval">Auto-refresh EPG data every:</label>
|
|
<select id="epg-refresh-interval" class="form-input">
|
|
<option value="1">1 hour</option>
|
|
<option value="2">2 hours</option>
|
|
<option value="4">4 hours</option>
|
|
<option value="12">12 hours</option>
|
|
<option value="24" selected>24 hours</option>
|
|
<option value="0">Manual only</option>
|
|
</select>
|
|
</div>
|
|
<p class="hint">Use the 🔄 button next to EPG sources for manual refresh</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Player Tab -->
|
|
<div id="tab-player" class="tab-content">
|
|
<div class="settings-section">
|
|
<h3>Keyboard Controls</h3>
|
|
<div class="setting-item">
|
|
<label class="setting-toggle">
|
|
<input type="checkbox" id="setting-arrow-keys" checked>
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
<div class="setting-info">
|
|
<span class="setting-label">Arrow keys change channels</span>
|
|
<span class="setting-hint">When enabled, ↑/↓ change channels and ←/→ adjust volume</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<h3>Now Playing Overlay</h3>
|
|
<div class="setting-item">
|
|
<label for="setting-overlay-duration">Display Duration (seconds)</label>
|
|
<input type="number" id="setting-overlay-duration" class="form-input setting-number" min="1" max="30"
|
|
value="5">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<h3>Volume Settings</h3>
|
|
<div class="setting-item">
|
|
<label for="setting-default-volume">Default Volume</label>
|
|
<div class="volume-slider-container">
|
|
<input type="range" id="setting-default-volume" class="volume-slider" min="0" max="100" value="80">
|
|
<span id="volume-value">80%</span>
|
|
</div>
|
|
</div>
|
|
<div class="setting-item">
|
|
<label class="setting-toggle">
|
|
<input type="checkbox" id="setting-remember-volume" checked>
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
<div class="setting-info">
|
|
<span class="setting-label">Remember Last Volume</span>
|
|
<span class="setting-hint">When enabled, volume persists between sessions</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<h3>Playback</h3>
|
|
<div class="setting-item">
|
|
<label class="setting-toggle">
|
|
<input type="checkbox" id="setting-autoplay-next">
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
<div class="setting-info">
|
|
<span class="setting-label">Auto-play Next Episode</span>
|
|
<span class="setting-hint">Automatically play the next episode when one ends</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<h3>Streaming</h3>
|
|
<div class="setting-item">
|
|
<label class="setting-toggle">
|
|
<input type="checkbox" id="setting-force-proxy">
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
<div class="setting-info">
|
|
<span class="setting-label">Force Backend Proxy</span>
|
|
<span class="setting-hint">Route all streams through the proxy server. Enable if streams don't play
|
|
directly.</span>
|
|
</div>
|
|
</div>
|
|
<div class="setting-item">
|
|
<label class="setting-toggle">
|
|
<input type="checkbox" id="setting-force-transcode">
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
<div class="setting-info">
|
|
<span class="setting-label">Force Audio Transcode</span>
|
|
<span class="setting-hint">Transcode audio to AAC for browser compatibility. Enable if you get video
|
|
but no audio (fixes Dolby/AC3/EAC3). Requires ffmpeg-static.</span>
|
|
</div>
|
|
</div>
|
|
<div class="setting-item">
|
|
<div class="setting-info" style="flex: 1;">
|
|
<span class="setting-label">Stream Output Format</span>
|
|
<span class="setting-hint">Container format for Xtream streams. Try TS if you experience buffering
|
|
issues.</span>
|
|
</div>
|
|
<select id="setting-stream-format" class="source-select" style="width: auto; min-width: 200px;">
|
|
<option value="m3u8">HLS (m3u8) - Recommended</option>
|
|
<option value="ts">MPEG-TS (ts) - Better buffer</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<h3>Keyboard Shortcuts</h3>
|
|
<div class="shortcuts-grid">
|
|
<div class="shortcut"><kbd>↑</kbd> / <kbd>↓</kbd> Channel up/down (or volume)</div>
|
|
<div class="shortcut"><kbd>←</kbd> / <kbd>→</kbd> Volume down/up (when arrows = channels)</div>
|
|
<div class="shortcut"><kbd>Space</kbd> / <kbd>K</kbd> Play/Pause</div>
|
|
<div class="shortcut"><kbd>F</kbd> Fullscreen</div>
|
|
<div class="shortcut"><kbd>M</kbd> Mute/Unmute</div>
|
|
<div class="shortcut"><kbd>I</kbd> Toggle info overlay</div>
|
|
<div class="shortcut"><kbd>PgUp</kbd> / <kbd>PgDn</kbd> Channel up/down</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Manage Content Tab -->
|
|
<div id="tab-content" class="tab-content">
|
|
<div class="content-browser">
|
|
<div class="content-browser-header">
|
|
<div class="content-type-toggle">
|
|
<button class="btn btn-sm active" id="content-type-channels">Channels</button>
|
|
<button class="btn btn-sm" id="content-type-movies">Movies</button>
|
|
<button class="btn btn-sm" id="content-type-series">Series</button>
|
|
</div>
|
|
<select id="content-source-select" class="source-select">
|
|
<option value="">Select a source...</option>
|
|
</select>
|
|
<div class="content-actions">
|
|
<button class="btn btn-sm btn-secondary" id="content-show-all">Show All</button>
|
|
<button class="btn btn-sm btn-secondary" id="content-hide-all">Hide All</button>
|
|
<button class="btn btn-sm btn-primary" id="content-save"><svg xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 24 24" fill="currentColor" class="icon">
|
|
<path
|
|
d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z" />
|
|
</svg> Save Changes</button>
|
|
</div>
|
|
</div>
|
|
<p class="hint" style="margin-bottom: var(--space-sm);">✓ Checked = Visible, ☐ Unchecked = Hidden</p>
|
|
<div id="content-tree" class="content-tree">
|
|
<p class="hint">Select a source to view groups and channels</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Modal Template -->
|
|
<div id="modal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 id="modal-title">Modal</h3>
|
|
<button class="modal-close">×</button>
|
|
</div>
|
|
<div id="modal-body" class="modal-body"></div>
|
|
<div id="modal-footer" class="modal-footer"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Context Menu Template -->
|
|
<div id="context-menu" class="context-menu">
|
|
<button class="context-item" data-action="play"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
|
fill="currentColor" class="icon">
|
|
<path d="M8 5v14l11-7z" />
|
|
</svg> Play</button>
|
|
<button class="context-item" data-action="hide"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
|
fill="currentColor" class="icon">
|
|
<path
|
|
d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-4.01.7l2.16 2.16C10.85 7.18 11.4 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z" />
|
|
</svg> Hide</button>
|
|
</div>
|
|
|
|
<!-- JavaScript -->
|
|
<script src="/js/icons.js"></script>
|
|
<script src="/js/api.js?v=2"></script>
|
|
<script src="/js/components/VideoPlayer.js?v=2"></script>
|
|
<script src="/js/components/ChannelList.js?v=3"></script>
|
|
<script src="/js/components/SourceManager.js?v=2"></script>
|
|
<script src="/js/components/EpgGuide.js?v=2"></script>
|
|
<script src="/js/pages/Home.js?v=3"></script>
|
|
<script src="/js/pages/Guide.js?v=2"></script>
|
|
<script src="/js/pages/MoviesPage.js?v=4"></script>
|
|
<script src="/js/pages/SeriesPage.js?v=1"></script>
|
|
<script src="/js/pages/Settings.js?v=2"></script>
|
|
<script src="/js/app.js?v=3"></script>
|
|
</body>
|
|
|
|
</html> |