Helios uses a driver-based architecture to synchronize animations across different rendering contexts. The TimeDriver interface allows you to create custom implementations for specialized workflows beyond DOM and canvas.
dispose() - Clean up resources when the instance is destroyedsubscribeToMetadata(callback) - Notify Helios about discovered audio tracksgetAudioContext() - Return Web Audio API context for mixinggetAudioSourceNode(trackId) - Return audio source node for a specific track
import { Helios } from '@helios-project/core';import { ThreeDriver } from './ThreeDriver';import * as THREE from 'three';const scene = new THREE.Scene();const mixer = new THREE.AnimationMixer(scene);// Load animations and add to mixerconst clip = THREE.AnimationClip.CreateFromMorphTargetSequence(/* ... */);const action = mixer.clipAction(clip);action.play();const helios = new Helios({ duration: 10, fps: 30, driver: new ThreeDriver(), animationScope: { scene, mixer }});helios.subscribe((state) => { renderer.render(scene, camera);});
Drivers can notify Helios about audio tracks for mixing and export.
import { TimeDriver, DriverMetadata, AudioTrackMetadata } from '@helios-project/core';export class CustomAudioDriver implements TimeDriver { private tracks = new Map<string, AudioTrackMetadata>(); private subscribers = new Set<(meta: DriverMetadata) => void>(); init(scope: unknown) { // Scan for audio sources this.discoverAudioTracks(); } private discoverAudioTracks() { // Example: Scan custom audio elements const audioElements = document.querySelectorAll('[data-audio-track]'); audioElements.forEach((el) => { const id = el.getAttribute('data-audio-track')!; const src = el.getAttribute('src') || ''; const offset = parseFloat(el.getAttribute('data-offset') || '0'); this.tracks.set(id, { id, src, startTime: offset, duration: 0, // Will be updated when metadata loads fadeInDuration: 0, fadeOutDuration: 0 }); }); this.notifySubscribers(); } subscribeToMetadata(callback: (meta: DriverMetadata) => void) { this.subscribers.add(callback); // Immediately notify with current state callback({ audioTracks: Array.from(this.tracks.values()) }); return () => this.subscribers.delete(callback); } private notifySubscribers() { const metadata = { audioTracks: Array.from(this.tracks.values()) }; this.subscribers.forEach(cb => cb(metadata)); } update(timeInMs: number, options) { // Sync audio playback } async waitUntilStable() { // Wait for audio to load }}
Custom drivers must be deterministic. The same timeInMs should always produce identical visual output, regardless of how many times update() is called or in what order.
Use existing drivers when possible - DomDriver handles most HTML/CSS/SVG use casesMake drivers stateless - Don’t accumulate state across update() callsHandle disposal properly - Clean up event listeners, contexts, and resourcesTest with manual ticker - Use ManualTicker to verify frame-perfect accuracyDocument scope requirements - Make it clear what animationScope should contain