Skills/Video Composition/Timegroup Element

ef-timegroup

Attributes

modeRequired
string

Duration calculation mode

Values:fixedsequencecontainfit
duration
timestring

Explicit duration (for fixed mode)

overlap
timestring

Overlap time between sequence items (e.g., "1s")

fps
number

Frame rate for rendering

Default:30
auto-init
boolean

Auto-seek to frame 0 on load (root only)

Default:false
workbench
boolean

Enable timeline/hierarchy UI (root only)

Default:false

Methods

async renderToVideo(options?: RenderToVideoOptions): Promise<Uint8Array | undefined>

Export timegroup to MP4 video using WebCodecs API

Returns: Promise<Uint8Array | undefined>
async createRenderClone(): Promise<RenderCloneResult>

Create independent off-DOM clone for background rendering without affecting preview

Returns: Promise<RenderCloneResult>
addFrameTask(callback: FrameTaskCallback): () => void

Register callback executed on each frame during rendering or playback

Returns: Cleanup function that removes the callback
async seek(timeMs: number): Promise<void>

Seek to specific time position and wait for content to be ready

Returns: Promise<void>

Container for sequencing and grouping elements.

Modes

  • fixed - Uses duration attribute

  • sequence - Sum of children (sequential playback)

  • contain - Longest child duration

  • fit - Inherit from parent

Root Timegroup

Live
Scene 1 Scene 2

Scene (Fixed Duration)

<ef-timegroup mode="fixed" duration="5s" class="absolute w-full h-full">
<ef-video src="clip.mp4" class="size-full object-cover"></ef-video>
<ef-text class="absolute top-4 left-4 text-white">Overlay</ef-text>
</ef-timegroup>

Nested Sequence

<ef-timegroup mode="sequence">
<ef-timegroup mode="fixed" duration="3s"><!-- Scene 1 --></ef-timegroup>
<ef-timegroup mode="fixed" duration="5s"><!-- Scene 2 --></ef-timegroup>
<ef-timegroup mode="fixed" duration="4s"><!-- Scene 3 --></ef-timegroup>
</ef-timegroup>

Sequence with Overlap

Use overlap to create transitions between items:

<ef-timegroup mode="sequence" overlap="1s">
<ef-timegroup mode="contain"><!-- Scene 1 --></ef-timegroup>
<ef-timegroup mode="contain"><!-- Scene 2 --></ef-timegroup>
</ef-timegroup>

See transitions.md for crossfade examples.

Methods

renderToVideo()

Export the timegroup composition to MP4 video using the WebCodecs API. See render-api.md for complete documentation.

async renderToVideo(options?: RenderToVideoOptions): Promise<Uint8Array | undefined>

Basic Example:

const tg = document.querySelector('ef-timegroup');
await tg.renderToVideo({
fps: 30,
codec: 'avc',
filename: 'output.mp4',
onProgress: (progress) => {
console.log(`${Math.round(progress.progress * 100)}% complete`);
}
});

RenderToVideoOptions:

  • fps - Frame rate (default: 30)
  • codec - Video codec: "avc", "hevc", "vp9", "av1", "vp8" (default: "avc")
  • bitrate - Video bitrate in bits/second (default: 5000000)
  • filename - Download filename (default: "video.mp4")
  • scale - Resolution multiplier (default: 1)
  • fromMs, toMs - Time range to export
  • onProgress - Progress callback receiving RenderProgress object
  • includeAudio - Include audio tracks (default: true)
  • signal - AbortSignal for cancellation
  • returnBuffer - Return Uint8Array instead of downloading
  • And more - see render-api.md

createRenderClone()

Create an independent off-DOM clone for background rendering without affecting the preview timeline.

async createRenderClone(): Promise<RenderCloneResult>

Example:

const tg = document.querySelector('ef-timegroup');
// Create isolated render clone
const { clone, container, cleanup } = await tg.createRenderClone();
try {
// Clone has independent time state
await clone.seek(5000); // Seek clone to 5 seconds
// Original timeline unaffected
console.log('Clone time:', clone.currentTimeMs);
console.log('Original time:', tg.currentTimeMs);
} finally {
// Always clean up
cleanup();
}

RenderCloneResult:

interface RenderCloneResult {
clone: EFTimegroup; // Cloned timegroup with independent state
container: HTMLElement; // Off-screen container holding the clone
cleanup: () => void; // Remove clone from DOM and clean up
}

Clones automatically re-run the initializer function if set, enabling JavaScript-driven animations in renders.

addFrameTask()

Register a callback that executes on each frame during playback or rendering. Returns cleanup function.

addFrameTask(callback: FrameTaskCallback): () => void

Example:

const tg = document.querySelector('ef-timegroup');
const cleanup = tg.addFrameTask((info) => {
// Update DOM based on current time
const progress = info.percentComplete;
console.log(`Frame at ${info.ownCurrentTimeMs}ms (${Math.round(progress * 100)}%)`);
// Modify child elements
const textEl = info.element.querySelector('.dynamic-text');
if (textEl) {
textEl.textContent = `Time: ${(info.ownCurrentTimeMs / 1000).toFixed(2)}s`;
}
});
// Remove callback when done
cleanup();

FrameTaskCallback:

type FrameTaskCallback = (info: {
ownCurrentTimeMs: number; // This element's local time
currentTimeMs: number; // Root timeline's global time
durationMs: number; // This element's duration
percentComplete: number; // Completion percentage (0.0 to 1.0)
element: EFTimegroup; // The timegroup instance
}) => void | Promise<void>;

The callback can be sync or async. Multiple callbacks can be registered and execute in parallel.

seek()

Seek to a specific time position and wait for all visible content to be ready.

async seek(timeMs: number): Promise<void>

Example:

const tg = document.querySelector('ef-timegroup');
// Seek to 3 seconds
await tg.seek(3000);
// Timeline is now at 3s, all visible media loaded
console.log('Current time:', tg.currentTimeMs);

Time is automatically quantized to frame boundaries based on the fps attribute.

Scripting

Add dynamic behavior with JavaScript using the initializer property. The initializer function runs on both the original timeline and all render clones.

<ef-timegroup id="dynamic-scene" mode="fixed" duration="5s">
<div class="dynamic-text"></div>
</ef-timegroup>
<script>
const tg = document.querySelector('#dynamic-scene');
// Initializer runs once per instance (original + clones)
tg.initializer = (instance) => {
instance.addFrameTask((info) => {
const text = instance.querySelector('.dynamic-text');
text.textContent = `Time: ${(info.ownCurrentTimeMs / 1000).toFixed(2)}s`;
});
};
</script>

TimegroupInitializer:

type TimegroupInitializer = (timegroup: EFTimegroup) => void;

Constraints:

  • Must be synchronous (no async/await, no Promise return)
  • Must complete quickly (<10ms warning, <100ms error)
  • Should only register callbacks and set up behavior
  • Runs once per instance (prime timeline + each render clone)
Understanding TimegroupsStep 1 of 3