Charlie KrugThe Build Log

← All posts

A branching video is a graph pretending to be a timeline

Branchreel turns plain video segments into a browser-side state machine, preloading every possible next scene before the viewer chooses it.

The viewer clicks “Open the door,” the picture cuts, and a different video starts. That simple moment has an awkward requirement: the player must prepare every door before it knows which one will open.

Branchreel is a small TypeScript library for putting branching video on a page I control. I describe the story as plain JSON, give the library an HTML <video> element, and render its choice events as buttons. There is no hosted editor, backend, or special media format between the story and the browser.

I built it for projects that already have their video segments but do not need an entire interactive-video platform. The alternative is surprisingly repetitive: switch sources, pause at choice points, preload possible scenes, remember the route taken, and invent a scrubber that does not lie about what “halfway” means.

The player is a state machine

A Branchreel story is a directed graph. Each node names a video source and can define start and end times. Its outgoing edges are choices with a label and a target node. The graph also names one starting node. That is enough information for a state machine to keep a current node and a history such as intro, hallway, roof.

The constructor checks the whole graph before playback. Duplicate node IDs and choices aimed at missing nodes throw immediately, rather than failing after a viewer has made three decisions. The state machine itself has no DOM dependency, so it can also be used alone when something other than a browser video element owns playback.

The player controller adds the media mechanics. It drives one <video> element and changes its src when the state machine moves. I deliberately avoided keeping one video element per scene. A branch should read as a jump cut inside one player, not as two players handing the screen back and forth.

Load both doors before one opens

As soon as a node begins playing, Branchreel starts preloading every video named by its choices. When the viewer picks one, choose() changes the host element's source synchronously. It does not put a network request on the critical path between the click and the cut. If a preload fails, the choice still works through the browser's normal load path, but that branch can pause to buffer. Preloading improves the common case; it cannot repeal a slow connection.

Branching also breaks the usual scrubber. There is no single duration for a graph when one route might contain two clips and another five. Branchreel reports progress through the current path instead of flattening scenes the viewer never visited into a fictional timeline. A separate graph-layout function exposes the full structure. It assigns each node a column based on its shortest-path distance from the start, then returns positions and edges for the app to draw in SVG, Canvas, or whatever fits its page.

What it leaves out

All state lives in browser memory. Refreshing clears the current node and history unless the integrator stores them, and Branchreel does not provide analytics, accounts, uploads, or an authoring interface. The JSON is easy to hand-write for a small story, but a graph with hundreds of scenes will want tooling of its own. Those omissions are the point of the package: the core has zero runtime dependencies and does one job without claiming ownership of the production around it.

Try it

Open the Branchreel playground, play until the first choice, and pick a route. The graph beside the player lights up the path you took while leaving the other branch visible. Then restart, choose differently, and inspect the plain JSON and TypeScript source on GitHub.

Branchreel is live. Free, in your browser, no signup.

This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.

Comments

Loading comments…