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 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.
Output video width in pixels. If omitted, uses composition width. Scales the canvas/DOM during capture.
Output video height in pixels. If omitted, uses composition height.
Video bitrate in bits per second. Higher values = better quality, larger file size.
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.
CSS selector for the canvas element when using canvas mode.
Whether to burn captions into the video frames.
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.
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:
Array of subtitle cues with index, startTime, endTime, and text properties.
Filename for the SRT file (include .srt extension).
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
Auto mode (recommended)
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();
});
- Use canvas mode when possible - Much faster than DOM mode
- Lower resolution for previews - Export at lower resolution for quick previews
- Adjust bitrate - Higher bitrate = better quality but larger files and longer encoding
- Minimize DOM complexity - Simpler DOMs render faster in DOM mode
- 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);
}
}