D
DreamLake

API

Core Classes

The core layer is framework-agnostic — it works without React.

TimelineClock

Pure time source with two events: tick (~60fps during playback) and seek (explicit user actions).

const clock = new TimelineClock(duration?: number)

Properties (read-only)

PropertyTypeDescription
timenumberCurrent playback time (seconds)
playingbooleanWhether playback is active
ratenumberPlayback speed (1 = normal)
durationnumberTotal duration
loopbooleanLoop enabled

Methods

MethodDescription
play()Start playback. Resets to 0 if at end.
pause()Stop playback.
seek(time)Jump to time (clamped to [0, duration]).
setRate(rate)Change playback speed. Supports negative (reverse).
setLoop(v)Toggle looping.
setDuration(d)Set duration to exactly d.
extendDuration(d)Extend to d if d > current. Safe for multiple engines.
tick(delta)Advance by delta seconds. For external driving (e.g. R3F useFrame).
destroy()Cleanup.
on(event, fn)Subscribe. Returns unsubscribe function.

Events

// ~60fps during playback
clock.on('tick', (e: { time: number; playing: boolean; rate: number }) => {});
 
// Explicit user actions
clock.on('seek', (e: { time: number; source: 'seek' | 'play' | 'pause' | 'rate' | 'loop' }) => {});

Usage

import { TimelineClock } from '@vuer-ai/vuer-m3u';
 
const clock = new TimelineClock();
clock.setDuration(60);
clock.play();
 
const unsub = clock.on('tick', (e) => console.log(e.time));
 
// External drive (e.g. Three.js useFrame)
clock.tick(deltaSeconds);
 
clock.destroy();

Playlist

Parses m3u8, loads segments on demand, caches in LRU, prefetches ahead, polls for live updates.

const engine = new Playlist(options: PlaylistOptions)

PlaylistOptions:

OptionTypeDefaultDescription
urlstringrequiredPlaylist URL
decoderSegmentDecoderauto by formatPer-engine decoder
cacheSizenumber20LRU max segments
prefetchCountnumber2Segments to prefetch ahead
pollIntervalnumbertargetDuration * 1000Live poll interval (ms)
fetchFntypeof fetchfetchCustom fetch function
baseUrlstringderived from urlBase URL for segment paths

Methods

MethodReturnsDescription
init()Promise<ParsedPlaylist>Fetch + parse playlist. Starts live polling if needed.
getDataAtTime<T>(time)Promise<SegmentData<T> | null>Get decoded segment at time. Auto-prefetches ahead.
getPlaylist()ParsedPlaylist | nullCurrent parsed playlist.
setDecoder(decoder)voidReplace the decoder.
abort()voidCancel all inflight fetches.
destroy()voidCleanup.

Events

engine.addEventListener('segment-loaded', (e: CustomEvent<SegmentData>) => {});
engine.addEventListener('playlist-updated', (e: CustomEvent<ParsedPlaylist>) => {});
engine.addEventListener('error', (e: CustomEvent<Error>) => {});

Usage

import { Playlist } from '@vuer-ai/vuer-m3u';
 
const engine = new Playlist({ url: '/data.m3u8', prefetchCount: 4 });
const playlist = await engine.init();
 
console.log(playlist.totalDuration);     // 30
console.log(playlist.segments.length);   // 3
console.log(playlist.chunkFormat);       // 'jsonl'
 
const result = await engine.getDataAtTime(15.3);
// result.decoded → your data
// result.segment → which segment
 
engine.destroy();

findBracket

O(1) amortized keyframe lookup for interpolation. Uses hint-based temporal coherence during sequential playback, falls back to binary search on seeks.

function findBracket(
  times: Float32Array,
  t: number,
  hint: number,
): [index: number, alpha: number]
ParameterTypeDescription
timesFloat32ArraySorted keyframe timestamps
tnumberQuery time
hintnumberLast returned index (for coherence)

Returns [leftIndex, interpolationFactor] where alpha is 0..1 between times[index] and times[index+1].

import { findBracket } from '@vuer-ai/vuer-m3u';
 
let hint = 0;
const [idx, alpha] = findBracket(track.times, currentTime, hint);
hint = idx;  // save for next frame
 
// Linear interpolation
const value = track.values[idx] + (track.values[idx + 1] - track.values[idx]) * alpha;

parsePlaylist

Parses m3u8 playlist text into a structured object.

function parsePlaylist(content: string): ParsedPlaylist

ParsedPlaylist:

FieldTypeDescription
segmentsPlaylistSegment[]Parsed segments with computed startTime/endTime
totalDurationnumberSum of all segment durations
targetDurationnumber#EXT-X-TARGETDURATION value
isLivebooleantrue when #EXT-X-ENDLIST is absent
chunkFormatChunkFormatInferred from segment file extensions
trackTypeTrackTypeSemantic track type
mediaSequencenumber#EXT-X-MEDIA-SEQUENCE value
programDateTimestring#EXT-X-PROGRAM-DATE-TIME value
customTagsRecord<string, string>Any unrecognized #BSS-* or #EXT-X-* tags

PlaylistSegment:

FieldTypeDescription
indexnumber0-based position
durationnumberSegment duration (seconds)
uristringSegment file path/URL
titlestring#EXTINF title field
startTimenumberCumulative start time (computed)
endTimenumberstartTime + duration

Segment Resolution

// Binary search: find segment containing time
function resolveSegment(segments: PlaylistSegment[], time: number): PlaylistSegment | null
 
// Find all segments overlapping a time range
function resolveSegmentRange(segments: PlaylistSegment[], start: number, end: number): PlaylistSegment[]
 
// Get segment at time + next N segments
function resolveSegmentWindow(segments: PlaylistSegment[], time: number, count: number): PlaylistSegment[]

Decoders

// Get decoder by format name, fallback to rawDecoder
function getDecoder(format?: string): SegmentDecoder
 
// Register a global decoder for a format name
function registerDecoder(format: string, decoder: SegmentDecoder): void

Built-in decoders:

NameOutputFormat
jsonlDecoderRecord<string, unknown>[]JSONL (one JSON object per line)
textDecoderstringPlain text / VTT
rawDecoderArrayBufferPass-through

SegmentDecoder signature:

type SegmentDecoder<T = unknown> = (
  raw: ArrayBuffer,
  segment: PlaylistSegment,
  playlist: ParsedPlaylist,
) => T | Promise<T>;