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)
| Property | Type | Description |
|---|---|---|
time | number | Current playback time (seconds) |
playing | boolean | Whether playback is active |
rate | number | Playback speed (1 = normal) |
duration | number | Total duration |
loop | boolean | Loop enabled |
Methods
| Method | Description |
|---|---|
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:
| Option | Type | Default | Description |
|---|---|---|---|
url | string | required | Playlist URL |
decoder | SegmentDecoder | auto by format | Per-engine decoder |
cacheSize | number | 20 | LRU max segments |
prefetchCount | number | 2 | Segments to prefetch ahead |
pollInterval | number | targetDuration * 1000 | Live poll interval (ms) |
fetchFn | typeof fetch | fetch | Custom fetch function |
baseUrl | string | derived from url | Base URL for segment paths |
Methods
| Method | Returns | Description |
|---|---|---|
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 | null | Current parsed playlist. |
setDecoder(decoder) | void | Replace the decoder. |
abort() | void | Cancel all inflight fetches. |
destroy() | void | Cleanup. |
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]| Parameter | Type | Description |
|---|---|---|
times | Float32Array | Sorted keyframe timestamps |
t | number | Query time |
hint | number | Last 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): ParsedPlaylistParsedPlaylist:
| Field | Type | Description |
|---|---|---|
segments | PlaylistSegment[] | Parsed segments with computed startTime/endTime |
totalDuration | number | Sum of all segment durations |
targetDuration | number | #EXT-X-TARGETDURATION value |
isLive | boolean | true when #EXT-X-ENDLIST is absent |
chunkFormat | ChunkFormat | Inferred from segment file extensions |
trackType | TrackType | Semantic track type |
mediaSequence | number | #EXT-X-MEDIA-SEQUENCE value |
programDateTime | string | #EXT-X-PROGRAM-DATE-TIME value |
customTags | Record<string, string> | Any unrecognized #BSS-* or #EXT-X-* tags |
PlaylistSegment:
| Field | Type | Description |
|---|---|---|
index | number | 0-based position |
duration | number | Segment duration (seconds) |
uri | string | Segment file path/URL |
title | string | #EXTINF title field |
startTime | number | Cumulative start time (computed) |
endTime | number | startTime + 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): voidBuilt-in decoders:
| Name | Output | Format |
|---|---|---|
jsonlDecoder | Record<string, unknown>[] | JSONL (one JSON object per line) |
textDecoder | string | Plain text / VTT |
rawDecoder | ArrayBuffer | Pass-through |
SegmentDecoder signature:
type SegmentDecoder<T = unknown> = (
raw: ArrayBuffer,
segment: PlaylistSegment,
playlist: ParsedPlaylist,
) => T | Promise<T>;