ems-editor
Version:
EMS Video Editor SDK - Universal JavaScript SDK for external video editor integration
1,881 lines (1,586 loc) • 49.6 kB
Markdown
# EMS Editor SDK - Simple & Reliable
A straightforward JavaScript SDK for controlling the EMS video editor from external applications.
## Quick Start
### 1. Install
```bash
npm install ems-editor-sdk
```
### 2. Basic Usage
```javascript
import EMSEditor from 'ems-editor-sdk';
// Initialize SDK
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
// Wait for editor to be ready
editor.on('ready', () => {
console.log('✅ Editor is ready!');
// Now you can control the editor
editor.setTheme('dark');
editor.loginToMAM(mamConfig);
editor.importTemplate(templateData);
// Or auto-generate from assets
editor.importFromAssets([
{
id: 1,
name: 'video',
type: 'video',
fileUrl: 'https://example.com/video.mp4',
duration: 5000
}
]);
});
// Set up iframe
const iframe = document.getElementById('editor-iframe');
editor.setIframe(iframe);
```
## Minimal Examples
### MAM Login Only
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
const iframe = document.getElementById('editor-iframe');
editor.setIframe(iframe);
editor.on('ready', async () => {
// MAM login with minimal config
await editor.loginToMAM({
apiUrl: 'https://your-mam-server.com',
tokens: {
accessToken: 'your-access-token'
},
selectedProject: {
id_project: 123,
name: 'My Project'
}
});
console.log('✅ MAM login complete');
});
```
### Import Template Only
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
const iframe = document.getElementById('editor-iframe');
editor.setIframe(iframe);
editor.on('ready', async () => {
// Import template with minimal data
await editor.importTemplate({
name: 'Simple Template',
settings: {
width: 1920,
height: 1080,
fps: 30
},
elements: [
{
id: 'title',
type: 'text',
content: 'Hello World!'
}
]
});
console.log('✅ Template imported');
});
```
### Import From Assets - Minimal
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
const iframe = document.getElementById('editor-iframe');
editor.setIframe(iframe);
editor.on('ready', async () => {
// Auto-generate template from assets
const assets = [
{
id: 2673,
name: 'my_video',
type: 'video',
fileUrl: 'https://example.com/video.mp4',
proxyUrl: 'https://example.com/proxy.mp4',
width: 1920,
height: 1080,
duration: 5000 // 5 seconds in milliseconds
}
];
await editor.importFromAssets(assets);
console.log('✅ Template auto-generated and imported');
});
```
### MAM + Template (Complete Flow)
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
const iframe = document.getElementById('editor-iframe');
editor.setIframe(iframe);
editor.on('ready', async () => {
try {
// 1. Login to MAM
await editor.loginToMAM({
apiUrl: 'https://your-mam-server.com',
tokens: { accessToken: 'your-token' },
selectedProject: { id_project: 123, name: 'Project' }
});
// 2. Import template
await editor.importTemplate({
name: 'My Template',
settings: { width: 1920, height: 1080, fps: 30 },
elements: [{ id: 'title', type: 'text', content: 'Video Title' }]
});
// 3. Start render
await editor.startRender({
title: 'My Video',
quality: 'high'
});
console.log('✅ Complete flow executed');
} catch (error) {
console.error('❌ Error:', error);
}
});
```
## TypeScript Interfaces
### Core Interfaces
```typescript
interface EMSEditorConfig {
serverUrl: string;
sessionId?: string;
}
interface AssetInfo {
id: string | number;
name: string;
type: 'video' | 'audio' | 'image';
fileUrl: string;
proxyUrl?: string;
width?: number;
height?: number;
duration?: number; // in milliseconds
mamAssetId?: string | number;
}
interface ImportOptions {
name?: string;
description?: string;
width?: number;
height?: number;
fps?: number;
backgroundColor?: string;
}
interface ImportFromAssetsResult {
success: boolean;
templateId: string;
workspaceId: string;
templateName: string;
tracksCount: number;
elementsCount: number;
timestamp: string;
error?: string;
}
interface TemplateSettings {
width: number;
height: number;
fps: number;
duration?: number; // in frames
backgroundColor?: string;
quality?: 'low' | 'medium' | 'high';
audioSampleRate?: number;
audioChannels?: number;
}
interface TemplateTrack {
id: string;
name: string;
type: 'video' | 'audio' | 'image' | 'text' | 'shape';
locked?: boolean;
visible?: boolean;
muted?: boolean;
volume?: number;
zIndex?: number;
elements: TemplateElement[];
}
interface TemplateElement {
id: string;
type: 'video' | 'audio' | 'image' | 'text' | 'shape';
name: string;
startFrame: number;
endFrame: number;
layerIndex?: number;
transform?: ElementTransform;
properties: Record<string, any>;
animations?: any[];
filters?: any[];
blendMode?: string;
opacity?: number;
}
interface ElementTransform {
x: number;
y: number;
width: number;
height: number;
rotation?: number;
scaleX?: number;
scaleY?: number;
skewX?: number;
skewY?: number;
originX?: number;
originY?: number;
}
interface Template {
id?: string;
name: string;
description?: string;
version?: string;
settings: TemplateSettings;
tracks: TemplateTrack[];
assets?: {
videos?: any[];
images?: any[];
audio?: any[];
fonts?: any[];
shapes?: any[];
templates?: any[];
};
animations?: any[];
transitions?: any[];
groups?: any[];
metadata?: TemplateMetadata;
}
interface TemplateMetadata {
author?: string;
createdAt?: string;
updatedAt?: string;
version?: string;
license?: string;
tags?: string[];
category?: string;
complexity?: 'beginner' | 'intermediate' | 'advanced';
description?: string;
requirements?: string[];
compatibility?: string[];
}
interface Workspace {
id: string;
name: string;
isActive: boolean;
template?: {
id?: string;
name: string;
description?: string;
settings: TemplateSettings;
tracksCount: number;
elementsCount: number;
} | null;
createdAt: string;
lastModified: string;
}
interface MAMConfig {
apiUrl: string;
tokens: {
accessToken: string;
refreshToken?: string;
accessTokenExpiration?: string;
systemTime?: string;
};
selectedProject: {
id_project: number;
name: string;
};
}
interface RenderOptions {
title?: string;
quality?: 'low' | 'medium' | 'high' | 'ultra';
format?: 'mp4' | 'webm' | 'mov' | 'mxf';
codec?: string;
width?: number;
height?: number;
fps?: number;
duration?: number;
compositionProps?: Record<string, any>;
uploadToMAM?: boolean;
mamToken?: string;
mamBaseUrl?: string;
mamProjectId?: number;
}
interface ThemeConfig {
primary?: string;
secondary?: string;
background?: string;
accent?: string;
}
interface CustomTheme {
id: string;
name: string;
primary: Record<string | number, string>;
secondary: Record<string | number, string>;
background: {
primary: string;
secondary: string;
tertiary: string;
accent: string;
};
text: {
primary: string;
secondary: string;
muted: string;
accent: string;
};
border: {
primary: string;
secondary: string;
accent: string;
};
shadows: {
sm: string;
md: string;
lg: string;
xl: string;
};
}
```
### Event Result Interfaces
```typescript
interface EventResult {
success: boolean;
timestamp: string;
error?: string;
}
interface MAMStatusResult extends EventResult {
status: 'connected' | 'disconnected' | 'error';
config?: MAMConfig;
}
interface ThemeChangedResult extends EventResult {
theme: 'light' | 'dark';
}
interface ThemeUpdateResult extends EventResult {
themeId?: string;
customTheme?: CustomTheme;
}
interface TemplateImportedResult extends EventResult {
workspaceId?: string;
templateId?: string;
templateName?: string;
}
interface WorkspacesClearedResult extends EventResult {
workspacesCleared: boolean;
clearedCount?: number;
}
interface WorkspacesListResult extends EventResult {
workspaces: Workspace[];
activeWorkspaceId?: string;
totalCount: number;
}
interface RenderProgressResult {
jobId: string;
progress: number;
stage: 'queued' | 'preprocessing' | 'in-progress' | 'uploading' | 'completed' | 'failed';
message: string;
timestamp: string;
error?: string;
estimatedTimeRemaining?: number;
currentStep?: string;
totalSteps?: number;
}
interface RenderCompleteResult {
jobId: string;
videoUrl: string;
mamAssetId?: number;
mamAssetUrl?: string;
mamAssetName?: string;
mamUploadSuccess?: boolean;
mamUploadError?: string;
renderTime?: number;
fileSize?: number;
format?: string;
width?: number;
height?: number;
duration?: number;
fps?: number;
}
interface MAMUploadEventResult {
jobId: string;
stage: 'preparing' | 'analyzing' | 'converting' | 'uploading' | 'completing' | 'completed' | 'failed';
progress: number;
message: string;
currentStep?: string;
totalSteps?: number;
currentStepIndex?: number;
fileInfo?: {
size: number;
sizeFormatted: string;
format: string;
duration?: number;
resolution?: string;
};
conversionInfo?: {
inputFormat: string;
outputFormat: string;
conversionProgress: number;
conversionSpeed?: string;
estimatedTimeRemaining?: string;
};
chunkProgress?: {
uploadedChunks: number;
totalChunks: number;
chunkSize: string;
uploadSpeed?: string;
estimatedTimeRemaining?: string;
};
binInfo?: {
binPath: string;
binId: number;
};
assetId?: number;
assetUrl?: string;
error?: string;
logs?: string[];
}
interface MAMUploadCompleteResult {
assetId: number;
assetName?: string;
assetUrl?: string;
jobId: string;
videoUrl: string;
uploadTime?: number;
fileSize?: number;
binPath?: string;
}
interface MAMUploadTracker {
trackingId: string;
stop: () => void;
}
interface StartRenderWithMAMUploadResult {
jobId?: string;
tracker: MAMUploadTracker;
}
```
### Method Return Types
```typescript
interface EMSEditorMethods {
// Connection Methods
isConnected(): boolean;
isReady(): boolean;
getSessionId(): string;
setIframe(iframe: HTMLIFrameElement): void;
destroy(): void;
// Authentication Methods
loginToMAM(mamConfig: MAMConfig): Promise<void>;
logoutFromMAM(): Promise<void>;
// Theme Methods
setTheme(theme: 'light' | 'dark'): Promise<void>;
updateTheme(themeId?: string, customTheme?: CustomTheme): Promise<void>;
customizeTheme(themeConfig: ThemeConfig): Promise<void>;
// Template Methods
importTemplate(templateData: Template): Promise<void>;
importFromAssets(assets: AssetInfo[], options?: ImportOptions): Promise<ImportFromAssetsResult>;
// Workspace Methods
clearWorkspaces(): Promise<void>;
getWorkspacesList(): Promise<Workspace[]>;
// Render Methods
startRender(renderOptions: RenderOptions): Promise<void>;
trackMAMUpload(jobId: string, callbacks?: {
onProgress?: (progress: MAMUploadEventResult) => void;
onStageChange?: (stage: string, data: MAMUploadEventResult) => void;
onComplete?: (result: MAMUploadCompleteResult) => void;
onError?: (error: string) => void;
}): MAMUploadTracker;
startRenderWithMAMUpload(
renderOptions: RenderOptions,
mamUploadCallbacks?: {
onProgress?: (progress: MAMUploadEventResult) => void;
onStageChange?: (stage: string, data: MAMUploadEventResult) => void;
onComplete?: (result: MAMUploadCompleteResult) => void;
onError?: (error: string) => void;
}
): Promise<StartRenderWithMAMUploadResult>;
// Event Methods
on(event: string, listener: Function): void;
once(event: string, listener: Function): void;
off(event: string, listener: Function): void;
}
```
### Usage Examples with Types
```typescript
import EMSEditor, {
EMSEditorConfig,
Template,
AssetInfo,
Workspace,
RenderOptions,
MAMConfig,
WorkspacesListResult,
RenderProgressResult
} from 'ems-editor-sdk';
// Initialize with typed config
const config: EMSEditorConfig = {
serverUrl: 'http://localhost:3000',
sessionId: 'my-session-123' // optional
};
const editor = new EMSEditor(config);
// Type-safe event handling
editor.on('ready', async () => {
// Get workspaces with typed result
const workspaces: Workspace[] = await editor.getWorkspacesList();
workspaces.forEach((workspace: Workspace) => {
console.log(`Workspace: ${workspace.name}`);
console.log(`Active: ${workspace.isActive}`);
console.log(`Tracks: ${workspace.template?.tracksCount || 0}`);
console.log(`Elements: ${workspace.template?.elementsCount || 0}`);
});
});
// Type-safe template import
const template: Template = {
name: 'My Template',
description: 'A sample template',
settings: {
width: 1920,
height: 1080,
fps: 30,
duration: 300,
backgroundColor: '#000000'
},
tracks: [
{
id: 'text-track',
name: 'Main Text',
type: 'text',
elements: [
{
id: 'title',
type: 'text',
name: 'Title Text',
startFrame: 0,
endFrame: 300,
properties: {
text: 'Hello World',
fontSize: 64,
color: '#ffffff'
}
}
]
}
]
};
await editor.importTemplate(template);
// Type-safe asset import with return result
const assets: AssetInfo[] = [
{
id: 'video-1',
name: 'Sample Video',
type: 'video',
fileUrl: 'https://example.com/video.mp4',
proxyUrl: 'https://example.com/video-proxy.mp4',
width: 1920,
height: 1080,
duration: 5000, // 5 seconds
mamAssetId: 12345
}
];
const result: ImportFromAssetsResult = await editor.importFromAssets(assets, {
name: 'Generated from Assets',
fps: 30,
backgroundColor: '#1a1a1a'
});
console.log(`Template created: ${result.templateId}`);
console.log(`Workspace: ${result.workspaceId}`);
console.log(`Tracks: ${result.tracksCount}, Elements: ${result.elementsCount}`);
// Type-safe render options
const renderOptions: RenderOptions = {
title: 'My Video',
quality: 'high',
format: 'mp4',
uploadToMAM: true,
mamToken: 'jwt-token',
mamBaseUrl: 'https://mam-server.com',
mamProjectId: 123
};
await editor.startRender(renderOptions);
// Type-safe event listeners with result types
editor.on('renderProgress', (progress: RenderProgressResult) => {
console.log(`Render ${progress.jobId}: ${progress.progress}% - ${progress.stage}`);
if (progress.estimatedTimeRemaining) {
console.log(`ETA: ${progress.estimatedTimeRemaining}ms`);
}
});
editor.on('workspacesListReceived', (result: WorkspacesListResult) => {
if (result.success) {
console.log(`Received ${result.totalCount} workspaces`);
result.workspaces.forEach(workspace => {
console.log(`- ${workspace.name} (${workspace.isActive ? 'active' : 'inactive'})`);
});
} else {
console.error('Failed to get workspaces:', result.error);
}
});
```
## API Reference
### Constructor
```typescript
const editor = new EMSEditor(config: EMSEditorConfig);
```
**Config Options:**
- `serverUrl` (string) - EMS Editor server URL
- `sessionId` (optional string) - Custom session ID
### Methods
#### `setIframe(iframe: HTMLIFrameElement)`
Sets the iframe element for the editor UI.
#### `loginToMAM(mamConfig: any): Promise<void>`
Login to MAM system with configuration.
#### `logoutFromMAM(): Promise<void>`
Logout from MAM system.
#### `setTheme(theme: 'light' | 'dark'): Promise<void>`
Set basic editor theme.
#### `updateTheme(themeId?: string, customTheme?: object): Promise<void>`
Apply preset theme by ID or custom theme object.
#### `customizeTheme(themeConfig: object): Promise<void>`
Customize theme with primary, secondary, background colors.
#### `importTemplate(templateData: any): Promise<void>`
Import a template into the editor.
#### `importFromAssets(assets: AssetInfo[], options?: ImportOptions): Promise<ImportFromAssetsResult>`
**NEW HELPER METHOD** - Auto-generate and import template from assets array. Handles video/audio track separation automatically. Returns template ID and metadata.
```javascript
// Usage with result
const result = await editor.importFromAssets([{
id: 1, name: 'video', type: 'video',
fileUrl: 'https://example.com/video.mp4', duration: 5000
}], {
name: 'My Auto Project',
fps: 30,
backgroundColor: '#1a1a1a'
});
console.log(`Template ID: ${result.templateId}`);
console.log(`Workspace ID: ${result.workspaceId}`);
```
#### `startRender(renderOptions: any): Promise<void>`
Start video rendering process.
#### `clearWorkspaces(): Promise<void>`
**NEW** Clear all open workspaces/tabs in the editor. Cannot be undone.
```javascript
// Clear all workspaces with confirmation
if (confirm('Clear all workspaces?')) {
await editor.clearWorkspaces();
}
```
#### `getWorkspacesList(): Promise<Workspace[]>`
**NEW** Get list of all open workspaces in the editor.
```javascript
const workspaces = await editor.getWorkspacesList();
console.log('Open workspaces:', workspaces);
```
#### `trackMAMUpload(jobId: string, callbacks?: object): { trackingId: string; stop: () => void }`
Track MAM upload progress for a specific render job.
#### `startRenderWithMAMUpload(renderOptions: any, mamUploadCallbacks?: object): Promise<{ jobId?: string; tracker: object }>`
Start render with automatic MAM upload tracking.
#### `destroy(): void`
Disconnect and cleanup resources.
#### Status Methods
- `isConnected(): boolean` - Check WebSocket connection
- `isReady(): boolean` - Check if editor is ready for commands
- `getSessionId(): string` - Get current session ID
### Complete Method Reference with Return Types
```typescript
// Connection & Status
editor.isConnected(): boolean // WebSocket connection status
editor.isReady(): boolean // Editor ready status
editor.getSessionId(): string // Current session ID
editor.setIframe(iframe: HTMLIFrameElement): void // Set iframe element
editor.destroy(): void // Cleanup and disconnect
// Authentication
editor.loginToMAM(config: MAMConfig): Promise<void> // Login to MAM
editor.logoutFromMAM(): Promise<void> // Logout from MAM
// Theme Management
editor.setTheme(theme: 'light' | 'dark'): Promise<void> // Set basic theme
editor.updateTheme(themeId?: string, customTheme?: CustomTheme): Promise<void> // Apply preset/custom theme
editor.customizeTheme(config: ThemeConfig): Promise<void> // Customize theme colors
// Template Management
editor.importTemplate(template: Template): Promise<void> // Import template
editor.importFromAssets(assets: AssetInfo[], options?: ImportOptions): Promise<ImportFromAssetsResult> // Generate from assets
// Workspace Management
editor.clearWorkspaces(): Promise<void> // Clear all workspaces
editor.getWorkspacesList(): Promise<Workspace[]> // Get workspace list
// Render Management
editor.startRender(options: RenderOptions): Promise<void> // Start render job
editor.trackMAMUpload(jobId: string, callbacks?: MAMUploadCallbacks): MAMUploadTracker // Track MAM upload
editor.startRenderWithMAMUpload(options: RenderOptions, callbacks?: MAMUploadCallbacks): Promise<StartRenderWithMAMUploadResult> // Render with MAM tracking
// Event Management
editor.on(event: string, listener: Function): void // Add event listener
editor.once(event: string, listener: Function): void // Add one-time listener
editor.off(event: string, listener: Function): void // Remove event listener
```
### Events
#### `on(event: string, listener: Function)`
Add event listener.
#### `once(event: string, listener: Function)`
Add one-time event listener.
#### `off(event: string, listener: Function)`
Remove event listener.
### Available Events
- `connected` - WebSocket connected
- `ready` - Editor is ready for commands
- `editorReady` - Editor iframe loaded
- `mamStatus` - MAM login/logout status changed
- `themeChanged` - Basic theme was changed (light/dark)
- `themeUpdate` - Advanced theme was updated
- `customTheme` - Custom theme was applied
- `templateImported` - Template imported successfully
- `workspacesCleared` - All workspaces cleared successfully
- `workspacesListReceived` - Workspace list received from editor
- `renderProgress` - Render progress update
- `renderComplete` - Render completed
- `pipelineEvent` - Pipeline stage updates (render, upload, etc.)
- `mamUploadStarted` - MAM upload process started
- `mamUploadEvent` - MAM upload progress event
- `mamUploadPreparing` - MAM upload preparation stage
- `mamUploadAnalyzing` - MAM upload analysis stage
- `mamUploadConverting` - MAM upload conversion stage
- `mamUploadUploading` - MAM upload in progress
- `mamUploadCompleting` - MAM upload completion stage
- `mamUploadComplete` - MAM upload successfully completed
- `mamUploadFailed` - MAM upload failed
- `editorDisconnected` - Editor disconnected
- `error` - Error occurred
- `disconnected` - WebSocket disconnected
## Examples
### Basic External App
```html
<!DOCTYPE html>
<html>
<head>
<title>EMS Editor External App</title>
</head>
<body>
<div>
<button id="setTheme">Set Dark Theme</button>
<button id="import">Import Template</button>
<button id="render">Start Render</button>
</div>
<iframe id="editor" width="100%" height="600px"></iframe>
<script type="module">
import EMSEditor from './ems-editor-sdk.js';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
// Set up iframe
const iframe = document.getElementById('editor');
editor.setIframe(iframe);
// Wait for ready
editor.on('ready', () => {
console.log('🎉 Editor ready!');
// Enable buttons
document.getElementById('setTheme').onclick = () => {
editor.setTheme('dark');
};
document.getElementById('import').onclick = () => {
editor.importTemplate({
name: 'Sample Template',
data: { /* template data */ }
});
};
document.getElementById('render').onclick = () => {
editor.startRender({
title: 'My Video',
quality: 'high'
});
};
});
// Listen for events
editor.on('themeChanged', (data) => {
console.log('Theme changed to:', data.theme);
});
editor.on('renderProgress', (progress) => {
console.log('Render progress:', progress);
});
editor.on('renderComplete', (result) => {
console.log('Render complete:', result);
});
</script>
</body>
</html>
### Auto-Generate Template from Assets
#### Simple Asset Import - Minimal Example
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
editor.on('ready', async () => {
// Simple asset array
const assets = [
{
id: 2673,
name: 'intro_video',
type: 'video',
fileUrl: 'https://example.com/video.mp4',
proxyUrl: 'https://example.com/proxy.mp4',
width: 1920,
height: 1080,
duration: 3000 // 3 seconds in milliseconds
}
];
// Auto-generate and import template
await editor.importFromAssets(assets);
console.log('✅ Template generated and imported!');
});
```
#### Multiple Assets with Options
```javascript
editor.on('ready', async () => {
const assets = [
{
id: 'video-1',
name: 'Opening Video',
type: 'video',
fileUrl: 'https://mam.server.com/video1.mp4',
proxyUrl: 'https://mam.server.com/video1_proxy.mp4',
width: 1920,
height: 1080,
duration: 5000, // 5 seconds
mamAssetId: 2673
},
{
id: 'image-1',
name: 'Logo Image',
type: 'image',
fileUrl: 'https://mam.server.com/logo.png',
width: 500,
height: 300,
duration: 3000, // Show for 3 seconds
mamAssetId: 2674
},
{
id: 'audio-1',
name: 'Background Music',
type: 'audio',
fileUrl: 'https://mam.server.com/music.mp3',
duration: 10000, // 10 seconds
mamAssetId: 2675
}
];
// Generate with custom options
await editor.importFromAssets(assets, {
name: 'My Auto Project',
description: 'Generated from MAM assets',
width: 1920,
height: 1080,
fps: 30,
backgroundColor: '#1a1a1a'
});
console.log('✅ Multi-asset template created!');
// Template duration will be 10 seconds (longest asset)
// Video gets separate video + audio tracks
// Image and audio get their own tracks
});
```
#### MAM Assets Integration
```javascript
// Real MAM integration example
editor.on('ready', async () => {
// Assets from MAM with all required fields
const mamAssets = [
{
id: 2673,
name: 'nablet_intro_final',
type: 'video',
fileUrl: 'https://easymam.net:6008/Watch/2025/7/31/nablet_intro_final.mp4?token=...',
proxyUrl: 'https://easymam.net:6008/Watch/2025/7/31/nablet_intro_final_proxy.mp4?token=...',
width: 1920,
height: 1080,
duration: 3067, // Exact duration from MAM
mamAssetId: 2673
},
{
id: 2674,
name: 'company_logo',
type: 'image',
fileUrl: 'https://easymam.net:6008/Watch/2025/7/31/logo.png?token=...',
width: 800,
height: 600,
duration: 2000, // Show logo for 2 seconds
mamAssetId: 2674
}
];
await editor.importFromAssets(mamAssets, {
name: 'MAM Generated Project',
fps: 30
});
// Result:
// - Video track with nablet_intro_final (video-only, muted)
// - Audio track with nablet_intro_final (audio-only)
// - Image track with company_logo
// - Total duration: 3067ms = ~92 frames at 30fps
});
```
#### Complete Workflow - Asset Import + Render (with TypeScript)
```typescript
import EMSEditor, {
EMSEditorConfig,
AssetInfo,
ImportFromAssetsResult,
RenderProgressResult,
RenderCompleteResult
} from 'ems-editor-sdk';
const config: EMSEditorConfig = {
serverUrl: 'http://localhost:3000'
};
const editor = new EMSEditor(config);
const iframe = document.getElementById('editor-iframe') as HTMLIFrameElement;
editor.setIframe(iframe);
editor.on('ready', async () => {
try {
// 1. Login to MAM
await editor.loginToMAM({
apiUrl: 'https://your-mam.com',
tokens: { accessToken: 'your-jwt-token' },
selectedProject: { id_project: 1, name: 'My Project' }
});
// 2. Define assets from MAM with full type safety
const assets: AssetInfo[] = [
{
id: 1001,
name: 'intro_video',
type: 'video',
fileUrl: 'https://mam.server.com/intro.mp4',
proxyUrl: 'https://mam.server.com/intro_proxy.mp4',
width: 1920,
height: 1080,
duration: 4000,
mamAssetId: 1001
},
{
id: 1002,
name: 'outro_video',
type: 'video',
fileUrl: 'https://mam.server.com/outro.mp4',
proxyUrl: 'https://mam.server.com/outro_proxy.mp4',
width: 1920,
height: 1080,
duration: 3000,
mamAssetId: 1002
}
];
// 3. Auto-generate template with typed result
const result: ImportFromAssetsResult = await editor.importFromAssets(assets, {
name: 'Intro + Outro Project',
description: 'Auto-generated video sequence',
fps: 30,
backgroundColor: '#1a1a1a'
});
console.log(`✅ Template created:`);
console.log(` - Template ID: ${result.templateId}`);
console.log(` - Workspace ID: ${result.workspaceId}`);
console.log(` - Tracks: ${result.tracksCount}, Elements: ${result.elementsCount}`);
// 4. Start render with MAM upload
await editor.startRender({
title: 'My Generated Video',
quality: 'high',
format: 'mp4',
uploadToMAM: true,
mamToken: 'your-jwt-token',
mamBaseUrl: 'https://your-mam.com',
mamProjectId: 1
});
console.log('✅ Complete workflow executed!');
} catch (error) {
console.error('❌ Workflow error:', error);
}
});
// Type-safe event listeners
editor.on('renderProgress', (progress: RenderProgressResult) => {
console.log(`Render ${progress.jobId}: ${progress.progress}% - ${progress.stage}`);
if (progress.estimatedTimeRemaining) {
console.log(`ETA: ${Math.round(progress.estimatedTimeRemaining / 1000)}s`);
}
});
editor.on('renderComplete', (result: RenderCompleteResult) => {
console.log('🎉 Render complete!');
console.log(` - Video URL: ${result.videoUrl}`);
console.log(` - File Size: ${result.fileSize} bytes`);
console.log(` - Duration: ${result.duration}ms at ${result.fps}fps`);
if (result.mamAssetId) {
console.log(` - MAM Asset ID: ${result.mamAssetId}`);
console.log(` - MAM Asset URL: ${result.mamAssetUrl}`);
}
});
// Real-world usage pattern with error handling
async function createProjectFromAssets(assetName: string, assets: AssetInfo[], fps?: string) {
try {
// Ensure editor is ready
if (!editor.isReady()) {
await new Promise(resolve => editor.once('ready', resolve));
}
// Import assets and get template info
const result = await editor.importFromAssets(assets, {
name: assetName || 'My Auto Project',
description: `Project created for asset ${assetName} from EasyMAM`,
width: 1920,
height: 1080,
fps: fps ? parseInt(fps) : 30,
backgroundColor: '#1a1a1a'
});
console.log(`Project created: ${result.templateId}`);
return result;
} catch (error) {
console.error('Failed to create project:', error);
throw error;
}
}
```
#### Asset Interface Reference
```typescript
interface AssetInfo {
id: string | number; // Unique asset identifier
name: string; // Display name for the asset
type: 'video' | 'audio' | 'image'; // Asset type
fileUrl: string; // Direct file URL (for rendering)
proxyUrl?: string; // Proxy URL (for preview, optional)
width?: number; // Asset width in pixels
height?: number; // Asset height in pixels
duration?: number; // Duration in milliseconds
mamAssetId?: string | number; // MAM system asset ID (optional)
}
```
#### Options Interface Reference
```typescript
interface ImportOptions {
name?: string; // Project name (default: 'Generated Project')
description?: string; // Project description
width?: number; // Canvas width (default: 1920)
height?: number; // Canvas height (default: 1080)
fps?: number; // Frames per second (default: 30)
backgroundColor?: string; // Background color (default: '#000000')
}
```
#### Key Features
- **Auto Video/Audio Separation**: Videos automatically create separate video and audio tracks with proper linking
- **Smart Duration Calculation**: Template duration is set to the longest asset duration
- **Sequential Placement**: Assets are placed one after another on the timeline
- **Proper Scaling**: Images and videos are automatically scaled and centered (object-fit: contain)
- **MAM Integration**: Full support for MAM asset URLs and IDs
- **Track Management**: Automatically creates and organizes tracks by type
### Theme Customization
#### Basic Theme Switching
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
editor.on('ready', async () => {
// Switch to light mode
await editor.setTheme('light');
// Switch to dark mode
await editor.setTheme('dark');
});
```
#### Preset Advanced Themes
```javascript
editor.on('ready', async () => {
// Apply preset themes
await editor.updateTheme('midnight'); // Midnight Blue theme
await editor.updateTheme('emerald'); // Emerald Pro theme
await editor.updateTheme('dark'); // Dark Professional theme
await editor.updateTheme('light'); // Light Professional theme
});
```
#### Custom Theme Colors - Minimal Example
```javascript
editor.on('ready', async () => {
// Customize with just primary colors
await editor.customizeTheme({
primary: '#10b981', // Emerald green
secondary: '#64748b', // Slate gray
background: '#0f172a', // Dark blue
accent: '#34d399' // Light emerald
});
});
```
#### Listen for Theme Changes
```javascript
// Listen for basic theme changes
editor.on('themeChanged', (data) => {
console.log('Basic theme changed to:', data.theme); // 'light' or 'dark'
});
// Listen for advanced theme updates
editor.on('themeUpdate', (data) => {
console.log('Advanced theme updated:', data.themeId || 'custom');
});
// Listen for custom theme applications
editor.on('customTheme', (data) => {
console.log('Custom theme applied:', data.customTheme.name);
});
```
#### Complete Theme Example
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
const iframe = document.getElementById('editor-iframe');
editor.setIframe(iframe);
editor.on('ready', async () => {
try {
// 1. Apply a preset theme
await editor.updateTheme('emerald');
console.log('✅ Emerald theme applied');
// Wait a moment, then customize
setTimeout(async () => {
// 2. Create custom brand colors
await editor.customizeTheme({
primary: '#6366f1', // Indigo
secondary: '#8b5cf6', // Violet
background: '#1e1b4b', // Dark indigo
accent: '#a78bfa' // Light violet
});
console.log('✅ Custom brand theme applied');
}, 2000);
} catch (error) {
console.error('❌ Theme error:', error);
}
});
// Theme change monitoring
editor.on('themeUpdate', (data) => {
console.log('🎨 Theme updated:', data);
});
```
### Workspace Management
#### Clear All Workspaces
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
editor.on('ready', async () => {
// Clear all workspaces with confirmation
if (confirm('Are you sure you want to clear all workspaces?')) {
await editor.clearWorkspaces();
console.log('✅ All workspaces cleared');
}
});
// Listen for workspace events
editor.on('workspacesCleared', (data) => {
if (data.success) {
console.log('✅ Workspaces cleared successfully');
} else {
console.error('❌ Failed to clear workspaces:', data.error);
}
});
```
#### Get Workspaces List
```javascript
editor.on('ready', async () => {
// Get list of open workspaces
const workspaces = await editor.getWorkspacesList();
workspaces.forEach((workspace, index) => {
console.log(`Workspace ${index + 1}:`, {
id: workspace.id,
name: workspace.name,
isActive: workspace.isActive,
template: workspace.template?.name || 'No template'
});
});
});
// Listen for workspace list updates
editor.on('workspacesListReceived', (workspaces) => {
console.log('Current workspaces:', workspaces);
});
```
#### Complete Workspace Management Example
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
const iframe = document.getElementById('editor-iframe');
editor.setIframe(iframe);
editor.on('ready', async () => {
try {
// 1. Get current workspaces
const workspaces = await editor.getWorkspacesList();
console.log(`Found ${workspaces.length} open workspaces`);
// 2. Import multiple templates
await editor.importTemplate({
name: 'Template 1',
settings: { width: 1920, height: 1080, fps: 30 }
});
await editor.importTemplate({
name: 'Template 2',
settings: { width: 1280, height: 720, fps: 30 }
});
// 3. Get updated workspace list
const updatedWorkspaces = await editor.getWorkspacesList();
console.log(`Now have ${updatedWorkspaces.length} workspaces`);
// 4. Clear all after confirmation
setTimeout(async () => {
if (confirm('Clear all workspaces?')) {
await editor.clearWorkspaces();
}
}, 5000);
} catch (error) {
console.error('❌ Workspace management error:', error);
}
});
// Event listeners for workspace changes
editor.on('templateImported', (data) => {
console.log('📄 Template imported:', data.workspaceId);
});
editor.on('workspacesCleared', (data) => {
console.log('🗑️ Workspaces cleared:', data.success);
});
editor.on('workspacesListReceived', (workspaces) => {
console.log('📋 Workspace list updated:', workspaces.length, 'workspaces');
});
```
### Async Initialization & WebSocket Handling
The EMS Editor SDK handles WebSocket connections and UI loading asynchronously. Here's how to ensure proper initialization:
#### Basic Async Initialization
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
// Method 1: Using event listeners (recommended)
editor.on('ready', async () => {
// Editor UI is fully loaded and WebSocket connected
// Safe to call any SDK method here
await editor.setTheme('dark');
const workspaces = await editor.getWorkspacesList();
});
// Method 2: Using promises with polling
async function waitForReady() {
return new Promise((resolve) => {
if (editor.isReady()) {
resolve();
} else {
editor.once('ready', resolve);
}
});
}
// Usage
await waitForReady();
console.log('Editor is ready!');
```
#### Advanced Async Initialization with Timeout
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
// Promise-based initialization with timeout
function initializeEditor(timeoutMs = 10000) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Editor initialization timeout'));
}, timeoutMs);
editor.on('ready', () => {
clearTimeout(timeout);
resolve(editor);
});
editor.on('error', (error) => {
clearTimeout(timeout);
reject(error);
});
});
}
// Usage with error handling
try {
await initializeEditor(5000); // 5 second timeout
console.log('✅ Editor initialized successfully');
// Now safe to use all SDK methods
const workspaces = await editor.getWorkspacesList();
await editor.setTheme('dark');
} catch (error) {
console.error('❌ Editor initialization failed:', error.message);
// Handle initialization failure
}
```
#### Ensuring WebSocket Connection Before UI Interaction
```javascript
import EMSEditor from 'ems-editor-sdk';
class EditorManager {
constructor(serverUrl) {
this.editor = new EMSEditor({ serverUrl });
this.isInitialized = false;
this.initPromise = null;
}
// Single initialization promise to avoid race conditions
async initialize() {
if (this.initPromise) {
return this.initPromise;
}
this.initPromise = new Promise((resolve, reject) => {
// Check connection status
if (!this.editor.isConnected()) {
console.log('⏳ Waiting for WebSocket connection...');
}
// Wait for both connection and UI ready
const checkReady = () => {
if (this.editor.isConnected() && this.editor.isReady()) {
this.isInitialized = true;
console.log('✅ Editor fully initialized');
resolve(this.editor);
}
};
// Listen for ready event
this.editor.on('ready', checkReady);
// Also check immediately in case already ready
checkReady();
// Timeout after 15 seconds
setTimeout(() => {
reject(new Error('Editor initialization timeout after 15s'));
}, 15000);
});
return this.initPromise;
}
// Safe method wrapper that ensures initialization
async safeCall(methodName, ...args) {
if (!this.isInitialized) {
await this.initialize();
}
return this.editor[methodName](...args);
}
// Convenience methods
async getWorkspaces() {
return this.safeCall('getWorkspacesList');
}
async clearAllWorkspaces() {
return this.safeCall('clearWorkspaces');
}
async importTemplate(template) {
return this.safeCall('importTemplate', template);
}
}
// Usage
const editorManager = new EditorManager('http://localhost:3000');
// Set iframe (can be done immediately)
const iframe = document.getElementById('editor-iframe');
editorManager.editor.setIframe(iframe);
// All other operations will wait for proper initialization
async function setupEditor() {
try {
// These will automatically wait for initialization
const workspaces = await editorManager.getWorkspaces();
console.log('Current workspaces:', workspaces.length);
await editorManager.importTemplate({
name: 'Auto Template',
settings: { width: 1920, height: 1080 }
});
const updatedWorkspaces = await editorManager.getWorkspaces();
console.log('Updated workspaces:', updatedWorkspaces.length);
} catch (error) {
console.error('❌ Editor setup failed:', error);
}
}
setupEditor();
```
#### Production-Ready Initialization Pattern
```javascript
import EMSEditor from 'ems-editor-sdk';
class ProductionEditorSDK {
constructor(config) {
this.config = config;
this.editor = null;
this.ready = false;
this.initPromise = null;
}
async init() {
if (this.initPromise) return this.initPromise;
this.initPromise = new Promise(async (resolve, reject) => {
try {
// Create editor instance
this.editor = new EMSEditor(this.config);
// Set up error handling
this.editor.on('error', (error) => {
console.error('Editor error:', error);
});
this.editor.on('disconnected', () => {
console.warn('Editor disconnected - will reconnect');
this.ready = false;
});
// Wait for ready with timeout
const readyPromise = new Promise((readyResolve) => {
this.editor.on('ready', () => {
this.ready = true;
readyResolve();
});
});
const timeoutPromise = new Promise((_, timeoutReject) => {
setTimeout(() => {
timeoutReject(new Error('Initialization timeout'));
}, this.config.timeout || 10000);
});
await Promise.race([readyPromise, timeoutPromise]);
console.log('✅ Production editor ready');
resolve(this.editor);
} catch (error) {
console.error('❌ Production editor init failed:', error);
reject(error);
}
});
return this.initPromise;
}
// Public API with built-in initialization
async getWorkspaces() {
await this.ensureReady();
return this.editor.getWorkspacesList();
}
async clearWorkspaces() {
await this.ensureReady();
return this.editor.clearWorkspaces();
}
async importTemplate(template) {
await this.ensureReady();
return this.editor.importTemplate(template);
}
async setTheme(theme) {
await this.ensureReady();
return this.editor.setTheme(theme);
}
// Internal ready check
async ensureReady() {
if (!this.ready || !this.editor) {
await this.init();
}
if (!this.ready) {
throw new Error('Editor not ready after initialization');
}
}
// Cleanup
destroy() {
if (this.editor) {
this.editor.destroy();
this.editor = null;
this.ready = false;
this.initPromise = null;
}
}
}
// Usage
const productionEditor = new ProductionEditorSDK({
serverUrl: 'http://localhost:3000',
timeout: 8000 // 8 second timeout
});
// Set iframe immediately
const iframe = document.getElementById('editor-iframe');
// Note: iframe setup needs special handling
productionEditor.init().then((editor) => {
editor.setIframe(iframe);
});
// Use the production API
async function useEditor() {
try {
// All methods handle initialization automatically
await productionEditor.setTheme('dark');
const workspaces = await productionEditor.getWorkspaces();
console.log('Workspaces:', workspaces);
await productionEditor.importTemplate({
name: 'Production Template',
settings: { width: 1920, height: 1080 }
});
} catch (error) {
console.error('Production editor error:', error);
}
}
useEditor();
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
productionEditor.destroy();
});
```
### With MAM Integration
```javascript
import EMSEditor from 'ems-editor-sdk';
const editor = new EMSEditor({
serverUrl: 'http://localhost:3000'
});
const mamConfig = {
apiUrl: 'https://your-mam-api.com',
tokens: {
accessToken: 'your-access-token'
},
selectedProject: {
id_project: 123,
name: 'My Project'
}
};
editor.on('ready', async () => {
try {
// Login to MAM
await editor.loginToMAM(mamConfig);
console.log('✅ MAM login successful');
// Set theme
await editor.setTheme('light');
// Import template
await editor.importTemplate({
name: 'Branded Template',
settings: {
width: 1920,
height: 1080,
fps: 30
}
});
} catch (error) {
console.error('❌ Error:', error);
}
});
// Listen for MAM status changes
editor.on('mamStatus', (status) => {
console.log('MAM status:', status);
});
```
## Error Handling
```javascript
editor.on('error', (error) => {
console.error('Editor error:', error);
});
editor.on('disconnected', () => {
console.log('Editor disconnected - will auto-reconnect');
});
// Graceful cleanup
window.addEventListener('beforeunload', () => {
editor.destroy();
});
```
## Architecture
### Session-Based Communication
- Each external app gets a unique session ID
- The iframe editor shares the same session ID
- WebSocket server handles message routing between sessions
### Flow
1. External app creates SDK instance with session ID
2. External app sets iframe with same session ID in URL
3. Both connect to WebSocket server with same session ID
4. Server detects both are ready and enables communication
5. Commands flow: External App → Server → Iframe Editor
6. Events flow: Iframe Editor → Server → External App
### Message Types
- `MAM_LOGIN/LOGOUT` - MAM authentication
- `SET_THEME` - Theme changes
- `IMPORT_TEMPLATE` - Template import
- `START_RENDER` - Video rendering
- `*_STATUS/*_PROGRESS` - Status updates
## Troubleshooting
### Editor not ready
- Check that iframe src includes correct sessionId
- Ensure both external app and iframe are connected
- Check browser console for WebSocket errors
### Commands not working
- Verify `isReady()` returns true before sending commands
- Check that both sessions have same sessionId
- Monitor WebSocket connection status
### Connection issues
- Ensure server is running on correct port
- Check for CORS issues
- Verify WebSocket endpoint is accessible
## Development
```bash
# Install dependencies
npm install
# Build SDK
npm run build
# Run examples server
npm run dev
```
## License
MIT