> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heliosrender.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Driver system

> Understanding TimeDriver, DomDriver, NoopDriver, and custom time sources

Drivers are the abstraction layer that connects Helios's timeline to the rendering environment. They control how time advances and how animations synchronize.

## Driver architecture

### The TimeDriver interface

All drivers implement the `TimeDriver` interface:

```typescript theme={null}
export 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>;
}
```

See `packages/core/src/drivers/TimeDriver.ts:15` for the full interface definition.

### Driver selection

Helios automatically selects a driver based on configuration:

```typescript theme={null}
const helios = new Helios({
  duration: 10,
  fps: 30,
  autoSyncAnimations: true  // Uses DomDriver
});
```

Driver selection logic at `Helios.ts:542`:

```typescript theme={null}
if (options.driver) {
  this.driver = options.driver;  // Explicit driver
} else if (this.autoSyncAnimations) {
  this.driver = new DomDriver();  // Auto-sync enabled
} else {
  this.driver = new NoopDriver();  // No-op driver
}
```

## Built-in drivers

### NoopDriver

The simplest driver—does nothing. Used when you don't need animation synchronization.

```typescript theme={null}
import { NoopDriver } from '@helios-project/core';

const helios = new Helios({
  duration: 10,
  fps: 30,
  driver: new NoopDriver()
});
```

**Use cases:**

* Compositions with no CSS/WAAPI animations
* Manual animation control only
* Headless calculations without rendering

Implementation at `packages/core/src/drivers/NoopDriver.ts:3`:

```typescript theme={null}
export class NoopDriver implements TimeDriver {
  init(scope: unknown) {}
  update(timeInMs: number, options?: {...}) {}
  waitUntilStable(): Promise<void> {
    return Promise.resolve();
  }
}
```

### DomDriver

Synchronizes CSS animations, WAAPI timelines, and media elements in the browser.

```typescript theme={null}
import { DomDriver } from '@helios-project/core';

const helios = new Helios({
  duration: 10,
  fps: 30,
  driver: new DomDriver(),
  animationScope: document  // Optional: scope to search for animations
});
```

**Features:**

* WAAPI animation seeking
* Media element (`<video>`, `<audio>`) synchronization
* Shadow DOM support
* Audio track discovery and mixing
* Stability checks (fonts, images, media)

**Implementation details:**

See `packages/core/src/drivers/DomDriver.ts:11` for the full implementation.

> [tab] WAAPI sync
>
> ```typescript theme={null}
> // Simplified from DomDriver.ts:372
> private syncWaapiAnimations(timeInMs: number) {
>   let anims: Animation[] = [];
>
>   // Discover animations in all scopes (document + shadow roots)
>   for (const scope of this.scopes) {
>     if (typeof (scope as any).getAnimations === 'function') {
>       const scopeAnims = (scope as any).getAnimations({ subtree: true });
>       scopeAnims.forEach((a: Animation) => anims.push(a));
>     }
>   }
>
>   // Seek all animations to target time
>   anims.forEach((anim: Animation) => {
>     anim.currentTime = timeInMs;
>     if (anim.playState !== 'paused') {
>       anim.pause();
>     }
>   });
> }
> ```

> [tab] Media sync
>
> ```typescript theme={null}
> // Simplified from DomDriver.ts:392
> private syncMediaElements(timeInMs: number, options: {...}) {
>   const timeInSeconds = timeInMs / 1000;
>
>   this.mediaElements.forEach((el) => {
>     const offset = parseFloat(el.getAttribute('data-helios-offset') || '0');
>     const seek = parseFloat(el.getAttribute('data-helios-seek') || '0');
>     const timeRelToStart = timeInSeconds - offset;
>
>     const isBeforeStart = timeRelToStart < 0;
>     const targetTime = Math.max(0, timeRelToStart + seek);
>
>     if (options.isPlaying && !isBeforeStart) {
>       // Playback mode: play with drift correction
>       if (el.paused) el.play().catch(() => {});
>       if (Math.abs(el.currentTime - targetTime) > 0.25) {
>         el.currentTime = targetTime;
>       }
>     } else {
>       // Scrubbing mode: exact seeking
>       if (!el.paused) el.pause();
>       if (Math.abs(el.currentTime - targetTime) > 0.001) {
>         el.currentTime = targetTime;
>       }
>     }
>   });
> }
> ```

