Merge pull request #124 from asandhu3/ffmpeg-fixes

Fix ffmpeg redirects. Fix max resolution setting.
This commit is contained in:
Technomancer702
2026-10-12 15:16:31 -07:00
committed by GitHub
6 changed files with 44 additions and 17 deletions

View File

@ -920,7 +920,8 @@ class VideoPlayer {
videoMode, videoMode,
videoCodec: info.video, videoCodec: info.video,
audioCodec: info.audio, audioCodec: info.audio,
audioChannels: info.audioChannels audioChannels: info.audioChannels,
videoHeight: info.height
}); });
this.currentUrl = playlistUrl; // Update currentUrl for HLS reload this.currentUrl = playlistUrl; // Update currentUrl for HLS reload

View File

@ -460,7 +460,8 @@ class WatchPage {
seekOffset: this.resumeTime, // Ensure seekOffset is passed seekOffset: this.resumeTime, // Ensure seekOffset is passed
videoCodec: info.video, videoCodec: info.video,
audioCodec: info.audio, audioCodec: info.audio,
audioChannels: info.audioChannels audioChannels: info.audioChannels,
videoHeight: info.height
}); });
this.playHls(playlistUrl); this.playHls(playlistUrl);
this.setVolumeFromStorage(); this.setVolumeFromStorage();

View File

