ems-editor
Version:
EMS Video Editor SDK - Universal JavaScript SDK for external video editor integration
794 lines (625 loc) โข 19.9 kB
Markdown
# 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
```bash
npm install ems-editor
```
## Quick Start
### Basic Setup
```javascript
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
```javascript
// 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.
```javascript
// 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
```javascript
// 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
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// Rename a workspace
await editor.renameWorkspace('workspace-123', 'Updated Project Name');
```
##### `clearWorkspaces(): Promise<void>`
Clear all workspaces/tabs in the editor.
```javascript
// Clear all workspaces
await editor.clearWorkspaces();
console.log('All workspaces cleared');
```
#### Video Rendering
##### `startRender(renderOptions: RenderOptions): Promise<void>`
Start the video rendering process.
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// 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.
```javascript
// Logout from MAM
await editor.logoutFromMAM();
console.log('Logged out from MAM');
```
### Events
The SDK extends EventEmitter and emits the following events:
#### Connection Events
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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.
```javascript
if (editor.isConnected()) {
console.log('WebSocket is connected');
}
```
##### `isReady(): boolean`
Check if editor is ready for operations.
```javascript
if (editor.isReady()) {
console.log('Editor is ready');
}
```
##### `getSessionId(): string`
Get the current session ID.
```javascript
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.
```javascript
// 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.
```javascript
// Clean up when done
editor.destroy();
```
## Examples
See the `../../examples/` directory for complete examples:
- `simple-workspace-loading.html` - Basic usage
- `workspace-loading-example.html` - Advanced usage with UI
- `nodejs-client-sdk-example.js` - Node.js integration
## Documentation
For detailed documentation, see:
- [Workspace Loading Guide](../../docs/WORKSPACE_LOADING_GUIDE.md)
- [WebSocket API Reference](../../docs/WEBSOCKET_API_REFERENCE.md)
## License
MIT License