Plugin API Reference

Complete reference for the mp4e JavaScript API available inside plugins.

Overview

Plugins run in a sandboxed iframe and communicate with the player via the mp4e API. This API provides access to video state, variables, and the ability to execute actions.

Interactive API Explorer
You can also explore the plugin API interactively in the Studio's Plugin Editor — open the API tab to see all available methods with live examples.
Sandboxed Environment
Plugin JavaScript runs in a sandboxed iframe (no same-origin access). It cannot access the parent page's DOM. All communication happens through the mp4e bridge API.
Overlay clicks & actions
If your plugin is used as an overlay, clicks inside the iframe are forwarded to the overlay click trigger. That means you can attach onClick actions/rules to the overlay without writing custom click handlers in plugin JavaScript.

Global Object

When a plugin is triggered by an object click or hover, the object global is available with information about the triggering object:

object global structure
// The 'object' global is available when plugin is triggered by an object click/hover
{
  id: "obj_watch_1",           // Unique object identifier
  label: "watch",              // Original AI-detected label
  userLabel: "Apple Watch",    // User-customized label (optional)
  position: {
    x: 320,                    // Center X position
    y: 180,                    // Center Y position
    width: 150,                // Bounding box width
    height: 100,               // Bounding box height
    bbox: [245, 130, 395, 230] // [x1, y1, x2, y2]
  },
  data: {                      // Custom data from metadata
    price: 399,
    sku: "WATCH-001",
    description: "Series 9 GPS",
    productUrl: "https://example.com/watch"
  }
}
Object May Be Null
For overlay plugins that aren't attached to specific objects, object may be null. Always check: if (object) { ... }

Lifecycle

Lifecycle methods control plugin initialization and visibility. The bridge automatically calls mp4e.ready() after DOMContentLoaded, but you can call it earlier if your plugin finishes setup before that.

mp4e.ready()

Signal that the plugin has finished initializing. Call after your DOM setup is complete. This is automatically called after DOMContentLoaded if your script does not call it first. Calling it multiple times is safe — only the first call has effect.

mp4e.ready();
mp4e.hide()

Hide this overlay. The plugin script continues running in the background and can still listen to events and update variables. The hide request is routed through the engine so the host can gate or track visibility changes. Call show() to make it visible again.

mp4e.hide();
mp4e.show()

Show this overlay after it was previously hidden with hide(). The show request is routed through the engine so the host can gate or track visibility changes.

mp4e.show();
mp4e.isVisible()

Check if the overlay is currently visible. Returns false after hide() has been called, true after show() or initially.

Returns: boolean

if (!mp4e.isVisible()) mp4e.show();
mp4e.close()

Closes the current plugin (modal or display). For display plugins triggered by object clicks/hovers, this dismisses the display. For modal plugins, this closes the modal and resumes video if it was paused.

mp4e.close();
mp4e.getPluginId()

Returns this plugin's unique ID. For pool-managed (group-bound) overlays, this includes the bound object ID.

Returns: string

const id = mp4e.getPluginId(); // e.g., "overlay_tooltip_1" or "pool-hotspot:obj_chair_1"

Sync Methods

These methods return values immediately (synchronously):

mp4e.getConfig()

Returns the plugin configuration object. Contains values from the plugin editor settings panel. Config values are automatically interpolated by the engine when they contain template expressions like {{variableName}}.

Returns: object

Common Mistake: Don't Use mp4e.config

Always use mp4e.getConfig() to access configuration in JavaScript. Do NOT use mp4e.config - it doesn't exist!

