More optimizations
This commit is contained in:
116
index.js
116
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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user