Skip to main content
The ClientSideExporter class provides browser-based export functionality for Helios compositions. It captures frames and audio, encodes them using WebCodecs, and outputs video files or images.

Installation

The exporter is included in the @helios-project/player package:
npm install @helios-project/player

Basic usage

import { ClientSideExporter } from '@helios-project/player';

const controller = player.controller; // Or create directly
const exporter = new ClientSideExporter(controller);

await exporter.export({
  format: 'mp4',
  onProgress: (progress) => {
    console.log(`Export: ${Math.round(progress * 100)}%`);
  }
});

Constructor

const exporter = new ClientSideExporter(controller);
Parameters:
controller
HeliosController
required
Controller instance connected to the composition.

Methods

export(options)

Exports the composition to a video file or image.
await exporter.export({
  format: 'mp4',
  width: 1920,
  height: 1080,
  bitrate: 8_000_000,
  filename: 'my-video',
  onProgress: (progress) => {
    progressBar.value = progress;
  },
  signal: abortController.signal
});
Parameters:
format
'mp4' | 'webm' | 'png' | 'jpeg'
default:"mp4"
Output format. Use mp4 or webm for video, png or jpeg for single-frame images.
width
number
Output video width in pixels. If omitted, uses composition width. Scales the canvas/DOM during capture.
height
number
Output video height in pixels. If omitted, uses composition height.
bitrate
number
default:"5000000"
Video bitrate in bits per second. Higher values = better quality, larger file size.
filename
string
default:"video"
Filename without extension. Extension is added automatically based on format.
mode
'auto' | 'canvas' | 'dom'
default:"auto"
Capture mode. auto attempts canvas first, falls back to DOM. canvas captures the canvas element directly. dom renders the entire document.
canvasSelector
string
default:"canvas"
CSS selector for the canvas element when using canvas mode.
includeCaptions
boolean
default:"true"
Whether to burn captions into the video frames.
captionStyle
object
Caption appearance customization:
  • color (string) - Text color (default: 'white')
  • backgroundColor (string) - Background color (default: 'rgba(0, 0, 0, 0.7)')
  • fontFamily (string) - Font family (default: 'sans-serif')
  • scale (number) - Font size as fraction of video height (default: 0.05)
onProgress
(progress: number) => void
required
Progress callback. Called with values from 0.0 to 1.0 as export progresses.
signal
AbortSignal
Abort signal to cancel the export. Connect to an AbortController.
Returns: Promise<void> - Resolves when export completes and download begins. Throws: Error if export fails or is aborted.

saveCaptionsAsSRT(cues, filename)

Saves captions as an SRT file.
const cues = [
  { index: 1, startTime: 0, endTime: 5, text: 'Hello world' },
  { index: 2, startTime: 5, endTime: 10, text: 'Second caption' }
];

exporter.saveCaptionsAsSRT(cues, 'captions.srt');
Parameters:
cues
SubtitleCue[]
required
Array of subtitle cues with index, startTime, endTime, and text properties.
filename
string
required
Filename for the SRT file (include .srt extension).

Export formats

MP4 video

await exporter.export({
  format: 'mp4',
  bitrate: 8_000_000,
  onProgress: (p) => console.log(`${Math.round(p * 100)}%`)
});
  • Codec: H.264 (AVC)
  • Audio codec: AAC
  • Best browser support
  • Recommended for web delivery

WebM video

await exporter.export({
  format: 'webm',
  bitrate: 8_000_000,
  onProgress: (p) => console.log(`${Math.round(p * 100)}%`)
});
  • Codec: VP9
  • Audio codec: Opus
  • Better compression than MP4
  • May have limited browser support on some platforms

PNG image

await exporter.export({
  format: 'png',
  onProgress: (p) => console.log(`${Math.round(p * 100)}%`)
});
  • Captures the current frame as a lossless PNG
  • Useful for thumbnails or poster images

JPEG image

await exporter.export({
  format: 'jpeg',
  onProgress: (p) => console.log(`${Math.round(p * 100)}%`)
});
  • Captures the current frame as a JPEG
  • Smaller file size than PNG
  • Lossy compression

Capture modes

Attempts canvas capture first, falls back to DOM if canvas is not accessible:
await exporter.export({
  mode: 'auto',
  onProgress: (p) => console.log(p)
});

Canvas mode

Captures directly from a canvas element (fast, efficient):
await exporter.export({
  mode: 'canvas',
  canvasSelector: '#my-canvas',
  onProgress: (p) => console.log(p)
});
Requirements:
  • Composition must render to a canvas
  • Canvas must be accessible (same-origin)

DOM mode

