Skip to main content
Solid Primitives 2

Primitives to manage HTML video playback.

StageCategoryVersionLast UpdatedDemo
3Display & Media1.0.0-next.3 (next)Aug 12, 2026Demo →
Terminal window
npm i @solid-primitives/video@next

Layered primitives for managing HTML video playback. The make* variants are non-reactive and require no Solid owner. The create* variants integrate with Solid's reactive system — createVideo covers essential playback state, and createVideoPlayer extends it with the full control surface.

How to use it

makeVideo

Creates a raw HTMLVideoElement with optional event handlers and initial configuration. No Solid owner required.

const [player, cleanup] = makeVideo("clip.mp4", {}, { muted: true, loop: true });
cleanup();
function makeVideo(
src: VideoSource | HTMLVideoElement,
handlers?: VideoEventHandlers,
options?: VideoOptions,
): [player: HTMLVideoElement, cleanup: VoidFunction];

makeVideoPlayer

Wraps makeVideo with imperative playback controls. No Solid owner required.

const [{ play, pause, seek, setVolume, setMuted, setPlaybackRate, setLoop }, cleanup] =
makeVideoPlayer("clip.mp4");
await play();
seek(30);
setPlaybackRate(1.5);
setLoop(true);
cleanup();
function makeVideoPlayer(
src: VideoSource | HTMLVideoElement,
handlers?: VideoEventHandlers,
options?: VideoOptions,
): [controls: VideoControls, cleanup: VoidFunction];

createVideo

Essential reactive playback state: playing, currentTime, ended, seeking, error, and an async duration that suspends until metadata is loaded.

const video = createVideo("clip.mp4");
// or with a reactive source:
const video = createVideo(() => selectedUrl());
video.playing(); // boolean — true while actively playing
video.setPlaying(true); // plays
video.currentTime(); // seconds
video.seek(30);
video.ended(); // boolean
video.seeking(); // boolean — true while scrubbing
video.error(); // MediaError | null

The duration accessor throws NotReadyError until video metadata has loaded, integrating with Solid 2.0's <Loading> boundary:

<Loading fallback="Loading…">
<span>{video.duration()}s</span>
</Loading>
function createVideo(src: VideoSource | Accessor<VideoSource>, options?: VideoOptions): VideoReturn;

createVideoPlayer

Extends createVideo with the full control surface: volume, muted, playback rate, loop, buffering state, and dimensions. Accepts all VideoOptions plus volume and playbackRate initial values.

const video = createVideoPlayer("clip.mp4", {
muted: true,
volume: 0.8,
playbackRate: 1,
});
// All fields from createVideo, plus:
video.volume(); // 0–1
video.setVolume(0.5);
video.muted(); // boolean
video.setMuted(true);
video.playbackRate(); // number
video.setPlaybackRate(1.5);
video.loop(); // boolean
video.setLoop(true);
video.buffered(); // TimeRanges | undefined
video.readyState(); // 0–4
video.videoWidth(); // intrinsic pixel width
video.videoHeight(); // intrinsic pixel height

Fullscreen is intentionally omitted — use the dedicated @solid-primitives/fullscreen primitive to manage fullscreen state and attach it to video.player.

function createVideoPlayer(
src: VideoSource | Accessor<VideoSource>,
options?: VideoControlsOptions,
): VideoControlsReturn;

makeVideoFrameCallback

Wraps HTMLVideoElement.requestVideoFrameCallback, which fires once per displayed video frame instead of once per display refresh — it stops naturally while the video is paused, and the metadata argument (mediaTime, presentedFrames, etc.) lets you sync work to actual playback instead of wall-clock time. No Solid owner required.

const [player, cleanup] = makeVideo("clip.mp4");
const [running, start, stop] = makeVideoFrameCallback(player, (now, metadata) => {
draw(metadata.mediaTime);
});
start();
stop();
cleanup();
function makeVideoFrameCallback(
video: HTMLVideoElement,
callback: VideoFrameRequestCallback,
): [running: () => boolean, start: VoidFunction, stop: VoidFunction];

createVideoFrameCallback

Reactive version of makeVideoFrameCallback — takes an accessor for the video element, so it re-attaches whenever the element changes and stops cleanly when it becomes undefined. running is a Solid signal, and playback is automatically stopped onCleanup.

const video = createVideo("clip.mp4");
const [running, start, stop] = createVideoFrameCallback(
() => video.player,
(now, metadata) => {
console.log(metadata.presentedFrames);
},
);
start();
function createVideoFrameCallback(
el: Accessor<HTMLVideoElement | undefined>,
callback: VideoFrameRequestCallback,
): [running: Accessor<boolean>, start: VoidFunction, stop: VoidFunction];

Types

type VideoSource = string | undefined | MediaProvider;
type VideoOptions = {
autoPlay?: boolean;
loop?: boolean;
muted?: boolean;
preload?: "" | "none" | "metadata" | "auto";
};
type VideoControlsOptions = VideoOptions & {
volume?: number;
playbackRate?: number;
};
type VideoReturn = {
player: HTMLVideoElement;
playing: Accessor<boolean>;
setPlaying: (v: boolean) => void;
currentTime: Accessor<number>;
seek: (time: number) => void;
ended: Accessor<boolean>;
seeking: Accessor<boolean>;
error: Accessor<MediaError | null>;
duration: Accessor<number>; // throws NotReadyError until loaded
};
type VideoControlsReturn = VideoReturn & {
volume: Accessor<number>;
setVolume: (v: number) => void;
muted: Accessor<boolean>;
setMuted: (v: boolean) => void;
playbackRate: Accessor<number>;
setPlaybackRate: (rate: number) => void;
loop: Accessor<boolean>;
setLoop: (v: boolean) => void;
buffered: Accessor<TimeRanges | undefined>;
readyState: Accessor<number>;
videoWidth: Accessor<number>;
videoHeight: Accessor<number>;
};

Changelog

See CHANGELOG.md

Solid Primitives 2High-quality reactive primitives for building applications in Solid2
Community
githubdiscord