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
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 field | Source |
|---|---|
title | item.title ?? '' |
artist | item.show ?? '' |
album | 'Season N' when item.season is non-undefined, non-null, and non-empty-string, otherwise '' |
artwork | Resolved 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:
| Action | What it does |
|---|---|
play | Calls player.play() |
pause | Calls player.pause() |
stop | Calls player.stop() |
previoustrack | Calls player.previous() |
nexttrack | Calls player.next() |
seekbackward | Calls player.rewind(offset), where offset is the browser-supplied seekOffset or 5 seconds |
seekforward | Calls player.forward(offset), same offset logic |
seekto | Calls 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:
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
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:
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
setPositionStateis skipped whendurationis zero,NaN,Infinity, or otherwise non-finite. Some browsers throw on those inputs, so the plugin guards every call.stopaction registration is wrapped in a try/catch because it is not supported everywhere.- Safari requires the
autoplayattribute on any parent<iframe>forMediaSessionto activate. Without it, OS-level controls never appear. - In Node, JSDOM, and other headless environments
navigator.mediaSessionis absent. The plugin checks for it on every call and silently no-ops.