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
|
// Create a temporary concat playlist file
|
||||||
playlistFile = path.join('/tmp', `playlist-${Date.now()}.txt`);
|
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 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'));
|
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
|
// Input 0: video frames
|
||||||
ffmpegArgs.push(
|
ffmpegArgs.push(
|
||||||
@@ -147,12 +150,12 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu
|
|||||||
'-f', 'concat',
|
'-f', 'concat',
|
||||||
'-safe', '0',
|
'-safe', '0',
|
||||||
'-stream_loop', '-1', // Loop playlist infinitely
|
'-stream_loop', '-1', // Loop playlist infinitely
|
||||||
'-probesize', '32', // Minimal probing for faster startup
|
|
||||||
'-analyzeduration', '0', // Skip analysis for faster startup
|
|
||||||
'-i', playlistFile
|
'-i', playlistFile
|
||||||
);
|
);
|
||||||
// Encoding
|
// Encoding with audio filtering for smooth transitions
|
||||||
ffmpegArgs.push(
|
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',
|
'-c:v', 'libx264',
|
||||||
'-preset', 'ultrafast',
|
'-preset', 'ultrafast',
|
||||||
'-tune', 'zerolatency',
|
'-tune', 'zerolatency',
|
||||||
@@ -166,6 +169,10 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu
|
|||||||
'-c:a', 'aac',
|
'-c:a', 'aac',
|
||||||
'-b:a', '128k',
|
'-b:a', '128k',
|
||||||
'-ar', '44100', // Set explicit audio sample rate
|
'-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',
|
'-f', 'hls',
|
||||||
'-hls_time', '1', // Smaller segments for faster startup
|
'-hls_time', '1', // Smaller segments for faster startup
|
||||||
'-hls_list_size', '3', // Fewer segments in playlist
|
'-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.stdout.pipe(res);
|
||||||
|
|
||||||
ffmpegProcess.stderr.on('data', (data) => {
|
ffmpegProcess.stderr.on('data', (data) => {
|
||||||
// Suppress verbose FFmpeg output
|
const message = data.toString();
|
||||||
// console.error(`FFmpeg: ${data}`);
|
// 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) => {
|
ffmpegProcess.on('error', (error) => {
|
||||||
@@ -290,12 +306,49 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu
|
|||||||
let sendBlackFrames = true;
|
let sendBlackFrames = true;
|
||||||
let waitingForCorrectUrl = !!lateGeocodePromise; // Track if we're waiting for geocoding to complete
|
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
|
// Start loading the page in the background - don't wait for it
|
||||||
const pageLoadPromise = page.goto(url, { waitUntil: 'load', timeout: 30000 })
|
const pageLoadPromise = waitForPageFullyLoaded(page, hideLogo)
|
||||||
.then(() => {
|
.then(async (loaded) => {
|
||||||
// Only switch to live frames if we're not waiting for the correct URL
|
// Only switch to live frames if we're not waiting for the correct URL
|
||||||
if (!waitingForCorrectUrl) {
|
if (!waitingForCorrectUrl && loaded) {
|
||||||
console.log('Page loaded, switching to live frames');
|
|
||||||
sendBlackFrames = false;
|
sendBlackFrames = false;
|
||||||
|
|
||||||
// Hide logo if requested
|
// Hide logo if requested
|
||||||
@@ -312,8 +365,9 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error('Page load error:', err.message);
|
console.error('Page load promise error:', err.message);
|
||||||
if (!waitingForCorrectUrl) {
|
if (!waitingForCorrectUrl) {
|
||||||
|
// Show page anyway after error to avoid black screen forever
|
||||||
sendBlackFrames = false;
|
sendBlackFrames = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -324,8 +378,30 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu
|
|||||||
if (!isCleaningUp && page && !page.isClosed() && updatedUrl && updatedUrl !== url) {
|
if (!isCleaningUp && page && !page.isClosed() && updatedUrl && updatedUrl !== url) {
|
||||||
try {
|
try {
|
||||||
console.log('Updating to correct location...');
|
console.log('Updating to correct location...');
|
||||||
await page.goto(updatedUrl, { waitUntil: 'load', timeout: 10000 });
|
// Wait for networkidle2 to ensure all resources load
|
||||||
console.log('Correct location loaded, switching to live frames');
|
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;
|
waitingForCorrectUrl = false;
|
||||||
sendBlackFrames = false; // Now show real frames
|
sendBlackFrames = false; // Now show real frames
|
||||||
|
|
||||||
@@ -342,18 +418,18 @@ async function streamHandler(req, res, useMusic = false, lateGeocodePromise = nu
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Location update error:', err.message);
|
console.error('Location update error:', err.message);
|
||||||
waitingForCorrectUrl = false;
|
waitingForCorrectUrl = false;
|
||||||
|
// Show page anyway to avoid black screen forever
|
||||||
sendBlackFrames = false;
|
sendBlackFrames = false;
|
||||||
}
|
}
|
||||||
} else if (!updatedUrl || updatedUrl === url) {
|
} else if (!updatedUrl || updatedUrl === url) {
|
||||||
// Geocoding completed but URL is the same (was already correct)
|
// Geocoding completed but URL is the same (was already correct)
|
||||||
waitingForCorrectUrl = false;
|
// Wait for the initial page load to complete before switching
|
||||||
sendBlackFrames = false;
|
console.log('Using initial URL, waiting for page load to complete...');
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Geocoding failed - use fallback location
|
// Geocoding failed - use fallback location
|
||||||
console.warn('Geocoding failed, using fallback location');
|
console.warn('Geocoding failed, waiting for fallback location to load');
|
||||||
waitingForCorrectUrl = false;
|
// Let the initial page load complete before switching
|
||||||
sendBlackFrames = false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user