This commit is contained in:
gpt-engineer-app[bot]
2025-12-21 18:10:00 +00:00
parent 043b06d6ea
commit 8ecd8da712
4 changed files with 264 additions and 122 deletions
+34 -38
View File
@@ -61,41 +61,29 @@ export const useOfflineVideoExport = () => {
setState(prev => ({ ...prev, stage: 'rendering', progress: 5 }));
// Load intro video
// Load intro video - always use webm
console.log('📹 Loading intro video...');
const introVideo = document.createElement('video');
introVideo.muted = true;
introVideo.playsInline = true;
introVideo.preload = 'auto';
introVideo.src = '/intro.webm';
// Try webm first, fallback to mp4
let introLoaded = false;
let introDuration = 0;
try {
await new Promise<void>((resolve, reject) => {
introVideo.onloadedmetadata = () => {
introDuration = introVideo.duration;
console.log(`✅ Intro video loaded: ${introDuration.toFixed(2)}s`);
introLoaded = true;
resolve();
};
introVideo.onerror = () => {
console.warn('⚠️ Could not load intro.webm, trying intro.mp4');
introVideo.src = '/intro.mp4';
};
introVideo.src = '/intro.webm';
// Timeout after 5 seconds
setTimeout(() => {
if (!introLoaded) {
console.warn('⚠️ Intro video loading timed out');
resolve();
}
}, 5000);
});
} catch (introError) {
console.warn('⚠️ Could not load intro video:', introError);
}
// Wait for video to be fully loaded
await new Promise<void>((resolve) => {
introVideo.onloadeddata = () => {
introDuration = introVideo.duration;
console.log(`✅ Intro video loaded: ${introDuration.toFixed(2)}s, ${introVideo.videoWidth}x${introVideo.videoHeight}`);
resolve();
};
introVideo.onerror = (e) => {
console.error('❌ Failed to load intro video:', e);
resolve();
};
introVideo.load();
});
setState(prev => ({ ...prev, progress: 10 }));
@@ -215,7 +203,7 @@ export const useOfflineVideoExport = () => {
recorder.start(1000); // 1 second chunks
// Calculate total frames including intro
const introFrames = introLoaded ? Math.ceil(introDuration * fps) : 0;
const introFrames = introDuration > 0 ? Math.ceil(introDuration * fps) : 0;
const mainFrames = Math.ceil(duration * fps);
const fadeFrames = Math.ceil(fps * 0.5); // 0.5 second fade
const totalFrames = introFrames + mainFrames;
@@ -223,22 +211,31 @@ export const useOfflineVideoExport = () => {
console.log(`🎬 Total frames: ${totalFrames} (intro: ${introFrames}, main: ${mainFrames}, fade: ${fadeFrames})`);
// Render intro frames first (if loaded)
if (introLoaded && introFrames > 0) {
// Render intro frames first
if (introFrames > 0) {
console.log('📹 Rendering intro frames...');
introVideo.currentTime = 0;
await introVideo.play();
for (let frameIndex = 0; frameIndex < introFrames; frameIndex++) {
if (cancelledRef.current) {
introVideo.pause();
recorder.stop();
setState({ isExporting: false, progress: 0, error: 'Cancelled', stage: 'idle', fps: 0 });
return null;
}
// Seek to correct time
introVideo.currentTime = frameIndex / fps;
// Seek to correct time and wait for frame
const targetTime = frameIndex / fps;
introVideo.currentTime = targetTime;
// Wait for the seek to complete
await new Promise<void>((resolve) => {
const onSeeked = () => {
introVideo.removeEventListener('seeked', onSeeked);
resolve();
};
introVideo.addEventListener('seeked', onSeeked);
// Fallback timeout
setTimeout(resolve, 50);
});
// Draw intro video frame scaled to canvas
ctx.fillStyle = '#0a0f0a';
@@ -268,7 +265,6 @@ export const useOfflineVideoExport = () => {
await new Promise(resolve => setTimeout(resolve, 1000 / fps));
}
introVideo.pause();
console.log('✅ Intro frames complete');
}
@@ -338,7 +334,7 @@ export const useOfflineVideoExport = () => {
}
// Apply fade-in effect from intro (first fadeFrames of main content)
if (introLoaded && frameIndex < fadeFrames) {
if (introDuration > 0 && frameIndex < fadeFrames) {
const fadeProgress = frameIndex / fadeFrames;
// Draw a semi-transparent black overlay that fades out
ctx.fillStyle = `rgba(10, 15, 10, ${1 - fadeProgress})`;