@ -33,6 +33,11 @@ function probeStream(url, ffprobePath, userAgent = null, timeout = 15000) {
const args = [ const args = [
'-v', 'error', '-v', 'error',
'-user_agent', userAgent || '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 || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'-http_persistent', '0',
'-reconnect', '1',
'-reconnect_streamed', '1',
'-reconnect_on_http_error', '4xx,5xx',
'-reconnect_delay_max', '10',
'-print_format', 'json', '-print_format', 'json',
'-show_streams', '-show_streams',
'-show_format', '-show_format',
@ -186,7 +191,7 @@ router.get('/', async (req, res) => {
console.error('[Probe] Failed:', err.message); console.error('[Probe] Failed:', err.message);
// On error, assume transcode needed to be safe // On error, assume transcode needed to be safe
res.json({ const fallback = {
video: 'unknown', video: 'unknown',
audio: 'unknown', audio: 'unknown',
container: 'unknown', container: 'unknown',
@ -194,7 +199,11 @@ router.get('/', async (req, res) => {
needsRemux: false, needsRemux: false,
needsTranscode: true, needsTranscode: true,
error: err.message error: err.message
}); };
probeCache.set(cacheKey, { result: fallback, timestamp: Date.now() - (CACHE_TTL - 30000) });
res.json(fallback);
} }
}); });

View File

@ -44,10 +44,12 @@ router.get('/', async (req, res) => {
'-err_detect', 'ignore_err', '-err_detect', 'ignore_err',
// Limit max demux delay to prevent buffering issues with bad timestamps // Limit max demux delay to prevent buffering issues with bad timestamps
'-max_delay', '5000000', '-max_delay', '5000000',
'-http_persistent', '0',
// Reconnect settings for network drops // Reconnect settings for network drops
'-reconnect', '1', '-reconnect', '1',
'-reconnect_streamed', '1', '-reconnect_streamed', '1',
'-reconnect_delay_max', '5', '-reconnect_on_http_error', '4xx,5xx',
'-reconnect_delay_max', '10',
// Prevent Range/HEAD requests that some providers reject with 405 // Prevent Range/HEAD requests that some providers reject with 405
'-seekable', '0', '-seekable', '0',
'-i', url, '-i', url,

View File

@ -29,7 +29,7 @@ transcodeSession.startCleanupInterval();
* Body: { url: string, seekOffset?: number } * Body: { url: string, seekOffset?: number }
*/ */
router.post('/session', async (req, res) => { router.post('/session', async (req, res) => {
const { url, seekOffset, videoMode, videoCodec, audioCodec, audioChannels } = req.body; const { url, seekOffset, videoMode, videoCodec, audioCodec, audioChannels, videoHeight } = req.body;
if (!url) { if (!url) {
return res.status(400).json({ error: 'URL is required' }); return res.status(400).json({ error: 'URL is required' });
@ -55,7 +55,8 @@ router.post('/session', async (req, res) => {
videoMode: videoMode, // 'copy' or 'encode' videoMode: videoMode, // 'copy' or 'encode'
videoCodec: videoCodec, // 'h264', 'hevc', etc. videoCodec: videoCodec, // 'h264', 'hevc', etc.
audioCodec: audioCodec, // 'aac', 'ac3', etc. audioCodec: audioCodec, // 'aac', 'ac3', etc.
audioChannels: audioChannels // number of channels (2=stereo) audioChannels: audioChannels, // number of channels (2=stereo)
videoHeight: videoHeight // source height from probe; used to cap max-resolution in JS
}); });
await session.start(); await session.start();
@ -191,10 +192,12 @@ router.get('/', async (req, res) => {
'-err_detect', 'ignore_err', '-err_detect', 'ignore_err',
// Limit max demux delay to prevent buffering issues // Limit max demux delay to prevent buffering issues
'-max_delay', '2000000', '-max_delay', '2000000',
'-http_persistent', '0',
// Reconnect settings for network drops (useful for live streams) // Reconnect settings for network drops (useful for live streams)
'-reconnect', '1', '-reconnect', '1',
'-reconnect_streamed', '1', '-reconnect_streamed', '1',
'-reconnect_delay_max', '3', '-reconnect_on_http_error', '4xx,5xx',
'-reconnect_delay_max', '10',
// Prevent Range/HEAD requests that some providers reject with 405 // Prevent Range/HEAD requests that some providers reject with 405
'-seekable', '0', '-seekable', '0',
'-i', url, '-i', url,

View File

@ -198,9 +198,11 @@ class TranscodeSession extends EventEmitter {
'-analyzeduration', '5000000', '-analyzeduration', '5000000',
'-fflags', '+genpts+discardcorrupt', '-fflags', '+genpts+discardcorrupt',
'-err_detect', 'ignore_err', '-err_detect', 'ignore_err',
'-http_persistent', '0',
'-reconnect', '1', '-reconnect', '1',
'-reconnect_streamed', '1', '-reconnect_streamed', '1',
'-reconnect_delay_max', '3' '-reconnect_on_http_error', '4xx,5xx',
'-reconnect_delay_max', '10'
); );
args.push('-i', this.url); args.push('-i', this.url);
@ -390,34 +392,43 @@ class TranscodeSession extends EventEmitter {
buildScaleFilter(encoder, height) { buildScaleFilter(encoder, height) {
const useUpscale = this.options.upscaleEnabled; const useUpscale = this.options.upscaleEnabled;
const upscaleMethod = this.options.upscaleMethod || 'hardware'; const upscaleMethod = this.options.upscaleMethod || 'hardware';
const sourceHeight = this.options.videoHeight | 0;
// Log upscaling status const effectiveHeight = useUpscale
? height
: (sourceHeight > 0 ? Math.min(height, sourceHeight) : height);
// Log upscaling / passthrough decision
if (useUpscale) { if (useUpscale) {
console.log(`[TranscodeSession ${this.id}] Upscaling: ${upscaleMethod} method to ${height}p`); console.log(`[TranscodeSession ${this.id}] Upscaling: ${upscaleMethod} method to ${effectiveHeight}p`);
} else if (sourceHeight > 0 && sourceHeight <= height) {
console.log(`[TranscodeSession ${this.id}] Source ${sourceHeight}p is within cap ${height}p, passing through`);
} }
const h = effectiveHeight;
// Hardware scaling filters (for both upscale and downscale) // Hardware scaling filters (for both upscale and downscale)
if (upscaleMethod === 'hardware' || !useUpscale) { if (upscaleMethod === 'hardware' || !useUpscale) {
switch (encoder) { switch (encoder) {
case 'nvenc': case 'nvenc':
// NVIDIA CUDA scaling with Lanczos // NVIDIA CUDA scaling with Lanczos
// Force nv12 (8-bit) output to handle 10-bit inputs (fixes "10 bit encode not supported") // Force nv12 (8-bit) output to handle 10-bit inputs (fixes "10 bit encode not supported")
return `scale_cuda=-2:${height}:interp_algo=lanczos:format=nv12`; return `scale_cuda=-2:${h}:interp_algo=lanczos:format=nv12`;
case 'vaapi': case 'vaapi':
return `scale_vaapi=w=-2:h=${height}:format=nv12`; return `scale_vaapi=w=-2:h=${h}:format=nv12`;
case 'qsv': case 'qsv':
return `scale_qsv=w=-2:h=${height}:format=nv12`; return `scale_qsv=w=-2:h=${h}:format=nv12`;
case 'amf': case 'amf':
// AMF uses CPU decode, so use software scale // AMF uses CPU decode, so use software scale
return useUpscale ? `scale=-2:${height}:flags=lanczos` : `scale=-2:${height}`; return useUpscale ? `scale=-2:${h}:flags=lanczos` : `scale=-2:${h}`;
case 'software': case 'software':
default: default:
return useUpscale ? `scale=-2:${height}:flags=lanczos` : `scale=-2:${height}`; return useUpscale ? `scale=-2:${h}:flags=lanczos` : `scale=-2:${h}`;
} }
} }
// Software Lanczos scaling (high quality, slower) // Software Lanczos scaling (high quality, slower)
return `scale=-2:${height}:flags=lanczos`; return `scale=-2:${h}:flags=lanczos`;
} }
/** /**