The RenderOrchestrator class enables distributed rendering by splitting a composition into multiple chunks that can be rendered in parallel. This dramatically reduces rendering time for long videos on multi-core systems.
How it works
- Plan: Divides the composition into frame ranges based on concurrency
- Render: Executes chunks in parallel using worker renderers
- Concatenate: Stitches chunk videos into a single file
- Mix: Applies final audio mixing and encoding
Static methods
plan
static plan(
compositionUrl: string,
outputPath: string,
options: DistributedRenderOptions
): RenderPlan
Creates a render plan without executing it. Useful for previewing the distribution strategy.
URL of the HTML composition to render.
Final output file path for the rendered video.
options
DistributedRenderOptions
required
Configuration object extending RendererOptions with distributed rendering settings.
Returns
A RenderPlan object containing:
Total number of frames in the composition.
Array of chunk specifications, each containing:
id: Chunk identifier
startFrame: First frame in this chunk
frameCount: Number of frames to render
outputFile: Temporary file path for chunk output
options: RendererOptions for this specific chunk
List of chunk file paths to concatenate.
Temporary file path for the concatenated video (before final audio mix).
Final output path (same as the outputPath parameter).
Options for the final audio mixing pass.
List of temporary files to delete after successful rendering.
Example
import { RenderOrchestrator } from '@helios/renderer';
const plan = RenderOrchestrator.plan(
'file:///composition.html',
'./output.mp4',
{
width: 1920,
height: 1080,
fps: 30,
durationInSeconds: 60,
concurrency: 4
}
);
console.log(`Rendering ${plan.totalFrames} frames across ${plan.chunks.length} chunks`);
plan.chunks.forEach(chunk => {
console.log(`Chunk ${chunk.id}: frames ${chunk.startFrame}-${chunk.startFrame + chunk.frameCount}`);
});
render
static async render(
compositionUrl: string,
outputPath: string,
options: DistributedRenderOptions,
jobOptions?: RenderJobOptions
): Promise<void>
Executes a distributed render job.
URL of the HTML composition to render.
Final output file path for the rendered video.
options
DistributedRenderOptions
required
Configuration object extending RendererOptions with distributed rendering settings.
Optional job configuration including progress callbacks and abort signals.
Example
await RenderOrchestrator.render(
'file:///composition.html',
'./output.mp4',
{
width: 1920,
height: 1080,
fps: 30,
durationInSeconds: 120,
concurrency: 8,
audioFilePath: './music.mp3'
},
{
onProgress: (progress) => {
console.log(`Overall progress: ${(progress * 100).toFixed(1)}%`);
}
}
);
Distributed render options
interface DistributedRenderOptions extends RendererOptions {
concurrency?: number;
executor?: RenderExecutor;
}
Number of parallel render workers. Defaults to os.cpus().length - 1.
Custom executor for running render chunks. Defaults to LocalExecutor which runs chunks as parallel processes on the local machine. Implement custom executors for cloud-based or distributed execution.
Concurrency behavior
Automatic concurrency
// Uses CPU count - 1 workers
await RenderOrchestrator.render(
'file:///composition.html',
'./output.mp4',
{
width: 1920,
height: 1080,
fps: 30,
durationInSeconds: 60
// concurrency not specified
}
);
Single worker fallback
When concurrency: 1, the orchestrator skips chunking and uses a standard Renderer directly:
await RenderOrchestrator.render(
'file:///composition.html',
'./output.mp4',
{
width: 1920,
height: 1080,
fps: 30,
durationInSeconds: 60,
concurrency: 1 // No parallelization
}
);
Custom concurrency
await RenderOrchestrator.render(
'file:///composition.html',
'./output.mp4',
{
width: 1920,
height: 1080,
fps: 30,
durationInSeconds: 60,
concurrency: 16 // Use 16 workers
}
);
Audio handling in distributed mode
The orchestrator uses a multi-stage audio pipeline:
Chunk rendering
- Each chunk renders with PCM audio (
pcm_s16le codec)
- Audio tracks are not mixed during chunk rendering
- Chunks output to
.mov container format to preserve PCM audio
Concatenation
- Chunk videos are concatenated without re-encoding
- PCM audio streams are preserved in the concatenated file
Final mix
- Concatenated video is processed with final audio options
- Audio tracks from
audioFilePath and audioTracks are mixed
- Output is encoded with specified
audioCodec (default: aac)
- Final video uses the container format from
outputPath extension
Example with audio
await RenderOrchestrator.render(
'file:///composition.html',
'./output.mp4',
{
width: 1920,
height: 1080,
fps: 30,
durationInSeconds: 60,
concurrency: 4,
audioTracks: [
{
path: './music.mp3',
volume: 0.7,
fadeInDuration: 2,
fadeOutDuration: 3
},
{
path: './sfx.wav',
offset: 10,
volume: 1.0
}
],
audioCodec: 'aac',
audioBitrate: '192k'
}
);
Progress tracking
The orchestrator aggregates progress from all workers:
const progressBar = new Map<number, number>();
await RenderOrchestrator.render(
'file:///composition.html',
'./output.mp4',
{
width: 1920,
height: 1080,
fps: 30,
durationInSeconds: 60,
concurrency: 4
},
{
onProgress: (globalProgress) => {
// globalProgress is weighted by chunk size
console.log(`Overall: ${(globalProgress * 100).toFixed(1)}%`);
}
}
);
Each worker’s progress is weighted by its frame count, ensuring accurate overall progress reporting.
Error handling and cancellation
Abort signal
const controller = new AbortController();
const renderPromise = RenderOrchestrator.render(
'file:///composition.html',
'./output.mp4',
{
width: 1920,
height: 1080,
fps: 30,
durationInSeconds: 60,
concurrency: 4
},
{
signal: controller.signal
}
);
// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10000);
try {
await renderPromise;
} catch (error) {
if (error.message === 'Aborted') {
console.log('Render was cancelled');
}
}
Worker failure propagation
If any worker fails, all other workers are immediately aborted:
try {
await RenderOrchestrator.render(
'file:///composition.html',
'./output.mp4',
{
width: 1920,
height: 1080,
fps: 30,
durationInSeconds: 60,
concurrency: 4
}
);
} catch (error) {
// If worker 2 fails, workers 0, 1, and 3 are aborted
console.error('Distributed render failed:', error);
}
Temporary file cleanup
The orchestrator automatically cleans up temporary files:
- Chunk video files (e.g.,
temp_123_part_0.mov)
- Concatenated intermediate file (e.g.,
temp_concat_123.mov)
Cleanup occurs in the finally block, ensuring files are removed even if rendering fails.
Custom executors
Implement the RenderExecutor interface to run chunks on cloud infrastructure:
import { RenderExecutor } from '@helios/renderer';
class CloudExecutor implements RenderExecutor {
async render(
compositionUrl: string,
outputPath: string,
options: RendererOptions,
jobOptions?: RenderJobOptions
): Promise<void> {
// Upload composition to cloud storage
// Trigger cloud render job
// Download rendered chunk
}
}
await RenderOrchestrator.render(
'file:///composition.html',
'./output.mp4',
{
width: 1920,
height: 1080,
fps: 30,
durationInSeconds: 60,
concurrency: 100, // Scale to cloud capacity
executor: new CloudExecutor()
}
);
Optimal concurrency
- CPU-bound: Set concurrency to CPU core count minus 1
- Memory-bound: Reduce concurrency if renders are memory-intensive
- Cloud: Scale concurrency based on available cloud workers
Chunk size
Chunk size is automatically calculated as totalFrames / concurrency. For very short videos, some workers may receive no frames:
// 90 frames, 4 workers = 22-23 frames per chunk
// 90 frames, 100 workers = 0-1 frames per chunk (inefficient)
When to use distributed rendering
Use distributed rendering when:
- Videos are longer than 30 seconds
- You have multiple CPU cores available
- Rendering time is critical
Use standard Renderer when:
- Videos are very short (< 10 seconds)
- Single-core environment
- Simplicity is preferred over speed
Console output
Distributed rendering produces detailed logs:
Starting distributed render with concurrency: 4
[Worker 0] Rendering frames 0 to 450 (450 frames) to /tmp/temp_123_part_0.mov
[Worker 1] Rendering frames 450 to 900 (450 frames) to /tmp/temp_123_part_1.mov
[Worker 2] Rendering frames 900 to 1350 (450 frames) to /tmp/temp_123_part_2.mov
[Worker 3] Rendering frames 1350 to 1800 (450 frames) to /tmp/temp_123_part_3.mov
All chunks rendered. Concatenating...
Mixing audio into concatenated video...
Spawning FFmpeg for audio mixing: /usr/local/bin/ffmpeg -i /tmp/concat.mov ...
Cleaning up temporary files...