Skip to content

EmbedPlugin

EmbedPlugin is what you reach for when you embed the music player inside an <iframe> and need the host page to talk to it. The host sends nm:command messages; the plugin validates the origin, then calls the matching player method. The plugin also listens to player events and forwards them back to the host as nm:event messages. Nothing else in the player knows about postMessage; this plugin owns the whole protocol.

This plugin is a direct re-export of the player core’s EmbedPlugin. No music-specific overrides.

TypeScript
import { EmbedPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';
import type { EmbedCommand, EmbedEventMessage, EmbedOptions } from '@nomercy-entertainment/nomercy-music-player/plugins';

Plugin id: 'embed'

Options

OptionTypeDefaultDescription
allowedOriginsstring | string[][]Origin(s) allowed to send inbound nm:command messages. '*' accepts any origin (development only). An empty list rejects all inbound commands. The plugin still forwards events outward even when the list is empty.
forwardEventsReadonlyArray<'ready' | 'play' | 'pause' | 'ended' | 'time' | 'volume' | 'mute' | 'error'>['ready','play','pause','ended','time','volume','mute']Player events to forward to the host as nm:event postMessages. 'error' is in the type but not in the default list; add it explicitly if you need it.
applyIframeTweaksbooleantrue when inside a nested browsing contextWhen true, adds the nm-embed CSS class to the player container. Defaults to true when inIframe() detects a nested browsing context. Pass false to suppress the class even when the player is iframed.

Inbound commands (host to embed)

The host page sends commands as postMessage objects matching the EmbedCommand type. All commands share the envelope { type: 'nm:command', action: '...' }.

actionExtra fieldsDescription
'play'Calls player.play()
'pause'Calls player.pause()
'stop'Calls player.stop()
'seek'time: numberCalls player.time(time)
'volume'level: numberCalls player.volume(level)
'mute'Calls player.mute()
'unmute'Calls player.unmute()
'next'Calls player.next()
'previous'Calls player.previous()

Outbound events (embed to host)

The plugin forwards the events listed in forwardEvents as nm:event postMessages. The default set is ready, play, pause, ended, time, volume, and mute. error is part of the EmbedEventMessage union but is not forwarded unless you add it to forwardEvents explicitly.

Every outbound message has the envelope { type: 'nm:event', name, data }. The player event payload is always nested under data; there are no extra fields at the top level alongside type and name.

namedata shapeDescription
'ready'{}Player is ready to play
'play'ActionOptions ({ source?, silent?, autoplay? })Playback started
'pause'ActionOptions ({ source?, silent?, autoplay? })Playback paused
'ended'{}Track ended
'time'{ time: number }Periodic time update (seconds)
'volume'{ level: number }Volume changed
'mute'{ muted: boolean }Mute state changed
'error'{ code: string; message?: string; severity: 'fatal' | 'error' | 'warning' | 'info'; scope: ErrorScope; suggestion?: string }Player error (not forwarded by default). Serialized to a plain, clone-safe object before posting.

Reading event data on the host

JavaScript
window.addEventListener('message', (event) => {
if (event.origin !== 'https://player.example.com') return;
if (event.data?.type !== 'nm:event') return;

const { name, data } = event.data;

if (name === 'time') {
console.log('current time:', data.time);
}

if (name === 'error') {
console.error('player error:', data.code, data.message);
}
});

Methods

sendToHost(message)

TypeScript
sendToHost(message: EmbedEventMessage): void

Sends a structured event message to the host page via window.parent.postMessage. When exactly one origin is listed in the allowlist, the message is pinned to that origin. When the list has more than one entry or contains '*', the target is '*'. Swallows any cross-origin DOMException so the player never crashes due to a missing or unloaded parent frame.

allowedOrigins()

TypeScript
allowedOrigins(): readonly string[]
allowedOrigins(origins: string | string[]): void

Reads or replaces the allowed-origins list at runtime. The read form returns a snapshot array; mutations to the returned array do not affect the live list. The write form replaces the list immediately; the new list takes effect on the next inbound message.

Registration

TypeScript
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { EmbedPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';

const player = nmMPlayer('main')
.addPlugin(EmbedPlugin, {
allowedOrigins: ['https://api.example.com'],
forwardEvents: ['ready', 'play', 'pause', 'ended', 'time', 'volume', 'mute', 'error'],
})
.setup({ playlist: myTracks });

Required <iframe> attributes

HTML
<iframe
src="https://player.example.com/embed"
allow="autoplay; fullscreen; picture-in-picture; encrypted-media;
accelerometer; gyroscope; web-share; clipboard-write"
allowfullscreen
loading="lazy"
></iframe>

autoplay is load-bearing. Without it, MediaSession never activates and OS-level controls (lock screen, Bluetooth, Now Playing) will not appear even if MediaSessionPlugin is registered.

Host page example

JavaScript
const iframe = document.getElementById('music-player');

// Send a command to the embedded player:
iframe.contentWindow.postMessage(
{ type: 'nm:command', action: 'play' },
'https://player.example.com',
);

// Receive events from the embedded player:
window.addEventListener('message', (event) => {
if (event.origin !== 'https://player.example.com') return;
if (event.data?.type !== 'nm:event') return;

const { name, data } = event.data;
console.log('Player event:', name, data);
});

Security

When exactly one origin is listed in allowedOrigins, outbound postMessages are pinned to that origin. When multiple origins are listed or '*' is used, outbound messages use '*' as the target origin. Always pin to a single explicit origin in production. An empty allowedOrigins list (the default) rejects all inbound commands but still sends outbound events.

Extension points

EmbedPlugin exposes four protected methods you can override in a subclass to customise the protocol:

  • handleCommand(msg) - add custom command types beyond the built-in nine.
  • formatEvent(name, data) - change the outbound message envelope or suppress specific events by returning null.
  • isOriginAllowed(origin) - apply custom logic such as wildcard subdomain matching.
  • inIframe() - override in tests to inject a fixed value.

See also