D
DreamLake

Overview

Getting Started

Install

npm install @vuer-ai/vuer-m3u

React 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.

Loading demo…
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

  1. useTimeline() created a TimelineClock and exposed discrete playback state (playing / rate / loop / duration).
  2. <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.
  3. <TrackerContainer> walked config.tracks, looked each dtype up in defaultTrackerViews, and rendered the matching view component: JointAngleView for joint_angles, ImuChartView for imu_6dof.
  4. <TimelineContainer> took the same config and dispatched each track through defaultTimelineViews, which picked LineChartLane for both, spreading each dtype's defaults (range, unit, channelGroups) onto the lane's props automatically.
  5. <TimelineController> read clock.time at ~30 fps via useClockValue to 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 + dtype work
  • 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

Customize beyond the built-ins

Understand the internals

  • Concepts — the unified data-vs-visualization model
  • Hooks — 4-layer hook model (useSegmentuseMergedTrackuseTrackSample)
  • Core ClassesTimelineClock and Playlist without React