Speed & freeze frames
Play a clip faster or slower, or hold one frame.
Coming in the next release
Clip speed and freeze frames are built and tested in @miraiclip/core, @miraiclip/renderer and @miraiclip/editor, and ship in the next SDK release.
// Play "screen" twice as fast. It keeps the same footage, so it gets half as long.
project.dispatch({ type: "clip/set-speed", payload: { clipId: "screen", speed: 2, ripple: true } });
// Hold the frame at 4 s for 1.5 s. Later clips on the track move back by 1.5 s.
project.dispatch({ type: "clip/freeze", payload: { clipId: "screen", atUs: 4_000_000, durationUs: 1_500_000 } });Video and audio clips have a speed. Absent means 1. Preview, audio, browser export, worker export and server export all map time the same way, so a sped-up clip looks and sounds the same everywhere.
speed | Effect | Clips |
|---|---|---|
1 (or absent) | Normal playback | video, audio |
2 | Twice as fast: 4 s on the timeline shows 8 s of footage | video, audio |
0.5 | Half speed | video, audio |
0 | Freeze frame: every frame shows the source frame at trimStartUs, with no sound | video only |
Allowed values are 0 or 1/16 to 16 (MIN_CLIP_SPEED, MAX_CLIP_SPEED). Audio pitch follows speed, like a tape played faster.
Change speed
project.dispatch({
type: "clip/set-speed",
payload: { clipId: "screen", speed: 0.5, keepDuration: false, ripple: true },
});| Field | Default | Meaning |
|---|---|---|
clipId | required | A video or audio clip |
speed | required | 0 (video only) or 1/16..16 |
keepDuration | false | false: the clip keeps its footage and its timeline length changes (2× halves it). true: the length stays and the clip plays more or less footage |
ripple | false | Move later clips on the same track by the change in length |
Transitions touching the clip are removed, because the footage around its edges changes.
| Rejection | Meaning |
|---|---|
no-source | The clip has no media to speed up (text, caption, html, image) |
cannot-freeze | speed: 0 on an audio clip |
Freeze a frame
clip/freeze splits a video clip and inserts a freeze-frame clip that holds the frame at atUs.
project.dispatch({
type: "clip/freeze",
payload: {
clipId: "screen",
atUs: 4_000_000, // inside the clip, or exactly at its end
durationUs: 1_500_000,
freezeClipId: "hold-1", // optional, for deterministic replay
rightClipId: "screen-2", // optional: the part after the freeze
},
});| Field | Default | Meaning |
|---|---|---|
atUs | required | Timeline time of the frame to hold. At the clip's end, the last frame is held and there is no right part |
durationUs | required | How long the freeze lasts |
freezeClipId, rightClipId | generated | Ids for the new clips. Supply them when commands are replayed, for example in collaboration |
ripple | true | Push later clips on the track back by durationUs |
The freeze clip is an ordinary video clip with speed: 0. It copies the original's transform and effects, but not its keyframes or fades. Delete it to remove the freeze, or change its length like any clip.
Markers don't move when a command ripples a track. Move them with marker/update if they need to follow. See Markers.
Map time
Use these helpers whenever you convert between timeline time and source time, for example to draw a waveform or thumbnails under a sped-up clip.
import { clipSpeed, isFrozenClip, toSourceUs, toTimelineUs, sourceSpanUs } from "@miraiclip/core";
const clip = project.getState().doc.clips["screen"]!;
const sourceAt = toSourceUs(clip, project.getState().playheadUs); // where in the file the playhead is
const onTimeline = toTimelineUs(clip, 12_000_000); // undefined for a freeze frame
const footage = sourceSpanUs(clip); // 0 for a freeze frame| Function | Returns |
|---|---|
clipSpeed(clip) | The clip's speed (1 for clips without a source) |
isFrozenClip(clip) | true when speed is 0 |
isValidClipSpeed(speed) | Whether a value is allowed |
toSourceUs(clip, timelineUs) | Position in the source media |
toTimelineUs(clip, sourceUs) | Position on the timeline, or undefined for a freeze frame |
sourceSpanUs(clip), sourceEndUs(clip) | How much footage the clip uses, and where it ends in the source |
durationForSource(sourceUs, speed) | Timeline length needed to play that much footage at that speed |
clipHeadroomTimelineUs(doc, clip, side) | Spare footage on one side, in timeline time, for transitions |
In an editor
@miraiclip/editor's command builders wrap both commands, and trimming a sped-up clip accounts for its speed.
import { createCommands } from "@miraiclip/editor";
const commands = createCommands(project);
commands.setClipSpeed("screen", 2, { ripple: true });
commands.freezeFrame("screen", 4_000_000, 1_500_000);