fix: 3 critical bugs — audio corruption, blob URLs, static duration

1. Audio/Video sync: During playback, let the browser handle media naturally
   and only force-seek when drift > 250ms. Prevents audio glitching caused
   by setting currentTime 30x/sec. When paused/scrubbing, sync precisely.

2. Blob URLs → Server uploads: All media uploads (BrandTabMedia, SceneFieldEditor,
   TemplateFieldInput) now POST to /api/upload and use persistent server URLs
   instead of blob: URLs that vanish on refresh.

3. Dynamic duration: Added useVideoDurations hook that probes actual video
   durations from uploaded form videos. getTemplateDuration and
   compileExpressToTimeline now accept videoDurations to override static
   durationSeconds for form-sourced scenes.
This commit is contained in:
2026-06-02 09:48:28 -05:00
parent 32505f95ce
commit a21675e5fc
9 changed files with 212 additions and 37 deletions
+23 -5
View File
@@ -78,9 +78,23 @@ export function getAspectDimensions(aspect: string): { w: number; h: number } {
}
}
/** Compute total duration of template in seconds */
export function getTemplateDuration(template: ExpressTemplate): number {
return template.scenes.reduce((sum, s) => sum + s.durationSeconds, 0);
/**
* Compute total duration of template in seconds.
* @param videoDurations Optional map of scene.id → actual video duration (seconds).
* When provided, overrides the static durationSeconds for scenes
* that source video from form uploads.
*/
export function getTemplateDuration(
template: ExpressTemplate,
videoDurations?: Record<string, number>,
): number {
return template.scenes.reduce((sum, scene) => {
// If this scene has a form-sourced video and we know its actual duration, use it
if (videoDurations && scene.segmentSource === 'form' && videoDurations[scene.id]) {
return sum + videoDurations[scene.id];
}
return sum + scene.durationSeconds;
}, 0);
}
/**
@@ -92,6 +106,7 @@ export function compileExpressToTimeline(
fieldData: Record<string, string>,
designMD: DesignMD,
company?: CompanyProfile,
videoDurations?: Record<string, number>,
): { elements: TimelineElement[]; layers: TimelineLayer[] } {
const fps = 30;
const elements: TimelineElement[] = [];
@@ -106,8 +121,11 @@ export function compileExpressToTimeline(
// Process each scene sequentially — the template's scenes are the sole source of truth
for (const scene of template.scenes) {
const sceneDurFrames = scene.durationSeconds * fps;
// Use actual video duration if available for form-sourced scenes
const sceneDuration = (videoDurations && scene.segmentSource === 'form' && videoDurations[scene.id])
? videoDurations[scene.id]
: scene.durationSeconds;
const sceneDurFrames = sceneDuration * fps;
const sceneStart = frameOffset;
const sceneEnd = frameOffset + sceneDurFrames;