Skip to main content

Overlay Integration

Overview

This section walks you through integrating overlays into your Maestro Web SDK implementation.

Your app does not render overlays

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:

FieldTypeDefaultDescription
canAppearbooleantrueWhether this overlay is allowed to appear. Pass false to suppress it.
canFocusbooleanfalseWhether the SDK should take D-pad focus when the overlay appears.
suggestedPosition{ x: number; y: number }right: 0; top: 0Where 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.

Silence does not suppress an overlay

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-appear callback 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: false explicitly 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__overlay alone. It is owned by the SDK. Don't style it, move it, or reparent it — use suggestedPosition and setDataToOverlay instead.