D
DreamLake

Data

M3U8 Transport

Each vuer-m3u track points at an HLS playlist (.m3u8). This page covers how the playlist + chunks are authored and how vuer-m3u fetches / decodes them.

For the schema of what's inside each chunk, see the per-dtype page under Dtypes.

The playlist

Standard HLS — no custom tags. Chunk format is inferred from segment file extension.

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
 
#EXTINF:10.000,
chunk-001.jsonl
#EXTINF:10.000,
chunk-002.jsonl
#EXTINF:10.000,
chunk-003.jsonl
#EXT-X-ENDLIST
  • #EXT-X-TARGETDURATION — max chunk length; informational only.
  • #EXTINF — chunk duration in seconds. parsePlaylist accumulates these into PlaylistSegment.startTime / endTime.
  • Segment lines — relative or absolute URL. File extension (.jsonl, .vtt, .ts, .aac) determines the decoder.
  • #EXT-X-ENDLISTabsent signals a live playlist; the engine polls for new segments.

Chunk time bounds live on the m3u8, not in the chunk. This is intentional — it keeps chunks self-contained (no envelope, no header) and makes time slicing a playlist-level operation.

Chunk format inference

.jsonl  →  jsonlDecoder  (one self-contained JSON object per line)
.vtt    →  textDecoder   (W3C WebVTT text; caller parses cues)
.ts     →  rawDecoder    (MPEG-TSpass-through; hls.js handles video)
other   →  rawDecoder    (ArrayBuffer; supply your own decoder)

Register a custom decoder for any new extension:

import { registerDecoder } from '@vuer-ai/vuer-m3u'
 
registerDecoder('mpk', (raw) => decodeMsgpack(new Uint8Array(raw)))

See Custom Decoders for the full interface + per-engine override pattern.

JSONL conventions

For dtypes with chunkFormat: 'jsonl':

  • One self-contained JSON object per line. No envelope, no header line.
  • ts for time (seconds from episode start). te for interval end time. Never start/end.
  • data is the universal field for continuous payloads (scalar or vector).
  • label is the universal tag for discrete events.
  • Seconds are absolute within the playlist — not relative to the chunk.
  • Monotonic ts within a chunk and across consecutive chunks. useMergedTrack uses binary search over timestamps; non-monotonic data silently breaks interpolation.

Continuous samples

{"ts": <number>, "data": <number | number[]>}

Shape is the dtype's convention. The library passes stride through to interpolators without interpreting the layout.

Discrete events

{"ts": <number>, "te": <number>?, "label": <string>?, ...extras}

te absent → instant event (markers). te present → interval (pills / ribbons).

Extra fields

Any additional fields survive decoding and are passed through to the renderer. E.g. detection_2d entries carry bbox, score, and any app-specific keys alongside ts/te/label.

Directory layout

The typical layout:

episodes/teleop_run_037/
├── config.json                       ← TimelineConfig (app-generated)
├── cameras/
│   └── wrist/
│       ├── playlist.m3u8
│       ├── chunk-001.ts
│       ├── chunk-002.ts
│       └── chunk-003.ts
├── robot/
│   └── arm/
│       └── qpos/
│           ├── playlist.m3u8
│           ├── chunk-001.jsonl
│           ├── chunk-002.jsonl
│           └── chunk-003.jsonl
└── events/
    └── markers/
        ├── playlist.m3u8
        └── chunk-001.jsonl

track.src is the URL to that track's playlist.m3u8. Chunks live alongside.

Live playlists

Omit #EXT-X-ENDLIST and the engine polls at targetDuration-derived intervals for new segments. New segments extend the timeline's duration via clock.extendDuration() — existing views/lanes pick up the growth automatically.

Live mode is best for real-time teleop dashboards where the robot is producing data as you watch. For replay of completed episodes, always include #EXT-X-ENDLIST so clients know the playlist is final and can cache aggressively.

Fetch behavior

For each track, vuer-m3u runs exactly one Playlist engine:

  1. Init — fetch the m3u8, parse into { segments, totalDuration, isLive, chunkFormat }.
  2. getDataAtTime(t) — binary-search segments to find which covers t, fetch + decode it, return.
  3. Prefetch — after a segment loads, fetch prefetchCount segments ahead in the background. Default is 2.
  4. Cache — LRU (default 20 segments) keyed by segment URL, so backward scrubs rehit the cache.
  5. Live poll — if isLive, re-fetch the m3u8 every targetDuration seconds and extend the segment list.

Multiple tracks on the same clock each have their own engine; their segment boundaries are independent. The TimelineClock itself never fetches anything — it's just the time source.

What the library does not do

  • No schema validation. Chunks that don't match the dtype's documented shape silently break the view that consumes them.
  • No auth handling. You can supply a custom fetchFn in PlaylistOptions to add headers / cookies; the library calls it for every request.
  • No transcoding. Chunks arrive as you wrote them.

Related

  • Dtypes — per-dtype JSONL schema (line shape + field table)
  • Custom Decoders — add support for MessagePack, Parquet, Arrow, custom binary
  • Core ClassesPlaylist class API