> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/BintzGavin/helios/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.

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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]);
    }
    ```
  </Tab>
</Tabs>

### 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.

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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);
      })
    ));
    ```
  </Tab>
</Tabs>

<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.
</Note>

## 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](/concepts/animations) for motion control
* Explore [timeline control](/concepts/timeline-control) for playback
* Understand [compositions](/concepts/compositions) for project structure
