Skip to content

Media Session

MediaSessionPlugin connects the player to the browser’s MediaSession API so the operating system’s Now Playing widget, lock screen controls, and hardware media keys all reflect what is currently playing. Register it whenever your users might background the tab, use a Bluetooth remote, or interact with OS-level media controls. It takes no options.

The player is headless — no built-in UI. Plugins like this one are how the player surfaces state to the outside world.

Plugin id

'media-session'

Import

TypeScript
import { MediaSessionPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins';

What it does

The plugin bridges two directions.

Outward: when the active playlist item changes, the plugin reads the item’s fields, resolves the artwork URL, and pushes a MediaMetadata object to navigator.mediaSession. It also keeps playbackState and setPositionState in sync as the player plays, pauses, seeks, and ends.

Inward: it registers OS action handlers so hardware keys and the lock screen scrubber route back into the player.

For video items the plugin uses the VideoPlaylistItem fields to produce TV-style metadata:

MediaSession fieldSource
titleitem.title ?? ''
artistitem.show ?? ''
album'Season N' when item.season is non-undefined, non-null, and non-empty-string, otherwise ''
artworkResolved from the first non-empty field among item.image, item.poster, item.thumbnail, item.cover

Options

MediaSessionPlugin takes no options. Metadata is read from the active VideoPlaylistItem at playback time.

OS action handlers

The plugin registers eight action handlers on navigator.mediaSession:

ActionWhat it does
playCalls player.play()
pauseCalls player.pause()
stopCalls player.stop()
previoustrackCalls player.previous()
nexttrackCalls player.next()
seekbackwardCalls player.rewind(offset), where offset is the browser-supplied seekOffset or 5 seconds
seekforwardCalls player.forward(offset), same offset logic
seektoCalls player.time(time) with the browser-supplied seekTime

stop is registered with a try/catch because a few browsers do not implement it. Any unsupported action is silently skipped, and all handlers are cleaned up when the plugin disposes.

Methods

These are available after the plugin is registered:

TypeScript
import { MediaSessionPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins';

const session = player.getPlugin(MediaSessionPlugin)!;

// Read the last metadata that was pushed to the OS:
const current = session.metadata();

// Push metadata manually (the plugin does this automatically on track changes):
session.metadata({
title: 'Sintel',
artist: 'Blender Foundation',
album: 'Season 1',
});

// Clear the OS Now Playing widget:
session.clearMetadata();

Registration

TypeScript
import nmplayer from '@nomercy-entertainment/nomercy-video-player';
import { MediaSessionPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins';

const player = nmplayer('player')
.addPlugin(MediaSessionPlugin)
.setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Films',
playlist: [
{
id: 1,
title: 'Sintel',
url: '/Sintel.(2010)/Sintel.(2010).NoMercy.m3u8',
image: 'https://image.tmdb.org/t/p/w780/q2bVM5z90tCGbmXYtq2J38T5hSX.jpg',
show: 'Blender Foundation',
season: 1,
episode: 1,
},
],
});

The OS Now Playing widget will show: Sintel - Blender Foundation - Season 1

Customising metadata extraction

If your playlist items carry fields beyond the standard VideoPlaylistItem shape, subclass the plugin and override getMetadata:

TypeScript
import { MediaSessionPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins';
import type { VideoPlaylistItem } from '@nomercy-entertainment/nomercy-video-player';

interface MyItem extends VideoPlaylistItem {
director?: string;
}

class MyMediaSession extends MediaSessionPlugin<MyItem> {
protected override getMetadata(item: MyItem) {
return {
title: item.title ?? '',
artist: item.director ?? item.show ?? '',
album: item.season != null ? `Season ${item.season}` : '',
};
}
}

const player = nmplayer('player')
.addPlugin(MyMediaSession)
.setup({ /* ... */ });

Artwork resolution is handled separately by the base class. Overriding getMetadata only affects the text fields.

Browser notes

  • setPositionState is skipped when duration is zero, NaN, Infinity, or otherwise non-finite. Some browsers throw on those inputs, so the plugin guards every call.
  • stop action registration is wrapped in a try/catch because it is not supported everywhere.
  • Safari requires the autoplay attribute on any parent <iframe> for MediaSession to activate. Without it, OS-level controls never appear.
  • In Node, JSDOM, and other headless environments navigator.mediaSession is absent. The plugin checks for it on every call and silently no-ops.

See also