LIGHTBIRD
Play everything. Entirely client-side.
npm install @lightbird/coreWhy LightBird?
MKV in the Browser
Play MKV files directly — no server transcoding. FFmpeg.wasm handles remuxing client-side.
Full Subtitle Pipeline
SRT, VTT, ASS/SSA with encoding detection, sync offset, and styled rendering.
Audio Track Switching
Multiple audio tracks in MKV containers. Switch between them without reloading.
Zero Server Needed
Everything runs in the browser. Your videos never leave the device.
Installation
npm install @lightbird/core @lightbird/player-react"use client"
import { LightBirdPlayer } from '@lightbird/player-react'
export default function VideoPage() {
return <LightBirdPlayer />
}Add ./node_modules/@lightbird/player-react/dist/**/*.js to your Tailwind content config, or import @lightbird/player-react/styles.css for zero-config styling.
All Features
| Category | Features |
|---|---|
| Formats | MP4, WebM, AVI, MOV, WMV, FLV, OGV, MKV |
| Subtitles | SRT, VTT, ASS/SSA, encoding detection, sync offset |
| Audio | Multi-track switching (MKV), volume control |
| Chapters | Auto-extraction from MKV, seek bar markers, navigation |
| Playlist | Drag-and-drop reorder, M3U8 import/export, folder import |
| Playback | Speed control, frame stepping, loop, progress persistence |
| Visuals | Video filters (brightness/contrast/saturation/hue), zoom |
| Integration | Picture-in-Picture, Media Session API, keyboard shortcuts |
| Errors | Auto-retry with backoff, error recovery UI, stall detection |
API Reference
Core — @lightbird/core
// Player factory
createVideoPlayer(
file: File,
subtitleFiles?: File[],
onProgress?: (n: number) => void
): VideoPlayer
interface VideoPlayer {
initialize(videoElement: HTMLVideoElement): Promise<ProcessedFile>
getAudioTracks(): AudioTrack[]
getSubtitles(): Subtitle[]
getChapters?(): Chapter[]
switchAudioTrack(trackId: string): Promise<void>
switchSubtitle(trackId: string): Promise<void>
destroy(): void
cancel?(): void
tracksReady?: Promise<void>
}
// Utilities
validateFile(file: File): { valid: boolean; reason?: string }
parseMediaError(error: MediaError): ParsedMediaError
configureLightBird({ ffmpegCDN: string }): voidReact Hooks — @lightbird/core/react
| Hook | Purpose |
|---|---|
| useVideoPlayback(videoRef) | Play/pause, seek, volume, rate, loop |
| useSubtitles(options?) | Subtitle management with onError callback |
| usePlaylist() | Playlist state, file parsing, reorder |
| useVideoFilters(videoRef) | CSS video filters (brightness, contrast, etc.) |
| useFullscreen(containerRef) | Fullscreen API wrapper |
| usePictureInPicture(videoRef) | PiP API wrapper |
| useChapters(videoRef, playerRef) | Chapter navigation |
| useKeyboardShortcuts(shortcuts, handlers) | Keyboard event binding |
| useMediaSession(options) | OS media controls |
| useProgressPersistence(videoRef, name) | localStorage resume |
| useVideoInfo(videoRef, file) | Video metadata extraction |
UI Components — @lightbird/player-react
| Component | Description |
|---|---|
| <LightBirdPlayer /> | Full player — drop in and done |
| <PlayerControls /> | Standalone control bar |
| <PlaylistPanel /> | Standalone playlist sidebar |
| <Toaster /> | Toast notification provider |
Web Component — @lightbird/player
The <lightbird-player> custom element wraps @lightbird/core as a framework-agnostic Web Component — usable in Vue, Svelte, Angular, Solid, or plain HTML with no React dependency. It uses Shadow DOM for style encapsulation and lazy-loads the core engine (and FFmpeg.wasm, for MKV) only when an HLS or MKV source is first encountered.
<!-- Register the element once, anywhere in your app -->
<script type="module">
import '@lightbird/player';
</script>
<!-- Then use it like any other element -->
<lightbird-player
src="video.mp4"
controls
poster="cover.jpg"
></lightbird-player>| Attribute / API | Description |
|---|---|
| src | Video URL — .m3u8 → HLS, .mkv → MKV, otherwise native |
| controls / autoplay / muted | Boolean playback attributes |
| poster | Poster image shown before playback |
| subtitles | JSON array of { src, label, srclang } tracks |
| CustomEvents | play, pause, timeupdate, ended, error, and more |
Bundle Size
LightBird's “lightweight” promise is enforced, not assumed. The base @lightbird/core entry contains zero FFmpeg.wasm code — FFmpeg is reached only through a dynamic import() and a lazily-created Web Worker. An app that only plays MP4/WebM downloads none of the multi-megabyte FFmpeg payload. A CI bundle-size budget fails the build if the base entry grows past its threshold or regains a static FFmpeg import.
| Package | Gzipped | When it loads |
|---|---|---|
| @lightbird/core | ~12 KB | Base entry: players, parsers, subtitle pipeline, utilities. Contains no FFmpeg.wasm code. |
| @lightbird/core/react | ~9 KB | Optional subpath: 11 headless React hooks. |
| @lightbird/player-react | ~21 KB + 6 KB CSS | Optional: styled drop-in components. |
| @lightbird/player | ~3 KB | Optional: framework-agnostic <lightbird-player> Web Component. Core stays a lazy chunk. |
| FFmpeg.wasm (deferred) | ~10 MB | Fetched only when an MKV actually needs remuxing — never loaded for MP4/WebM playback. |
Sizes are gzipped transfer sizes measured from the latest build.
Browser Support
| Browser | Version | Notes |
|---|---|---|
| Chrome | 90+ | Full support |
| Firefox | 90+ | Full support |
| Safari | 15+ | No MKV (no SharedArrayBuffer) |
| Edge | 90+ | Full support |
MKV playback requires Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers.