VideoFlowcodeGitHubStudioTry itCoreRenderersReact Video EditorPlaygroundExamplesDocscodeGitHubStudioTry it
← Back to Blog

Mastering Timing in VideoFlow: The Wait and Parallel Playbook

October 3, 2026 · By VideoFlowStop fighting with manual frame math. Learn how to use VideoFlow's wait and parallel primitives to build robust, cinematic video timelines with code.Mastering Timing in VideoFlow: The Wait and Parallel Playbook

Managing a complex video timeline through code can quickly become a mess of manual frame math and absolute timestamps. If you've ever tried to build a 60-second video by hardcoding the startTime of every single layer, you know the pain: inserting a single two-second clip at the beginning forces you to recalculate every subsequent timestamp in the entire project. This fragility is the primary reason many developers abandon programmatic video pipelines before they even ship.

In VideoFlow, we solve this by treating time as a flow rather than a coordinate system. By using just two primitives—$.wait() and $.parallel()—you can build robust, adaptive timelines that handle changes gracefully. In this playbook, we’ll dive into how to master VideoFlow timing to build cinematic sequences that are as easy to maintain as they are to watch.

Sequential Flow: The Power of the Pointer

By default, the VideoFlow core builder maintains an internal "time pointer." When you add a layer and apply an animation, that pointer automatically advances by the duration of the animation. This sequential-by-default behavior means you can write code that reads like a script.

import VideoFlow from '@videoflow/core';

const $ = new VideoFlow({ width: 1920, height: 1080, fps: 30 });

const title = $.addText({ text: 'The Timing Playbook', fontSize: 6 });

// Flow pointer starts at 0s
title.fadeIn('500ms');   // Pointer moves to 0.5s
$.wait('2s');            // Pointer moves to 2.5s
title.fadeOut('500ms');  // Pointer moves to 3.0s

This simple mechanism ensures that if you decide to change the $.wait('2s') to $.wait('5s'), the fadeOut call automatically shifts forward in time. You don't have to touch a single frame index. This is the foundation of building dynamic social share videos where the content length might vary based on metadata.

Branching logic illustration

Branching with Parallel

Of course, videos aren't just one thing happening after another. You often need multiple layers to animate simultaneously—perhaps a background music track, a zooming image, and a text overlay all starting at once. This is where $.parallel() comes in.

$.parallel accepts an array of functions. Each function executes its own independent flow starting from the current project pointer. The main project pointer only advances once all branches have completed, jumping to the end of the longest branch.

$.parallel([
  // Branch A: The music starts and stays for 10s
  () => {
    const music = $.addAudio({ volume: 0.5 }, { source: '/music.mp3' });
    $.wait('10s');
    music.remove();
  },
  // Branch B: A title card sequence
  () => {
    const text = $.addText({ text: 'Parallel Action', fontSize: 8 });
    text.fadeIn('1s');
    $.wait('3s');
    text.fadeOut('1s');
  }
]);

// The pointer is now at 10s (the length of the longest branch)
$.addText({ text: 'The End', fontSize: 5 }).fadeIn('500ms');

The Background Escape Hatch: wait: false

Sometimes, you want to trigger an animation that runs in the background for the entire duration of a scene without blocking the rest of your flow. While you could use a parallel branch, VideoFlow provides a simpler shorthand: the wait: false option.

When wait: false is passed to an animation call, the flow pointer does not advance. This is perfect for "backdrop" effects like a slow Ken Burns zoom on a background image or a pulsing glow effect.

const bg = $.addImage({ fit: 'cover' }, { source: 'landscape.jpg' });

// Start a slow zoom that takes 10 seconds, but don't block the flow
bg.animate(
  { scale: 1 }, 
  { scale: 1.1 }, 
  { duration: '10s', wait: false }
);

// These calls run immediately while the background continues to zoom
$.addText({ text: 'Overlay 1' }).fadeIn('1s');
$.wait('2s');
$.addText({ text: 'Overlay 2' }).fadeIn('1s');

Modular blocks of timing

Mastering Staggered Animations

One of the most common cinematic patterns is the staggered entrance—where a list of items or characters appears one by one with a slight delay. Combining parallel with wait makes this trivial to implement with a loop.

const items = ['Fast', 'Open', 'Portable'].map((t, i) => 
  $.addText({ text: t, position: [0.5, 0.3 + i * 0.15], opacity: 0 })
);

$.parallel(items.map((layer, index) => () => {
  $.wait(`${index * 200}ms`); // Stagger each start by 200ms
  layer.animate(
    { opacity: 0, scale: 0.8 }, 
    { opacity: 1, scale: 1 }, 
    { duration: '600ms', easing: 'easeOut' }
  );
}));

Why This Architecture Wins

By separating the visual properties from the flow timing, VideoFlow allows your code to remain readable and declarative. Whether you are using the in-browser renderer for zero-latency export or the server-side renderer for batch processing, the timing logic remains identical and portable.

This approach is particularly powerful when building LLM-driven video pipelines. Agents can easily emit nested arrays of actions without needing to calculate global timestamps, making it much harder for the model to "break" the timeline.

Get Started with Flow

Ready to experiment with these primitives yourself? The fastest way to see wait and parallel in action is to jump into our interactive Playground. You can also check out our full timing and builder guide for a deep dive into more advanced patterns like nested groups and parallel scopes.

VideoFlow is fully open-source (Apache-2.0). If you find these timing primitives helpful, or have ideas for how to make programmatic video even more intuitive, come say hi on GitHub.

VideoFlow

Open-source toolkit for composing videos from code.

Product

CoreRenderersReact Video EditorPlaygroundStudio

Learn

DocsAPI referenceExamplesvs. Remotionvs. FFmpeg

Project

GitHubLicenseContactTermsPrivacy

From the blog

All posts →Beyond Static Images: Generating Dynamic Social Share Videos from MetadataHow to Embed a Full-Featured Video Editor in Your React AppStop Concatenating Strings: A Migration Guide from FFmpeg to VideoFlowHow to Give Your AI Agent a Video Tool: An LLM-to-Video PlaybookMarkdown to MP4: Automating Technical Explainer Videos with VideoFlowMastering GLSL Effects: Building Cinematic Pipelines with CodeMastering Timing in VideoFlow: The Wait and Parallel PlaybookHow to Render MP4s in Node.js Without FFmpeg
© 2026 VideoFlow. Apache-2.0 core.