CORRECT:const config = window.mp4e?.getConfig();
WRONG:const config = mp4e.config;(doesn't exist)
WRONG:const title = '{{title}}';(template syntax only works in HTML/CSS)
getConfig() example
// Access plugin configuration values
const config = mp4e.getConfig();

// config contains values from the plugin editor:
{
  title: "Product Details",
  showPrice: true,
  currency: "USD",
  buttonText: "Add to Cart",
  // ... any values defined in your plugin settings
}

// Use in your code:
document.getElementById('title').textContent = config.title;
if (config.showPrice) {
  document.getElementById('price').textContent =
    config.currency + ' ' + object.data.price;
}
mp4e.getData()

Returns the full data object passed to the plugin (most commonly { object, groups }). For controls plugins, this also includes menuConfig, tracks, markers, thumbnails, and state.

Returns: { object?: any, groups?: any[], menuConfig?: any, tracks?: any, markers?: any[], state?: any }

getData() example
// Access the full data object
const data = mp4e.getData();

// data structure:
{
  object: { ... },        // Same as the 'object' global
  groups: [               // Object groups this object belongs to
    { id: "products", name: "Products", color: "#3b82f6" },
    { id: "featured", name: "Featured Items", color: "#10b981" }
  ]
}
mp4e.getPageUrl()

Returns the URL of the host page where the player is embedded, or null if not available.

Returns: string | null

const pageUrl = mp4e.getPageUrl();
mp4e.getVideoState()async

Gets the full video state object from the engine including current time, duration, playing state, volume, and more.

Returns: Promise<object>

const state = await mp4e.getVideoState();
mp4e.log(message)(message: string)

Logs a message to the browser console with a plugin prefix. Useful for debugging.

mp4e.log('Button clicked!');

Async Methods

These methods return Promises and must be awaited. They communicate with the parent player via postMessage and resolve when the response arrives (with a 5-second timeout).

mp4e.getVariable(name)(name: string)async

Gets the current value of a variable from the engine.

Returns: Promise<any>

mp4e.setVariable(name, value)(name: string, value: any)async

Sets a variable value in the engine. This triggers the rules engine to re-evaluate any rules that depend on this variable.

Returns: Promise<void>

Variable examples
// GET a variable (async - returns Promise)
const cartCount = await mp4e.getVariable('cartCount');
const userName = await mp4e.getVariable('userName');

// SET a variable (async - returns Promise)
await mp4e.setVariable('selectedProduct', object.label);
await mp4e.setVariable('cartCount', cartCount + 1);

// Multiple operations
const [price, quantity] = await Promise.all([
  mp4e.getVariable('unitPrice'),
  mp4e.getVariable('quantity')
]);
const total = price * quantity;
await mp4e.setVariable('total', total);

Video Control

Methods for controlling and querying video playback. Time and state getters are synchronous (updated in real-time via bridge messages). Playback commands are fire-and-forget.

Playback Commands

mp4e.playVideo()

Starts or resumes video playback. Alias: mp4e.play().

mp4e.playVideo();
mp4e.pauseVideo()

Pauses video playback. Alias: mp4e.pause().

mp4e.pauseVideo();
mp4e.seekToTime(seconds)(seconds: number)

Seeks the video to a specific time in seconds.

mp4e.seekToTime(30);
mp4e.goToScene(sceneId)(sceneId: string)

Jumps to a named scene. The engine resolves the scene to a seek time internally.

mp4e.goToScene('chapter-2');
mp4e.setVolume(volume)(volume: number)

Sets the video volume. Value should be between 0 (muted) and 1 (full volume).

mp4e.setVolume(0.5);
mp4e.setPlaybackRate(rate)(rate: number)

Sets the playback speed. 1 is normal speed, 0.5 is half speed, 2 is double speed.

mp4e.setPlaybackRate(1.5);
mp4e.setSubtitleTrack(trackId)(trackId: string)

Activates a subtitle track by its ID.

mp4e.setSubtitleTrack('track_es');
mp4e.setAudioTrack(trackId)(trackId: string)

Switches to a different audio track by its ID.

mp4e.setAudioTrack('track_en');

Playback State (Synchronous)

These methods return values synchronously. They are kept up-to-date by the parent player via mp4e-playback-state messages.

mp4e.getCurrentTime()

Gets the current video playback time in seconds. Clamped to the video duration.

Returns: number

const t = mp4e.getCurrentTime(); // e.g., 42.5
mp4e.getCurrentFrame()

Gets the current video frame number.

Returns: number

const frame = mp4e.getCurrentFrame(); // e.g., 1275
mp4e.getDuration()

Gets the total video duration in seconds. Returns 0 if not yet loaded.

Returns: number

const dur = mp4e.getDuration(); // e.g., 120.5
mp4e.isPlaying()

Check if the video is currently playing.

Returns: boolean

if (mp4e.isPlaying()) mp4e.pauseVideo();
mp4e.isBuffering()

Check if the video is currently buffering.

Returns: boolean

if (mp4e.isBuffering()) showSpinner();
mp4e.getVolume()

Gets the current volume level (0 to 1).

Returns: number

const vol = mp4e.getVolume(); // e.g., 0.8
mp4e.isMuted()

Check if the video is currently muted.

Returns: boolean

if (mp4e.isMuted()) muteIcon.classList.add('active');
mp4e.getPlaybackRate()

Gets the current playback speed.

Returns: number

const rate = mp4e.getPlaybackRate(); // e.g., 1.5
mp4e.isFullscreen()

Check if the player is currently in fullscreen mode.

Returns: boolean

if (mp4e.isFullscreen()) showExitButton();
Video control examples
// Get current playback position (synchronous - updated via bridge)
const currentTime = mp4e.getCurrentTime();  // seconds
const currentFrame = mp4e.getCurrentFrame(); // frame number

// Control playback (fire-and-forget)
mp4e.playVideo();
mp4e.pauseVideo();
mp4e.seekToTime(30);       // Seek to 30 seconds
mp4e.goToScene('chapter-2'); // Jump to named scene

// Volume and rate control
mp4e.setVolume(0.5);          // Set volume to 50%
mp4e.setPlaybackRate(1.5);    // Set playback speed to 1.5x

// Query playback state (synchronous)
const duration = mp4e.getDuration();         // Total duration in seconds
const playing = mp4e.isPlaying();            // true if currently playing
const buffering = mp4e.isBuffering();        // true if buffering
const volume = mp4e.getVolume();             // Current volume (0-1)
const muted = mp4e.isMuted();               // true if muted
const rate = mp4e.getPlaybackRate();         // Current playback rate
const fullscreen = mp4e.isFullscreen();      // true if in fullscreen

Track Information

mp4e.getTracks()

Returns all available tracks organized by type.

Returns: { subtitles: Track[], audio: Track[] }

const tracks = mp4e.getTracks(); // { subtitles: [{ id: 't1', label: 'English', language: 'en' }], audio: [...] }
mp4e.getSubtitleTracks()

Returns available subtitle tracks.

Returns: Track[]

const subs = mp4e.getSubtitleTracks(); // [{ id: 't1', label: 'English', language: 'en', source: 'file' }]
mp4e.getAudioTracks()

Returns available audio tracks.

Returns: Track[]

const audio = mp4e.getAudioTracks();
mp4e.getActiveSubtitleTrackIds()

Returns the IDs of currently active subtitle tracks.

Returns: string[]

const activeIds = mp4e.getActiveSubtitleTrackIds();
mp4e.getState()

Returns the current player state including viewer preferences and active tracks.

Returns: object

const state = mp4e.getState();

Events

Subscribe to events from the player to react to changes in real time. Events are forwarded from the parent player to the plugin iframe via postMessage.

mp4e.on(eventName, callback)(eventName: string, callback: (data: any) => void)

Subscribe to a named event. Commonly used for input events (e.g., 'input:keydown', 'input:keyup') forwarded from the parent window. Events are also dispatched as DOM CustomEvents on document with 'mp4e:' prefix.

mp4e.on('input:keydown', (data) => { if (data.key === 'Escape') mp4e.close(); });
mp4e.off(eventName, callback)(eventName: string, callback: (data: any) => void)

Unsubscribe from a named event. Pass the same callback reference used with on().

mp4e.off('input:keydown', myHandler);
mp4e.onVariableChange(callback)(callback: (variables: Record<string, any>, changedIds?: string[]) => void)

Register a callback that fires whenever variables change. The callback receives the full variables map and an optional array of changed variable IDs (undefined means full broadcast, e.g., initial load). Use changedIds to filter and avoid unnecessary work.

mp4e.onVariableChange((variables, changedIds) => { if (changedIds && !changedIds.includes('score')) return; scoreEl.textContent = variables.score; });
mp4e.onConfigUpdate(callback)(callback: (config: object) => void)

Register a callback that fires when the plugin configuration changes at runtime. This happens when template variables in config values are re-interpolated by the engine, or when media:// URLs are resolved.

mp4e.onConfigUpdate((config) => { document.getElementById('title').textContent = config.title; });
mp4e.onSubtitleWordClick(callback)(callback: (wordData: object) => void)

Register a callback that fires when a user clicks on a subtitle word. The callback receives word data including the clicked text and position.

mp4e.onSubtitleWordClick((data) => { console.log('Clicked word:', data.text); });
mp4e.emit(event, data)(event: string, data?: any)

Emits a custom event that the engine can listen for via event rules. Rules configured for 'plugin:<namespace>:<id>:<event>' will fire when this is called.

mp4e.emit('product-selected', { id: 'prod_123', price: 29.99 });
Event examples
// Subscribe to keyboard events forwarded from the parent
mp4e.on('input:keydown', (data) => {
  if (data.key === 'Escape') {
    mp4e.close();
  }
});

// Listen for config updates (when variables change config values)
mp4e.onConfigUpdate((newConfig) => {
  document.getElementById('title').textContent = newConfig.title;
});

// Listen for variable changes
mp4e.onVariableChange((variables, changedIds) => {
  // changedIds is an array of variable IDs that changed,
  // or undefined for full broadcast (e.g., initial load)
  if (changedIds && !changedIds.includes('score')) return;
  document.getElementById('score').textContent = variables.score;
});

// Unsubscribe from a named event
mp4e.off('input:keydown', myHandler);
DOM Custom Events
Most bridge events are also dispatched as DOM CustomEvents on document. For example, variable updates fire mp4e:variableUpdate, playback state fires mp4e:playbackState, and time updates fire mp4e:video:timeupdate. You can use document.addEventListener() as an alternative to the callback API.

Objects

Access detected objects in the video and their interaction statistics.

mp4e.getObject()async

Gets the object that triggered this plugin (for display plugins triggered by object clicks/hovers). Returns the object data from the engine. For overlay plugins not bound to objects, returns null.

Returns: Promise<object | null>

const obj = await mp4e.getObject(); if (obj) console.log('Object:', obj.label, obj.data);
mp4e.getVisibleObjects()async

Gets all currently visible objects in the video frame. Each object includes id, label, userLabel, data, and groupIds.

Returns: Promise<object[]>

const objects = await mp4e.getVisibleObjects(); objects.forEach(obj => console.log(obj.label, obj.data));
mp4e.getVisibleOverlayTexts()async

Gets text content summaries of all currently visible overlays. Each entry includes id, type, and texts array. Useful for accessibility plugins or AI integrations.

Returns: Promise<{ id: string, type: string, texts: string[] }[]>

const texts = await mp4e.getVisibleOverlayTexts();
mp4e.getStats(objectId?)(objectId?: string)async

Gets interaction statistics. Pass an objectId for a single object's stats (hover count, click count, total view time), or omit to get the full stats map for all objects.

Returns: Promise<ObjectStats | Record<string, ObjectStats>>

const stats = await mp4e.getStats('obj_watch_1');

Action Methods

Fire-and-forget methods that trigger actions. These are convenience wrappers around common operations:

mp4e.openUrl(url, target)(url: string, target?: string)

Opens a URL. Target can be '_blank' (new tab), '_self' (same tab), etc. Default is '_blank'.

mp4e.openUrl('https://example.com', '_blank');

Styling & Animation

Style and animate the overlay container that wraps your plugin. These methods automatically target your own overlay — no need to know your overlay ID.

mp4e.setStyle(style, animate?)(style: StyleObject, animate?: AnimateOptions)async

Set visual style on the overlay container. Supports opacity, scale, rotation, borderRadius, boxShadow, filter, backdropFilter, backgroundColor, translateX, and translateY. Optionally animate the transition.

Returns: Promise

mp4e.setStyle({ opacity: 1, scale: 1.05 }, { durationMs: 300, easing: 'ease-out' });
mp4e.resetStyle()async

Reset overlay style back to metadata defaults, removing any styles applied by setStyle().

Returns: Promise

mp4e.resetStyle();
setStyle() / resetStyle() examples
// Fade in with scale
mp4e.setStyle(
  { opacity: 1, scale: 1.05 },
  { durationMs: 300, easing: 'ease-out' }
);

// Reset after delay
setTimeout(() => mp4e.resetStyle(), 2000);

// Apply style without animation
mp4e.setStyle({ borderRadius: '50%', rotation: 45 });

// Combine with wait for timed effects
mp4e.executeActions([
  { type: 'setOverlayStyle', overlayId: 'my-overlay',
    scale: 1.5, durationMs: 300, easing: 'ease-out' },
  { type: 'wait', durationMs: 2000 },
  { type: 'resetOverlayStyle', overlayId: 'my-overlay' }
]);
Available style properties
// Available style properties:
mp4e.setStyle({
  opacity: 0.5,              // 0-1 opacity
  scale: 1.2,                // Scale factor
  rotation: 45,              // Degrees
  borderRadius: '12px',      // CSS border-radius
  boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
  backgroundColor: '#ff000080',
  translateX: '10px',        // Horizontal offset
  translateY: '-5px',        // Vertical offset
  filter: 'blur(2px)',       // CSS filter
  backdropFilter: 'blur(8px)', // CSS backdrop-filter
}, {
  durationMs: 500,           // Animation duration (ms)
  easing: 'ease-out',        // CSS easing function
  delayMs: 100               // Animation delay (ms)
});
Use with wait for timed sequences
Combine setStyle with the wait action in executeActions to create timed animation sequences — for example, scale up on click, wait, then reset.

Positioning & Scale

Methods for responsive layout within the video player's coordinate system. The player applies CSS transforms for scaling, so these helpers ensure your plugin renders correctly at any size.

mp4e.getScaleFactor()

Returns the current viewport scale factor. The player scales overlays based on the video container size relative to the native video dimensions. Use this to adjust pixel-based measurements.

Returns: number

const scale = mp4e.getScaleFactor(); // e.g., 0.75
mp4e.scale(pixels)(pixels: number)

Scales a pixel value by the current scale factor. Shorthand for pixels * getScaleFactor().

Returns: number

const scaledSize = mp4e.scale(16); // e.g., 12 if scale is 0.75
mp4e.getCSSRect(element)(element: HTMLElement)

Returns element dimensions in unscaled CSS coordinates. Use this instead of getBoundingClientRect() when you need to set CSS properties like style.left or style.width. The player applies CSS transforms for scaling, so getBoundingClientRect() returns visual (post-transform) coordinates while CSS properties expect pre-transform values.

Returns: { left: number, top: number, width: number, height: number, right: number, bottom: number }

const rect = mp4e.getCSSRect(progressBar); const leftPx = (percent / 100) * (rect.width - thumbWidth); preview.style.left = leftPx + 'px';
When to use getCSSRect()
Use mp4e.getCSSRect(element) when positioning elements with CSS properties. The standard getBoundingClientRect() returns scaled/visual coordinates, but style.left, style.width, etc. expect unscaled CSS coordinates.
mp4e.requestLayout(opts)(opts: { position?: object, width?: number, height?: number, zIndex?: number, pointerEvents?: string })

Request a container position/size change. The parent player handles the actual DOM changes to the overlay container. Use this when your plugin needs to dynamically resize or reposition itself.

mp4e.requestLayout({ width: 400, height: 300, zIndex: 100 });
mp4e.getViewport()

Get the current viewport dimensions of the video player area. Returns actual screen pixel dimensions, the scale factor, whether touch is available, and the orientation.

Returns: { width: number, height: number, scaleFactor: number, hasTouch: boolean, orientation: 'landscape' | 'portrait' }

const vp = mp4e.getViewport(); if (vp.hasTouch) enableTouchControls();
mp4e.onViewportChange(callback)(callback: (viewport: ViewportInfo) => void)

Subscribe to viewport size changes. The callback fires on resize, orientation change, and scale factor updates. Receives the same object shape as getViewport().

mp4e.onViewportChange((vp) => { if (vp.width < 480) switchToCompactLayout(); });
mp4e.offViewportChange(callback)(callback: (viewport: ViewportInfo) => void)

Unsubscribe from viewport changes. Pass the same callback reference used with onViewportChange().

mp4e.offViewportChange(myCallback);
Viewport examples
// Get current viewport dimensions
const viewport = mp4e.getViewport();
// { width: 1280, height: 720, scaleFactor: 0.75, hasTouch: false, orientation: 'landscape' }

// Respond to viewport changes (resize, orientation, scale)
mp4e.onViewportChange((viewport) => {
  if (viewport.orientation === 'portrait') {
    document.body.classList.add('mobile-layout');
  } else {
    document.body.classList.remove('mobile-layout');
  }
});

// Later, unsubscribe
mp4e.offViewportChange(myCallback);
CSS Custom Properties
The bridge automatically sets --vw, --vh, --scale, --s, and --has-touch CSS custom properties on the document root. You can use these in your plugin CSS for responsive layouts without any JavaScript.

Media

Access media assets embedded directly in the MP4 file. Media items use portable media:// URLs that the bridge resolves to usable blob URLs at runtime.

mp4e.getMediaUrl(urlOrId)(urlOrId: string)async

Resolve a media:// URL (or raw media ID) to a usable blob URL. The host bridge loads the media from the MP4's custom atoms and returns a cached blob URL. Works with both 'media://my-image' and 'my-image' formats.

Returns: Promise<string>

const url = await mp4e.getMediaUrl('media://logo'); document.getElementById('logo').src = url;
mp4e.downloadMedia(urlOrId, filename?)(urlOrId: string, filename?: string)async

Download an embedded media asset. Optionally provide a filename; otherwise uses the manifest metadata if available.

Returns: Promise<void>

await mp4e.downloadMedia('media://brochure', 'product-brochure.pdf');
mp4e.listMedia()async

List all media items embedded in the video file. Returns an array of objects with id, type (MIME), purpose, and metadata.

Returns: Promise<MediaItem[]>

const items = await mp4e.listMedia(); items.forEach(item => console.log(item.id, item.type));
mp4e.statMedia(mediaId)(mediaId: string)async

Get metadata for a specific media item without loading the binary data. Returns the media item info or null if not found.

Returns: Promise<MediaItem | null>

const info = await mp4e.statMedia('product-image'); if (info) console.log('Type:', info.type, 'Size:', info.metadata?.size);
Media examples
// Resolve a media:// URL to a usable blob URL
const blobUrl = await mp4e.getMediaUrl('media://product-image-001');
document.getElementById('img').src = blobUrl;

// Download an embedded asset
await mp4e.downloadMedia('media://brochure-pdf', 'brochure.pdf');

// List all embedded media
const items = await mp4e.listMedia();
// [{ id: 'product-image-001', type: 'image/png', purpose: 'asset', metadata: {...} }, ...]

// Get metadata for a specific item without loading the binary
const info = await mp4e.statMedia('product-image-001');
// { id: 'product-image-001', type: 'image/png', purpose: 'asset', metadata: { width: 800, height: 600 } }
Automatic media:// Resolution in HTML
You do not need to call getMediaUrl() manually for media:// URLs used in HTML template attributes (e.g., src="media://my-image"). The bridge automatically resolves these via the template interpolation system.

Storage

Persistent key-value storage for plugins. Storage is sandboxed per plugin ID — each plugin can only access its own data. All methods are asynchronous because they proxy through the parent window.

mp4e.storage.get(key)(key: string)async

Get a stored value by key. Returns the deserialized value, or undefined if the key does not exist.

Returns: Promise<any>

const theme = await mp4e.storage.get('theme');
mp4e.storage.set(key, value)(key: string, value: any)async

Store a value. The value is serialized and stored in the parent window's localStorage, namespaced to this plugin ID.

Returns: Promise<void>

await mp4e.storage.set('theme', 'dark');
mp4e.storage.remove(key)(key: string)async

Remove a stored value by key.

Returns: Promise<void>

await mp4e.storage.remove('theme');
mp4e.storage.clear()async

Clear all stored values for this plugin. Does not affect other plugins' storage.

Returns: Promise<void>

await mp4e.storage.clear();
mp4e.storage.keys()async

List all stored keys for this plugin.

Returns: Promise<string[]>

const keys = await mp4e.storage.keys(); // ['theme', 'fontSize']
Storage examples
// Store user preferences
await mp4e.storage.set('theme', 'dark');
await mp4e.storage.set('fontSize', 14);

// Retrieve stored values
const theme = await mp4e.storage.get('theme');   // 'dark'
const size = await mp4e.storage.get('fontSize');  // 14

// List all keys
const keys = await mp4e.storage.keys();  // ['theme', 'fontSize']

// Remove a single key
await mp4e.storage.remove('theme');

// Clear all plugin storage
await mp4e.storage.clear();
Viewer Privacy Preferences
When the viewer has enabled the limitStorage preference, storage automatically falls back to sessionStorage instead of localStorage. This means data will not persist across sessions. Your plugin should handle this gracefully.

Network

Plugin iframes are sandboxed with a Content Security Policy that blocks direct fetch() and XMLHttpRequest calls. Use mp4e.fetch() to proxy network requests through the parent window.

mp4e.fetch(url, options?)(url: string, options?: { method?: string, headers?: object, body?: any })async

CSP-proxied fetch. Performs a network request through the parent window, which enforces domain allowlists from the plugin security configuration. The host can gate requests via the onNetworkGate callback for domains not in the allowlist.

Returns: Promise<{ ok: boolean, status: number, headers: object, data: any }>

const res = await mp4e.fetch('https://api.example.com/data'); if (res.ok) console.log(res.data);
Fetch examples
// GET request
const response = await mp4e.fetch('https://api.example.com/products');
if (response.ok) {
  const products = response.data;
  console.log('Products:', products);
}

// POST request with body
const result = await mp4e.fetch('https://api.example.com/cart', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ productId: 'prod_123', quantity: 1 })
});

