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 @@
+
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 => {