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.parsePlaylistaccumulates these intoPlaylistSegment.startTime/endTime.- Segment lines — relative or absolute URL. File extension (
.jsonl,.vtt,.ts,.aac) determines the decoder. #EXT-X-ENDLIST— absent 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-TS — pass-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.
tsfor time (seconds from episode start).tefor interval end time. Neverstart/end.datais the universal field for continuous payloads (scalar or vector).labelis the universal tag for discrete events.- Seconds are absolute within the playlist — not relative to the chunk.
- Monotonic
tswithin a chunk and across consecutive chunks.useMergedTrackuses 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.jsonltrack.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:
- Init — fetch the m3u8, parse into
{ segments, totalDuration, isLive, chunkFormat }. - getDataAtTime(t) — binary-search segments to find which covers
t, fetch + decode it, return. - Prefetch — after a segment loads, fetch
prefetchCountsegments ahead in the background. Default is 2. - Cache — LRU (default 20 segments) keyed by segment URL, so backward scrubs rehit the cache.
- Live poll — if
isLive, re-fetch the m3u8 everytargetDurationseconds 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
fetchFninPlaylistOptionsto 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 Classes —
Playlistclass API