if (result.ok) {
  mp4e.executeActions([
    { type: 'showNotification', message: 'Added to cart!', type: 'success' }
  ]);
}
Domain Allowlist
The host application controls which domains plugins can access. Requests to domains not in the allowlist will be routed through the onNetworkGate callback for approval. If no callback is configured, ungated requests will be rejected.

Methods for custom controls plugins to access and interact with the player menu system, chapter markers, and thumbnail previews.

mp4e.getMenuConfig()

Get the player menu configuration from metadata. Returns the menu structure including system items, plugin items, custom items, and settings.

Returns: object | null

const menu = mp4e.getMenuConfig();
mp4e.executeMenuItemActions(item)(item: { actions: Action[] })

Execute the configured actions for a menu item. Useful when building custom menu UIs that need to trigger the same actions as the built-in player menu.

mp4e.executeMenuItemActions(menuItem);
mp4e.getVisibleMenuItems()async

Get currently visible custom menu items from the menu configuration. Filters out disabled items.

Returns: Promise<MenuItem[]>

const items = await mp4e.getVisibleMenuItems();
mp4e.getMarkers()

Get timeline chapter markers. Returns an array of marker objects with time, label, and optional color.

Returns: Marker[]

const markers = mp4e.getMarkers(); // [{ time: 0, label: 'Intro' }, { time: 30, label: 'Chapter 1' }]
mp4e.getCurrentMarker()

