TimeDriver
The TimeDriver interface defines the contract for time synchronization drivers. Drivers manage media elements, animations, and ensure frame-accurate playback.
interface TimeDriver {
init(scope: unknown): void;
update(timeInMs: number, options?: {
isPlaying: boolean;
playbackRate: number;
volume?: number;
muted?: boolean;
audioTracks?: Record<string, { volume: number; muted: boolean }>;
}): void;
waitUntilStable(): Promise<void>;
dispose?(): void;
subscribeToMetadata?(callback: (meta: DriverMetadata) => void): () => void;
getAudioContext?(): Promise<unknown>;
getAudioSourceNode?(trackId: string): Promise<unknown>;
}
Required methods
Initialize the driver with an animation scope. For browser drivers, this is typically the document object.
update
(timeInMs: number, options?) => void
Update the driver to a specific time in milliseconds with playback options.Parameters:
timeInMs: Target time in milliseconds
options.isPlaying: Whether playback is active
options.playbackRate: Speed multiplier
options.volume: Master volume (0.0 to 1.0)
options.muted: Master mute state
options.audioTracks: Per-track audio state
Returns a promise that resolves when all asynchronous operations (media seeking, image loading, etc.) are complete. Critical for deterministic rendering.
Optional methods
Clean up resources when the driver is no longer needed.
subscribeToMetadata
(callback: (meta: DriverMetadata) => void) => () => void
Subscribe to metadata updates (e.g., discovered audio tracks). Returns an unsubscribe function.
Get the Web Audio API AudioContext for custom audio processing.
getAudioSourceNode
(trackId: string) => Promise<unknown>
Get a MediaElementAudioSourceNode for a specific audio track, useful for visualization.
Metadata reported by drivers about available resources.
interface DriverMetadata {
audioTracks?: AudioTrackMetadata[];
}
Audio tracks discovered by the driver from the DOM.
Detailed metadata for an audio track.
interface AudioTrackMetadata {
id: string;
src: string;
startTime: number;
duration: number;
fadeInDuration?: number;
fadeOutDuration?: number;
fadeEasing?: string;
}
Audio source URL or path.
Track start time in the composition timeline.
Track duration in seconds.
Optional fade-in duration in seconds.
Optional fade-out duration in seconds.
Optional easing function name for fade transitions.
Ticker
The Ticker interface defines the contract for playback loop implementations.
interface Ticker {
start(callback: TickCallback): void;
stop(): void;
}
start
(callback: TickCallback) => void
Start the ticker loop, calling the callback on each tick with the delta time since the last tick.
TickCallback
Callback type for ticker implementations.
type TickCallback = (deltaTime: number) => void;
Time elapsed since the last tick in milliseconds.
Built-in implementations
Helios provides several built-in driver and ticker implementations:
Drivers
- DomDriver: Synchronizes WAAPI animations and media elements with Helios playback
- NoopDriver: Minimal driver with no synchronization (default when
autoSyncAnimations is false)
Tickers
- RafTicker: Uses
requestAnimationFrame for smooth browser-based playback (default in browsers)
- TimeoutTicker: Uses
setTimeout for Node.js environments (default in Node.js)
- ManualTicker: Manual tick control for testing or custom playback loops
Usage examples
Custom time driver
import { TimeDriver, Helios } from '@helios/core';
class CustomDriver implements TimeDriver {
private mediaElements: HTMLMediaElement[] = [];
init(scope: unknown): void {
if (scope instanceof Document) {
this.mediaElements = Array.from(
scope.querySelectorAll('audio, video')
);
}
}
update(timeInMs: number, options?: {
isPlaying: boolean;
playbackRate: number;
volume?: number;
muted?: boolean;
}): void {
const timeSec = timeInMs / 1000;
this.mediaElements.forEach(el => {
el.currentTime = timeSec;
el.playbackRate = options?.playbackRate ?? 1;
if (options?.volume !== undefined) {
el.volume = options.volume;
}
if (options?.muted !== undefined) {
el.muted = options.muted;
}
if (options?.isPlaying) {
el.play();
} else {
el.pause();
}
});
}
async waitUntilStable(): Promise<void> {
// Wait for all media to finish seeking
await Promise.all(
this.mediaElements.map(el => new Promise<void>(resolve => {
if (el.seeking) {
el.addEventListener('seeked', () => resolve(), { once: true });
} else {
resolve();
}
}))
);
}
dispose(): void {
this.mediaElements = [];
}
}
// Use the custom driver
const helios = new Helios({
duration: 10,
fps: 30,
driver: new CustomDriver()
});
Custom ticker
import { Ticker, TickCallback, Helios } from '@helios/core';
class CustomTicker implements Ticker {
private intervalId: number | null = null;
private lastTime = 0;
start(callback: TickCallback): void {
this.lastTime = Date.now();
this.intervalId = setInterval(() => {
const now = Date.now();
const delta = now - this.lastTime;
this.lastTime = now;
callback(delta);
}, 1000 / 60) as unknown as number; // 60 FPS
}
stop(): void {
if (this.intervalId !== null) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
}
// Use the custom ticker
const helios = new Helios({
duration: 10,
fps: 30,
ticker: new CustomTicker()
});
Accessing audio context
import { Helios } from '@helios/core';
const helios = new Helios({
duration: 10,
fps: 30,
autoSyncAnimations: true // Uses DomDriver
});
// Get the audio context for custom processing
const audioContext = await helios.getAudioContext();
if (audioContext instanceof AudioContext) {
// Create an analyser for visualization
const analyser = audioContext.createAnalyser();
// Get source node for a specific track
const sourceNode = await helios.getAudioSourceNode('background-music');
if (sourceNode instanceof MediaElementAudioSourceNode) {
sourceNode.connect(analyser);
analyser.connect(audioContext.destination);
// Now you can visualize audio
const dataArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(dataArray);
}
}
import { Helios } from '@helios/core';
const helios = new Helios({
duration: 10,
fps: 30,
autoSyncAnimations: true
});
// Subscribe to available audio tracks
const unsubscribe = helios.availableAudioTracks.subscribe(tracks => {
console.log('Available audio tracks:', tracks);
tracks.forEach(track => {
console.log(`Track ${track.id}:`, track.src);
console.log(` Duration: ${track.duration}s`);
console.log(` Start: ${track.startTime}s`);
});
});
// Clean up when done
unsubscribe();