Upload once.
Stream anywhere.
One type-safe API for video across Rehelios, Mux, Bunny Stream, and Cloudflare Stream. Swap the adapter, keep every call site — and let the compiler catch what a provider can't do.
Live snippet
The exact same code. Any provider.
Switch the adapter, keep every call site. Here is the same upload, playback, and thumbnail sequence across four providers.
import { createVideos } from "videos-sdk";import { rehelios } from "videos-sdk/rehelios";const videos = createVideos({ adapter: rehelios({ apiKey: process.env.REHELIOS_API_KEY! }),});await videos.upload("intro.mp4", file);const { hls } = await videos.playback(id);const poster = videos.thumbnail(id, { time: 3 });Capabilities
Everything you need for video.
A complete set of operations behind one interface, capability-safe by types, with a normalized asset lifecycle and playback that works the same on every provider.
Every operation, one interface
Read the docs →create, upload, get, list, delete — the same calls on every adapter, returning one normalized Asset. No provider types leak into your code.
await videos.upload("intro.mp4", file);const asset = await videos.get(id);const page = await videos.list({ limit: 20 });await videos.delete(id);Unsupported is a compile error
Read the docs →Each adapter declares its capabilities as literal types. The API narrows to match, so a provider that can't do something never lets you call it.
const mux = createVideos({ adapter: mux(cfg) });(await mux.playback(id)).dash;// ❌ Mux has no DASHconst cf = createVideos({ adapter: cloudflare(cfg) });(await cf.playback(id)).dash; // ✅ stringOne normalized lifecycle
Read the docs →Every provider's status codes collapse to five canonical states, so your app reads the same asset lifecycle no matter what's behind the adapter.
const { status } = await videos.get(id);// "waiting_upload" | "uploading"// "processing" | "ready" | "errored"Playback & signed URLs
Read the docs →HLS and DASH manifests, poster thumbnails at any timestamp, and short-lived signed playback URLs — one shape across every backend.
const { hls, dash } = await videos.playback(id);const poster = videos.thumbnail(id, { time: 3 });const url = await videos.signedPlayback(id, { expiresInSeconds: 3600,});Honest by design
One interface, honest about the differences.
Where a provider can't do something, the SDK says so — in the types and in this table.
| Capability | Rehelios | Mux | Bunny | Cloudflare |
|---|---|---|---|---|
| Resumable upload | ||||
| Ingest from URL | ||||
| HLS playback | ||||
| DASH playback | — | — | ||
| Signed playback | ||||
| Thumbnail at time | — | — | ||
| Captions | ||||
| Webhooks |
Ship the video layer once.
Open source, MIT licensed, built around web standards. Drop in an adapter and forget the difference.