Captures the entire document using DOM-to-bitmap rendering (slower, more flexible):
await exporter.export({
  mode: 'dom',
  width: 1920,
  height: 1080,
  onProgress: (p) => console.log(p)
});
Use cases:
  • Compositions without canvas
  • HTML/CSS-based animations
  • SVG content
Performance: Slower than canvas mode. Export time increases with DOM complexity.

Caption rendering

Burn captions into video

await exporter.export({
  format: 'mp4',
  includeCaptions: true,
  captionStyle: {
    color: 'yellow',
    backgroundColor: 'rgba(0, 0, 0, 0.8)',
    fontFamily: 'Arial',
    scale: 0.06 // 6% of video height
  },
  onProgress: (p) => console.log(p)
});

Export captions as separate SRT file

// Export video
await exporter.export({ format: 'mp4', includeCaptions: false });

// Export captions separately
const state = controller.getState();
const cues = state.activeCaptions.map((c, i) => ({
  index: i + 1,
  startTime: c.startTime,
  endTime: c.endTime,
  text: c.text
}));

exporter.saveCaptionsAsSRT(cues, 'captions.srt');

Progress tracking

const progressBar = document.querySelector('progress');
const statusText = document.querySelector('.status');

await exporter.export({
  format: 'mp4',
  onProgress: (progress) => {
    progressBar.value = progress;
    statusText.textContent = `Exporting: ${Math.round(progress * 100)}%`;
  }
});

statusText.textContent = 'Export complete!';

Abort handling

const abortController = new AbortController();
const cancelButton = document.querySelector('.cancel-btn');

cancelButton.addEventListener('click', () => {
  abortController.abort();
});

try {
  await exporter.export({
    format: 'mp4',
    signal: abortController.signal,
    onProgress: (p) => console.log(`Progress: ${p}`)
  });
  console.log('Export completed');
} catch (err) {
  if (err.message === 'Export aborted') {
    console.log('Export was cancelled');
  } else {
    console.error('Export failed:', err);
  }
}

Playback range export

Export only a portion of the composition:
// Set playback range
controller.setPlaybackRange(60, 240); // Frames 60-240

// Export the range
await exporter.export({
  format: 'mp4',
  onProgress: (p) => console.log(p)
});

// Clear range
controller.clearPlaybackRange();

Full example: Export UI

import { ClientSideExporter } from '@helios-project/player';

const player = document.querySelector('helios-player');
const exporter = new ClientSideExporter(player.controller);

const exportButton = document.querySelector('.export-btn');
const progressBar = document.querySelector('progress');
const statusText = document.querySelector('.status');
const formatSelect = document.querySelector('.format-select');
const abortController = new AbortController();

exportButton.addEventListener('click', async () => {
  // Disable button during export
  exportButton.disabled = true;
  statusText.textContent = 'Exporting...';
  progressBar.value = 0;
  
  try {
    await exporter.export({
      format: formatSelect.value,
      width: 1920,
      height: 1080,
      bitrate: 8_000_000,
      filename: 'my-composition',
      includeCaptions: true,
      signal: abortController.signal,
      onProgress: (progress) => {
        progressBar.value = progress;
        statusText.textContent = `Exporting: ${Math.round(progress * 100)}%`;
      }
    });
    
    statusText.textContent = 'Export complete! Download started.';
  } catch (err) {
    if (err.message === 'Export aborted') {
      statusText.textContent = 'Export cancelled';
    } else {
      statusText.textContent = 'Export failed: ' + err.message;
      console.error(err);
    }
  } finally {
    exportButton.disabled = false;
  }
});

// Cancel button
document.querySelector('.cancel-btn').addEventListener('click', () => {
  abortController.abort();
});

Performance tips

  1. Use canvas mode when possible - Much faster than DOM mode
  2. Lower resolution for previews - Export at lower resolution for quick previews
  3. Adjust bitrate - Higher bitrate = better quality but larger files and longer encoding
  4. Minimize DOM complexity - Simpler DOMs render faster in DOM mode
  5. Close VideoFrames - Always close captured frames to avoid memory leaks

Browser compatibility

Requires:
  • WebCodecs API (Chrome 94+, Edge 94+)
  • OffscreenCanvas (Chrome 69+, Firefox 105+)
  • MediaRecorder API for encoding
Not supported in:
  • Safari (WebCodecs not available)
  • Older browsers
For unsupported browsers, use server-side rendering instead.

Error handling

Common errors and solutions:
try {
  await exporter.export({ ... });
} catch (err) {
  if (err.message.includes('Canvas not found')) {
    // Use DOM mode instead
    await exporter.export({ mode: 'dom', ... });
  } else if (err.message.includes('WebCodecs')) {
    // Browser doesn't support WebCodecs
    alert('Your browser does not support video export');
  } else {
    console.error('Export failed:', err);
  }
}