npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ems-editor

v1.2.6

Published

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

Readme

EMS Editor SDK

A powerful JavaScript SDK for integrating with the EMS Video Editor through WebSocket communication. Provides real-time workspace loading, template importing, render management, and MAM integration.

Features

  • 🚀 Real-time WebSocket Communication - Bidirectional communication with the video editor
  • 📥 Workspace Loading - Load workspaces from server with progress tracking
  • 📋 Template Import - Import video templates and assets dynamically
  • 🎬 Render Management - Start renders and track progress in real-time
  • Quick Render - One-click rendering of current workspace with minimal setup
  • 📊 Render Job Tracking - Monitor render job status and progress by job ID
  • 📤 MAM Integration - Upload rendered videos to Media Asset Management systems
  • 🎨 Theme Customization - Apply custom themes and branding
  • 🔄 Template Change Tracking - Monitor template modifications with dirty flag system and real-time notifications

Installation

npm install ems-editor

Quick Start

Basic Setup

import EMSEditor from 'ems-editor';

const editor = new EMSEditor({
  serverUrl: 'http://localhost:5200',
  sessionId: 'your-session-id' // Optional
});

// Set iframe reference
const iframe = document.getElementById('editor-iframe');
editor.setIframe(iframe);

// Wait for editor to be ready
editor.once('ready', () => {
  console.log('Editor is ready!');
});

Load Workspace from Server

// Load workspace with progress tracking
const result = await editor.loadWorkspaceFromServer({
  workspaceId: 'workspace-uuid-123',
  mergeMode: 'replace',
  switchToWorkspace: true
}, {
  onProgress: (progress) => {
    console.log(`${progress.stage}: ${progress.progress}% - ${progress.message}`);
  },
  onComplete: (result) => {
    console.log('✅ Workspace loaded:', result.workspaceName);
  },
  onError: (error) => {
    console.error('❌ Loading failed:', error);
  }
});

Template Change Tracking

The SDK provides built-in change tracking to monitor when templates have been modified after being imported from external sources. This is useful for showing "unsaved changes" indicators and preventing data loss.

// Check current dirty status
if (editor.isDirty()) {
  console.log('⚠️ Template has been modified since import');
} else {
  console.log('✅ Template is clean (no modifications)');
}

// Listen for real-time status changes (throttled to once per second)
editor.on('templateStatusChanged', (status) => {
  const { isDirty, templateId, templateName, timestamp } = status;
  
  if (isDirty) {
    console.log(`🔄 Template "${templateName}" has unsaved changes`);
    // Show save prompt, enable save button, add * to title, etc.
    updateUI({ showUnsavedIndicator: true });
  } else {
    console.log(`✅ Template "${templateName}" is saved`);
    // Remove save indicators, disable save button, etc.
    updateUI({ showUnsavedIndicator: false });
  }
});

// Example UI integration
function createSaveButton() {
  const button = document.createElement('button');
  
  const updateButton = () => {
    if (editor.isDirty()) {
      button.textContent = 'Save Changes *';
      button.disabled = false;
      button.classList.add('has-changes');
    } else {
      button.textContent = 'Saved';
      button.disabled = true;
      button.classList.remove('has-changes');
    }
  };
  
  // Initial state
  updateButton();
  
  // Update on changes
  editor.on('templateStatusChanged', updateButton);
  
  return button;
}

Key Features:

  • 🔄 Real-time tracking - Automatically detects when users modify templates in the editor
  • Throttled notifications - WebSocket messages are throttled to once per second to prevent spam
  • 🎯 Accurate state - Distinguishes between templates imported from external sources vs. user modifications
  • 🧹 Auto-reset - Dirty flag automatically resets when new templates are imported

Quick Render Current Workspace

// Quick render with default settings (MP4, H.264)
const renderJob = await editor.quickRender();

if (renderJob.success) {
  // Track the render progress
  const finalResult = await editor.waitForRenderComplete(renderJob.jobId, {
    onProgress: (result) => {
      console.log(`🎬 ${result.status}: ${result.progress || 0}%`);
    }
  });
  
  if (finalResult.status === 'completed') {
    console.log('✅ Video ready:', finalResult.videoUrl);
  }
} else {
  console.error('❌ Quick render failed:', renderJob.error);
}

Track Render Job by ID

// Get current status of a render job
const jobStatus = await editor.getRenderResult('job-abc-123');
console.log('Job Status:', jobStatus.status, jobStatus.progress + '%');

// Or wait for completion with polling
const completed = await editor.waitForRenderComplete('job-abc-123');
console.log('Completed:', completed.videoUrl);

API Reference

Methods

Template and Asset Management

importTemplate(templateData: any): Promise<ImportFromAssetsResult>