> [tab] Stability checks
>
> ```typescript theme={null}
> // Simplified from DomDriver.ts:313
> async waitUntilStable(): Promise<void> {
>   // 1. Wait for fonts
>   const fontPromise = document.fonts ? document.fonts.ready : Promise.resolve();
>
>   // 2. Wait for images
>   const images = Array.from(this.scope.querySelectorAll('img'));
>   const imagePromises = images.map(img => {
>     if (img.complete) return Promise.resolve();
>     return img.decode().catch(() => {});
>   });
>
>   // 3. Wait for media seeking
>   const mediaPromises = Array.from(this.mediaElements).map(el => {
>     return new Promise<void>(resolve => {
>       if (!el.seeking && el.readyState >= 2) return resolve();
>       el.addEventListener('seeked', () => resolve(), { once: true });
>       el.addEventListener('error', () => resolve(), { once: true });
>     });
>   });
>
>   await Promise.all([fontPromise, ...imagePromises, ...mediaPromises]);
> }
> ```

### CdpTimeDriver (Renderer)

Uses Chrome DevTools Protocol to virtualize time for deterministic server-side rendering.

```typescript theme={null}
import { CdpTimeDriver } from '@helios-project/renderer';

const driver = new CdpTimeDriver(30000); // 30s timeout
await driver.init(page);
await driver.prepare(page);
await driver.setTime(page, 1.5); // Advance to 1.5 seconds
```

**How it works:**

1. **Pause virtual time**: Freeze the browser's internal clock
2. **Sync media elements**: Manually seek `<video>`/`<audio>` to target time
3. **Advance virtual time**: Resume clock for exact frame duration
4. **Wait for stability**: Check for async operations to complete

See `packages/renderer/src/drivers/CdpTimeDriver.ts:6` for implementation.

> [tab] Initialization
>
> ```typescript theme={null}
> // From CdpTimeDriver.ts:19
> async prepare(page: Page): Promise<void> {
>   this.client = await page.context().newCDPSession(page);
>
>   // Initialize virtual time to deterministic epoch
>   const INITIAL_VIRTUAL_TIME = 1704067200; // 2024-01-01T00:00:00Z
>   await this.client.send('Emulation.setVirtualTimePolicy', {
>     policy: 'pause',
>     initialVirtualTime: INITIAL_VIRTUAL_TIME
>   });
>
>   // Override performance.now() to match virtual time
>   await page.evaluate((epoch) => {
>     window.performance.now = () => Date.now() - epoch;
>   }, INITIAL_VIRTUAL_TIME * 1000);
>
>   this.currentTime = 0;
> }
> ```

> [tab] Time advancement
>
> ```typescript theme={null}
> // From CdpTimeDriver.ts:39
> async setTime(page: Page, timeInSeconds: number): Promise<void> {
>   const delta = timeInSeconds - this.currentTime;
>   if (delta <= 0) return;
>
>   const budget = delta * 1000; // Convert to milliseconds
>
>   // 1. Manually sync media elements BEFORE advancing time
>   await this.syncMediaElements(page, timeInSeconds);
>
>   // 2. Advance virtual time by exact budget
>   await new Promise<void>((resolve, reject) => {
>     this.client.once('Emulation.virtualTimeBudgetExpired', () => resolve());
>     this.client.send('Emulation.setVirtualTimePolicy', {
>       policy: 'advance',
>       budget: budget
>     }).catch(reject);
>   });
>
>   this.currentTime = timeInSeconds;
>
>   // 3. Wait for custom stability checks
>   await this.waitForStability(page);
> }
> ```

> [tab] Media synchronization
>
> ```typescript theme={null}
> // From CdpTimeDriver.ts:58
> const mediaSyncScript = `
>   (async (t) => {
>     const findAllMedia = (root) => { /* ... */ };
>     const syncMedia = (el, time) => {
>       const offset = parseFloat(el.getAttribute('data-helios-offset') || '0');
>       const seek = parseFloat(el.getAttribute('data-helios-seek') || '0');
>       const targetTime = Math.max(0, time - offset + seek);
>
>       if (Math.abs(el.currentTime - targetTime) > 0.001) {
>         el.currentTime = targetTime;
>       }
>     };
>
>     const mediaElements = findAllMedia(document);
>     mediaElements.forEach(el => syncMedia(el, t));
>   })(${timeInSeconds})
> `;
>
> // Execute in all frames (supports iframes)
> await Promise.all(page.frames().map(frame =>
>   frame.evaluate(mediaSyncScript).catch(e => {
>     console.warn('Failed to sync media in frame:', e);
>   })
> ));
> ```

