@fr0/player
v0.1.0
Published
React preview component with playback controls
Readme
@fr0/player
React preview component with playback controls.
Status
✅ Phase α-2 — done
Purpose
A zero-config preview component: pass a Timeline and get a scrubbable preview with play / pause / seek controls. Internally composes @fr0/renderer-browser's <TimelineRenderer> with a RAF-driven playback state machine (usePlayback).
For custom UIs, skip <Player> and build against the usePlayback hook directly.
Quick start
import { Player } from '@fr0/player';
import { buildSample } from '@fr0/templates';
const timeline = buildSample('titleCard', { title: 'Hello, world!' });
export function App() {
return <Player timeline={timeline} autoPlay loop />;
}Public API
| Export | Purpose |
|---|---|
| <Player> | Drop-in preview that composes usePlayback + <TimelineRenderer> + optional <PlaybackControls>. Props: timeline, autoPlay, loop, initialFrame, registry, controls, style, className. |
| usePlayback({ timeline, autoPlay, loop, initialFrame }) | Hook returning { frame, isPlaying, play, pause, toggle, seek, reset }. The state machine behind <Player>, exposed for custom UIs. |
| <PlaybackControls> | Presentational strip with a play / pause button, scrubber slider, and frame / total counter. Wires user input to supplied callbacks. |
Design notes
- Real-time playback. The RAF loop tracks
performance.now()deltas and divides by1000/fpsto compute the frame delta, so slow hosts drop frames rather than slipping out of sync with wall-clock time. - Mutable refs inside
usePlaybackmirror the latest props and state so the RAF callback never reads stale closures. The effect re-subscribes only onisPlayingtransitions. - Loop handling. At the final frame, the hook either wraps to 0 (
loop: true, default) or pauses (loop: false). - Seek floors and clamps the target into
[0, totalFrames - 1]and resets the internal clock so playback resumes smoothly from the new position. - Frame accuracy is not guaranteed. This is a preview tool, not a deterministic exporter. Server-side / encoder paths (Phase β, γ) drive the frame counter externally instead of via RAF.
Testing
pnpm --filter @fr0/player test15 tests across 3 files. requestAnimationFrame and performance.now are mocked with vi.stubGlobal so playback is driven deterministically without fighting fake timers + React scheduling.
What this package is NOT
- Not the renderer (see
@fr0/renderer-browser) - Not the full editor UI (see
@fr0/editor, Phase v1.0) - Not an encoder — playback only