Get the chapter marker for the current playback position.

Returns: Marker | null

const marker = mp4e.getCurrentMarker(); if (marker) chapterLabel.textContent = marker.label;
mp4e.getMarkerAtTime(time)(time: number)

Get the chapter marker active at a specific time. Returns the last marker whose time is less than or equal to the given time.

Returns: Marker | null

const marker = mp4e.getMarkerAtTime(45.0);
mp4e.getMarkerSettings()

Get marker display settings from metadata (colors, visibility, positioning).

Returns: object | null

const settings = mp4e.getMarkerSettings();
mp4e.getThumbnails()

Get thumbnail strip configuration including enabled state, interval, dimensions, and sprite column count.

Returns: object | null

const thumbs = mp4e.getThumbnails(); if (thumbs?.enabled) console.log('Thumbnail interval:', thumbs.interval);
mp4e.getThumbnailSpriteUrl()

Get the URL of the thumbnail sprite sheet image.

Returns: string | null

const spriteUrl = mp4e.getThumbnailSpriteUrl();
mp4e.getThumbnailPosition(time)(time: number)

Calculate the sprite sheet position for a thumbnail at a specific time. Returns the backgroundPosition CSS value, dimensions, and grid coordinates.

Returns: { backgroundPosition: string, width: number, height: number, index: number, col: number, row: number } | null

