Overview
The VideoEditor component provides a powerful video editing interface with timeline, effects, text overlays, transitions, and export capabilities.
Import
'use client';
import dynamic from 'next/dynamic';
const VideoEditor = dynamic(
() => import('@distralabs/media-editor').then(mod => ({ default: mod.VideoEditor })),
{ ssr: false }
);
Always use dynamic import with ssr: false to avoid server-side rendering issues.
Prerequisites
CRITICAL: VideoEditor requires WASM files to be copied to your public/ folder. See Installation Guide.
Required WASM Files
public/
├── MediaInfoModule.wasm (2.3 MB)
├── worker.js
├── const.js
├── errors.js
├── decode_worker.js
├── encode_worker.js
└── umd/
└── ffmpeg-core.wasm (31 MB)
URL Rewrites
Add to next.config.js:
async rewrites() {
return [
{
source: '/studio/:path(MediaInfoModule.wasm|worker.js|const.js|errors.js|decode_worker.js|encode_worker.js)',
destination: '/:path',
},
{
source: '/studio/umd/:path*',
destination: '/umd/:path*',
},
];
}
Props
Required Props
Your SDK license key (JWT format). Contact your account manager to obtain a license key.licenseKey="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Callback function called when the user closes the editor.onClose={() => setShowEditor(false)}
Optional Props
Override the default license validation API endpoint.Default: https://api.kloudleads.com/license/validateapiUrl="https://localhost:3030/social"
Initial video file to load into the editor.const [videoFile, setVideoFile] = useState<File | null>(null);
<VideoEditor
defaultVideo={videoFile}
// ...
/>
Supported Formats: MP4, WebM, MOV, AVI
onExport
(result: EditorCallback) => void
Callback function called when the user exports the edited video.EditorCallback:interface EditorCallback {
videoUrl?: string; // Blob URL of exported video
base64?: string; // Data URL (for images)
thumbnail?: string; // Video thumbnail
duration?: number; // Video duration in seconds
width?: number; // Video width
height?: number; // Video height
fps?: number; // Frames per second
}
Example:const handleExport = (result: EditorCallback) => {
if (result.videoUrl) {
// Download the video
const link = document.createElement('a');
link.href = result.videoUrl;
link.download = 'edited-video.mp4';
link.click();
// Or upload to server
fetch(result.videoUrl)
.then(res => res.blob())
.then(blob => {
const formData = new FormData();
formData.append('video', blob, 'video.mp4');
return fetch('/api/upload', {
method: 'POST',
body: formData,
});
});
}
};
Custom theme object to override default colors and styling.const customTheme = {
'background.primary': '#0f172a',
'background.secondary': '#1e293b',
'text.primary': '#ffffff',
'accent.primary': '#3b82f6',
};
<VideoEditor theme={customTheme} />
See Theme Customization for all available keys.
Show theme customization UI to users. Set to false for production.showThemeCreator={false} // Hide theme UI
Array of brand presets for consistent styling.interface BrandDetails {
id: string;
name: string;
primaryColor?: string;
secondaryColor?: string;
fontFamily?: string;
logo?: string;
}
const brands = [
{
id: 'brand-1',
name: 'My Brand',
primaryColor: '#3b82f6',
fontFamily: 'Inter',
},
];
<VideoEditor brands={brands} />
Load a template on editor startup.interface Template {
id?: string;
name?: string;
sceneData: EditorItem[];
width: number;
height: number;
thumbnail?: string;
brandId?: string;
}
<VideoEditor defaultTemplate={myTemplate} />
onSaveTemplate
(props: { brandId: string; template: Template }) => Promise<void>
Callback when user saves a template.const handleSaveTemplate = async ({ brandId, template }) => {
await fetch('/api/templates', {
method: 'POST',
body: JSON.stringify({ brandId, template }),
});
};
onGetTemplates
(brandIdList: string[]) => Promise<{ success: { data: Template[] } }>
Callback to fetch templates for display.const handleGetTemplates = async (brandIds) => {
const response = await fetch('/api/templates?' + new URLSearchParams({
brandIds: brandIds?.join(',') || ''
}));
return response.json();
};
AI-powered content creation function (advanced feature).
Complete Example
'use client';
import { useState, useCallback, useRef } from 'react';
import dynamic from 'next/dynamic';
const VideoEditor = dynamic(
() => import('@distralabs/media-editor').then(mod => ({ default: mod.VideoEditor })),
{ ssr: false }
);
const customTheme = {
'background.primary': '#0f172a',
'background.secondary': '#1e293b',
'text.primary': '#ffffff',
'accent.primary': '#3b82f6',
};
export default function VideoStudioPage() {
const fileInputRef = useRef<HTMLInputElement>(null);
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [showEditor, setShowEditor] = useState(false);
const [isExporting, setIsExporting] = useState(false);
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file && file.type.startsWith('video/')) {
setSelectedFile(file);
setShowEditor(true);
}
};
const handleExport = useCallback((result: any) => {
setIsExporting(false);
if (result.videoUrl) {
// Download
const link = document.createElement('a');
link.href = result.videoUrl;
link.download = `edited-${Date.now()}.mp4`;
link.click();
setShowEditor(false);
}
}, []);
const handleClose = () => {
setShowEditor(false);
setSelectedFile(null);
};
return (
<div className="min-h-screen p-6">
{!showEditor && (
<div>
<h1 className="text-3xl font-bold mb-4">Video Studio</h1>
<input
ref={fileInputRef}
type="file"
accept="video/*"
onChange={handleFileSelect}
className="hidden"
/>
<button
onClick={() => fileInputRef.current?.click()}
className="px-6 py-3 bg-blue-500 text-white rounded-lg"
>
Choose Video
</button>
</div>
)}
{showEditor && selectedFile && (
<div className="fixed inset-0 z-50">
<VideoEditor
licenseKey={process.env.NEXT_PUBLIC_LICENSE_KEY || ''}
apiUrl="https://localhost:3030/social"
defaultVideo={selectedFile}
onClose={handleClose}
onExport={handleExport}
theme={customTheme}
showThemeCreator={false}
/>
</div>
)}
{/* Export Loading Overlay */}
{isExporting && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/90">
<div className="text-center">
<div className="animate-spin rounded-full h-16 w-16 border-4 border-blue-500 border-t-transparent mx-auto" />
<p className="text-white mt-4 text-xl">Exporting video...</p>
<p className="text-gray-400 text-sm">This may take 1-2 minutes</p>
</div>
</div>
)}
</div>
);
}
Features
Video Editing
- Upload & Load: MP4, WebM, MOV, AVI formats
- Trim: Cut video start and end times
- Split: Cut video into multiple clips
- Arrange: Drag-and-drop clips on timeline
- Speed: Adjust playback speed (0.25x - 4x)
- Volume: Audio volume control and mute
Timeline
- Multi-track: Video, audio, text, subtitle tracks
- Zoom: Timeline zoom in/out
- Scrubbing: Frame-accurate seeking
- Snap: Snap-to-grid for precise alignment
- Markers: Add markers for reference points
Text & Graphics
- Text Overlays: Add text with custom fonts, colors, sizes
- Shapes: Rectangles, circles, arrows, custom shapes
- Stickers: GIF stickers and emojis
- Images: Overlay images on video
Effects & Filters
- Color Filters: Grayscale, Sepia, Invert, Colorize
- Adjustments: Brightness, Contrast, Saturation
- Blur: Gaussian blur with adjustable radius
- Transitions: Fade in/out, crossfade (coming soon)
Audio
- Audio Tracks: Multiple audio tracks
- Background Music: Add music files
- Volume Control: Per-track volume adjustment
- Mute: Mute original video audio
Subtitles
- SRT Support: Import .srt subtitle files
- Styling: Font, color, size, position
- Word Highlighting: Highlight current word (karaoke style)
- Background: Subtitle background with opacity
Export
- Format: MP4 (H.264 video, AAC audio)
- Quality: Adjustable bitrate and resolution
- Frame Rate: Original or custom FPS
- Audio: Include/exclude audio tracks
Video processing is CPU-intensive and happens in the browser using WebAssembly (FFmpeg).
Typical Export Times
| Video Quality | Duration | Export Time |
| 720p | 10s | ~30 seconds |
| 1080p | 30s | ~2 minutes |
| 1080p | 60s | ~4 minutes |
| 4K | Any | Not recommended |
Recommendations
- Show Progress Indicator: Always display a loading overlay during export
- Warn Users: Inform users about processing time upfront
- Limit Duration: Recommend videos under 60 seconds for browser processing
- Lower Resolution: Suggest 720p for faster exports
- Desktop Only: Mobile browsers not supported for video editing
- Server-Side Alternative: Consider server-side processing for production apps
Mobile Support
Mobile browsers have limited support due to memory constraints and missing WebAssembly features. Desktop browsers strongly recommended.
Browser Support
| Browser | Version | Status |
| Chrome | 90+ | ✅ Full Support |
| Firefox | 88+ | ✅ Full Support |
| Safari | 14+ | ✅ Full Support |
| Edge | 90+ | ✅ Full Support |
| Mobile | Any | ❌ Not Supported |
Troubleshooting
WASM Files Not Loading
Error: MediaInfoModule.wasm 404 Not Found
Solution:
- Copy WASM files to
public/ folder
- Add URL rewrites to
next.config.js
- Restart dev server:
rm -rf .next && npm run dev
See FAQ: WASM Loading Failures
Audio Not Playing
Cause: Browser autoplay policies require user interaction.
Solution: Ensure editor is opened via user click/gesture, not programmatically on page load.
Export Fails or Hangs
Causes:
- Video too long (>2 minutes)
- Resolution too high (4K)
- Insufficient memory
Solutions:
- Use shorter clips
- Lower resolution to 720p
- Close other browser tabs
- Increase Node.js memory:
NODE_OPTIONS=--max-old-space-size=4096
Next Steps