Export a video
VideoExportWizard is the whole fullscreen flow: format, camera, overlay, review, with a live preview that flies the exact plan the recorder will encode. It is the heaviest surface in the package, so lazy() it: the encoder and the second map load only when somebody opens it.
Mount the wizard
The wizard reads the open export off the studio store. Mount it once inside the provider and dispatch studio.video.open to raise it.
The preview map is not the studio's map, so it takes its own client and transform. Mountaya's own wizard authenticates as the itinerary's owner, which opens a preview for a member holding no publishable key. Pin the published snapshot in the transform: a transform resolving the draft records geometry no embed or share link serves.
Build the transform once. MapLibre reads it at construction. A fresh reference on every render reads as a changed prop and rebuilds the map underneath the flight. invalidateAiConfig is optional, and it is called with the organization id whose AI configuration a director run reported as stale.
What the wizard walks through
The wizard takes the aspect ratio, the resolution and the length, then the camera style and the shot list, with the AI Director's conversation beside them. Picking a style regenerates the plan, and the director edits it shot by shot. Both go through the bus, so the preview follows either. It then decides whether the elevation overlay is drawn, in which design, and in whose units and language, and ends on a summary with the capture's progress once Record is pressed. The steps are the wizard's own: @mountaya/studio-react/video publishes VideoExportWizard, its props and its strings, and a host words a step through strings.
Each step reads and writes the wizard's own slice of the studio store, so it stays with the preview. The preview frame is sized from the plan's output ratio, so changing a preset reshapes live what the recorder targets.
Two surfaces are gated on video-organization-modes, above the video-export that opens the wizard at all: the Custom format card, and the AI Director. A member holds both while the organization holds Map Publishing beside Map Studio, since a video is something an organization publishes. The tool layer's own gate on the director verbs is the real boundary. A reader is never offered a control the run would refuse.
No video is made of a personal itinerary, one no organization owns. Every verb that opens, configures, previews, records or hands over a video refuses it with forbidden before it runs, and the wizard over one shows no step, only the sentence saying why.
Record without the wizard
@mountaya/elements/video is the engine underneath. Fill the map's video seam with a factory, and a map mounted without one constructs no recorder at all.
One run is active at a time. Starting a preview or a recording aborts the run before it. Every run snapshots the camera and restores it when the flight settles or aborts. A cancel resolves rather than rejects, so it is a branch to handle and not an exception to catch.
previewPoses flies the plan on the live map with no frame capture. It takes the plan's wall-clock length where there is one. A hold extends the time the frames span without adding any. recordPoses is the same flight with the frames read off the canvas, and it composites its own position dot: a DOM marker sits outside gl.readPixels.
RecorderSeams is what makes the loop testable without a GPU. createCanvasFrameCapture and createCanvasSurface are the production builders, and a suite injects its own for deterministic buffers. The encoder stays off this barrel, so a consumer never installs mediabunny's types to use the recorder. It rides the EncoderFactory seam, and a host substituting one imports the module path directly.
Draw the elevation overlay
Three designs, one registry, and the renderers take a 2D context and know nothing about frames. The studio draws the same designs through the same registry into a React canvas beside the map. A surface showing an elevation strip and recording nothing reads them here too.
The three designs draw here through the same registry the recorder composites with, and nothing is recorded.
buildProfileCurve normalizes an elevation series into the curve every design draws. videoOverlayStringsFor resolves the overlay's own words. They are burnt into pixels, so they cannot come from a runtime the frame does not have. overlayScale and OVERLAY_REFERENCE_SHORT_SIDE land a design tuned at one output size proportionally at another.
The engine does not choose the plan. Poses arrive fully resolved: what to fly over, how long to hold and where to look are the studio's decisions. It uploads nothing, and resolveBitrateBps and resolveDimensions answer from the output you asked for.