Miraiclip SDK
Creative

Transitions

Dissolves, dips, wipes and slides.

// Split a clip and bridge the cut
project.transaction(() => {
  project.dispatch({ type: "clip/split", payload: { clipId: "intro", atUs: 4_000_000, newClipId: "intro-b" } });
  project.dispatch({
    type: "transition/add",
    payload: { kind: "dipToBlack", fromClipId: "intro", toClipId: "intro-b", durationUs: 700_000 },
  });
});

A transition bridges a cut: the point where one clip ends and the next starts on the same track. The transition is centered on the cut. Both halves of a split come from the same source, so a fresh split always has room for a transition.

Kinds

kindLookparams
crossDissolveThe incoming clip fades in over the outgoing onenone
dipToBlackFade to black, then back innone
dipToWhiteFade to white, then back innone
wipeA moving edge reveals the incoming clipdirection: "left" (default), "right", "up", "down"
slideThe incoming clip slides in over the outgoing onedirection: "left" (default), "right", "up", "down"
project.dispatch({
  type: "transition/add",
  payload: { kind: "slide", fromClipId: "a", toClipId: "b", durationUs: 600_000, params: { direction: "up" } },
});

Audio on both clips ramps with an equal-power curve: the outgoing clip fades out up to the cut, the incoming clip fades in after it. This applies to every kind.

Commands

project.dispatch({
  type: "transition/add",
  payload: { id: "t1", kind: "crossDissolve", fromClipId: "a", toClipId: "b", durationUs: 800_000 },
});
project.dispatch({ type: "transition/update", payload: { transitionId: "t1", durationUs: 1_000_000 } });
project.dispatch({ type: "transition/update", payload: { transitionId: "t1", params: {} } });
project.dispatch({ type: "transition/remove", payload: { transitionId: "t1" } });
CommandPayloadNotes
transition/add{ kind, fromClipId, toClipId, durationUs, params?, id? }fromClipId ends at the cut, toClipId starts there. id is generated if omitted
transition/update{ transitionId, durationUs?, params? }params merge. A new duration is checked against headroom again
transition/remove{ transitionId }

Transitions live in doc.transitions, keyed by id: { id, trackId, fromClipId, toClipId, kind, durationUs, alignment: "centered", params }.

Rejections

CodeMeaning
different-tracksThe two clips are on different tracks
not-adjacenttoClipId doesn't start exactly where fromClipId ends
duplicate-boundaryThis cut already has a transition
insufficient-handlesA clip lacks the source media the transition needs (see below)
unknown-kindNo transition kind with that name is registered
invalid-paramsparams don't match the kind, e.g. direction: "diagonal"
transition-not-foundtransition/update or transition/remove with an unknown id

Headroom

During the transition, the outgoing clip keeps playing past its end and the incoming clip starts before its start. Each side needs half the transition's length of source media outside the clip's visible range. This is the clip's headroom.

import { clipHeadroomUs } from "@miraiclip/core";

const doc = project.getState().doc;
const outgoing = doc.clips["a"]!;
const incoming = doc.clips["b"]!;

clipHeadroomUs(doc, outgoing, "out"); // source after the clip's end: asset.durationUs - (trimStartUs + durationUs)
clipHeadroomUs(doc, incoming, "in"); // source before the clip's start: trimStartUs
  • Two clips that each play a whole file have no headroom. Trim their edges, or split a longer clip.
  • Image, text, caption and HTML clips have unlimited headroom.
  • An asset without durationUs counts as unlimited on the "out" side. Set durationUs on video and audio assets so the check is real.
  • Core checks headroom for every kind, dips included.

Find the cuts

findCuts(doc) lists the cuts on video tracks, with the longest transition each one allows. Use it for a transitions panel or a "dissolve everything" action.

import { applyCommands, findCuts } from "@miraiclip/core";

const commands = findCuts(project.getState().doc)
  .filter((cut) => !cut.transition && cut.maxTransitionUs >= 200_000)
  .map((cut) => ({
    type: "transition/add" as const,
    payload: {
      kind: "crossDissolve",
      fromClipId: cut.fromClipId,
      toClipId: cut.toClipId,
      durationUs: Math.min(600_000, cut.maxTransitionUs),
    },
  }));

applyCommands(project, commands); // one undo step
Cut fieldMeaning
trackId, fromClipId, toClipIdWhere the cut is
atUsTimeline position of the cut
transitionThe transition on this cut, if any
maxTransitionUsLongest transition the headroom and both clip lengths allow. 0 when a side has no headroom
short"from", "to" or "both": the side without headroom, when there is one

findCuts covers video, image, text and HTML clips on video tracks. Its second argument, { minTransitionUs }, marks a side short when its headroom allows less than that length (default 1: only a side with no headroom).

Notes

  • Moving, trimming, splitting or removing either clip removes the transition, because the cut it was built on changes.
  • Two clips of the same asset can transition.
  • Keyframes and effects still apply to both clips during a transition.
  • Your own kinds: Custom transitions.

On this page