LIGHTBIRD

Play everything. Entirely client-side.

8+ formats11 React hooksZero serverMKV native
npm install @lightbird/core
Try the demo

Why 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

CategoryFeatures
FormatsMP4, WebM, AVI, MOV, WMV, FLV, OGV, MKV
SubtitlesSRT, VTT, ASS/SSA, encoding detection, sync offset
AudioMulti-track switching (MKV), volume control
ChaptersAuto-extraction from MKV, seek bar markers, navigation
PlaylistDrag-and-drop reorder, M3U8 import/export, folder import
PlaybackSpeed control, frame stepping, loop, progress persistence
VisualsVideo filters (brightness/contrast/saturation/hue), zoom
IntegrationPicture-in-Picture, Media Session API, keyboard shortcuts
ErrorsAuto-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 }): void

React Hooks — @lightbird/core/react

HookPurpose
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

ComponentDescription
<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 / APIDescription
srcVideo URL — .m3u8 → HLS, .mkv → MKV, otherwise native
controls / autoplay / mutedBoolean playback attributes
posterPoster image shown before playback
subtitlesJSON array of { src, label, srclang } tracks
CustomEventsplay, 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.

PackageGzippedWhen it loads
@lightbird/core~12 KBBase entry: players, parsers, subtitle pipeline, utilities. Contains no FFmpeg.wasm code.
@lightbird/core/react~9 KBOptional subpath: 11 headless React hooks.
@lightbird/player-react~21 KB + 6 KB CSSOptional: styled drop-in components.
@lightbird/player~3 KBOptional: framework-agnostic <lightbird-player> Web Component. Core stays a lazy chunk.
FFmpeg.wasm (deferred)~10 MBFetched 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

BrowserVersionNotes
Chrome90+Full support
Firefox90+Full support
Safari15+No MKV (no SharedArrayBuffer)
Edge90+Full support

MKV playback requires Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers.