@streamiq/react
v1.1.0
Published
React bindings for the Streamq playback SDK.
Maintainers
Readme
@streamiq/react
React bindings for the Streamq playback SDK — StreamqProvider, external stores, RAF-batched event-to-store bindings, granular hooks, and headless <Video> / <Audio> / <PlayerRoot>.
Stable
1.0.x. Requires@streamiq/core. Install@streamiq/hlsor@streamiq/dashfor adaptive streaming.
Install
npm install @streamiq/core @streamiq/react react react-dom
npm install @streamiq/hls # or @streamiq/dashQuick start
import { createHlsPlaybackAdapter } from '@streamiq/hls';
import {
StreamqProvider,
PlayerRoot,
Video,
usePlay,
usePause,
usePlaybackState,
useCurrentTime,
} from '@streamiq/react';
function Controls() {
const state = usePlaybackState();
const time = useCurrentTime();
const play = usePlay();
const pause = usePause();
return (
<div>
<button type="button" onClick={play}>Play</button>
<button type="button" onClick={pause}>Pause</button>
<span>{state} · {time.toFixed(1)}s</span>
</div>
);
}
export default function App() {
return (
<StreamqProvider
options={{
createPlaybackAdapter: createHlsPlaybackAdapter,
autoplay: true,
source: { src: 'https://example.com/master.m3u8', type: 'hls' },
}}
>
<PlayerRoot>
<Video playsInline />
</PlayerRoot>
<Controls />
</StreamqProvider>
);
}There is no monolithic StreamqPlayer export here — compose StreamqProvider + media + your UI. For a drop-in cinematic preset, see @streamiq/player.
Architecture
@streamiq/react/src
├── provider/
│ └── StreamqProvider.ts Mounts Player, owns stores, provides context
│
├── context/
│ └── StreamqContext.ts { player, stores } container
│
├── media/ Headless media elements
│ ├── PlayerRoot.ts Container + media-element ref handoff
│ ├── Video.ts <video> with Streamq wiring
│ ├── Audio.ts <audio> with Streamq wiring
│ └── MediaContext.ts Element context for nested components
│
├── stores/ External stores (useSyncExternalStore-friendly)
│ ├── createStore.ts Generic factory (WritableStore, ExternalStore)
│ ├── playbackStore.ts (+ types) time, duration, paused, seeking, ended, buffered
│ ├── volumeStore.ts (+ types) volume, muted
│ ├── qualityStore.ts (+ types) levels, active, auto, capabilities
│ ├── audioTrackStore.ts tracks, active
│ ├── subtitleStore.ts (+ types) tracks, active, style
│ ├── cueStore.ts (+ types) active cues, primary cue
│ ├── pipStore.ts (+ types) supported, active
│ ├── fullscreenStore.ts (+ types) active, request element
│ └── batchStoreUpdates.ts BatchMode + flushRafNow
│
├── bindings/ Wire Player events → stores
│ ├── bindPlayerToStores.ts Orchestrator
│ ├── playbackBindings.ts
│ ├── volumeBindings.ts
│ ├── qualityBindings.ts
│ ├── audioTrackBindings.ts
│ ├── subtitleBindings.ts
│ ├── cueBindings.ts
│ ├── pipBindings.ts
│ ├── fullscreenBindings.ts
│ └── rafSynchronizer.ts createRAFSynchronizer for high-rate updates
│
├── selectors/ Selector pattern over stores
│ ├── createSelector.ts EqualityFn-aware
│ ├── playbackSelectors.ts selectCurrentTime, selectPaused, …
│ ├── shallowEqual.ts
│ └── useStoreSelector.ts + useShallowSelector
│
├── hooks/ Public hook surface
│ ├── usePlayer.ts / usePlayerContext.ts
│ ├── usePlayback.ts / usePlaybackSelectors.ts
│ ├── usePlaybackControls.ts play / pause / toggle / seek / restart
│ ├── useVolumeControls.ts volume / muted / set / toggle mute
│ ├── useFullscreen.ts fullscreen state + actions
│ ├── usePiP.ts PiP state + actions
│ ├── useQuality.ts levels, active, auto, select
│ ├── useAudioTrack.ts tracks, current, select
│ ├── useTextTrack.ts tracks, active, select, disable
│ ├── useCue.ts active cue / cues
│ ├── useSubtitleAnnouncer.ts a11y live region helper
│ ├── useBufferingAnnouncer.ts a11y live region helper
│ ├── useMediaReady.ts metadata / canPlay readiness
│ ├── useAbortSignal.ts Per-effect AbortSignal
│ ├── useHydrated.ts SSR safety
│ └── useDeferredBrowser.ts Defer browser-only logic
│
├── debug/ Tree-shakeable instrumentation (no-op when off)
│ ├── debugCore.ts __streamqDebug, getDebugStats, …
│ ├── debugReporter.ts
│ ├── useDebugEvents.ts
│ ├── useDebugPlayback.ts
│ ├── useStoreDebug.ts
│ └── useRenderDebug.ts
│
├── utils/
│ ├── env.ts isBrowser
│ ├── subscriptionGuard.ts createSubscriptionGuard
│ ├── structuralSharing.ts
│ └── throttle.ts
│
└── types/ Public types (context.types, media.types)Data flow
Player (core)
│ events (mitt)
▼
bindPlayerToStores()
├─ playbackBindings ──► playbackStore (RAF-batched timeupdate via createRAFSynchronizer)
├─ volumeBindings ──► volumeStore
├─ qualityBindings ──► qualityStore
├─ audioTrackBindings──► audioTrackStore
├─ subtitleBindings ──► subtitleStore
├─ cueBindings ──► cueStore
├─ pipBindings ──► pipStore
└─ fullscreenBindings──► fullscreenStore
│
▼
useStoreSelector / granular hooks (subscribe to one slice)
│
▼
Your app UI (components re-render only on the slice they read)Rules
PlayerContextcarriesPlayer | nullplus stores — no duplicated FSM in React.- Hooks subscribe to specific store slices to minimize re-renders.
- Adapters are composed at the app edge via
createPlaybackAdapter. - All hooks are SSR-safe;
useHydrated()anduseDeferredBrowser()cover hydration andwindowaccess.
StreamqProvider
<StreamqProvider options={playerFactoryOptions}>
{children}
</StreamqProvider>| Prop | Type | Description |
|------|------|-------------|
| options | PlayerFactoryOptions | Everything except target (set via PlayerRoot / Video) |
| children | ReactNode | App tree |
Common options fields:
| Field | Description |
|-------|-------------|
| createPlaybackAdapter | Factory from @streamiq/hls or @streamiq/dash |
| source | Initial source (optional — can also player.load() imperatively) |
| autoplay | Autoplay after load |
| plugins | Array of StreamqPlugin instances |
| recovery | Load retry / fallback policy |
Media components
| Component | Role |
|-----------|------|
| PlayerRoot | Attaches Player to a media element ref; provides container context |
| Video | <video> with Streamq wiring |
| Audio | <audio> with Streamq wiring |
| MediaContext | Element context for nested components |
Hooks reference
Player access
| Hook | Returns |
|------|---------|
| usePlayer() | Player instance (imperative API) |
| usePlayerContext() | Full context (stores + player) |
Playback
| Hook | Returns |
|------|---------|
| usePlaybackState() | 'idle' \| 'loading' \| 'ready' \| 'playing' \| … |
| usePlayback(selector) | Custom selector over playback store |
| useCurrentTime() | Current time (seconds) |
| useDuration() | Duration (seconds) |
| useBuffered() | Buffered end (seconds) |
| usePaused() | Whether paused |
| useSeeking() | Whether seeking |
| useEnded() | Whether ended |
| usePlay() | Stable () => void play callback |
| usePause() | Stable pause callback |
| useTogglePlayback() | Toggle play/pause |
| useSeek() | (time: number) => void |
| useRestart() | Seek to start |
Volume
| Hook | Returns |
|------|---------|
| useVolume() | 0–1 |
| useMuted() | boolean |
| useSetVolume() | (v: number) => void |
| useToggleMute() | toggle callback |
Fullscreen & PiP
| Hook | Returns |
|------|---------|
| useFullscreen() | { isFullscreen, enterFullscreen, exitFullscreen, toggleFullscreen } |
| usePiP() | { isPiP, canPiP, enterPiP, exitPiP, togglePiP } |
Quality
| Hook | Returns |
|------|---------|
| useQualities() | Full qualities snapshot |
| useQualityLevels() | Available levels array |
| useCurrentQuality() | Active quality id |
| useAutoQuality() | Whether ABR is active |
| useSelectQuality() | (id: string) => void |
Subtitles & text tracks
| Hook | Returns |
|------|---------|
| useTextTracks() | Discovered tracks |
| useActiveTextTrack() | Active track or null |
| useSelectTextTrack() | Select by id |
| useDisableTextTrack() | Turn captions off |
| useActiveCues() | Current cue list |
| useActiveCue() | Primary active cue |
| useCue() | Alias for active cue |
Audio tracks
| Hook | Returns |
|------|---------|
| useAudioTracks() | Full snapshot |
| useAudioTrackList() | Track list |
| useCurrentAudioTrack() | Active track id |
| useSelectAudioTrack() | Select by id |
SSR & browser safety
| Hook | Returns |
|------|---------|
| useHydrated() | false during SSR/hydration, true after |
| useDeferredBrowser(fn, fallback) | Defer browser-only logic |
| useMediaReady() | MediaReadyState (metadata / canPlay) |
| useAbortSignal() | AbortSignal for async effects |
Advanced: stores & bindings
For custom integrations, the package exposes the store layer:
| Export | Role |
|--------|------|
| createPlaybackStore, createStore | External store factories |
| bindPlayerToStores, BindOptions, PlayerStores | Wire Player events → stores |
| bindPlaybackEvents, bindVolumeEvents, bindQualityEvents, bindAudioTrackEvents, bindSubtitleEvents, bindCueEvents, bindPiPEvents, bindFullscreenEvents | Per-domain bindings |
| createRAFSynchronizer | rAF-batched time updates |
| batchStoreUpdates, flushRafNow, BatchMode | Batch React updates |
| createSelector, useStoreSelector, useShallowSelector, EqualityFn | Selector pattern |
| selectPlaybackState, selectCurrentTime, selectDuration, selectBuffered, selectPaused, selectSeeking, selectEnded, shallowEqual | Pre-built selectors |
| Snapshot types | PlaybackSnapshot, VolumeSnapshot, CueSnapshot, SubtitleSnapshot, PiPSnapshot, FullscreenSnapshot, WritableStore, ExternalStore |
Debug (tree-shakeable)
| Export | Role |
|--------|------|
| __streamqDebug | Debug namespace |
| useDebugEvents, useDebugPlayback, useRenderDebug, useStoreDebug | Dev probes |
| getDebugStats, resetDebugStats, printDebugSummary, startRenderBudgetMonitor | Stats helpers |
| DebugEvent, DebugEventType, DebugListener, DebugStats, PlaybackDebugInfo, PlayerEvent | Types |
When __streamqDebug.enabled = false, debug code paths are eliminated by the bundler.
SSR / Next.js
- All hooks are SSR-safe
- Use
useHydrated()before rendering browser-only UI - Use
useDeferredBrowser()for APIs that touchwindow/document - Call
player.load()after mount if not usingoptions.source
Custom UI
Wire your own controls to hooks — no bundled UI in this package:
import { StreamqProvider, PlayerRoot, Video, usePlay, usePause, usePlaybackState } from '@streamiq/react';
import { createHlsPlaybackAdapter } from '@streamiq/hls';
function PlayControl() {
const play = usePlay();
const pause = usePause();
const state = usePlaybackState();
return state === 'playing' ? (
<button type="button" onClick={() => pause()}>Pause</button>
) : (
<button type="button" onClick={() => play()}>Play</button>
);
}
<StreamqProvider options={{ createPlaybackAdapter: createHlsPlaybackAdapter }}>
<PlayerRoot>
<Video />
<PlayControl />
</PlayerRoot>
</StreamqProvider>For a polished default UI, install @streamiq/ui primitives and/or the @streamiq/player preset.
Dependencies
| | |
|-|-|
| Runtime | @streamiq/core |
| Peer | react ≥ 18, react-dom ≥ 18 |
Adapters (@streamiq/hls, @streamiq/dash) are not bundled — install separately.
License
MIT