> [!NOTE]
>
> CDP virtual time is deterministic because the browser's event loop is paused until the time budget expires. This means `requestAnimationFrame` callbacks and CSS animations all update to the exact target time before rendering continues.

## Media element synchronization

### Data attributes

Control media playback with custom attributes:

```html theme={null}
<audio
  data-helios-track-id="bgm"
  data-helios-offset="2"
  data-helios-seek="5"
  data-helios-fade-in="1"
  data-helios-fade-out="2"
  data-helios-fade-easing="easeOut.cubic"
  src="background.mp3"
></audio>
```

**Attributes:**

* `data-helios-track-id`: Unique identifier for track control
* `data-helios-offset`: When the track starts in composition time (seconds)
* `data-helios-seek`: In-point in the media file (seconds)
* `data-helios-fade-in`: Fade-in duration (seconds)
* `data-helios-fade-out`: Fade-out duration (seconds)
* `data-helios-fade-easing`: Easing function for fades

### Audio track control

```typescript theme={null}
const helios = new Helios({
  duration: 10,
  fps: 30,
  autoSyncAnimations: true,
  volume: 0.8,  // Master volume
  audioTracks: {
    'bgm': { volume: 0.5, muted: false },
    'sfx': { volume: 1.0, muted: false }
  }
});

// Change track volume
helios.setAudioTrackVolume('bgm', 0.3);

// Mute track
helios.setAudioTrackMuted('sfx', true);

// Master controls
helios.setAudioVolume(0.5);
helios.setAudioMuted(true);
```

Final volume calculation:

```typescript theme={null}
finalVolume = baseVolume * masterVolume * trackVolume * fadeInMultiplier * fadeOutMultiplier;
```

See `DomDriver.ts:461` for fade and volume logic.

### Audio track discovery

DomDriver automatically discovers audio tracks:

```typescript theme={null}
helios.availableAudioTracks.subscribe(tracks => {
  console.log('Discovered tracks:', tracks);
  // [
  //   {
  //     id: 'bgm',
  //     src: 'background.mp3',
  //     startTime: 2,
  //     duration: 120,
  //     fadeInDuration: 1,
  //     fadeOutDuration: 2,
  //     fadeEasing: 'easeOut.cubic'
  //   }
  // ]
});
```

## Custom drivers

Create a custom driver to integrate with non-standard time sources.

### Example: External clock driver

```typescript theme={null}
import { TimeDriver } from '@helios-project/core';

export class ExternalClockDriver implements TimeDriver {
  private externalClock: ExternalTimeSource;

  init(scope: unknown) {
    this.externalClock = new ExternalTimeSource();
  }

  update(timeInMs: number, options: {...}) {
    // Sync external clock to Helios time
    this.externalClock.setTime(timeInMs);
    
    // Update dependent systems
    if (options.isPlaying) {
      this.externalClock.play(options.playbackRate);
    } else {
      this.externalClock.pause();
    }
  }

  async waitUntilStable(): Promise<void> {
    // Wait for external system to stabilize
    await this.externalClock.waitForSync();
  }

  dispose() {
    this.externalClock.disconnect();
  }
}

// Use custom driver
const helios = new Helios({
  duration: 10,
  fps: 30,
  driver: new ExternalClockDriver()
});
```

### Example: MIDI timecode driver

```typescript theme={null}
import { TimeDriver } from '@helios-project/core';

export class MidiTimecodeDriver implements TimeDriver {
  private midiAccess: any;

  async init(scope: unknown) {
    this.midiAccess = await navigator.requestMIDIAccess();
    // Listen for MIDI timecode messages
  }

  update(timeInMs: number, options: {...}) {
    // Send MIDI timecode to hardware
    const timecode = this.convertToMTC(timeInMs);
    this.sendMTC(timecode);
  }

  async waitUntilStable(): Promise<void> {
    // Wait for MIDI buffer to flush
    return new Promise(resolve => setTimeout(resolve, 10));
  }

  private convertToMTC(ms: number) {
    // Convert milliseconds to MIDI Timecode format
    // ...
  }

  private sendMTC(timecode: any) {
    // Send via MIDI output
    // ...
  }
}
```