const pos = mp4e.getThumbnailPosition(30); if (pos) { preview.style.backgroundPosition = pos.backgroundPosition; preview.style.width = pos.width + 'px'; preview.style.height = pos.height + 'px'; }
mp4e.enableThumbnailPreview(progressBarElement)(progressBarElement: HTMLElement)

Attach thumbnail hover detection to a progress bar element. On hover, the bridge sends thumbnail position data to the parent player for rendering the preview outside the iframe (to avoid clipping). Supports both mouse and touch events. Only needs to be called once per element. Controls plugins can also add the data-mp4e-timeline attribute to their progress bar for automatic detection.

const bar = document.getElementById('progressBar'); mp4e.enableThumbnailPreview(bar);

Plugin-to-Plugin Communication

Plugins can communicate with each other through a request-response action system. One plugin registers action handlers, and other plugins call those actions by the target plugin's ID. This is the primary mechanism for service plugins (e.g., shopping cart, analytics) to expose functionality to other plugins.

mp4e.onAction(actionId, handler)(actionId: string, handler: (config: object) => any | Promise<any>)

Register an action handler that other plugins can call. The handler receives the config object passed by the caller and can return a value (or a Promise). Multiple actions can be registered per plugin.

mp4e.onAction('addItem', (config) => { cart.push({ name: config.productName, price: config.price }); return { success: true, itemCount: cart.length }; });
mp4e.callAction(targetPluginId, actionId, config?)(targetPluginId: string, actionId: string, config?: object)async

