Overlay Integration
Overview
This section walks you through integrating overlays into your Maestro Web SDK implementation.
There is no renderOverlay(). Unlike panels, where your app owns the container and
calls renderPanel(), the SDK creates its own #maestro__sdk__overlay
element during userDidStartWatchingEvent() and tears it down
during userDidStopWatchingEvent(). Your integration work is to
respond to lifecycle events, not to mount anything.
Integration Steps
1. Implement onOverlayEvent
Add onOverlayEvent to your IMaestroEventDelegate implementation.
This one method is the whole host-side API surface for overlays:
import SDK, {
IMaestroEventDelegate,
OverlayEvent,
} from "@maestro_io/maestro-web-sdk";
class MyEventDelegate implements IMaestroEventDelegate {
onOverlayEvent(event: OverlayEvent): void {
switch (event.type) {
case "will-appear":
// Approve the overlay and tell the SDK where to put it.
event.payload.callback({
canAppear: true,
canFocus: false,
suggestedPosition: { x: 64, y: 96 },
});
break;
case "did-appear":
console.log("Overlay appeared");
break;
case "did-engage":
console.log(
"Overlay CTA:",
event.payload.ctaType,
event.payload.ctaValue,
);
break;
case "did-dismiss":
console.log("Overlay dismissed");
break;
}
}
// ...the rest of your delegate implementation
}
const delegate = new MyEventDelegate();
const event = await SDK.userDidStartWatchingEvent({
pageId: "YOUR_PAGE_ID",
delegate,
});
That is enough to have overlays render. Everything below refines the behavior.
2. Handle the will-appear callback
When the SDK is about to show an overlay it emits will-appear with a callback. Call it with any combination of three
optional fields:
| Field | Type | Default | Description |
|---|---|---|---|
canAppear | boolean | true | Whether this overlay is allowed to appear. Pass false to suppress it. |
canFocus | boolean | false | Whether the SDK should take D-pad focus when the overlay appears. |
suggestedPosition | { x: number; y: number } | right: 0; top: 0 | Where to place the overlay, in pixels. |
Coordinates. The overlay container is position: fixed. x is the offset from the right edge of the viewport
and y is the offset from the top, both in pixels. With no suggestedPosition, the SDK uses right: 0; top: 0.
If you never invoke the callback, the SDK waits 2 seconds, applies its defaults (canAppear: true,
canFocus: false), and shows the overlay anyway. This is deliberate — a delegate that throws, hangs, or forgets a
branch must not be able to silently swallow scheduled content. Once the timeout has elapsed, calling the callback is a
no-op.
Call the callback synchronously, inside the will-appear branch. Deferring it behind a promise, a timer, or a
network request risks missing the window, and once the timeout has elapsed your answer is discarded.
3. Feed player timecode
Your application must push playback position into the SDK as playback progresses:
const event = SDK.getMaestroEventViewModel();
videoElement.addEventListener("timeupdate", () => {
event.updatePlayerTimeCode(videoElement.currentTime);
});
This is a requirement, not an optimization. TIMECODED overlays are scheduled against playback position, so without a
timecode the SDK has nothing to compare against and those overlays never fire. See
updatePlayerTimeCode.
4. Focus hand-off
If you answer canFocus: true, the SDK takes D-pad focus when the overlay appears. It hands focus back to your
application — by calling
delegate.startFocusManagement({ fromTarget: 'overlay', keyPress }) —
when the viewer arrows out of the overlay, presses back, or the overlay auto-dismisses while focused.
To send focus the other way, call
event.startFocusManagement({ toTarget: 'overlay' })
on the event instance. Both methods, and the wider focus model, are covered in the
Focus Management guide.
Save the element that had focus before the overlay appeared so you can restore it when focus comes back:
import { StartClientFocusManagementParams } from '@maestro_io/maestro-web-sdk';
// Inside your IMaestroEventDelegate implementation:
startFocusManagement(params: StartClientFocusManagementParams): Promise<void> {
if (params.fromTarget === 'overlay') {
this.lastFocusedBeforeOverlay?.focus();
} else {
document.getElementById('main-nav')?.focus();
}
return Promise.resolve();
}
5. Handle engagement
When the viewer activates an overlay's call-to-action, the SDK emits did-engage. For
ctaType: 'show_panel', ctaValue names the panel to open — pass it to
setActivePanel:
import SDK, { MaestroPanelType } from '@maestro_io/maestro-web-sdk';
// Inside the switch in onOverlayEvent:
case 'did-engage': {
const { ctaType, ctaValue } = event.payload;
if (ctaType === 'show_panel') {
SDK.getMaestroEventViewModel().setActivePanel(ctaValue as MaestroPanelType);
}
break;
}
6. Reposition a live overlay
If your layout changes while an overlay is on screen — the player goes full screen, your controls appear — push a new
position with setDataToOverlay:
SDK.getMaestroEventViewModel().setDataToOverlay({
suggestedPosition: { x: 64, y: 240 },
});
Use isOverlayShowing() if you need to know whether
an overlay is currently visible before adjusting your own UI.
Device & Browser Support
Overlays are rendered with Rive, which needs WebGL2 and WebAssembly. Both are available on the current connected-TV platforms listed in Device & Browser Support, but older TV browser builds — early Tizen and webOS models in particular — may ship neither.
The SDK feature-detects both at runtime. When either is missing it logs and renders nothing: the rest of the SDK, including panels, works normally, and overlays simply never appear. There is no delegate event for this case, so if overlays are silently absent on a specific device, check the console for the capability log before investigating your delegate.
Best Practices
- Invoke the
will-appearcallback synchronously. Deferred answers risk missing the 2-second window, and past it they are the same as not answering at all. - Don't rely on silence to suppress an overlay. Pass
canAppear: falseexplicitly when you want to hold one back — for example while your own modal is open. - Clean up your events. Call
userDidStopWatchingEvent()when the viewer leaves; that is what removes the SDK's overlay container. - Leave
#maestro__sdk__overlayalone. It is owned by the SDK. Don't style it, move it, or reparent it — usesuggestedPositionandsetDataToOverlayinstead.