Import a video template into the editor.

// Import a template
const result = await editor.importTemplate({
  name: 'My Video Template',
  tracks: [...],
  assets: {...},
  settings: {
    width: 1920,
    height: 1080,
    fps: 30,
    duration: 600
  }
});

console.log('Template imported:', result.templateId);
importFromAssets(assets: AssetInfo[], options?): Promise<ImportFromAssetsResult>

Generate and import a template from an array of media assets.

// Import from assets
const assets = [
  {
    id: 'video1',
    name: 'Intro Video',
    type: 'video',
    fileUrl: 'https://example.com/video.mp4',
    duration: 5000,
    width: 1920,
    height: 1080
  },
  {
    id: 'audio1',
    name: 'Background Music',
    type: 'audio',
    fileUrl: 'https://example.com/music.mp3',
    duration: 30000
  }
];

const result = await editor.importFromAssets(assets, {
  name: 'Auto-Generated Video',
  width: 1920,
  height: 1080,
  fps: 30
});

console.log('Assets imported:', result.templateName);

Workspace Management

createWorkspace(options?): Promise<{workspaceId: string, success: boolean}>

Create a new workspace/tab in the editor.

// Create a new workspace
const result = await editor.createWorkspace({
  name: 'New Project',
  description: 'My new video project',
  width: 1920,
  height: 1080,
  fps: 30,
  duration: 450,
  quality: 'high'
});

console.log('Workspace created:', result.workspaceId);
getWorkspacesList(): Promise<any[]>

Get list of all open workspaces.

// Get all workspaces
const workspaces = await editor.getWorkspacesList();
workspaces.forEach(workspace => {
  console.log(`Workspace: ${workspace.name} (${workspace.id})`);
});
renameWorkspace(workspaceId: string, newName: string): Promise<{success: boolean}>

Rename an existing workspace.

// Rename a workspace
await editor.renameWorkspace('workspace-123', 'Updated Project Name');
clearWorkspaces(): Promise<void>

Clear all workspaces/tabs in the editor.

// Clear all workspaces
await editor.clearWorkspaces();
console.log('All workspaces cleared');

Video Rendering

startRender(renderOptions: RenderOptions): Promise<void>

Start the video rendering process.

// Start render with basic options
await editor.startRender({
  title: 'My Video',
  color: '#FF5733',
  duration: 300,
  fps: 30,
  width: 1920,
  height: 1080,
  format: 'mp4',
  quality: 'high'
});

// Listen for render progress
editor.on('renderProgress', (progress) => {
  console.log(`Render progress: ${progress.progress}%`);
});

editor.on('renderComplete', (result) => {
  console.log('Video rendered:', result.videoUrl);
});
startRenderWithMAMUpload(options: RenderOptions, callbacks?: MAMUploadCallbacks): Promise<StartRenderWithMAMUploadResult>

Start rendering with MAM (Media Asset Management) upload tracking.

// Start render with MAM upload
const result = await editor.startRenderWithMAMUpload({
  title: 'Corporate Video',
  format: 'mp4',
  quality: 'ultra',
  uploadToMAM: true,
  mamToken: 'your-mam-token',
  mamBaseUrl: 'https://your-mam-system.com/api',
  mamProjectId: 12345,
  mamBinId: 67890
}, {
  onProgress: (progress) => {
    console.log(`MAM Upload: ${progress.stage} - ${progress.progress}%`);
  },
  onStageChange: (stage, data) => {
    console.log(`Stage changed to: ${stage}`);
  },
  onComplete: (result) => {
    console.log('Uploaded to MAM:', result.assetId);
  },
  onError: (error) => {
    console.error('MAM upload failed:', error);
  }
});

console.log('MAM upload tracking ID:', result.tracker.trackingId);
trackMAMUpload(jobId: string, callbacks?: MAMUploadCallbacks): MAMUploadTracker

Track MAM upload progress for a specific job.

// Track existing MAM upload
const tracker = editor.trackMAMUpload('job-123', {
  onProgress: (progress) => {
    console.log(`Upload progress: ${progress.progress}%`);
  },
  onComplete: (result) => {
    console.log('MAM upload completed:', result.assetUrl);
  }
});

// Stop tracking when done
setTimeout(() => {
  tracker.stop();
}, 60000);
quickRender(options?: QuickRenderOptions): Promise<QuickRenderResult>

Start a quick render of the current workspace with minimal configuration. Uses the current workspace template with optional parameter overrides.

// Quick render with defaults (MP4, H.264)
const result = await editor.quickRender();

// Quick render with custom options
const result = await editor.quickRender({
  title: 'My Quick Video',
  format: 'mp4',
  codec: 'h264',
  fps: 30
});

