Quick Start Guide
Get started with the Stoked UI Video Renderer in minutes.
What You'll Build
In this guide, you'll create a simple video composition system that:
- Renders frames with Rust backend (10-50x faster than Node.js)
- Provides real-time preview in the browser with WASM
- Integrates with your existing Node.js infrastructure
Prerequisites
# Required tools
node --version # >= 18.0.0
cargo --version # >= 1.70.0
If you don't have Rust installed:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Step 1: Install Dependencies
For Rust Backend
cd packages/sui-video-renderer-rust/compositor
cargo build --release
For WASM Frontend
cd packages/sui-video-renderer-rust/wasm-preview
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
wasm-pack build --target web --release
For Node.js Integration
npm install @stoked-ui/video-renderer
# or
pnpm add @stoked-ui/video-renderer
Step 2: Your First Composition (Rust)
Create a new Rust project:
cargo new my-video-app
cd my-video-app
Add to Cargo.toml:
[dependencies]
video-compositor = { path = "../sui-video-renderer-rust/compositor" }
Create src/main.rs:
use video_compositor::{Compositor, Layer, Transform, Color};
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Creating compositor...");
let compositor = Compositor::new(1920, 1080)?;
let layers = vec![
// Dark background
Layer::solid_color(
Color::rgb(30, 30, 30),
Transform::new()
),
// Red rectangle
Layer::solid_color(
Color::rgb(255, 50, 50),
Transform::new()
.with_position(400.0, 300.0)
.with_scale(0.4)
.with_opacity(0.9)
),
// Blue rectangle
Layer::solid_color(
Color::rgb(50, 100, 255),
Transform::new()
.with_position(800.0, 300.0)
.with_scale(0.4)
.with_opacity(0.9)
),
];
println!("Composing frame...");
let frame = compositor.compose(&layers)?;
frame.save("output.png")?;
println!("✅ Frame saved to output.png");
Ok(())
}
Run it:
cargo run --release
# ✅ Frame saved to output.png
Step 3: Browser Preview (WASM)
Install the WASM package in your React project:
npm install @stoked-ui/wasm-preview
Create a preview component:
// src/VideoPreview.tsx
import { useWasmRenderer, createSolidColorLayer } from '@stoked-ui/wasm-preview';
import { useEffect, useState } from 'react';
export function VideoPreview() {
const { canvasRef, renderFrame, isLoading, metrics } = useWasmRenderer(1920, 1080);
const [layers, setLayers] = useState([
createSolidColorLayer([30, 30, 30, 255]),
createSolidColorLayer(
[255, 50, 50, 255],
{ x: 400, y: 300, scale_x: 0.4, scale_y: 0.4, opacity: 0.9 }
),
createSolidColorLayer(
[50, 100, 255, 255],
{ x: 800, y: 300, scale_x: 0.4, scale_y: 0.4, opacity: 0.9 }
),
]);
useEffect(() => {
if (!isLoading) {
renderFrame(layers);
}
}, [isLoading, layers, renderFrame]);
if (isLoading) return <div>Loading...</div>;
return (
<div>
<canvas
ref={canvasRef}
style={{ maxWidth: '100%', border: '1px solid #ccc' }}
/>
{metrics && (
<p>Performance: {metrics.lastFrameTime?.toFixed(2)}ms | {metrics.fps} FPS</p>
)}
</div>
);
}
Use it in your app:
// src/App.tsx
import { VideoPreview } from './VideoPreview';
export default function App() {
return (
<div>
<h1>My Video Editor</h1>
<VideoPreview />
</div>
);
}
Step 4: Node.js Backend Integration
Create a NestJS service:
// src/render/rust-compositor.service.ts
import { Injectable } from '@nestjs/common';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs/promises';
const execAsync = promisify(exec);
@Injectable()
export class RustCompositorService {
private readonly cliPath = '../sui-video-renderer-rust/target/release/video-render';
async composeFrame(layers: any[], outputPath: string): Promise<string> {
const inputPath = `/tmp/input-${Date.now()}.json`;
await fs.writeFile(
inputPath,
JSON.stringify({
width: 1920,
height: 1080,
layers,
outputPath,
})
);
await execAsync(`${this.cliPath} compose --input "${inputPath}" --output "${outputPath}"`);
await fs.unlink(inputPath);
return outputPath;
}
}
Create a controller:
// src/render/render.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { RustCompositorService } from './rust-compositor.service';
@Controller('render')
export class RenderController {
constructor(private readonly compositor: RustCompositorService) {}
@Post('compose')
async compose(@Body() body: { layers: any[] }) {
const outputPath = `/tmp/output-${Date.now()}.png`;
await this.compositor.composeFrame(body.layers, outputPath);
return {
success: true,
outputPath,
};
}
}
Step 5: Full Video Rendering
Extend your service to render complete videos:
import { Injectable } from '@nestjs/common';
import { RustCompositorService } from './rust-compositor.service';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as path from 'path';
const execAsync = promisify(exec);
@Injectable()
export class VideoService {
constructor(private readonly compositor: RustCompositorService) {}
async renderVideo(frameCount: number, getLayersForFrame: (i: number) => any[]) {
const outputDir = `/tmp/render-${Date.now()}`;
// 1. Render all frames
console.log(`Rendering ${frameCount} frames...`);
for (let i = 0; i < frameCount; i++) {
const layers = getLayersForFrame(i);
const framePath = path.join(outputDir, `frame-${i.toString().padStart(4, '0')}.png`);
await this.compositor.composeFrame(layers, framePath);
if (i % 30 === 0) {
console.log(`Progress: ${Math.floor((i / frameCount) * 100)}%`);
}
}
// 2. Encode with FFmpeg
console.log('Encoding video...');
const videoPath = path.join(outputDir, 'output.mp4');
await execAsync(`
ffmpeg -framerate 30 \
-i ${outputDir}/frame-%04d.png \
-c:v libx264 \
-preset medium \
-crf 23 \
-pix_fmt yuv420p \
${videoPath}
`);
console.log('✅ Video complete!');
return videoPath;
}
}
Use it:
@Post('render-video')
async renderVideo() {
const videoPath = await this.videoService.renderVideo(
300, // 10 seconds at 30fps
(i) => {
// Animate position
const x = (i * 5) % 1920;
return [
{ type: 'solidColor', color: [30, 30, 30, 255], transform: { x: 0, y: 0, scale_x: 1, scale_y: 1, opacity: 1 }, z_index: 0 },
{ type: 'solidColor', color: [255, 50, 50, 255], transform: { x, y: 500, scale_x: 0.3, scale_y: 0.3, opacity: 0.9 }, z_index: 1 },
];
}
);
return { videoPath };
}
Step 6: Performance Benchmarking
Compare Rust vs Node.js performance:
Node.js Benchmark
cd benchmark/compositor-comparison
pnpm install
pnpm bench:node
Rust Benchmark
cd packages/sui-video-renderer-rust
cargo bench
open target/criterion/report/index.html
Expected results:
- 10-50x faster than Node.js
- 50-70% less memory
Common Use Cases
Use Case 1: Social Media Graphics
let layers = vec![
Layer::image("template.jpg", Transform::new()),
Layer::text(
"Your Text Here".to_string(),
64.0,
Color::white(),
Transform::new().with_position(100.0, 100.0)
),
Layer::image("logo.png", Transform::new().with_position(1700.0, 50.0).with_scale(0.2)),
];
Use Case 2: Video Overlays
let layers = vec![
Layer::image("video_frame.jpg", Transform::new()),
Layer::solid_color(
Color::new(0, 0, 0, 128), // Semi-transparent black
Transform::new().with_scale_xy(1.0, 0.2).with_position(0.0, 900.0)
),
Layer::text(
"Live Stream".to_string(),
48.0,
Color::white(),
Transform::new().with_position(50.0, 920.0)
),
];
Use Case 3: Thumbnail Generation
let layers = vec![
Layer::image("video_still.jpg", Transform::new()),
Layer::solid_color(
Color::new(0, 0, 0, 180),
Transform::new().with_position(0.0, 800.0).with_scale_xy(1.0, 0.26)
),
Layer::text(
"Click to Play ▶".to_string(),
72.0,
Color::white(),
Transform::new().with_position(50.0, 850.0)
),
];
Troubleshooting
WASM module won't load
Problem: "WebAssembly module cannot be instantiated"
Solution: Ensure webpack config includes:
config.experiments = {
asyncWebAssembly: true,
};
Rust CLI not found
Problem: "spawn ENOENT"
Solution: Build the CLI first:
cd packages/sui-video-renderer-rust/cli
cargo build --release
Poor performance
Problem: "Rendering is slow"
Solution:
- Use
cargo build --release(not debug mode) - Process frames in parallel
- Consider using native module instead of CLI subprocess
Next Steps
Explore Features
- Rust Backend Examples - Advanced composition techniques
- WASM Frontend - Interactive browser demos
- Node.js Integration - Production deployment patterns
Learn More
- API Reference - Complete API documentation
- Architecture Guide - System design details
- Performance Benchmarks - Detailed performance data
Community
Get Help
If you run into issues:
- Check the Troubleshooting section
- Search existing GitHub Issues
- Ask on Discord
- Create a new GitHub Issue
Congratulations! 🎉 You've successfully set up the Stoked UI Video Renderer. You're now ready to build high-performance video applications.