1
0

More optimizations

This commit is contained in:
2025-11-09 10:07:21 -05:00
parent f59b0de539
commit 428e0cc739

116
index.js
View File

@@ -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
});
}