Added experimental force remux setting to remux raw ts streams to mp4 container
This commit is contained in:
@@ -129,10 +129,38 @@ For streams with Dolby Digital (AC3/EAC3) audio that browsers can't decode nativ
|
||||
NodeCast TV is optimized for **HLS (HTTP Live Streaming)**.
|
||||
|
||||
- **✅ HLS (`.m3u8`)**: Fully supported and recommended. Best for adaptive bitrate and network resilience.
|
||||
- **✅ MPEG-TS (`.ts`)**: Supported via the backend proxy. Works well for standard IPTV providers (Xtream Codes).
|
||||
- **✅ MPEG-TS (`.ts`)**: Supported via Force Remux or Force Audio Transcode settings.
|
||||
- **⚠️ High Latency/P2P**: For sources like Acestream, prefer HLS output (`.m3u8`) over raw TS streams to avoid timeouts during buffering.
|
||||
- **❌ RTMP/RTSP**: Not supported natively by browsers.
|
||||
|
||||
## Streaming Settings
|
||||
|
||||
All streaming settings are found in **Settings → Player → Streaming**.
|
||||
|
||||
| Setting | What It Does | When to Enable |
|
||||
|---------|--------------|----------------|
|
||||
| **Force Backend Proxy** | Routes streams through the NodeCast TV server, adding proper CORS headers | When streams fail with "Access-Control-Allow-Origin" errors, or when using IPTV middleware |
|
||||
| **Force Audio Transcode** | Transcodes audio to AAC using FFmpeg (video passes through unchanged) | When you have video but no audio (Dolby/AC3/EAC3 streams) |
|
||||
| **Force Remux** | Remuxes MPEG-TS to MP4 container using FFmpeg (no re-encoding, very lightweight) | When using raw `.ts` streams from m3u-editor, dispatcharr, or similar middleware |
|
||||
| **Stream Output Format** | Controls whether Xtream API requests use HLS (.m3u8) or TS format | Try TS if you experience buffering issues with HLS |
|
||||
|
||||
### Which Setting Do I Need?
|
||||
|
||||
```
|
||||
Stream won't play at all?
|
||||
├── CORS error in console? → Enable "Force Backend Proxy"
|
||||
├── Raw .ts URL from middleware? → Enable "Force Remux"
|
||||
└── Still not working? → Check codec support table above
|
||||
|
||||
Video plays but no audio?
|
||||
├── Stream has Dolby/AC3/EAC3? → Enable "Force Audio Transcode"
|
||||
└── Using Safari? → Should work natively, check volume
|
||||
|
||||
Buffering issues?
|
||||
└── Try changing "Stream Output Format" to TS
|
||||
```
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Video Won't Play (Black Screen or Loading Forever)
|
||||
@@ -198,6 +226,7 @@ If you're using IPTV middleware like **m3u-editor**, **dispatcharr**, **Threadfi
|
||||
| Setting | Location | When to Enable |
|
||||
|---------|----------|----------------|
|
||||
| **Force Backend Proxy** | Settings → Player → Streaming | Always recommended when using middleware |
|
||||
| **Force Remux** | Settings → Player → Streaming | For raw `.ts` streams (lightweight, no re-encoding) |
|
||||
| **Force Audio Transcode** | Settings → Player → Streaming | If you have no audio (Dolby/AC3/EAC3 streams) |
|
||||
| **Stream Format: TS** | Settings → Streaming | If HLS streams buffer excessively |
|
||||
|
||||
@@ -209,8 +238,8 @@ m3u-editor includes an internal proxy that remuxes streams to MPEG-TS.
|
||||
|
||||
**Setup:**
|
||||
1. In m3u-editor, configure your playlist and enable the proxy if needed
|
||||
2. In NodeCast TV, enable **"Force Backend Proxy"** in Settings → Streaming
|
||||
3. If audio doesn't play, enable **"Force Audio Transcode"**
|
||||
2. In NodeCast TV, enable **"Force Remux"** in Settings → Streaming (for raw .ts streams)
|
||||
3. If audio doesn't play, enable **"Force Audio Transcode"** instead
|
||||
|
||||
**Note:** m3u-editor's proxy preserves original codecs. If your source has HEVC or Dolby, you'll need transcoding or a compatible browser (Safari).
|
||||
|
||||
@@ -222,8 +251,8 @@ dispatcharr uses FFmpeg stream profiles to process streams. By default it output
|
||||
|
||||
**Setup:**
|
||||
1. In dispatcharr, streams are proxied by default via stream profiles
|
||||
2. In NodeCast TV, enable **"Force Backend Proxy"** in Settings → Streaming
|
||||
3. If audio doesn't play, enable **"Force Audio Transcode"**
|
||||
2. In NodeCast TV, enable **"Force Remux"** in Settings → Streaming (for raw .ts streams)
|
||||
3. If audio doesn't play, enable **"Force Audio Transcode"** instead
|
||||
|
||||
**Custom dispatcharr profile for browser compatibility:**
|
||||
If you want dispatcharr to transcode audio for you instead of NodeCast TV:
|
||||
@@ -240,8 +269,8 @@ These HDHomeRun emulators work similarly to other middleware.
|
||||
|
||||
**Setup:**
|
||||
1. Add your Threadfin/xTeVe M3U URL as an M3U source in NodeCast TV
|
||||
2. Enable **"Force Backend Proxy"** in Settings → Streaming
|
||||
3. If needed, enable **"Force Audio Transcode"**
|
||||
2. Enable **"Force Remux"** in Settings → Streaming (for raw .ts streams)
|
||||
3. If needed, enable **"Force Audio Transcode"** instead for Dolby audio
|
||||
|
||||
### TVHeadend
|
||||
|
||||
|
||||
@@ -365,6 +365,17 @@
|
||||
but no audio (fixes Dolby/AC3/EAC3). Requires ffmpeg-static.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label class="setting-toggle">
|
||||
<input type="checkbox" id="setting-force-remux">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">Force Remux</span>
|
||||
<span class="setting-hint">Remux streams to MP4 container (no re-encoding). Enable for raw .ts streams
|
||||
from IPTV middleware like m3u-editor or dispatcharr. Very lightweight.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-info" style="flex: 1;">
|
||||
<span class="setting-label">Stream Output Format</span>
|
||||
|
||||
@@ -34,7 +34,8 @@ class VideoPlayer {
|
||||
lastVolume: 80, // Last used volume (if rememberVolume is true)
|
||||
autoPlayNextEpisode: false, // Auto-play next episode in series
|
||||
forceProxy: false, // Force all streams through backend proxy
|
||||
forceTranscode: false // Force audio transcoding (fixes Dolby/AC3 audio)
|
||||
forceTranscode: false, // Force audio transcoding (fixes Dolby/AC3 audio)
|
||||
forceRemux: false // Force remux to MP4 (fixes raw .ts streams, lightweight)
|
||||
};
|
||||
try {
|
||||
const saved = localStorage.getItem('nodecast_tv_player_settings');
|
||||
@@ -263,9 +264,40 @@ class VideoPlayer {
|
||||
const finalUrl = needsProxy ? this.getProxiedUrl(streamUrl) : streamUrl;
|
||||
|
||||
// Detect if this is likely an HLS stream (vs direct video file like MP4)
|
||||
// Note: .ts files are raw MPEG-TS, NOT HLS manifests - exclude them
|
||||
const looksLikeHls = finalUrl.includes('.m3u8') ||
|
||||
finalUrl.includes('m3u8') ||
|
||||
(!finalUrl.includes('.mp4') && !finalUrl.includes('.mkv') && !finalUrl.includes('.avi'));
|
||||
(!finalUrl.includes('.mp4') && !finalUrl.includes('.mkv') &&
|
||||
!finalUrl.includes('.avi') && !finalUrl.includes('.ts'));
|
||||
|
||||
// Check for raw TS streams that browsers can't play directly
|
||||
const isRawTs = finalUrl.includes('.ts') && !finalUrl.includes('.m3u8');
|
||||
if (isRawTs) {
|
||||
// If Force Remux is enabled, route through remux endpoint
|
||||
if (this.settings.forceRemux) {
|
||||
console.log('[Player] Raw TS stream detected. Using remux to convert to MP4...');
|
||||
const remuxUrl = this.getRemuxUrl(streamUrl);
|
||||
this.video.src = remuxUrl;
|
||||
this.video.play().catch(e => console.log('[Player] Autoplay prevented:', e));
|
||||
|
||||
// Update UI and dispatch events
|
||||
this.updateNowPlaying(channel);
|
||||
this.showNowPlayingOverlay();
|
||||
this.fetchEpgData(channel);
|
||||
window.dispatchEvent(new CustomEvent('channelChanged', { detail: channel }));
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn('[Player] Raw MPEG-TS stream detected. Browsers cannot play .ts files directly.');
|
||||
this.showError(
|
||||
'This stream uses raw MPEG-TS format (.ts) which browsers cannot play directly.<br><br>' +
|
||||
'<strong>To fix this:</strong><br>' +
|
||||
'1. Enable <strong>"Force Remux"</strong> in Settings → Streaming (lightweight)<br>' +
|
||||
'2. Or enable <strong>"Force Audio Transcode"</strong> if you also have audio issues<br>' +
|
||||
'3. Or configure your source to output HLS (.m3u8) format'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Priority 1: Use HLS.js for HLS streams on browsers that support it
|
||||
if (looksLikeHls && Hls.isSupported()) {
|
||||
@@ -465,6 +497,14 @@ class VideoPlayer {
|
||||
return `/api/transcode?url=${encodeURIComponent(url)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remuxed URL for a stream (container conversion only, no re-encoding)
|
||||
* Used for raw .ts streams that browsers can't play directly
|
||||
*/
|
||||
getRemuxUrl(url) {
|
||||
return `/api/remux?url=${encodeURIComponent(url)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode base64 EPG data
|
||||
*/
|
||||
|
||||
@@ -30,6 +30,7 @@ class SettingsPage {
|
||||
const autoPlayNextToggle = document.getElementById('setting-autoplay-next');
|
||||
const forceProxyToggle = document.getElementById('setting-force-proxy');
|
||||
const forceTranscodeToggle = document.getElementById('setting-force-transcode');
|
||||
const forceRemuxToggle = document.getElementById('setting-force-remux');
|
||||
|
||||
// Load current settings
|
||||
if (this.app.player?.settings) {
|
||||
@@ -45,6 +46,9 @@ class SettingsPage {
|
||||
if (forceTranscodeToggle) {
|
||||
forceTranscodeToggle.checked = this.app.player.settings.forceTranscode || false;
|
||||
}
|
||||
if (forceRemuxToggle) {
|
||||
forceRemuxToggle.checked = this.app.player.settings.forceRemux || false;
|
||||
}
|
||||
}
|
||||
|
||||
// Arrow keys toggle
|
||||
@@ -93,6 +97,12 @@ class SettingsPage {
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// Force remux toggle
|
||||
forceRemuxToggle?.addEventListener('change', () => {
|
||||
this.app.player.settings.forceRemux = forceRemuxToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// EPG refresh interval
|
||||
const epgRefreshSelect = document.getElementById('epg-refresh-interval');
|
||||
if (epgRefreshSelect) {
|
||||
|
||||
@@ -37,6 +37,7 @@ app.use('/api/proxy', require('./routes/proxy'));
|
||||
app.use('/api/channels', require('./routes/channels'));
|
||||
app.use('/api/favorites', require('./routes/favorites'));
|
||||
app.use('/api/transcode', require('./routes/transcode'));
|
||||
app.use('/api/remux', require('./routes/remux'));
|
||||
|
||||
// SPA fallback - serve index.html for all non-API routes
|
||||
app.get('*', (req, res) => {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
/**
|
||||
* Remux stream (container conversion only)
|
||||
* GET /api/remux?url=...
|
||||
*
|
||||
* Remuxes MPEG-TS to fragmented MP4 for browser playback.
|
||||
* This is a lightweight operation - no video/audio re-encoding.
|
||||
* Use this for raw .ts streams that browsers can't play directly.
|
||||
*
|
||||
* Note: This does NOT fix Dolby/AC3 audio issues - use /api/transcode for that.
|
||||
*/
|
||||
router.get('/', (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';
|
||||
console.log(`[Remux] Starting remux for: ${url}`);
|
||||
|
||||
// FFmpeg arguments for pure remux (no encoding)
|
||||
// Very lightweight - just changes container from TS to fragmented MP4
|
||||
const args = [
|
||||
'-hide_banner',
|
||||
'-loglevel', 'warning',
|
||||
'-fflags', '+genpts+discardcorrupt',
|
||||
'-i', url,
|
||||
// Copy ALL streams without re-encoding
|
||||
'-c', 'copy',
|
||||
// Fragmented MP4 for streaming (browser-compatible)
|
||||
'-f', 'mp4',
|
||||
'-movflags', 'frag_keyframe+empty_moov+default_base_moof',
|
||||
'-' // Output to stdout
|
||||
];
|
||||
|
||||
console.log(`[Remux] Full command: ${ffmpegPath} ${args.join(' ')}`);
|
||||
|
||||
let ffmpeg;
|
||||
try {
|
||||
ffmpeg = spawn(ffmpegPath, args);
|
||||
} catch (spawnErr) {
|
||||
console.error('[Remux] Failed to spawn FFmpeg:', spawnErr);
|
||||
return res.status(500).json({ error: 'FFmpeg spawn failed', details: spawnErr.message });
|
||||
}
|
||||
|
||||
// Set headers for fragmented MP4
|
||||
res.setHeader('Content-Type', 'video/mp4');
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
|
||||
// Pipe stdout to response
|
||||
ffmpeg.stdout.pipe(res);
|
||||
|
||||
// Log stderr (useful for debugging)
|
||||
ffmpeg.stderr.on('data', (data) => {
|
||||
const msg = data.toString();
|
||||
// Only log warnings/errors, not progress
|
||||
if (msg.includes('Warning') || msg.includes('Error') || msg.includes('error')) {
|
||||
console.log(`[Remux FFmpeg] ${msg}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup on client disconnect
|
||||
req.on('close', () => {
|
||||
console.log('[Remux] Client disconnected, killing FFmpeg process');
|
||||
ffmpeg.kill('SIGKILL');
|
||||
});
|
||||
|
||||
// Handle process exit
|
||||
ffmpeg.on('exit', (code) => {
|
||||
if (code !== null && code !== 0 && code !== 255) {
|
||||
console.error(`[Remux] FFmpeg exited with code ${code}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle spawn errors
|
||||
ffmpeg.on('error', (err) => {
|
||||
console.error('[Remux] Failed to spawn FFmpeg:', err);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: 'Remux failed to start' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user