From 428e0cc739a0d506190046f39e6366d9a9d652a5 Mon Sep 17 00:00:00 2001 From: sethwv-alt Date: Sun, 9 Nov 2025 10:07:21 -0500 Subject: [PATCH] More optimizations --- index.js | 116 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index 8871cfd..247efe8 100644 --- a/index.js +++ b/index.js @@ -128,12 +128,15 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu // Create a temporary concat playlist file playlistFile = path.join('/tmp', `playlist-${Date.now()}.txt`); - // Build playlist content - just 1 repetition since we loop infinitely with -stream_loop + // Build playlist content - repeat the list 3 times to reduce loop edge case issues const currentShuffle = shuffleArray([...allMusicFiles]); - const playlistLines = currentShuffle.map(f => `file '${f}'`); + const playlistLines = []; + for (let i = 0; i < 3; i++) { + currentShuffle.forEach(f => playlistLines.push(`file '${f}'`)); + } fs.writeFileSync(playlistFile, playlistLines.join('\n')); - console.log(`Created shuffled playlist with ${allMusicFiles.length} tracks (infinite loop)`); + // console.log(`Created shuffled playlist with ${allMusicFiles.length} tracks x3 repetitions (infinite loop)`); // Input 0: video frames ffmpegArgs.push( @@ -147,12 +150,12 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu '-f', 'concat', '-safe', '0', '-stream_loop', '-1', // Loop playlist infinitely - '-probesize', '32', // Minimal probing for faster startup - '-analyzeduration', '0', // Skip analysis for faster startup '-i', playlistFile ); - // Encoding + // Encoding with audio filtering for smooth transitions ffmpegArgs.push( + // Use audio filter to ensure smooth transitions and consistent format + '-af', 'aresample=async=1:min_hard_comp=0.100000:first_pts=0,aformat=sample_rates=44100:channel_layouts=stereo', '-c:v', 'libx264', '-preset', 'ultrafast', '-tune', 'zerolatency', @@ -166,6 +169,10 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu '-c:a', 'aac', '-b:a', '128k', '-ar', '44100', // Set explicit audio sample rate + '-ac', '2', // Stereo output + '-avoid_negative_ts', 'make_zero', // Prevent timestamp issues + '-fflags', '+genpts+igndts', // Generate presentation timestamps, ignore decode timestamps + '-max_interleave_delta', '0', // Reduce audio/video sync issues during transitions '-f', 'hls', '-hls_time', '1', // Smaller segments for faster startup '-hls_list_size', '3', // Fewer segments in playlist @@ -246,8 +253,17 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu ffmpegProcess.stdout.pipe(res); ffmpegProcess.stderr.on('data', (data) => { - // Suppress verbose FFmpeg output - // console.error(`FFmpeg: ${data}`); + const message = data.toString(); + // Log important warnings about audio discontinuities or errors + if (message.includes('Non-monotonous DTS') || + message.includes('Application provided invalid') || + message.includes('past duration') || + message.includes('Error while decoding stream') || + message.includes('Invalid data found')) { + console.warn(`FFmpeg warning: ${message.trim()}`); + } + // Uncomment for full FFmpeg output during debugging: + // console.error(`FFmpeg: ${message}`); }); ffmpegProcess.on('error', (error) => { @@ -290,12 +306,49 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu let sendBlackFrames = true; let waitingForCorrectUrl = !!lateGeocodePromise; // Track if we're waiting for geocoding to complete + // Helper function to wait for page to be fully loaded with all resources + const waitForPageFullyLoaded = async (page, hideLogo) => { + try { + // Wait for networkidle2 (waits until no more than 2 network connections for 500ms) + await page.goto(url, { waitUntil: 'networkidle2', timeout: 45000 }); + console.log('Page network idle, waiting for content to render...'); + + // Additional wait for dynamic content to fully render (weather data, radar, etc.) + await page.waitForTimeout(3000); + + // Verify content is actually visible before switching + const hasVisibleContent = await page.evaluate(() => { + // Check if there's actual content beyond just background + const body = document.body; + if (!body) return false; + + // Check for common weather page elements + const hasContent = document.querySelector('canvas') || + document.querySelector('.weather-display') || + document.querySelector('img[src*="radar"]') || + document.querySelectorAll('div').length > 10; + return hasContent; + }).catch(() => false); + + if (!hasVisibleContent) { + console.log('Content not fully visible yet, waiting additional time...'); + await page.waitForTimeout(2000); + } + + console.log('Page fully loaded with all resources, switching to live frames'); + return true; + } catch (err) { + console.error('Page load error:', err.message); + // Still show the page even if timeout occurs + return true; + } + }; + // Start loading the page in the background - don't wait for it - const pageLoadPromise = page.goto(url, { waitUntil: 'load', timeout: 30000 }) - .then(() => { + const pageLoadPromise = waitForPageFullyLoaded(page, hideLogo) + .then(async (loaded) => { // Only switch to live frames if we're not waiting for the correct URL - if (!waitingForCorrectUrl) { - console.log('Page loaded, switching to live frames'); + if (!waitingForCorrectUrl && loaded) { sendBlackFrames = false; // Hide logo if requested @@ -312,8 +365,9 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu } }) .catch(err => { - console.error('Page load error:', err.message); + console.error('Page load promise error:', err.message); if (!waitingForCorrectUrl) { + // Show page anyway after error to avoid black screen forever sendBlackFrames = false; } }); @@ -324,8 +378,30 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu if (!isCleaningUp && page && !page.isClosed() && updatedUrl && updatedUrl !== url) { try { console.log('Updating to correct location...'); - await page.goto(updatedUrl, { waitUntil: 'load', timeout: 10000 }); - console.log('Correct location loaded, switching to live frames'); + // Wait for networkidle2 to ensure all resources load + await page.goto(updatedUrl, { waitUntil: 'networkidle2', timeout: 45000 }); + console.log('Correct location network idle, waiting for content...'); + + // Additional wait for dynamic content + await page.waitForTimeout(3000); + + // Verify content is visible + const hasVisibleContent = await page.evaluate(() => { + const body = document.body; + if (!body) return false; + const hasContent = document.querySelector('canvas') || + document.querySelector('.weather-display') || + document.querySelector('img[src*="radar"]') || + document.querySelectorAll('div').length > 10; + return hasContent; + }).catch(() => false); + + if (!hasVisibleContent) { + console.log('Content not fully visible yet, waiting additional time...'); + await page.waitForTimeout(2000); + } + + console.log('Correct location fully loaded, switching to live frames'); waitingForCorrectUrl = false; sendBlackFrames = false; // Now show real frames @@ -342,18 +418,18 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu } catch (err) { console.error('Location update error:', err.message); waitingForCorrectUrl = false; + // Show page anyway to avoid black screen forever sendBlackFrames = false; } } else if (!updatedUrl || updatedUrl === url) { // Geocoding completed but URL is the same (was already correct) - waitingForCorrectUrl = false; - sendBlackFrames = false; + // Wait for the initial page load to complete before switching + console.log('Using initial URL, waiting for page load to complete...'); } }).catch(() => { // Geocoding failed - use fallback location - console.warn('Geocoding failed, using fallback location'); - waitingForCorrectUrl = false; - sendBlackFrames = false; + console.warn('Geocoding failed, waiting for fallback location to load'); + // Let the initial page load complete before switching }); }