diff --git a/README.md b/README.md index 25f04b2..b6010c1 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ nodecast-tv is a modern, web-based IPTV player featuring Live TV, EPG, Movies (V - **🎬 VOD Support**: Dedicated sections for Movies and TV Series with rich metadata, posters, and seasonal episode lists. - **❤️ Favorites System**: Unified favorites for channels, movies, and series with instant synchronization. - **🔐 Authentication**: User login system with admin and viewer roles ([details](https://github.com/technomancer702/nodecast-tv/pull/23)). +- **🆔 OIDC SSO**: Support for Single Sign-On via OIDC providers (Authentik, Keycloak, etc.). - **⚡ High Performance**: Optimized for large playlists (7000+ channels) using virtual scrolling and batch rendering. - **⚙️ Management**: - Support for Xtream Codes and M3U playlists. @@ -26,10 +27,12 @@ nodecast-tv is a modern, web-based IPTV player featuring Live TV, EPG, Movies (V ## Screenshots
- Screenshot 1 - Screenshot 2 - Screenshot 3 - Screenshot 4 + Dashboard + Live TV + TV Guide + Movies + Series + Settings
## Getting Started diff --git a/public/css/main.css b/public/css/main.css index e95ceb5..5af3e93 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -1866,6 +1866,21 @@ kbd { animation: spin 0.8s linear infinite; } +.loading-state { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); + padding: var(--space-xl); + color: var(--color-text-muted); + width: 100%; +} + +.horizontal-scroll .empty-state { + padding: var(--space-xl); + min-height: 150px; +} + @keyframes spin { to { transform: rotate(360deg); @@ -4109,7 +4124,6 @@ kbd { border-radius: 4px; font-size: 0.8rem; font-weight: 600; - margin-left: 10px; background: rgba(0, 0, 0, 0.5); color: white; display: inline-flex; @@ -4139,6 +4153,30 @@ kbd { pointer-events: none; } +/* Badge Container for right alignment */ +.player-badges { + display: flex; + align-items: center; + gap: 8px; + margin-left: auto; + /* Fallback for cases without flex-grow middle element */ +} + +/* Quality Badge */ +.quality-badge { + display: inline-block; + background: rgba(158, 158, 158, 0.8); + color: white; + padding: 4px 10px; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 600; +} + +.quality-badge.hidden { + display: none; +} + /* Watch Header Bar (Title + Transcode Status) */ /* Moved to original definition around line 3311 */ diff --git a/public/img/screenshots/screenshot-dashboard.png b/public/img/screenshots/screenshot-dashboard.png new file mode 100644 index 0000000..25cfd56 Binary files /dev/null and b/public/img/screenshots/screenshot-dashboard.png differ diff --git a/public/img/screenshots/screenshot-settings.png b/public/img/screenshots/screenshot-settings.png new file mode 100644 index 0000000..db1e75b Binary files /dev/null and b/public/img/screenshots/screenshot-settings.png differ diff --git a/public/index.html b/public/index.html index c45df97..cfb728c 100644 --- a/public/index.html +++ b/public/index.html @@ -168,7 +168,10 @@

- +
+ + +
@@ -788,7 +791,10 @@

- +
+ + +
diff --git a/public/js/components/VideoPlayer.js b/public/js/components/VideoPlayer.js index fdc954a..751c8e2 100644 --- a/public/js/components/VideoPlayer.js +++ b/public/js/components/VideoPlayer.js @@ -522,6 +522,17 @@ class VideoPlayer { // Setup custom video controls this.initCustomControls(); + // Detect video resolution when metadata loads (works for all streams) + this.video.addEventListener('loadedmetadata', () => { + if (this.video.videoHeight > 0) { + this.currentStreamInfo = { + width: this.video.videoWidth, + height: this.video.videoHeight + }; + this.updateQualityBadge(); + } + }); + // Initialize HLS.js if supported if (Hls.isSupported()) { this.hls = new Hls(this.getHlsConfig()); @@ -775,7 +786,11 @@ class VideoPlayer { try { const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(streamUrl)}`); const info = await probeRes.json(); - console.log(`[Player] Probe result: video=${info.video}, audio=${info.audio}, compatible=${info.compatible}`); + console.log(`[Player] Probe result: video=${info.video}, audio=${info.audio}, ${info.width}x${info.height}, compatible=${info.compatible}`); + + // Store probe result for quality badge display + this.currentStreamInfo = info; + this.updateQualityBadge(); // Handle subtitles from probe result // Clear existing remote tracks (from previous streams) @@ -1122,6 +1137,34 @@ class VideoPlayer { el.classList.remove('hidden'); } + /** + * Get quality label from video height + */ + getQualityLabel(height) { + if (height >= 2160) return '4K'; + if (height >= 1440) return '1440p'; + if (height >= 1080) return '1080p'; + if (height >= 720) return '720p'; + if (height >= 480) return '480p'; + if (height > 0) return `${height}p`; + return null; + } + + /** + * Update quality badge display + */ + updateQualityBadge() { + const badge = document.getElementById('player-quality-badge'); + if (!badge) return; + + if (this.currentStreamInfo?.height > 0) { + badge.textContent = this.getQualityLabel(this.currentStreamInfo.height); + badge.classList.remove('hidden'); + } else { + badge.classList.add('hidden'); + } + } + /** * Fetch EPG data for current channel */ @@ -1267,6 +1310,11 @@ class VideoPlayer { this.controlsOverlay?.classList.add('hidden'); // Hide controls this.loadingSpinner?.classList.remove('show'); this.nowPlaying.classList.add('hidden'); + + // Hide quality badge + this.currentStreamInfo = null; + const badge = document.getElementById('player-quality-badge'); + if (badge) badge.classList.add('hidden'); } /** diff --git a/public/js/icons.js b/public/js/icons.js index bbaab05..7ef6f8a 100644 --- a/public/js/icons.js +++ b/public/js/icons.js @@ -26,6 +26,7 @@ const Icons = { check: ``, circle: ``, close: ``, + fingerprint: ``, }; window.Icons = Icons; diff --git a/public/js/pages/HomePage.js b/public/js/pages/HomePage.js index 66e255c..45793a0 100644 --- a/public/js/pages/HomePage.js +++ b/public/js/pages/HomePage.js @@ -40,7 +40,10 @@ class HomePage {
-
Loading favorites...
+
+
+ Loading favorites... +
-
Loading history...
+
+
+ Loading history... +
-
Coming soon
+
+
+ Loading recently added... +
-
Coming soon
+
+
+ Loading recently added... +
+ + diff --git a/server/routes/probe.js b/server/routes/probe.js index 1b12e33..751ef43 100644 --- a/server/routes/probe.js +++ b/server/routes/probe.js @@ -128,6 +128,8 @@ function analyzeProbeResult(probeResult, url) { return { video: videoCodec, audio: audioCodec, + width: videoStream?.width || 0, + height: videoStream?.height || 0, audioChannels: audioStream?.channels || 0, // For Smart Audio Copy container: container, compatible: compatible,