console.log('Quick render started:', result.jobId);

// Check if successful
if (result.success) {
  console.log('Job ID:', result.jobId);
} else {
  console.error('Error:', result.error);
}
getRenderResult(jobId: string): Promise<RenderResult>

Get the current status and result of a render job by its job ID.

// Get current job status
const result = await editor.getRenderResult('job-abc-123');

console.log('Job Status:', {
  jobId: result.jobId,
  status: result.status,        // 'queued', 'in-progress', 'completed', 'failed'
  progress: result.progress,    // Progress percentage (0-100)
  videoUrl: result.videoUrl,    // Available when completed
  mamAssetId: result.mamAssetId // MAM asset ID if uploaded
});
waitForRenderComplete(jobId: string, options?): Promise<RenderResult>

Wait for a render job to complete with automatic polling and progress tracking.

// Wait for render completion with progress callbacks
const finalResult = await editor.waitForRenderComplete('job-abc-123', {
  onProgress: (result) => {
    console.log(`Progress: ${result.status} ${result.progress || 0}%`);
  },
  pollInterval: 2000,  // Poll every 2 seconds
  timeout: 300000      // 5 minute timeout
});

// Result when completed
if (finalResult.status === 'completed') {
  console.log('✅ Video ready:', finalResult.videoUrl);
} else {
  console.error('❌ Render failed:', finalResult.status);
}

// Advanced usage with detailed progress tracking
try {
  const result = await editor.waitForRenderComplete(jobId, {
    onProgress: (result) => {
      switch (result.status) {
        case 'queued':
          console.log('⏳ Job queued...');
          break;
        case 'in-progress':
          console.log(`🎬 Rendering: ${result.progress || 0}%`);
          break;
      }
    },
    pollInterval: 1500,
    timeout: 600000 // 10 minutes
  });
  
  console.log('Final result:', result);
} catch (error) {
  console.error('Render failed or timed out:', error.message);
}

Theme and UI Customization

setTheme(theme: 'light' | 'dark'): Promise<void>

Set the editor theme.

// Set dark theme
await editor.setTheme('dark');

// Set light theme
await editor.setTheme('light');
updateTheme(themeId?: string, customTheme?: any): Promise<void>

Update theme with preset or custom theme.

// Use preset theme
await editor.updateTheme('professional-dark');

// Use custom theme object
await editor.updateTheme(null, {
  name: 'Custom Theme',
  colors: {
    primary: '#007cba',
    secondary: '#6c757d',
    background: '#f8f9fa'
  }
});
customizeTheme(themeConfig): Promise<void>

Customize theme with specific colors.

// Customize theme colors
await editor.customizeTheme({
  primary: '#007cba',
  secondary: '#6c757d',
  background: '#ffffff',
  accent: '#17a2b8'
});

MAM Integration

loginToMAM(mamConfig: any): Promise<void>

Login to MAM system.

// Login to MAM
await editor.loginToMAM({
  apiUrl: 'https://your-mam-system.com/api',
  tokens: {
    accessToken: 'your-access-token',
    refreshToken: 'your-refresh-token'
  },
  selectedProject: {
    id_project: 12345,
    name: 'My Project'
  }
});

console.log('Logged into MAM system');
logoutFromMAM(): Promise<void>

Logout from MAM system.

// Logout from MAM
await editor.logoutFromMAM();
console.log('Logged out from MAM');

Events

The SDK extends EventEmitter and emits the following events:

Connection Events

// WebSocket connected
editor.on('connected', () => {
  console.log('Connected to server');
});

// Editor ready for operations
editor.on('ready', () => {
  console.log('Editor is ready');
});

// WebSocket disconnected
editor.on('disconnected', () => {
  console.log('Disconnected from server');
});

// Connection error
editor.on('error', (error) => {
  console.error('Connection error:', error);
});

Workspace Events

// Workspace loading progress
editor.on('workspaceLoadingProgress', (progress) => {
  console.log(`Loading: ${progress.stage} - ${progress.progress}%`);
});

// Workspace loaded successfully
editor.on('workspaceLoaded', (result) => {
  console.log('Workspace loaded:', result.workspaceName);
});

// Workspace loading failed
editor.on('workspaceLoadingError', (error) => {
  console.error('Loading failed:', error.error);
});

// Workspaces cleared
editor.on('workspacesCleared', () => {
  console.log('All workspaces cleared');
});

// Workspace created
editor.on('workspaceCreated', (result) => {
  console.log('Workspace created:', result.workspaceId);
});

// Workspace renamed
editor.on('workspaceRenamed', (result) => {
  console.log('Workspace renamed:', result.newName);
});

Template Events

// Template imported
editor.on('templateImported', (result) => {
  console.log('Template imported:', result.templateName);
});

