Limeplay - Open Source Video Player UI ComponentsLimeplay

Media Provider

Creates the Zustand store, event emitter, and feature setup for a media player instance.

Installation

npx shadcn add @limeplay/media-provider

Setup

Use createMediaKit to compose the features you need into a single runtime:

lib/media.ts
"use client"

import { createMediaKit } from "@/components/limeplay/media-provider"
import { mediaFeature } from "@/hooks/limeplay/use-media"
import { playerFeature } from "@/hooks/limeplay/use-player"
import { playbackFeature } from "@/hooks/limeplay/use-playback"
import { volumeFeature } from "@/hooks/limeplay/use-volume"
import { timelineFeature } from "@/hooks/limeplay/use-timeline"

export const media = createMediaKit({
  features: [
    mediaFeature(),
    playerFeature(),
    playbackFeature(),
    volumeFeature(),
    timelineFeature(),
  ] as const,
})

export const { MediaProvider, useMediaStore, useMediaApi, useMediaEvents } =
  media

Wrap your player with MediaProvider:

components/media-player.tsx
import { MediaProvider } from "@/lib/media"
import { Media } from "@/components/limeplay/media"

export function MediaPlayer() {
  return (
    <MediaProvider>
      <Media as="video" />
      {/* Controls go here */}
    </MediaProvider>
  )
}

Accessing State

Selectors (reactive)

import { useMediaStore } from "@/lib/media"
// or per-feature hooks:
import { useVolumeStore } from "@/hooks/limeplay/use-volume"

// Option A: unified store (namespaced)
function VolumeDisplayNamespaced() {
  const level = useMediaStore((s) => s.volume.level)
  return <span>{Math.round(level * 100)}%</span>
}

// Option B: feature-scoped hook (flat)
function VolumeDisplayScoped() {
  const level = useVolumeStore((s) => s.level)
  return <span>{Math.round(level * 100)}%</span>
}

Store API (imperative)

For effects and callbacks where you don't need reactivity:

import { useMediaApi } from "@/lib/media"

function SomeEffect() {
  const api = useMediaApi()

  React.useEffect(() => {
    // Read without subscribing
    const { level } = api.getState().volume

    // Mutate with Immer draft
    api.setState(({ volume }) => {
      volume.level = 0.5
    })
  }, [api])
}

Events

Subscribe to typed events from any feature:

import { useMediaEvents } from "@/lib/media"

function Logger() {
  const events = useMediaEvents()

  React.useEffect(() => {
    return events.on("volumechange", ({ volume }) => {
      console.log("Volume:", volume)
    })
  }, [events])

  return null
}

See Events for the full event reference.

Multi-Instance

Each MediaProvider creates an isolated store. Multiple players on the same page are fully independent:

<MediaProvider>
  <PlayerOne />
</MediaProvider>

<MediaProvider>
  <PlayerTwo />
</MediaProvider>

API Reference

createMediaKit

createMediaKit({ features }) => {
  MediaProvider,    // React component
  useMediaStore,    // Reactive selector hook
  useMediaApi,      // Imperative store API hook
  useMediaEvents,   // Event emitter hook
  features,         // Features array reference
}

MediaProvider

Wraps player components. Creates the Zustand store, event emitter, and auto-mounts each feature's Setup component.

<MediaProvider>{children}</MediaProvider>

On this page