UNPKG

ems-editor

Version:

EMS Video Editor SDK - Universal JavaScript SDK for external video editor integration

1,881 lines (1,586 loc) 49.6 kB
# 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