Call another plugin's registered action. The request is routed through the parent to the target plugin's iframe. Returns the handler's return value.

Returns: Promise<any>

const result = await mp4e.callAction('cart', 'addItem', { productName: 'Blue T-Shirt', price: 29.99 });
mp4e.callPluginAction(targetPluginId, actionId, config?)(targetPluginId: string, actionId: string, config?: object)async

Alias for callAction(). Matches the naming convention used by the service plugin system.

Returns: Promise<any>

const result = await mp4e.callPluginAction('analytics', 'track', { event: 'view' });
Plugin-to-plugin communication
// Shopping cart plugin registers action handler
mp4e.onAction('addItem', (config) => {
  cart.push({ name: config.productName, price: config.price });
  mp4e.setVariable('cart:itemCount', cart.length);
  return { success: true };
});

// Product card plugin calls cart action
const result = await mp4e.callAction('cart', 'addItem', {
  productName: 'Blue T-Shirt',
  price: 29.99
});
Service Plugins
Service plugins (type: 'service') run in the background and are the ideal place to register action handlers. They auto-create variables with a plugin ID prefix (e.g., cart:items) and can emit events that other plugins' rules listen for.

executeActions()

The most powerful method - execute any action or sequence of actions. Actions are processed in order by the engine. This routes through the WASM engine, which means all actions trigger proper rule evaluation and state updates.