## Stability patterns

### Custom stability checks

Register async operations that should block rendering:

```typescript theme={null}
helios.registerStabilityCheck(async () => {
  // Wait for Three.js scene to load
  await scene.loadAssets();
});

helios.registerStabilityCheck(async () => {
  // Wait for custom data fetch
  await fetch('/api/data').then(r => r.json());
});

// Both checks run in parallel during waitUntilStable()
await helios.waitUntilStable();
```

See `Helios.ts:876` for the stability check API.

### Virtual time synchronization

When using `bindToDocumentTimeline()`, Helios polls `__HELIOS_VIRTUAL_TIME__`:

```typescript theme={null}
// Set by renderer (CdpTimeDriver)
window.__HELIOS_VIRTUAL_TIME__ = 1500; // 1.5 seconds

// Helios automatically syncs
helios.currentTime.value; // 1.5
```

Stability waiting ensures Helios catches up to virtual time:

```typescript theme={null}
// From Helios.ts:952
const virtualTimePromise = new Promise<void>((resolve) => {
  const checkSync = () => {
    const virtualTime = window.__HELIOS_VIRTUAL_TIME__;
    const targetFrame = (virtualTime / 1000) * this._fps.value;
    
    if (Math.abs(targetFrame - this._currentFrame.peek()) <= 0.01) {
      resolve(); // Synced within 0.01 frame tolerance
    } else {
      requestAnimationFrame(checkSync); // Keep polling
    }
  };
  checkSync();
});
```

## Tickers

Drivers use tickers to manage the playback loop. Tickers are separate from drivers to allow different timing strategies.

### RafTicker (default)

Uses `requestAnimationFrame` for smooth browser playback:

```typescript theme={null}
import { RafTicker } from '@helios-project/core';

const helios = new Helios({
  duration: 10,
  fps: 30,
  ticker: new RafTicker()
});
```

### TimeoutTicker

Uses `setTimeout` for Node.js or non-browser environments:

```typescript theme={null}
import { TimeoutTicker } from '@helios-project/core';

const helios = new Helios({
  duration: 10,
  fps: 30,
  ticker: new TimeoutTicker()
});
```

### ManualTicker

Manually advance time for testing:

```typescript theme={null}
import { ManualTicker } from '@helios-project/core';

const ticker = new ManualTicker();
const helios = new Helios({
  duration: 10,
  fps: 30,
  ticker
});

helios.play();

// Manually tick
ticker.tick(33.33); // Advance by one frame (at 30fps)
ticker.tick(33.33);
```

See `packages/core/src/drivers/` for all ticker implementations.

## Best practices

### Choose the right driver

* **NoopDriver**: Manual control, no animations
* **DomDriver**: CSS/WAAPI animations, media elements
* **CdpTimeDriver**: Server-side rendering only

### Scope animations carefully

```typescript theme={null}
// Narrow scope for better performance
const helios = new Helios({
  duration: 10,
  fps: 30,
  autoSyncAnimations: true,
  animationScope: document.querySelector('#composition')
});
```

### Preload media elements

```html theme={null}
<audio preload="auto" src="audio.mp3"></audio>
```

This ensures media is ready before rendering starts.

### Handle stability timeouts

For long-running async operations, increase the timeout:

```typescript theme={null}
const driver = new CdpTimeDriver(60000); // 60 second timeout
```

Or split into smaller stability checks:

```typescript theme={null}
helios.registerStabilityCheck(async () => {
  await loadCriticalAssets();
});

helios.registerStabilityCheck(async () => {
  await loadSecondaryAssets();
});
```

## Next steps

* Learn about [animations](https://docs.heliosrender.com/concepts/animations.md) for motion control
* Explore [timeline control](https://docs.heliosrender.com/concepts/timeline-control.md) for playback
* Understand [compositions](https://docs.heliosrender.com/concepts/compositions.md) for project structure


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.