Overview
Getting Started
Install
npm install @vuer-ai/vuer-m3uReact 18+ is a peer dependency for the hooks and components. The core engine (Playlist, TimelineClock) works without React.
Minimal example — one config, two presentations
The library's thesis: one TimelineConfig drives two container components. <TrackerContainer> renders each track as its instant-state view; <TimelineContainer> renders the same tracks as lanes along a scrubbable time axis. Both share a clock — play/seek on either propagates everywhere.
import {
ClockProvider,
TimelineContainer,
TimelineController,
TrackerContainer,
defaultTimelineViews,
defaultTrackerViews,
useTimeline,
type TimelineConfig,
} from '@vuer-ai/vuer-m3u'
// Same config drives both containers.
const config: TimelineConfig = {
container: { id: 'quickstart', duration: 15 },
tracks: [
{ id: 'joints', path: 'robot/joints', dtype: 'joint_angles',
src: '/vuer-m3u-demo/joints/playlist.m3u8' },
{ id: 'imu', path: 'robot/imu', dtype: 'imu_6dof',
src: '/vuer-m3u-demo/imu/playlist.m3u8' },
],
}
export default function App() {
const { clock, state, play, pause, seek, setPlaybackRate, setLoop } =
useTimeline(config.container.duration)
return (
<ClockProvider clock={clock}>
{/* A. Instant state — one view per track, dispatched by dtype */}
<TrackerContainer config={config} views={defaultTrackerViews} />
{/* B. Trends — same tracks along a shared time axis */}
<TimelineContainer config={config} views={defaultTimelineViews} />
{/* Both surfaces share this clock; play/seek propagates everywhere. */}
<TimelineController
state={state}
onPlay={play}
onPause={pause}
onSeek={seek}
onSpeedChange={setPlaybackRate}
onLoopChange={setLoop}
/>
</ClockProvider>
)
}Press play: both surfaces animate in lockstep. There is no glue code between them — a single <ClockProvider> does the work.
What just happened
useTimeline()created aTimelineClockand exposed discrete playback state (playing/rate/loop/duration).<ClockProvider clock={clock}>published the clock to everything below it. Both containers resolve the clock from context (prop > context > internal), so wrapping them in a single provider is all the sync they need.<TrackerContainer>walkedconfig.tracks, looked each dtype up indefaultTrackerViews, and rendered the matching view component:JointAngleViewforjoint_angles,ImuChartViewforimu_6dof.<TimelineContainer>took the sameconfigand dispatched each track throughdefaultTimelineViews, which pickedLineChartLanefor both, spreading each dtype's defaults (range, unit, channelGroups) onto the lane's props automatically.<TimelineController>readclock.timeat ~30 fps viauseClockValueto animate the scrubber.
The key idea: the same TimelineConfig feeds both presentations. Swap <TimelineContainer> ↔ <TrackerContainer> and you've changed how the data renders, not what the data is. dtype is the bridge on both sides.
Next steps — pick your path
Start building a dashboard
If you have real m3u8 data and want to wire up your own app:
- Tracks — what a Track is, how
path+dtypework - Dtypes — the 13 built-in data types with JSONL schemas and Python generators
- Views — the 10 instant-state views with props and compatible dtypes
- Timeline —
<TimelineContainer>anatomy and authoring
Author data in Python
- dreamlake-py SDK —
episode.track(name, dtype)and chunk append
Customize beyond the built-ins
- Custom Views — four-hook patterns + checklist
- Custom Lanes — register your own
DtypeSpec+ lane component - Custom Decoders — support new chunk formats (MessagePack, Parquet, …)
Understand the internals
- Concepts — the unified data-vs-visualization model
- Hooks — 4-layer hook model (
useSegment→useMergedTrack→useTrackSample) - Core Classes —
TimelineClockandPlaylistwithout React