<< All versions
Skill v1.0.1
currentAutomated scan100/100lichtblick-suite/lichtblick/player-internals
+3 new
──Details
PublishedAugust 18, 2026 at 11:05 PM
Content Hashsha256:7a9fab289c3aa7e9...
Git SHA355014d99402
Bump Typepatch
──Files
Files (1 file, 4.6 KB)
SKILL.md4.6 KBactive
SKILL.md · 114 lines · 4.6 KB
version: "1.0.1" name: "player-internals" description: "Deep implementation details of the IterablePlayer state machine, tick loop, and data source iteration patterns."
Player Internals Skill
State Machine Detail
preinit ──► initialize ──► start-play ──► idle│ ▲▼ │play│▼seek-backfill ──► idleidle/play ──► reset-playback-iterator ──► idle/play (re-enters)any ──► close
State Transitions
preinit → initialize: triggered the first time playback starts after construction (source supplied via constructor).initialize → start-play: Sourceinitialize()resolved, topics/schemas availablestart-play → idle: Initial backfill complete, first state emittedidle → play: User presses play orsetPlaybackSpeed(speed > 0)play → idle: Reached end of data or user pausesplay → seek-backfill: User seeks during playbackidle → seek-backfill: User seeks while pausedseek-backfill → idle: Backfill messages found, state emitted
Tick Loop Implementation
typescript
// Simplified tick loop logicasync #statePlay() {const tickStart = performance.now();const budgetMs = 300; // Max time per tick before yielding to UIwhile (performance.now() - tickStart < budgetMs) {const result = await this.#iterator.next();if (result.done) { return "idle"; }this.#pendingMessages.push(result.value.msgEvent);// Check if we've passed the target wall-clock timeif (this.#hasReachedPlaybackTarget()) { break; }}this.#emitState();return "play"; // continue playing next tick}
Debounced State Emission
#emitStateImpl()is scheduled viaqueueMicrotaskto coalesce rapid updates- State includes:
activeData(messages, currentTime, topics),progress(caching status) - Only emits if state actually changed (reference equality check on key fields)
Iterator Architecture
There is no concrete DataSource type in this layering. Sources implement one of two interfaces: ISerializedIterableSource (yields raw bytes) or IDeserializedIterableSource (yields decoded MessageEvents). A serialized source must be wrapped by DeserializingIterableSource; an already-deserialized source skips that wrapper.
Concrete source (e.g. McapIndexedIterableSource, RemoteFileReadable-backed, WebSocket, …)│ implements ISerializedIterableSource OR IDeserializedIterableSource▼DeserializingIterableSource (ONLY for serialized sources — applies parseChannel-based decode)│ packages/suite-base/src/players/IterablePlayer/DeserializingIterableSource.ts▼CachingIterableSource (LRU block cache, ~600MB budget)│▼BufferedIterableSource (producer-consumer, read-ahead, default { sec: 10 })│▼IterablePlayer (tick loop consumes messages)
⚠️DeserializingIterableSourceis optional — it is only inserted when the underlying sourceis serialized (ISerializedIterableSource). Sources that already returnIDeserializedIterableSourcebypass it.IIterableSourcealso exposes optionalprewarm?(): Promise<void>for pooled/multi-file sourcesto warm up before becoming active; see.github/skills/remote-caching/SKILL.mdforHydratedSourcePool.
Backfill Strategy
When seeking to time T:
- For each subscribed topic, find the last message at or before T
- Uses reverse iteration in indexed sources (MCAP) for efficiency
- These messages become the "latched" state — panels see them immediately
- Critical for panels that display "latest value" (e.g., 3D transforms, image)
Subscription Management
- Subscriptions are set by panels via
MessagePipeline.setSubscriptions() - Player diffs new vs old subscriptions to avoid unnecessary re-iteration
- Topic preloading is separate from active subscriptions (handled by BlockLoader)
reset-playback-iteratorstate: when subscriptions change mid-play, iterator must restart from current time
Performance Critical Paths
- Tick loop budget: 300ms cap prevents UI freeze during catch-up
- Message accumulation: Messages are batched per tick, not emitted individually
- Iterator yielding:
awaitin the loop allows microtask scheduling - Worker sources: Heavy parsing happens in
WorkerIterableSourceoff main thread - Seek optimization: Indexed MCAP enables O(log n) seek via chunk indexes