diff --git a/public/index.html b/public/index.html index 661e59c..d06ce66 100644 --- a/public/index.html +++ b/public/index.html @@ -296,6 +296,20 @@ +
+

Streaming

+
+ +
+ Force Backend Proxy + Route all streams through the proxy server. Enable if streams don't play directly. +
+
+
+

Keyboard Shortcuts

diff --git a/public/js/components/VideoPlayer.js b/public/js/components/VideoPlayer.js index 40bb7c8..ba3d52a 100644 --- a/public/js/components/VideoPlayer.js +++ b/public/js/components/VideoPlayer.js @@ -32,7 +32,8 @@ class VideoPlayer { defaultVolume: 80, // 0-100 rememberVolume: true, // Persist volume between sessions lastVolume: 80, // Last used volume (if rememberVolume is true) - autoPlayNextEpisode: false // Auto-play next episode in series + autoPlayNextEpisode: false, // Auto-play next episode in series + forceProxy: false // Force all streams through backend proxy }; try { const saved = localStorage.getItem('nodecast_tv_player_settings'); @@ -201,10 +202,12 @@ class VideoPlayer { // Determine if HLS or direct stream this.currentUrl = streamUrl; - // Proactively use proxy for known CORS-restricted domains (like Pluto TV) + // Proactively use proxy for: + // 1. User enabled "Force Proxy" in settings + // 2. Known CORS-restricted domains (like Pluto TV) // Note: Xtream sources are NOT auto-proxied because many providers IP-lock streams const proxyRequiredDomains = ['pluto.tv']; - const needsProxy = proxyRequiredDomains.some(domain => streamUrl.includes(domain)); + const needsProxy = this.settings.forceProxy || proxyRequiredDomains.some(domain => streamUrl.includes(domain)); this.isUsingProxy = needsProxy; const finalUrl = needsProxy ? this.getProxiedUrl(streamUrl) : streamUrl; diff --git a/public/js/pages/Settings.js b/public/js/pages/Settings.js index 7896a74..2ced51d 100644 --- a/public/js/pages/Settings.js +++ b/public/js/pages/Settings.js @@ -28,6 +28,7 @@ class SettingsPage { const volumeValueDisplay = document.getElementById('volume-value'); const rememberVolumeToggle = document.getElementById('setting-remember-volume'); const autoPlayNextToggle = document.getElementById('setting-autoplay-next'); + const forceProxyToggle = document.getElementById('setting-force-proxy'); // Load current settings if (this.app.player?.settings) { @@ -37,6 +38,9 @@ class SettingsPage { volumeValueDisplay.textContent = this.app.player.settings.defaultVolume + '%'; rememberVolumeToggle.checked = this.app.player.settings.rememberVolume; autoPlayNextToggle.checked = this.app.player.settings.autoPlayNextEpisode; + if (forceProxyToggle) { + forceProxyToggle.checked = this.app.player.settings.forceProxy || false; + } } // Arrow keys toggle @@ -73,6 +77,12 @@ class SettingsPage { this.app.player.saveSettings(); }); + // Force proxy toggle + forceProxyToggle?.addEventListener('change', () => { + this.app.player.settings.forceProxy = forceProxyToggle.checked; + this.app.player.saveSettings(); + }); + // EPG refresh interval const epgRefreshSelect = document.getElementById('epg-refresh-interval'); if (epgRefreshSelect) { diff --git a/server/routes/proxy.js b/server/routes/proxy.js index 8e57ec7..1dae91e 100644 --- a/server/routes/proxy.js +++ b/server/routes/proxy.js @@ -295,17 +295,19 @@ router.get('/stream', async (req, res) => { // Check if it's an HLS manifest by looking at first bytes (#EXTM3U = 23 45 58 54 4D 33 55) const textPrefix = String.fromCharCode(...bytes.slice(0, 7)); const contentLooksLikeHls = textPrefix === '#EXTM3U'; - const urlLooksLikeHls = contentType.includes('mpegurl') || contentType.includes('application/x-mpegURL') || url.toLowerCase().includes('.m3u8'); if (contentLooksLikeHls) { - console.log(`[Proxy] Processing HLS manifest from: ${url.substring(0, 80)}...`); + // Use the final URL after redirects for base URL calculation + // This is critical for Xtream CDNs that redirect to different servers + const finalUrl = response.url || url; + console.log(`[Proxy] Processing HLS manifest from: ${finalUrl.substring(0, 80)}...`); res.set('Content-Type', 'application/vnd.apple.mpegurl'); let manifest = Buffer.from(buffer).toString('utf-8'); // Rewrite URLs inside manifest - const urlObj = new URL(url); - const baseUrl = urlObj.origin + urlObj.pathname.substring(0, urlObj.pathname.lastIndexOf('/') + 1); + const finalUrlObj = new URL(finalUrl); + const baseUrl = finalUrlObj.origin + finalUrlObj.pathname.substring(0, finalUrlObj.pathname.lastIndexOf('/') + 1); console.log(`[Proxy] Base URL for rewriting: ${baseUrl}`); manifest = manifest.split('\n').map(line => {