Host Overrides
Inject layers, plugins, variables, and settings into any video at runtime without modifying the .mp4 file.
Overview
The overrides prop lets a host application deep-merge partial metadata into any MP4E video at runtime. The original .mp4 file is never modified — overrides are applied in memory by the player before the engine processes the metadata.
This keeps videos fully portable. The same video file plays identically on any host, while each host can layer its own branding, analytics, variables, restrictions, and rules on top.
metadata nor overrides requires the source file to carry MP4E metadata at all. An ordinary .mp4 with no embedded atom still gets the full interactive engine — the player generates a minimal base document from the video’s own dimensions and duration, and overrides merges on top of that exactly the same way it merges on top of an authored file. This is what makes it possible to make any video interactive purely from the host side.metadata containing the correct videoDetails.fps — the generated base for an unauthored video defaults fps to 30, which may not match the source.How It Works
Once the player has an effective base document — host-provided, extracted from the MP4 atom, or generated when the video carries no embedded metadata — it applies overrides using a deep-merge algorithm before passing the result to the WASM engine. The merge rules are:
| Data Shape | Merge Behavior |
|---|---|
| Record objects | Deep-merged recursively. Override keys win; base-only keys are preserved. |
| Arrays of { id } objects | Matched by id. If an override item has the same ID as a base item, the two are deep-merged. New IDs are appended to the end. |
| layers (special case) | ID-match merge with cascade: the overlays array within each matched layer is also ID-merged. |
| Plain arrays (no id) | Override replaces entirely. |
| Primitives | Override replaces entirely. |
The following example shows a full merge from base metadata through overrides to the final result:
1// Original metadata (embedded in the .mp4 file)2{3 settings: {4 restrictions: { navigate: true, download: true }5 },6 layers: [7 {8 id: 'layer-1',9 name: 'Products',10 overlays: [11 { id: 'overlay-a', type: 'core:button', config: { text: 'Buy' } }12 ]13 }14 ],15 variables: {16 score: { type: 'number', value: 0 }17 }18}1920// Overrides (provided by host at runtime)21{22 settings: {23 restrictions: { navigate: false }24 },25 layers: [26 {27 id: 'layer-1',28 overlays: [29 { id: 'overlay-a', config: { text: 'Purchase Now' } }30 ]31 },32 {33 id: '__host_brand__',34 name: 'Branding',35 overlays: [{ id: 'logo', type: 'core:image', config: { src: '/logo.png' } }]36 }37 ],38 variables: {39 userName: { type: 'text', value: 'Alice' }40 }41}4243// Result after merge44{45 settings: {46 restrictions: { navigate: false, download: true } // deep-merged: navigate overridden47 },48 layers: [49 {50 id: 'layer-1',51 name: 'Products', // preserved from base52 overlays: [53 { id: 'overlay-a', type: 'core:button', config: { text: 'Purchase Now' } }54 // ^ ID-matched, config overridden55 ]56 },57 {58 id: '__host_brand__', // new layer appended59 name: 'Branding',60 overlays: [{ id: 'logo', type: 'core:image', config: { src: '/logo.png' } }]61 }62 ],63 variables: {64 score: { type: 'number', value: 0 }, // preserved from base65 userName: { type: 'text', value: 'Alice' } // new key added66 }67}Updating Overrides at Runtime
Re-render <MP4EPlayer> with a new overrides value at any point — during playback, in response to a state change, on a timer — and the player picks it up without reconstructing the engine or reloading the video source.
Each new overrides value replaces the previous one outright; it is not merged into whatever was showing a moment ago. Passing {}, null, or undefined clears the override layer entirely, restoring metadata (or the generated base, if none was provided) unchanged. A parent re-render that produces a new object with identical content is a no-op — only an actual content change triggers an update.
overrides live doesn’t reset the video. Playback position, paused state, variable values, running timers, counters, the current scene, and any layers the viewer has toggled at runtime all carry through the swap when the same ids still exist in the new effective document — only content that actually changed (a new/removed layer, overlay, or variable, or a variable whose type changed) updates. A viewer mid-video with items in a cart or progress through a quiz keeps that state while you push a new promo banner, swap in updated pricing, or toggle a restriction.1function VideoWithPromo({ videoUrl, promoActive }) {2 return (3 <MP4EPlayer4 src={videoUrl}5 // Re-rendering with a new overrides value REPLACES the current6 // override layer outright — it does not merge into whatever was7 // showing before. The base video (or generated base, if there's no8 // authored metadata) is untouched either way.9 overrides={10 promoActive11 ? {12 layers: [{13 id: '__host_promo__',14 name: 'Promo',15 order: 1000, // explicit — see "Host-Injected Layers" below16 overlays: [{17 id: 'promo-banner',18 type: 'core:banner',19 zIndex: 1000,20 config: { text: '20% off today only' },21 }],22 }],23 }24 : undefined // clears the override layer, restoring the base25 }26 />27 );28}Both <MP4EPlayer overrides={...}> and the imperative playerRef.current?.reloadMetadata() method (see Methods Reference) apply the new document asynchronously — already-mounted plugins are refreshed with freshly re-interpolated content once the swap lands, with no visible flash or DOM teardown.
Override Structure
The overrides prop mirrors the MP4E metadata structure exactly. Any top-level metadata field can be overridden: layers, plugins, variables, settings, projectSettings, scenes, and more.
1<MP4EPlayer2 src="/video.mp4"3 overrides={{4 layers: [{5 id: '__host_branding__',6 name: 'Host Branding',7 overlays: [{8 id: 'watermark',9 type: 'core:image',10 position: {11 x: 90, y: 5,12 width: 8, height: 8,13 xUnit: '%', yUnit: '%',14 widthUnit: '%', heightUnit: '%'15 },16 config: { src: 'https://myapp.com/logo.png' }17 }]18 }],19 plugins: {20 'host-analytics': {21 id: 'host-analytics',22 type: 'mp4e:analytics',23 source: 'external',24 config: { trackingId: 'UA-XXXXX' }25 }26 },27 variables: {28 hostApiKey: { type: 'text', value: 'sk_live_xxx' },29 userName: { type: 'text', value: 'John' }30 },31 settings: {32 restrictions: { navigate: false, download: false }33 }34 }}35/>Common Use Cases
Branding Watermark
Add a persistent logo overlay to every video on your platform without editing each file.
1// Add a watermark logo to every video2<MP4EPlayer3 src={videoUrl}4 overrides={{5 layers: [{6 id: '__host_watermark__',7 name: 'Watermark',8 overlays: [{9 id: 'logo',10 type: 'core:image',11 position: {12 x: 92, y: 4,13 width: 6, height: 6,14 xUnit: '%', yUnit: '%',15 widthUnit: '%', heightUnit: '%'16 },17 config: {18 src: 'https://myapp.com/assets/logo-white.png',19 opacity: 0.720 }21 }]22 }]23 }}24/>Analytics Service Plugin
Inject a service plugin that tracks viewer interactions and reports them to your analytics backend.
1// Inject an analytics service plugin2<MP4EPlayer3 src={videoUrl}4 overrides={{5 plugins: {6 'host-analytics': {7 id: 'host-analytics',8 type: 'mp4e:analytics',9 source: 'external',10 config: {11 trackingId: 'UA-XXXXX',12 endpoint: 'https://myapp.com/api/analytics'13 }14 }15 }16 }}17/>Override Restrictions
Enforce sequential playback or disable downloads for content that requires it, regardless of what the video creator originally configured.
1// Disable seeking and downloading for paid content2<MP4EPlayer3 src={videoUrl}4 overrides={{5 settings: {6 restrictions: {7 navigate: false, // Disable seeking (sequential viewing only)8 download: false // Remove download option9 }10 }11 }}12/>Inject Host-Specific Variables
Provide runtime variables that plugins can reference via {{variableName}} interpolation. This is how hosts pass user context, API keys, and locale data into videos.
1// Inject host-specific variables that plugins can reference2<MP4EPlayer3 src={videoUrl}4 overrides={{5 variables: {6 userName: { type: 'text', value: currentUser.name },7 userTier: { type: 'text', value: currentUser.tier },8 apiBaseUrl: { type: 'text', value: 'https://myapp.com/api' },9 currency: { type: 'text', value: userLocale.currency }10 }11 }}12/>Host-Controlled Rules
Add event handlers that fire on scene transitions, overlay clicks, or any engine event. The host can wire up its own analytics, navigation, or business logic.
1// Add host-controlled event handlers2<MP4EPlayer3 src={videoUrl}4 overrides={{5 projectSettings: {6 eventRules: {7 'scene:enter': [{8 type: 'pluginAction',9 pluginId: 'host-analytics',10 actionId: 'trackEvent',11 config: { event: 'scene_view' }12 }]13 }14 }15 }}16/>Overrides vs Config
overridesinjects content (additive) — adds layers, plugins, variables, settings into the metadata before the engine processes it.configrestricts behavior (subtractive) — disables actions, sandboxes plugins, gates dangerous operations, and limits what the engine can do at runtime.
Use overrides to put content in. Use config to keep behavior out. They are complementary, not interchangeable.
Host-Injected Layers
Layers added via overrides are appended after the video creator’s layers, but appending alone does not make them render on top. Stacking is controlled by each layer’s order and each overlay’s zIndex, exactly like creator content. A host layer that needs to sit above everything else must set an explicit order/zIndex higher than the creator’s content — there is no implicit host-priority tier.
By convention, prefix host layer and overlay IDs with __host__ to avoid collisions with creator-defined IDs. This also makes it easy to identify host-injected content when debugging.
1// Convention: prefix host layer IDs with __host__.2// order/zIndex are set explicitly here so this content renders ABOVE the3// creator's layers — appending alone would not guarantee that.4<MP4EPlayer5 src={videoUrl}6 overrides={{7 layers: [8 {9 id: '__host_branding__',10 name: 'Host Branding',11 order: 1000,12 overlays: [{13 id: '__host_logo__',14 type: 'core:image',15 zIndex: 1000,16 position: { x: 90, y: 5, width: 8, height: 8,17 xUnit: '%', yUnit: '%', widthUnit: '%', heightUnit: '%' },18 config: { src: '/logo.png' }19 }]20 },21 {22 id: '__host_cta__',23 name: 'Host Call-to-Action',24 order: 1001,25 overlays: [{26 id: '__host_signup_btn__',27 type: 'core:button',28 zIndex: 1001,29 position: { x: 50, y: 90, width: 20, height: 6,30 xUnit: '%', yUnit: '%', widthUnit: '%', heightUnit: '%' },31 config: { text: 'Sign Up Free' }32 }]33 }34 ]35 }}36/>order on the layer and zIndex on its overlays whenever a host override needs to render above (or below) the creator’s content.id as a creator layer, the two will be deep-merged (including their overlays). Use the __host__ prefix to ensure host layers are always separate from creator content.