Skip to content

Media Session API

Published Updated

In one line: the Media Session API “provides a way to customize media notifications,” per MDN — it lets a page describe what is playing and receive events from a device’s physical or onscreen media controls, alongside whatever on-page playback controls the page already has.

The entry point is navigator.mediaSession, a MediaSession object. Per MDN’s setActionHandler() reference, its actions “let a web app receive notifications when the user engages a device’s built-in physical or onscreen media controls, such as play, stop, or seek buttons,” and per MDN’s metadata reference, MediaSession.metadata holds a MediaMetadata object that provides descriptive information about the currently playing media for the device’s own media control UI.

Per MDN’s browser-compat-data, the MediaSession interface (and its setActionHandler() method) is supported in Chrome from version 73, in Safari from version 15, and in Firefox from version 82. Chrome for Android has supported it from version 57, but Firefox for Android is a partial implementation — the same data notes that Firefox for Android “exposes the API, but does not provide a corresponding user-facing media control interface.” The data records Android WebView as unsupported (version_added: false).

Set metadata with a MediaMetadata object, then register handlers for the actions the page can respond to:

const playlist = [
{ title: "Unforgettable", artist: "Nat King Cole", album: "The Ultimate Collection (Remastered)" },
{ title: "L-O-V-E", artist: "Nat King Cole", album: "The Ultimate Collection (Remastered)" },
];
let currentTrackIndex = 0;
const audioElement = document.querySelector("audio") ?? document.createElement("audio");
function loadTrack(index) {
currentTrackIndex = index;
const track = playlist[currentTrackIndex];
audioElement.src = `/audio/${track.title}.mp3`;
navigator.mediaSession.metadata = new MediaMetadata({
title: track.title,
artist: track.artist,
album: track.album,
artwork: [{ src: "/art/unforgettable-96.png", sizes: "96x96", type: "image/png" }],
});
}
function playPreviousTrack() {
if (currentTrackIndex > 0) {
loadTrack(currentTrackIndex - 1);
audioElement.play();
}
}
function playNextTrack() {
if (currentTrackIndex < playlist.length - 1) {
loadTrack(currentTrackIndex + 1);
audioElement.play();
}
}
loadTrack(0);
const actionHandlers = {
play: () => audioElement.play(),
pause: () => audioElement.pause(),
previoustrack: playPreviousTrack,
nexttrack: playNextTrack,
};
for (const [action, handler] of Object.entries(actionHandlers)) {
try {
navigator.mediaSession.setActionHandler(action, handler);
} catch {
// This browser doesn't implement the "action" media session action — skip it.
}
}

Per MDN’s setActionHandler() reference, passing null as the callback removes a previously-set handler for that action, e.g. navigator.mediaSession.setActionHandler("nexttrack", null).

Feature-detect mediaSession on navigator before reading or writing it, and fall back to showing the page’s own on-page playback controls as the only control surface. This example creates that fallback element itself (a native <audio controls>) so it works when pasted as-is, instead of assuming markup that was never added to the page:

function supportsMediaSession() {
return "mediaSession" in navigator;
}
const playerControls = document.querySelector("#player-controls") ?? createPlayerControls();
if (supportsMediaSession()) {
navigator.mediaSession.metadata = new MediaMetadata({ title: "Unforgettable" });
} else {
// No Media Session support here — rely on the page's own play/pause/seek
// controls instead of OS-level media controls.
showOnPageTransportControls();
}
function createPlayerControls() {
const audio = document.createElement("audio");
audio.id = "player-controls";
audio.controls = true;
audio.src = "/audio/unforgettable.mp3";
audio.hidden = true;
document.body.appendChild(audio);
return audio;
}
function showOnPageTransportControls() {
playerControls.hidden = false;
}
  • Per MDN’s compat data, Firefox for Android “exposes the API, but does not provide a corresponding user-facing media control interface” — do not assume a set handler is reachable by the user on that platform just because setActionHandler() did not throw.
  • Android WebView is recorded as unsupported in the same data — an app embedded in a WebView still needs the on-page fallback controls.
  • Per MDN’s setActionHandler() reference, support for individual action strings (e.g. skipad, previousslide, nextslide) varies by browser, and calling it with an action a given browser doesn’t implement can throw — MDN’s own examples wrap each setActionHandler() call in its own try...catch so one unsupported action doesn’t break the rest of the session setup.
  • Per MDN’s metadata reference, MediaSession.metadata is null until the page sets it — read it defensively rather than assuming a MediaMetadata object is already present.