ems-editor
v1.2.6
Published
EMS Video Editor SDK - Universal JavaScript SDK for external video editor integration
Maintainers
Readme
EMS Editor SDK
A powerful JavaScript SDK for integrating with the EMS Video Editor through WebSocket communication. Provides real-time workspace loading, template importing, render management, and MAM integration.
Features
- 🚀 Real-time WebSocket Communication - Bidirectional communication with the video editor
- 📥 Workspace Loading - Load workspaces from server with progress tracking
- 📋 Template Import - Import video templates and assets dynamically
- 🎬 Render Management - Start renders and track progress in real-time
- ⚡ Quick Render - One-click rendering of current workspace with minimal setup
- 📊 Render Job Tracking - Monitor render job status and progress by job ID
- 📤 MAM Integration - Upload rendered videos to Media Asset Management systems
- 🎨 Theme Customization - Apply custom themes and branding
- 🔄 Template Change Tracking - Monitor template modifications with dirty flag system and real-time notifications
Installation
npm install ems-editorQuick Start
Basic Setup
import EMSEditor from 'ems-editor';
const editor = new EMSEditor({
serverUrl: 'http://localhost:5200',
sessionId: 'your-session-id' // Optional
});
// Set iframe reference
const iframe = document.getElementById('editor-iframe');
editor.setIframe(iframe);
// Wait for editor to be ready
editor.once('ready', () => {
console.log('Editor is ready!');
});Load Workspace from Server
// Load workspace with progress tracking
const result = await editor.loadWorkspaceFromServer({
workspaceId: 'workspace-uuid-123',
mergeMode: 'replace',
switchToWorkspace: true
}, {
onProgress: (progress) => {
console.log(`${progress.stage}: ${progress.progress}% - ${progress.message}`);
},
onComplete: (result) => {
console.log('✅ Workspace loaded:', result.workspaceName);
},
onError: (error) => {
console.error('❌ Loading failed:', error);
}
});Template Change Tracking
The SDK provides built-in change tracking to monitor when templates have been modified after being imported from external sources. This is useful for showing "unsaved changes" indicators and preventing data loss.
// Check current dirty status
if (editor.isDirty()) {
console.log('⚠️ Template has been modified since import');
} else {
console.log('✅ Template is clean (no modifications)');
}
// Listen for real-time status changes (throttled to once per second)
editor.on('templateStatusChanged', (status) => {
const { isDirty, templateId, templateName, timestamp } = status;
if (isDirty) {
console.log(`🔄 Template "${templateName}" has unsaved changes`);
// Show save prompt, enable save button, add * to title, etc.
updateUI({ showUnsavedIndicator: true });
} else {
console.log(`✅ Template "${templateName}" is saved`);
// Remove save indicators, disable save button, etc.
updateUI({ showUnsavedIndicator: false });
}
});
// Example UI integration
function createSaveButton() {
const button = document.createElement('button');
const updateButton = () => {
if (editor.isDirty()) {
button.textContent = 'Save Changes *';
button.disabled = false;
button.classList.add('has-changes');
} else {
button.textContent = 'Saved';
button.disabled = true;
button.classList.remove('has-changes');
}
};
// Initial state
updateButton();
// Update on changes
editor.on('templateStatusChanged', updateButton);
return button;
}Key Features:
- 🔄 Real-time tracking - Automatically detects when users modify templates in the editor
- ⚡ Throttled notifications - WebSocket messages are throttled to once per second to prevent spam
- 🎯 Accurate state - Distinguishes between templates imported from external sources vs. user modifications
- 🧹 Auto-reset - Dirty flag automatically resets when new templates are imported
Quick Render Current Workspace
// Quick render with default settings (MP4, H.264)
const renderJob = await editor.quickRender();
if (renderJob.success) {
// Track the render progress
const finalResult = await editor.waitForRenderComplete(renderJob.jobId, {
onProgress: (result) => {
console.log(`🎬 ${result.status}: ${result.progress || 0}%`);
}
});
if (finalResult.status === 'completed') {
console.log('✅ Video ready:', finalResult.videoUrl);
}
} else {
console.error('❌ Quick render failed:', renderJob.error);
}Track Render Job by ID
// Get current status of a render job
const jobStatus = await editor.getRenderResult('job-abc-123');
console.log('Job Status:', jobStatus.status, jobStatus.progress + '%');
// Or wait for completion with polling
const completed = await editor.waitForRenderComplete('job-abc-123');
console.log('Completed:', completed.videoUrl);API Reference
Methods
Template and Asset Management
importTemplate(templateData: any): Promise<ImportFromAssetsResult>
Import a video template into the editor.
// Import a template
const result = await editor.importTemplate({
name: 'My Video Template',
tracks: [...],
assets: {...},
settings: {
width: 1920,
height: 1080,
fps: 30,
duration: 600
}
});
console.log('Template imported:', result.templateId);importFromAssets(assets: AssetInfo[], options?): Promise<ImportFromAssetsResult>
Generate and import a template from an array of media assets.
// Import from assets
const assets = [
{
id: 'video1',
name: 'Intro Video',
type: 'video',
fileUrl: 'https://example.com/video.mp4',
duration: 5000,
width: 1920,
height: 1080
},
{
id: 'audio1',
name: 'Background Music',
type: 'audio',
fileUrl: 'https://example.com/music.mp3',
duration: 30000
}
];
const result = await editor.importFromAssets(assets, {
name: 'Auto-Generated Video',
width: 1920,
height: 1080,
fps: 30
});
console.log('Assets imported:', result.templateName);Workspace Management
createWorkspace(options?): Promise<{workspaceId: string, success: boolean}>
Create a new workspace/tab in the editor.
// Create a new workspace
const result = await editor.createWorkspace({
name: 'New Project',
description: 'My new video project',
width: 1920,
height: 1080,
fps: 30,
duration: 450,
quality: 'high'
});
console.log('Workspace created:', result.workspaceId);getWorkspacesList(): Promise<any[]>
Get list of all open workspaces.
// Get all workspaces
const workspaces = await editor.getWorkspacesList();
workspaces.forEach(workspace => {
console.log(`Workspace: ${workspace.name} (${workspace.id})`);
});renameWorkspace(workspaceId: string, newName: string): Promise<{success: boolean}>
Rename an existing workspace.
// Rename a workspace
await editor.renameWorkspace('workspace-123', 'Updated Project Name');clearWorkspaces(): Promise<void>
Clear all workspaces/tabs in the editor.
// Clear all workspaces
await editor.clearWorkspaces();
console.log('All workspaces cleared');Video Rendering
startRender(renderOptions: RenderOptions): Promise<void>
Start the video rendering process.
// Start render with basic options
await editor.startRender({
title: 'My Video',
color: '#FF5733',
duration: 300,
fps: 30,
width: 1920,
height: 1080,
format: 'mp4',
quality: 'high'
});
// Listen for render progress
editor.on('renderProgress', (progress) => {
console.log(`Render progress: ${progress.progress}%`);
});
editor.on('renderComplete', (result) => {
console.log('Video rendered:', result.videoUrl);
});startRenderWithMAMUpload(options: RenderOptions, callbacks?: MAMUploadCallbacks): Promise<StartRenderWithMAMUploadResult>
Start rendering with MAM (Media Asset Management) upload tracking.
// Start render with MAM upload
const result = await editor.startRenderWithMAMUpload({
title: 'Corporate Video',
format: 'mp4',
quality: 'ultra',
uploadToMAM: true,
mamToken: 'your-mam-token',
mamBaseUrl: 'https://your-mam-system.com/api',
mamProjectId: 12345,
mamBinId: 67890
}, {
onProgress: (progress) => {
console.log(`MAM Upload: ${progress.stage} - ${progress.progress}%`);
},
onStageChange: (stage, data) => {
console.log(`Stage changed to: ${stage}`);
},
onComplete: (result) => {
console.log('Uploaded to MAM:', result.assetId);
},
onError: (error) => {
console.error('MAM upload failed:', error);
}
});
console.log('MAM upload tracking ID:', result.tracker.trackingId);trackMAMUpload(jobId: string, callbacks?: MAMUploadCallbacks): MAMUploadTracker
Track MAM upload progress for a specific job.
// Track existing MAM upload
const tracker = editor.trackMAMUpload('job-123', {
onProgress: (progress) => {
console.log(`Upload progress: ${progress.progress}%`);
},
onComplete: (result) => {
console.log('MAM upload completed:', result.assetUrl);
}
});
// Stop tracking when done
setTimeout(() => {
tracker.stop();
}, 60000);quickRender(options?: QuickRenderOptions): Promise<QuickRenderResult>
Start a quick render of the current workspace with minimal configuration. Uses the current workspace template with optional parameter overrides.
// Quick render with defaults (MP4, H.264)
const result = await editor.quickRender();
// Quick render with custom options
const result = await editor.quickRender({
title: 'My Quick Video',
format: 'mp4',
codec: 'h264',
fps: 30
});
console.log('Quick render started:', result.jobId);
// Check if successful
if (result.success) {
console.log('Job ID:', result.jobId);
} else {
console.error('Error:', result.error);
}getRenderResult(jobId: string): Promise<RenderResult>
Get the current status and result of a render job by its job ID.
// Get current job status
const result = await editor.getRenderResult('job-abc-123');
console.log('Job Status:', {
jobId: result.jobId,
status: result.status, // 'queued', 'in-progress', 'completed', 'failed'
progress: result.progress, // Progress percentage (0-100)
videoUrl: result.videoUrl, // Available when completed
mamAssetId: result.mamAssetId // MAM asset ID if uploaded
});waitForRenderComplete(jobId: string, options?): Promise<RenderResult>
Wait for a render job to complete with automatic polling and progress tracking.
// Wait for render completion with progress callbacks
const finalResult = await editor.waitForRenderComplete('job-abc-123', {
onProgress: (result) => {
console.log(`Progress: ${result.status} ${result.progress || 0}%`);
},
pollInterval: 2000, // Poll every 2 seconds
timeout: 300000 // 5 minute timeout
});
// Result when completed
if (finalResult.status === 'completed') {
console.log('✅ Video ready:', finalResult.videoUrl);
} else {
console.error('❌ Render failed:', finalResult.status);
}
// Advanced usage with detailed progress tracking
try {
const result = await editor.waitForRenderComplete(jobId, {
onProgress: (result) => {
switch (result.status) {
case 'queued':
console.log('⏳ Job queued...');
break;
case 'in-progress':
console.log(`🎬 Rendering: ${result.progress || 0}%`);
break;
}
},
pollInterval: 1500,
timeout: 600000 // 10 minutes
});
console.log('Final result:', result);
} catch (error) {
console.error('Render failed or timed out:', error.message);
}Theme and UI Customization
setTheme(theme: 'light' | 'dark'): Promise<void>
Set the editor theme.
// Set dark theme
await editor.setTheme('dark');
// Set light theme
await editor.setTheme('light');updateTheme(themeId?: string, customTheme?: any): Promise<void>
Update theme with preset or custom theme.
// Use preset theme
await editor.updateTheme('professional-dark');
// Use custom theme object
await editor.updateTheme(null, {
name: 'Custom Theme',
colors: {
primary: '#007cba',
secondary: '#6c757d',
background: '#f8f9fa'
}
});customizeTheme(themeConfig): Promise<void>
Customize theme with specific colors.
// Customize theme colors
await editor.customizeTheme({
primary: '#007cba',
secondary: '#6c757d',
background: '#ffffff',
accent: '#17a2b8'
});MAM Integration
loginToMAM(mamConfig: any): Promise<void>
Login to MAM system.
// Login to MAM
await editor.loginToMAM({
apiUrl: 'https://your-mam-system.com/api',
tokens: {
accessToken: 'your-access-token',
refreshToken: 'your-refresh-token'
},
selectedProject: {
id_project: 12345,
name: 'My Project'
}
});
console.log('Logged into MAM system');logoutFromMAM(): Promise<void>
Logout from MAM system.
// Logout from MAM
await editor.logoutFromMAM();
console.log('Logged out from MAM');Events
The SDK extends EventEmitter and emits the following events:
Connection Events
// WebSocket connected
editor.on('connected', () => {
console.log('Connected to server');
});
// Editor ready for operations
editor.on('ready', () => {
console.log('Editor is ready');
});
// WebSocket disconnected
editor.on('disconnected', () => {
console.log('Disconnected from server');
});
// Connection error
editor.on('error', (error) => {
console.error('Connection error:', error);
});Workspace Events
// Workspace loading progress
editor.on('workspaceLoadingProgress', (progress) => {
console.log(`Loading: ${progress.stage} - ${progress.progress}%`);
});
// Workspace loaded successfully
editor.on('workspaceLoaded', (result) => {
console.log('Workspace loaded:', result.workspaceName);
});
// Workspace loading failed
editor.on('workspaceLoadingError', (error) => {
console.error('Loading failed:', error.error);
});
// Workspaces cleared
editor.on('workspacesCleared', () => {
console.log('All workspaces cleared');
});
// Workspace created
editor.on('workspaceCreated', (result) => {
console.log('Workspace created:', result.workspaceId);
});
// Workspace renamed
editor.on('workspaceRenamed', (result) => {
console.log('Workspace renamed:', result.newName);
});Template Events
// Template imported
editor.on('templateImported', (result) => {
console.log('Template imported:', result.templateName);
});
// Template added to workspace
editor.on('templateAddedToWorkspace', (result) => {
console.log('Template added to workspace:', result.workspaceId);
});
// Template modification status changed
editor.on('templateStatusChanged', (status) => {
console.log('Template dirty status:', status.isDirty);
console.log('Template ID:', status.templateId);
console.log('Template name:', status.templateName);
console.log('Changed at:', status.timestamp);
// Update UI based on dirty status
if (status.isDirty) {
console.log('⚠️ Template has unsaved changes');
// Show save indicators, enable save buttons, etc.
} else {
console.log('✅ Template is clean');
// Hide save indicators, disable save buttons, etc.
}
});Render Events
// Render progress updates
editor.on('renderProgress', (progress) => {
console.log(`Rendering: ${progress.progress}%`);
});
// Render completed
editor.on('renderComplete', (result) => {
console.log('Render completed:', result.videoUrl);
});
// Quick render events
editor.on('quickRenderStarted', (result) => {
console.log('Quick render started:', result.jobId);
});
editor.on('quickRenderCompleted', (result) => {
console.log('Quick render completed:', result.jobId);
});
// Pipeline events (detailed render stages)
editor.on('pipelineEvent', (event) => {
console.log(`Pipeline: ${event.stage} - ${event.message}`);
});MAM Events
// MAM login status
editor.on('mamStatus', (status) => {
console.log('MAM status:', status.connected ? 'Connected' : 'Disconnected');
});
// MAM upload events
editor.on('mamUploadEvent', (event) => {
console.log(`MAM Upload: ${event.stage} - ${event.progress}%`);
});
// MAM upload completed
editor.on('mamUploadComplete', (result) => {
console.log('MAM upload completed:', result.assetId);
});
// Specific MAM upload stages
editor.on('mamUploadPreparing', (data) => console.log('Preparing upload...'));
editor.on('mamUploadAnalyzing', (data) => console.log('Analyzing media...'));
editor.on('mamUploadConverting', (data) => console.log('Converting format...'));
editor.on('mamUploadUploading', (data) => console.log('Uploading to MAM...'));
editor.on('mamUploadCompleting', (data) => console.log('Finalizing...'));
editor.on('mamUploadCompleted', (data) => console.log('Upload complete!'));
editor.on('mamUploadFailed', (data) => console.log('Upload failed:', data.error));Theme Events
// Theme changed
editor.on('themeChanged', (theme) => {
console.log('Theme changed to:', theme.name);
});
// Theme updated
editor.on('themeUpdate', (themeData) => {
console.log('Theme updated:', themeData);
});
// Custom theme applied
editor.on('customTheme', (theme) => {
console.log('Custom theme applied:', theme.name);
});Editor Events
// Editor ready (from iframe)
editor.on('editorReady', () => {
console.log('Editor iframe is ready');
});
// Editor disconnected
editor.on('editorDisconnected', () => {
console.log('Editor disconnected');
});
// Workspaces list received
editor.on('workspacesListReceived', (workspaces) => {
console.log('Received workspaces list:', workspaces.length);
});Utility Methods
isConnected(): boolean
Check if WebSocket is connected.
if (editor.isConnected()) {
console.log('WebSocket is connected');
}isReady(): boolean
Check if editor is ready for operations.
if (editor.isReady()) {
console.log('Editor is ready');
}getSessionId(): string
Get the current session ID.
const sessionId = editor.getSessionId();
console.log('Session ID:', sessionId);isDirty(): boolean
Check if the current template/workspace has unsaved changes. Returns true if the template has been modified since it was last imported from an external source.
// Check if template has been modified
if (editor.isDirty()) {
console.log('Template has unsaved changes');
// Show save prompt or disable certain actions
} else {
console.log('Template is clean (no changes since import)');
}
// Example usage in UI
const saveButton = document.getElementById('save-btn');
const updateSaveButton = () => {
if (editor.isDirty()) {
saveButton.textContent = 'Save Changes*';
saveButton.disabled = false;
} else {
saveButton.textContent = 'Saved';
saveButton.disabled = true;
}
};
// Update save button when template status changes
editor.on('templateStatusChanged', updateSaveButton);destroy(): void
Clean up resources and disconnect.
// Clean up when done
editor.destroy();Examples
See the ../../examples/ directory for complete examples:
simple-workspace-loading.html- Basic usageworkspace-loading-example.html- Advanced usage with UInodejs-client-sdk-example.js- Node.js integration
Documentation
For detailed documentation, see:
License
MIT License
