Merge pull request #124 from asandhu3/ffmpeg-fixes
Fix ffmpeg redirects. Fix max resolution setting.
This commit is contained in:
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user