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.
mp4e bridge API.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:
// 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. 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.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.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.isVisible()Check if the overlay is currently visible. Returns false after hide() has been called, true after show() or initially.
Returns: boolean
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.getPluginId()Returns this plugin's unique ID. For pool-managed (group-bound) overlays, this includes the bound object ID.
Returns: string
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
Always use mp4e.getConfig() to access configuration in JavaScript. Do NOT use mp4e.config - it doesn't exist!
const config = window.mp4e?.getConfig();const config = mp4e.config;(doesn't exist)const title = '{{title}}';(template syntax only works in HTML/CSS)// 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 }
// 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
mp4e.getVideoState()asyncGets the full video state object from the engine including current time, duration, playing state, volume, and more.
Returns: Promise<object>
mp4e.log(message)(message: string)Logs a message to the browser console with a plugin prefix. Useful for debugging.
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)asyncGets the current value of a variable from the engine.
Returns: Promise<any>
mp4e.setVariable(name, value)(name: string, value: any)asyncSets a variable value in the engine. This triggers the rules engine to re-evaluate any rules that depend on this variable.
Returns: Promise<void>
// 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.pauseVideo()Pauses video playback. Alias: mp4e.pause().
mp4e.seekToTime(seconds)(seconds: number)Seeks the video to a specific time in seconds.
mp4e.goToScene(sceneId)(sceneId: string)Jumps to a named scene. The engine resolves the scene to a seek time internally.
mp4e.setVolume(volume)(volume: number)Sets the video volume. Value should be between 0 (muted) and 1 (full volume).
mp4e.setPlaybackRate(rate)(rate: number)Sets the playback speed. 1 is normal speed, 0.5 is half speed, 2 is double speed.
mp4e.setSubtitleTrack(trackId)(trackId: string)Activates a subtitle track by its ID.
mp4e.setAudioTrack(trackId)(trackId: string)Switches to a different audio track by its ID.
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
mp4e.getCurrentFrame()Gets the current video frame number.
Returns: number
mp4e.getDuration()Gets the total video duration in seconds. Returns 0 if not yet loaded.
Returns: number
mp4e.isPlaying()Check if the video is currently playing.
Returns: boolean
mp4e.isBuffering()Check if the video is currently buffering.
Returns: boolean
mp4e.getVolume()Gets the current volume level (0 to 1).
Returns: number
mp4e.isMuted()Check if the video is currently muted.
Returns: boolean
mp4e.getPlaybackRate()Gets the current playback speed.
Returns: number
mp4e.isFullscreen()Check if the player is currently in fullscreen mode.
Returns: boolean
// 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 fullscreenTrack Information
mp4e.getTracks()Returns all available tracks organized by type.
Returns: { subtitles: Track[], audio: Track[] }
mp4e.getSubtitleTracks()Returns available subtitle tracks.
Returns: Track[]
mp4e.getAudioTracks()Returns available audio tracks.
Returns: Track[]
mp4e.getActiveSubtitleTrackIds()Returns the IDs of currently active subtitle tracks.
Returns: string[]
mp4e.getState()Returns the current player state including viewer preferences and active tracks.
Returns: object
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.off(eventName, callback)(eventName: string, callback: (data: any) => void)Unsubscribe from a named event. Pass the same callback reference used with on().
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.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.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.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.
// 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);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()asyncGets 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>
mp4e.getVisibleObjects()asyncGets all currently visible objects in the video frame. Each object includes id, label, userLabel, data, and groupIds.
Returns: Promise<object[]>
mp4e.getVisibleOverlayTexts()asyncGets 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[] }[]>
mp4e.getStats(objectId?)(objectId?: string)asyncGets 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>>
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'.
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)asyncSet 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.resetStyle()asyncReset overlay style back to metadata defaults, removing any styles applied by setStyle().
Returns: Promise
// 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:
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)
});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
mp4e.scale(pixels)(pixels: number)Scales a pixel value by the current scale factor. Shorthand for pixels * getScaleFactor().
Returns: number
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 }
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.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' }
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.offViewportChange(callback)(callback: (viewport: ViewportInfo) => void)Unsubscribe from viewport changes. Pass the same callback reference used with onViewportChange().
// 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);--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)asyncResolve 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>
mp4e.downloadMedia(urlOrId, filename?)(urlOrId: string, filename?: string)asyncDownload an embedded media asset. Optionally provide a filename; otherwise uses the manifest metadata if available.
Returns: Promise<void>
mp4e.listMedia()asyncList all media items embedded in the video file. Returns an array of objects with id, type (MIME), purpose, and metadata.
Returns: Promise<MediaItem[]>
mp4e.statMedia(mediaId)(mediaId: string)asyncGet 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>
// 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 } }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)asyncGet a stored value by key. Returns the deserialized value, or undefined if the key does not exist.
Returns: Promise<any>
mp4e.storage.set(key, value)(key: string, value: any)asyncStore a value. The value is serialized and stored in the parent window's localStorage, namespaced to this plugin ID.
Returns: Promise<void>
mp4e.storage.remove(key)(key: string)asyncRemove a stored value by key.
Returns: Promise<void>
mp4e.storage.clear()asyncClear all stored values for this plugin. Does not affect other plugins' storage.
Returns: Promise<void>
mp4e.storage.keys()asyncList all stored keys for this plugin.
Returns: Promise<string[]>
// 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();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 })asyncCSP-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 }>
// 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' }
]);
}onNetworkGate callback for approval. If no callback is configured, ungated requests will be rejected.Menu & UI
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
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.getVisibleMenuItems()asyncGet currently visible custom menu items from the menu configuration. Filters out disabled items.
Returns: Promise<MenuItem[]>
mp4e.getMarkers()Get timeline chapter markers. Returns an array of marker objects with time, label, and optional color.
Returns: Marker[]
mp4e.getCurrentMarker()Get the chapter marker for the current playback position.
Returns: Marker | null
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
mp4e.getMarkerSettings()Get marker display settings from metadata (colors, visibility, positioning).
Returns: object | null
mp4e.getThumbnails()Get thumbnail strip configuration including enabled state, interval, dimensions, and sprite column count.
Returns: object | null
mp4e.getThumbnailSpriteUrl()Get the URL of the thumbnail sprite sheet image.
Returns: string | null
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
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.
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.callAction(targetPluginId, actionId, config?)(targetPluginId: string, actionId: string, config?: object)asyncCall 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>
mp4e.callPluginAction(targetPluginId, actionId, config?)(targetPluginId: string, actionId: string, config?: object)asyncAlias for callAction(). Matches the naming convention used by the service plugin system.
Returns: Promise<any>
// 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
});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.
// 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
// 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 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 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 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
// 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 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
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:
1// Complete example: Interactive product card plugin23// ===== 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>1112// ===== 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; }2829// ===== JavaScript =====30(async function() {31 // Get config and format price32 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;3940 // Add to Cart button41 document.getElementById('add-to-cart').addEventListener('click', async () => {42 // Get current cart count43 const cartCount = await mp4e.getVariable('cartCount') || 0;4445 // Update cart46 await mp4e.setVariable('selectedProduct', object.userLabel || object.label);47 await mp4e.setVariable('selectedPrice', price);48 await mp4e.setVariable('cartCount', cartCount + 1);4950 // Show notification51 mp4e.executeActions([52 { type: 'showNotification', message: 'Added to cart!', type: 'success' },53 { type: 'trackEvent', event: 'add_to_cart', data: { product: object.id, price } }54 ]);5556 // Close this plugin57 mp4e.close();58 });5960 // Buy Now button61 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?.sku72 },73 pauseVideo: true,74 showBackdrop: true75 },76 { type: 'trackEvent', event: 'buy_now_click', data: { product: object.id } }77 ]);78 });79})();