mp4e.executeActions(actions)(actions: Action[])

Executes an array of actions. Actions are processed in order by the engine. Each action is a plain object with a 'type' field and type-specific parameters.

mp4e.executeActions([{ type: 'pause' }, { type: 'showNotification', message: 'Hello!' }]);
executeActions() overview
// Execute one or more actions
mp4e.executeActions([
  // Playback actions
  { type: 'pause' },
  { type: 'seek', time: 45 },
  { type: 'setPlaybackRate', rate: 1.5 },
  { type: 'setVolume', volume: 0.8 },

  // Navigation
  { type: 'openUrl', url: 'https://example.com', target: '_blank' },
  { type: 'goToScene', sceneId: 'ending' },

  // UI actions
  { type: 'showOverlay', overlayId: 'promo-banner' },
  { type: 'hideOverlay', overlayId: 'intro-tooltip' },
  { type: 'showNotification', message: 'Added to cart!', type: 'success' },
  { type: 'enterFullscreen' },

  // Variable actions
  { type: 'setVariable', name: 'cartCount', value: 5 },
  { type: 'setVariable', name: 'total', operation: 'add', value: 99.99 },

  // Custom plugin actions
  {
    type: 'showCustomModal',
    pluginId: 'plugin_checkout_form',
    config: {
      title: 'Complete Purchase',
      productName: object.label,
      price: object.data.price
    },
    pauseVideo: true
  },

  // Analytics
  { type: 'trackEvent', event: 'add_to_cart', data: { product: object.id } },

  // API calls
  {
    type: 'api',
    url: 'https://api.example.com/cart',
    method: 'POST',
    body: { productId: object.data.sku },
    storeResultIn: 'apiResponse'
  }
]);

Playback Actions

Playback actions
// Control video playback
mp4e.executeActions([
  { type: 'play' },                    // Start playback
  { type: 'pause' },                   // Pause playback
  { type: 'seek', time: 30 },          // Seek to 30 seconds
  { type: 'seekRelative', offset: -5 }, // Seek back 5 seconds
  { type: 'setPlaybackRate', rate: 2 }, // 2x speed
  { type: 'setVolume', volume: 0.5 },  // 50% volume
  { type: 'mute' },                     // Mute audio
  { type: 'unmute' },                   // Unmute audio
  { type: 'setPlaybackStart', time: 10 }, // Set start point
  { type: 'setPlaybackEnd', time: 60 },   // Set end point
]);

Navigation Actions

Navigation actions
// Navigation and URL handling
mp4e.executeActions([
  { type: 'goToScene', sceneId: 'chapter-3' },
  { type: 'openUrl', url: 'https://example.com', target: '_blank' },
  { type: 'openUrl', url: '/product/123', target: '_self' },
]);

UI Actions

UI actions
// UI control
mp4e.executeActions([
  { type: 'showOverlay', overlayId: 'overlay_banner' },
  { type: 'hideOverlay', overlayId: 'overlay_intro' },
  { type: 'toggleOverlay', overlayId: 'overlay_menu' },
  { type: 'activateLayer', layerId: 'layer_products' },
  { type: 'deactivateLayer', layerId: 'layer_tutorial' },
  { type: 'highlightObject', objectId: 'obj_chair_1', duration: 3000 },
  { type: 'showNotification', message: 'Welcome!', type: 'info', duration: 5000 },
  { type: 'enterFullscreen' },
  { type: 'exitFullscreen' },
]);

Variable Actions

Variable actions
// Variable manipulation
mp4e.executeActions([
  // Set value directly
  { type: 'setVariable', name: 'score', value: 100 },

  // Mathematical operations
  { type: 'setVariable', name: 'score', operation: 'add', value: 10 },
  { type: 'setVariable', name: 'score', operation: 'subtract', value: 5 },
  { type: 'setVariable', name: 'score', operation: 'multiply', value: 2 },
  { type: 'setVariable', name: 'score', operation: 'divide', value: 4 },

  // String operations
  { type: 'setVariable', name: 'message', operation: 'append', value: ' World' },

  // Timer control
  { type: 'timerControl', name: 'watchTimer', action: 'start' },
  { type: 'timerControl', name: 'watchTimer', action: 'pause' },
  { type: 'timerControl', name: 'watchTimer', action: 'reset' },

  // Array operations
  { type: 'arrayControl', name: 'cart', action: 'push', value: 'item-1' },
  { type: 'arrayControl', name: 'cart', action: 'pop' },
  { type: 'arrayControl', name: 'cart', action: 'remove', value: 'item-1' },
  { type: 'arrayControl', name: 'cart', action: 'clear' },

  // Custom events
  { type: 'emitEvent', event: 'product-selected', data: { id: 'prod_123' } },
]);