// Template added to workspace
editor.on('templateAddedToWorkspace', (result) => {
  console.log('Template added to workspace:', result.workspaceId);
});

// Template modification status changed
editor.on('templateStatusChanged', (status) => {
  console.log('Template dirty status:', status.isDirty);
  console.log('Template ID:', status.templateId);
  console.log('Template name:', status.templateName);
  console.log('Changed at:', status.timestamp);
  
  // Update UI based on dirty status
  if (status.isDirty) {
    console.log('⚠️ Template has unsaved changes');
    // Show save indicators, enable save buttons, etc.
  } else {
    console.log('✅ Template is clean');
    // Hide save indicators, disable save buttons, etc.
  }
});

Render Events

// Render progress updates
editor.on('renderProgress', (progress) => {
  console.log(`Rendering: ${progress.progress}%`);
});

// Render completed
editor.on('renderComplete', (result) => {
  console.log('Render completed:', result.videoUrl);
});

// Quick render events
editor.on('quickRenderStarted', (result) => {
  console.log('Quick render started:', result.jobId);
});

editor.on('quickRenderCompleted', (result) => {
  console.log('Quick render completed:', result.jobId);
});

// Pipeline events (detailed render stages)
editor.on('pipelineEvent', (event) => {
  console.log(`Pipeline: ${event.stage} - ${event.message}`);
});

MAM Events

// MAM login status
editor.on('mamStatus', (status) => {
  console.log('MAM status:', status.connected ? 'Connected' : 'Disconnected');
});

// MAM upload events
editor.on('mamUploadEvent', (event) => {
  console.log(`MAM Upload: ${event.stage} - ${event.progress}%`);
});

// MAM upload completed
editor.on('mamUploadComplete', (result) => {
  console.log('MAM upload completed:', result.assetId);
});

// Specific MAM upload stages
editor.on('mamUploadPreparing', (data) => console.log('Preparing upload...'));
editor.on('mamUploadAnalyzing', (data) => console.log('Analyzing media...'));
editor.on('mamUploadConverting', (data) => console.log('Converting format...'));
editor.on('mamUploadUploading', (data) => console.log('Uploading to MAM...'));
editor.on('mamUploadCompleting', (data) => console.log('Finalizing...'));
editor.on('mamUploadCompleted', (data) => console.log('Upload complete!'));
editor.on('mamUploadFailed', (data) => console.log('Upload failed:', data.error));

Theme Events

// Theme changed
editor.on('themeChanged', (theme) => {
  console.log('Theme changed to:', theme.name);
});

// Theme updated
editor.on('themeUpdate', (themeData) => {
  console.log('Theme updated:', themeData);
});

// Custom theme applied
editor.on('customTheme', (theme) => {
  console.log('Custom theme applied:', theme.name);
});

Editor Events

// Editor ready (from iframe)
editor.on('editorReady', () => {
  console.log('Editor iframe is ready');
});

// Editor disconnected
editor.on('editorDisconnected', () => {
  console.log('Editor disconnected');
});

// Workspaces list received
editor.on('workspacesListReceived', (workspaces) => {
  console.log('Received workspaces list:', workspaces.length);
});

Utility Methods

isConnected(): boolean

Check if WebSocket is connected.

if (editor.isConnected()) {
  console.log('WebSocket is connected');
}
isReady(): boolean

Check if editor is ready for operations.

if (editor.isReady()) {
  console.log('Editor is ready');
}
getSessionId(): string

Get the current session ID.

const sessionId = editor.getSessionId();
console.log('Session ID:', sessionId);
isDirty(): boolean

Check if the current template/workspace has unsaved changes. Returns true if the template has been modified since it was last imported from an external source.

// Check if template has been modified
if (editor.isDirty()) {
  console.log('Template has unsaved changes');
  // Show save prompt or disable certain actions
} else {
  console.log('Template is clean (no changes since import)');
}

// Example usage in UI
const saveButton = document.getElementById('save-btn');
const updateSaveButton = () => {
  if (editor.isDirty()) {
    saveButton.textContent = 'Save Changes*';
    saveButton.disabled = false;
  } else {
    saveButton.textContent = 'Saved';
    saveButton.disabled = true;
  }
};

// Update save button when template status changes
editor.on('templateStatusChanged', updateSaveButton);
destroy(): void

Clean up resources and disconnect.

// Clean up when done
editor.destroy();

Examples

See the ../../examples/ directory for complete examples:

  • simple-workspace-loading.html - Basic usage
  • workspace-loading-example.html - Advanced usage with UI
  • nodejs-client-sdk-example.js - Node.js integration

Documentation

For detailed documentation, see:

License

MIT License