Concepts
The key ideas behind Limeplay's architecture.
Features
Every capability — playback, volume, timeline, captions — is a feature. A feature is a self-contained unit that provides:
- Store slice — A namespaced piece of state (e.g.
volume.level,playback.paused) - Setup component — An internal component that attaches DOM event listeners and syncs state (auto-mounted by
MediaProvider) - Events — Typed events emitted when state changes (e.g.
volumechange,seek)
You compose features with createMediaKit:
"use client"
import { createMediaKit } from "@/components/limeplay/media-provider"
import { mediaFeature } from "@/hooks/limeplay/use-media"
import { playbackFeature } from "@/hooks/limeplay/use-playback"
import { volumeFeature } from "@/hooks/limeplay/use-volume"
export const media = createMediaKit({
features: [
mediaFeature(),
playbackFeature(),
volumeFeature(),
] as const,
})This returns a MediaProvider component and typed hooks. Each feature's Setup component is auto-mounted inside MediaProvider — no manual wiring required.
Available Features
| Feature | Key | Factory | Events |
|---|---|---|---|
| Media | media | mediaFeature() | — |
| Player | player | playerFeature() | playerready, playererror, playbackerror, bufferingchange |
| Playback | playback | playbackFeature() | play, pause, ended, buffering, statuschange |
| Volume | volume | volumeFeature() | volumechange, mute |
| Timeline | timeline | timelineFeature() | timeupdate, durationchange, seek |
| Playlist | playlist | playlistFeature() | playlistchange |
| Playback Rate | playbackRate | playbackRateFeature() | ratechange |
| Picture-in-Picture | pictureInPicture | pictureInPictureFeature() | enterpictureinpicture, leavepictureinpicture |
| Captions | captions | captionsFeature() | — |
| Asset | asset | assetFeature() | — |
Store
State is managed by Zustand with Immer middleware. Each MediaProvider creates an isolated store instance via React Context, so multiple players on the same page maintain independent state.
Reading State
Use the typed selector hooks to subscribe to specific fields:
// Per-feature convenience hook
const level = useVolumeStore(s => s.level)
const paused = usePlaybackStore(s => s.paused)
// Or the unified hook from createMediaKit
const level = media.useMediaStore(s => s.volume.level)Components only re-render when the selected value changes.
Mutating State
For imperative mutations (effects, callbacks), use the store API:
const api = media.useMediaApi()
// Read without subscribing
api.getState().volume.level
// Mutate with Immer draft
api.setState(({ volume }) => {
volume.level = 0.5
})Event & Action Bridge
Limeplay separates concerns into two bridges:
Event Bridge
Each feature's Setup component attaches native DOM event listeners to the <video> / <audio> element and syncs changes into the Zustand store. For example, the playback feature listens to native play, pause, ended, waiting events and updates playback.paused, playback.status, etc.
This is automatic — registering a feature in createMediaKit handles everything.
Action Bridge
UI components call store methods that directly manipulate the native media element. For example, playback.togglePaused() calls mediaElement.play() or mediaElement.pause(). The native element fires events, the Event Bridge captures them, and the store updates — completing the cycle.
For high-frequency operations like seeking, both native and React states are updated simultaneously to prevent visual jank.
┌─────────────┐ action ┌─────────────┐ native event ┌─────────────┐
│ UI Component│ ──────────▶ │ Media Element │ ──────────────▶ │ Store (Zustand)│
│ (React) │ ◀────────── │ (DOM) │ │ │
└─────────────┘ re-render └─────────────┘ └──────────────┘Component Hierarchy
MediaProvider ← Isolated store + event emitter + feature Setup components
└─ RootContainer ← Accessible wrapper, manages control visibility
└─ PlayerContainer ← Media element container with aspect ratio
├─ Media ← Native <video> or <audio> element
├─ ControlsContainer ← Layout for controls (top, middle, bottom)
│ ├─ PlaybackControl, VolumeControl, TimelineControl, etc.
│ └─ ...
└─ FallbackPoster ← Poster image for PiP / loading statesCore Dependencies
- Shaka Player — Streaming engine for HLS, DASH, and DRM. Peer dependency you control directly.
- Zustand — Lightweight state management (1.2kb) with Immer middleware for immutable updates.