diff --git a/package-lock.json b/package-lock.json index 015d8b2..4395944 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "nodecast-tv", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/public/index.html b/public/index.html index 01450cc..4b9bca2 100644 --- a/public/index.html +++ b/public/index.html @@ -540,6 +540,27 @@ +
+
+ User-Agent + HTTP User-Agent for stream requests. Try VLC if your provider blocks + Chrome. +
+ +
+
diff --git a/public/js/pages/Settings.js b/public/js/pages/Settings.js index 25239ae..084dbe6 100644 --- a/public/js/pages/Settings.js +++ b/public/js/pages/Settings.js @@ -145,6 +145,39 @@ class SettingsPage { this.app.player.saveSettings(); }); } + + // User-Agent preset + const userAgentSelect = document.getElementById('setting-user-agent'); + const userAgentCustomInput = document.getElementById('setting-user-agent-custom'); + const customUaContainer = document.getElementById('custom-user-agent-container'); + + if (userAgentSelect && this.app.player?.settings) { + // Load saved values + userAgentSelect.value = this.app.player.settings.userAgentPreset || 'chrome'; + if (userAgentCustomInput) { + userAgentCustomInput.value = this.app.player.settings.userAgentCustom || ''; + } + + // Show/hide custom input + const toggleCustomInput = () => { + if (customUaContainer) { + customUaContainer.style.display = userAgentSelect.value === 'custom' ? 'flex' : 'none'; + } + }; + toggleCustomInput(); + + // Save on change + userAgentSelect.addEventListener('change', () => { + this.app.player.settings.userAgentPreset = userAgentSelect.value; + this.app.player.saveSettings(); + toggleCustomInput(); + }); + + userAgentCustomInput?.addEventListener('change', () => { + this.app.player.settings.userAgentCustom = userAgentCustomInput.value; + this.app.player.saveSettings(); + }); + } } initUserManagement() { @@ -297,6 +330,18 @@ class SettingsPage { if (autoTranscodeToggle) autoTranscodeToggle.checked = s.autoTranscode || false; if (epgRefreshSelect) epgRefreshSelect.value = s.epgRefreshInterval || '24'; if (streamFormatSelect) streamFormatSelect.value = s.streamFormat || 'm3u8'; + + // User-Agent settings + const userAgentSelect = document.getElementById('setting-user-agent'); + const userAgentCustomInput = document.getElementById('setting-user-agent-custom'); + const customUaContainer = document.getElementById('custom-user-agent-container'); + if (userAgentSelect) { + userAgentSelect.value = s.userAgentPreset || 'chrome'; + if (customUaContainer) { + customUaContainer.style.display = userAgentSelect.value === 'custom' ? 'flex' : 'none'; + } + } + if (userAgentCustomInput) userAgentCustomInput.value = s.userAgentCustom || ''; } // Update EPG last refreshed display diff --git a/server/db.js b/server/db.js index 9368912..ef38102 100644 --- a/server/db.js +++ b/server/db.js @@ -67,10 +67,30 @@ function getDefaultSettings() { forceRemux: false, autoTranscode: true, streamFormat: 'm3u8', - epgRefreshInterval: '24' + epgRefreshInterval: '24', + // User-Agent settings + userAgentPreset: 'chrome', // chrome | vlc | tivimate | custom + userAgentCustom: '', // Custom UA string when preset is 'custom' + // Probe cache settings + probeCacheTTL: 300, // 5 minutes for URL probe cache + seriesProbeCacheDays: 7 // 7 days for series episode probe cache }; } +// User-Agent presets +const USER_AGENT_PRESETS = { + chrome: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + vlc: 'VLC/3.0.20 LibVLC/3.0.20', + tivimate: 'TiviMate/4.7.0', +}; + +function getUserAgent(settings) { + if (settings.userAgentPreset === 'custom' && settings.userAgentCustom) { + return settings.userAgentCustom; + } + return USER_AGENT_PRESETS[settings.userAgentPreset] || USER_AGENT_PRESETS.chrome; +} + // Write lock to prevent concurrent writes from corrupting db.json let writeQueue = Promise.resolve(); const tmpPath = dbPath + '.tmp'; @@ -422,4 +442,4 @@ const users = { } }; -module.exports = { loadDb, saveDb, sources, hiddenItems, favorites, settings, users, getDefaultSettings }; +module.exports = { loadDb, saveDb, sources, hiddenItems, favorites, settings, users, getDefaultSettings, getUserAgent, USER_AGENT_PRESETS }; diff --git a/server/routes/remux.js b/server/routes/remux.js index db5fddd..57d4cad 100644 --- a/server/routes/remux.js +++ b/server/routes/remux.js @@ -1,6 +1,7 @@ const express = require('express'); const router = express.Router(); const { spawn } = require('child_process'); +const db = require('../db'); /** * Remux stream (container conversion only) @@ -12,21 +13,27 @@ const { spawn } = require('child_process'); * * Note: This does NOT fix Dolby/AC3 audio issues - use /api/transcode for that. */ -router.get('/', (req, res) => { +router.get('/', async (req, res) => { const { url } = req.query; if (!url) { return res.status(400).json({ error: 'URL parameter is required' }); } const ffmpegPath = req.app.locals.ffmpegPath || 'ffmpeg'; + + // Get User-Agent from settings + const settings = await db.settings.get(); + const userAgent = db.getUserAgent(settings); + console.log(`[Remux] Starting remux for: ${url}`); + console.log(`[Remux] Using User-Agent: ${settings.userAgentPreset}`); // FFmpeg arguments for pure remux (no encoding) // Very lightweight - just changes container from TS to fragmented MP4 const args = [ '-hide_banner', '-loglevel', 'warning', - '-user_agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + '-user_agent', userAgent, // Low-latency startup: reduce probe/analyze time for faster first bytes '-probesize', '32768', '-analyzeduration', '500000', // 0.5 seconds - enough to detect audio @@ -40,6 +47,8 @@ router.get('/', (req, res) => { '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', + // Prevent Range/HEAD requests that some providers reject with 405 + '-seekable', '0', '-i', url, // STRICT MAPPING: Only map video and audio, ignore subtitles/data/attachments // This prevents remux failure when source container has incompatible subtitle tracks (e.g. MKV -> MP4) diff --git a/server/routes/transcode.js b/server/routes/transcode.js index bdc0402..f1e25d9 100644 --- a/server/routes/transcode.js +++ b/server/routes/transcode.js @@ -1,6 +1,7 @@ const express = require('express'); const router = express.Router(); const { spawn } = require('child_process'); +const db = require('../db'); /** * Transcode stream @@ -9,14 +10,20 @@ const { spawn } = require('child_process'); * Transcodes audio to AAC for browser compatibility while passing video through. * This fixes playback issues with Dolby/AC3/EAC3 audio that browsers can't decode. */ -router.get('/', (req, res) => { +router.get('/', async (req, res) => { const { url } = req.query; if (!url) { return res.status(400).json({ error: 'URL parameter is required' }); } const ffmpegPath = req.app.locals.ffmpegPath || 'ffmpeg'; + + // Get User-Agent from settings + const settings = await db.settings.get(); + const userAgent = db.getUserAgent(settings); + console.log(`[Transcode] Starting transcoding for: ${url}`); + console.log(`[Transcode] Using User-Agent: ${settings.userAgentPreset}`); console.log(`[Transcode] Using binary: ${ffmpegPath}`); // FFmpeg arguments for transcoding @@ -25,7 +32,7 @@ router.get('/', (req, res) => { const args = [ '-hide_banner', '-loglevel', 'warning', - '-user_agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + '-user_agent', userAgent, // Faster startup - reduced probe/analyze for quicker first bytes '-probesize', '2000000', // 2MB (reduced from 5MB) '-analyzeduration', '3000000', // 3 seconds (reduced from 10s) @@ -39,6 +46,8 @@ router.get('/', (req, res) => { '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '3', + // Prevent Range/HEAD requests that some providers reject with 405 + '-seekable', '0', '-i', url, // Map only first video and audio stream (avoid subtitle streams causing issues) '-map', '0:v:0',