Providers
The layer that hides each media engine behind one interface.
A provider plays media. Every provider reports the same state and takes the same commands, so the controls and plugins never need to know which engine is running.
The native provider ships in the kernel and handles anything the browser plays itself. hls() and dash() register providers for adaptive streams.
How a source finds a provider
When a source loads, each registered provider is asked whether it can play it. That check is synchronous and imports nothing, based only on the source and the browser. The engine downloads only when its provider is chosen, and only when load allows.
const plugins = [hls(), dash()];<VideoPlayer src="/stream.mpd" plugins={plugins} />;Here, a page that only ever plays MP4 downloads neither engine.
Providers stack
A plugin can hand playback to another provider for a while. Only the top provider writes state and feeds the clock. The ones below it are suspended, and they resume with their own state when it's removed. A pushed provider can bring its own timeline and capabilities, and the ones from before it come back afterwards.
This is how casting works. Chromecast pushes a provider that mirrors the remote device, so the timeline and play button keep working with nothing Cast-specific in them.
Write your own
A provider implements this interface:
A media engine behind the normalized player interface.
| Field | Type | Description |
|---|---|---|
| name | string | Appears in MediaState.provider while this provider is active. |
| attach | (element: HTMLVideoElement | null, sink: Sink, scope: Scope) => void | Called once. element is null for remote providers such as Cast. |
| load | (request: LoadRequest, signal: AbortSignal) => Promise<void> | Loads the request, and should stop if signal aborts. |
| play | () => Promise<void> | Starts playback, rejecting if the browser refuses. |
| pause | () => void | Pauses playback. |
| seek | (time: number) => void | Moves to time, in seconds. |
| setVolume | (volume: number, muted: boolean) => void | Applies the volume, from 0 to 1, and the muted setting. |
| setRate | (rate: number) => void | Applies a playback rate, where 1 is normal speed. |
| time | number | Hot read used by the clock. Must not allocate. |
| setQuality? | ((id: string | "auto") => void) | Switches to the level with this id, or to automatic choice with auto. |
| setAudioTrack? | ((id: string) => void) | Switches to the audio track with this id. |
| setTextTrack? | ((id: string | null) => void) | Uses the text track with this id, or none with null. |
| snapshot? | (() => Partial<MediaState>) | Current state, re-applied when the provider resumes from suspension. |
| destroy | () => void | Releases everything the provider holds, when it's removed. |
Register it from a plugin, with a check that imports nothing, and a load that imports your engine only when needed:
import { definePlugin } from "react-video-kit/core";export const myEngine = definePlugin("my-engine", () => ({ setup(context) { context.registerProvider({ name: "my-engine", canPlay: ({ src, type }) => type === "application/x-mine" || src.endsWith(".mine"), load: () => import("./my-provider").then(({ MyProvider }) => MyProvider), }); },}));Providers report through a Sink, and only that way, which is what lets the stack suspend them:
The only way a provider reports state. Ignored while the provider is suspended.
| Field | Type | Description |
|---|---|---|
| update | (state: Partial<MediaState>) => void | Reports changed state, which the player merges into MediaState. |
| sync | () => void | Reports that time moved discontinuously, so the clock must write now. |
| error | (error: MediaErrorInfo) => void | Reports a failure, which stops playback if it's fatal. |
time is read by the clock on every tick, so it must not allocate. Report failures through the sink's error, with a code from Errors or one of your own.