Dialog Actions

Dialog actions
// Built-in dialogs
mp4e.executeActions([
  // Simple alert
  { type: 'alert', message: 'Video complete!' },

  // Confirmation dialog
  {
    type: 'confirm',
    message: 'Add to cart?',
    onConfirm: [
      { type: 'setVariable', name: 'cartCount', operation: 'add', value: 1 },
      { type: 'showNotification', message: 'Added!', type: 'success' }
    ],
    onCancel: [
      { type: 'showNotification', message: 'Cancelled', type: 'info' }
    ]
  },

  // Prompt for input
  {
    type: 'prompt',
    message: 'Enter quantity:',
    inputType: 'number',
    defaultValue: '1',
    storeResultIn: 'userQuantity'
  }
]);

Plugin Actions

Custom plugin actions
// Custom plugin modal
mp4e.executeActions([
  {
    type: 'showCustomModal',
    pluginId: 'plugin_checkout',  // Your plugin ID from the editor
    config: {
      title: 'Complete Purchase',
      productName: object.label,
      productImage: object.data.imageUrl,
      price: object.data.price,
      currency: 'USD'
    },
    showBackdrop: true,
    pauseVideo: true   // Pause video while modal is open
  }
]);

// Custom overlay
mp4e.executeActions([
  {
    type: 'showCustomOverlay',
    pluginId: 'plugin_banner',
    position: { x: 50, y: 10 },  // Percentage position
    config: { text: 'Limited time offer!' }
  },
  { type: 'hideCustomOverlay', pluginId: 'plugin_banner' }
]);

Analytics & API Actions

Analytics and API actions
// Analytics and API
mp4e.executeActions([
  // Track custom event
  {
    type: 'trackEvent',
    event: 'product_view',
    data: {
      productId: object.data.sku,
      productName: object.label,
      category: 'electronics'
    }
  },

  // Make API call
  {
    type: 'api',
    url: 'https://api.example.com/track',
    method: 'POST',
    headers: { 'Authorization': 'Bearer token123' },
    body: {
      event: 'video_interaction',
      objectId: object.id
    },
    storeResultIn: 'apiResponse'
  },

  // Debug logging
  { type: 'log', message: 'User clicked: ' + object.label },

  // Wait before next action
  { type: 'wait', durationMs: 1000 }  // 1 second
]);

Complete Example

A full example showing a product card plugin with add-to-cart and buy-now functionality:

Complete product card plugin
1// Complete example: Interactive product card plugin
2
3// ===== HTML (in plugin editor) =====
4<div class="product-card">
5 <img id="product-image" src="{{object.data.imageUrl}}" />
6 <h3 id="product-name">{{object.userLabel}}</h3>
7 <p id="product-price"></p>
8 <button id="add-to-cart">Add to Cart</button>
9 <button id="buy-now">Buy Now</button>
10</div>
11
12// ===== CSS =====
13.product-card {
14 background: white;
15 border-radius: 12px;
16 padding: 16px;
17 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
18}
19#product-image { width: 100%; border-radius: 8px; }
20button {
21 padding: 8px 16px;
22 border-radius: 6px;
23 cursor: pointer;
24 margin-top: 8px;
25}
26#add-to-cart { background: #3b82f6; color: white; }
27#buy-now { background: #10b981; color: white; }
28
29// ===== JavaScript =====
30(async function() {
31 // Get config and format price
32 const config = mp4e.getConfig();
33 const price = object.data?.price || 0;
34 const formatted = new Intl.NumberFormat('en-US', {
35 style: 'currency',
36 currency: config.currency || 'USD'
37 }).format(price);
38 document.getElementById('product-price').textContent = formatted;
39
40 // Add to Cart button
41 document.getElementById('add-to-cart').addEventListener('click', async () => {
42 // Get current cart count
43 const cartCount = await mp4e.getVariable('cartCount') || 0;
44
45 // Update cart
46 await mp4e.setVariable('selectedProduct', object.userLabel || object.label);
47 await mp4e.setVariable('selectedPrice', price);
48 await mp4e.setVariable('cartCount', cartCount + 1);
49
50 // Show notification
51 mp4e.executeActions([
52 { type: 'showNotification', message: 'Added to cart!', type: 'success' },
53 { type: 'trackEvent', event: 'add_to_cart', data: { product: object.id, price } }
54 ]);
55
56 // Close this plugin
57 mp4e.close();
58 });
59
60 // Buy Now button
61 document.getElementById('buy-now').addEventListener('click', () => {
62 mp4e.executeActions([
63 { type: 'pause' },
64 {
65 type: 'showCustomModal',
66 pluginId: 'plugin_checkout_form',
67 config: {
68 productName: object.userLabel || object.label,
69 productImage: object.data?.imageUrl,
70 price: price,
71 sku: object.data?.sku
72 },
73 pauseVideo: true,
74 showBackdrop: true
75 },
76 { type: 'trackEvent', event: 'buy_now_click', data: { product: object.id } }
77 ]);
78 });
79})();