webdevpuneet.com

Media Session API Controls — Free navigator.mediaSession Mini Player

Media Session API Controls · Media · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<section class="msc-wrap">
  <span class="msc-tag">navigator.mediaSession</span>
  <h1>Mini player</h1>
  <p id="mscStatus">Wired to the real Media Session API — supporting devices show this track on the lock screen, notification shade, and hardware media keys.</p>

  <div class="msc-player">
    <div class="msc-art" id="mscArt">♪</div>
    <div class="msc-meta">
      <strong id="mscTitle">Nebula Drift</strong>
      <span id="mscArtist">Auric Fields</span>
      <span class="msc-album" id="mscAlbum">Wandering Signals</span>
    </div>
  </div>

  <div class="msc-progress">
    <div class="msc-progress-fill" id="mscFill"></div>
  </div>
  <div class="msc-times"><span id="mscElapsed">0:00</span><span id="mscDuration">3:12</span></div>

  <div class="msc-controls">
    <button class="msc-ctrl" id="mscPrev" aria-label="Previous track">⏮</button>
    <button class="msc-ctrl main" id="mscPlay" aria-label="Play">▶</button>
    <button class="msc-ctrl" id="mscNext" aria-label="Next track">⏭</button>
  </div>

  <p class="msc-note" id="mscNote">Checking Media Session API support…</p>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Fully working on-page player
Play, pause, skip, and progress work with zero API dependency.
Real MediaMetadata updates
Title, artist, album pushed to the OS on every track change.
Real setActionHandler wiring
play/pause/previoustrack/nexttrack/seekto all registered.
Shared handler functions
Hardware controls call the identical on-page functions.
Honest OS-only effect
States plainly the API's payoff isn't visible in-page.
Graceful unsupported path
Player works identically with mediaSession absent.
Seek support
seekto handler updates elapsed time from OS scrubbing.
No dependencies
Pure vanilla JS against the native API.

About this UI Snippet

Media Session API Controls — A Real Player With Real OS Integration

Screenshot of the Media Session API Controls snippet rendered live

This snippet is a working mini player first, and a Media Session API demo second — because navigator.mediaSession's entire effect is invisible inside a page: it only shows up on a lock screen, in a notification shade, or when a hardware media key is pressed. The on-page buttons have to carry the whole visible demo on their own.

MediaMetadata drives OS surfaces

Every time the track changes or play state flips, updateMetadata() assigns a fresh new MediaMetadata({ title, artist, album }) to navigator.mediaSession.metadata and sets navigator.mediaSession.playbackState. On a real device with this player embedded in a real page (not a sandboxed iframe preview), that's what populates the lock-screen now-playing card and notification-shade media widget — artwork, title, and artist, kept in sync automatically as the track advances.

setActionHandler wires hardware to page state

navigator.mediaSession.setActionHandler('play', play) (and 'pause', 'previoustrack', 'nexttrack', 'seekto') registers this exact page's own functions as the targets for OS-level media controls — a Bluetooth headset's play button, a car head unit's skip button, or a keyboard's media keys all route through these same handlers, calling the identical play()/pause()/nextTrack() functions the on-page buttons call.

Why the effect can't be shown here

There's no way for a snippet running inside a preview iframe to render a real OS lock screen or notification shade — that's chrome owned entirely by the operating system, several privilege layers above what any web page can draw. So this snippet is explicit that setActionHandler and MediaMetadata are wired for real, with zero expectation that anything extra appears in the preview itself.

The on-page player never depends on Media Session

Every piece of visible functionality — play/pause, previous/next, the progress bar, elapsed time — runs through plain setInterval-driven page state and DOM updates, gated behind a hasMediaSession check only for the OS-integration calls. Strip navigator.mediaSession out entirely and the player still works exactly the same on-page; that's a deliberate design choice, not an accident.

Pair this with a screen wake lock toggle so a played track keeps the screen alive, or a canvas audio frequency bars visualizer for a fuller player UI.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA working mini player with three tracks renders.
  2. 2
    Click playThe progress bar advances and elapsed time counts up.
  3. 3
    Use next/previousTrack metadata updates on-page and via MediaMetadata.
  4. 4
    Check a real deviceOn mobile with a real page, the lock screen shows this track.
  5. 5
    Try hardware media keysA keyboard or headset play/pause routes to the same handlers.
  6. 6
    Read the noteClarifies the API's effect is OS-level, not visible on-page.

Real-world uses

Common Use Cases

Web music and podcast players
Pair with canvas audio bars so a page player also shows lock-screen title, artist and album from MediaMetadata.
Audiobook chapter controls
Surface chapter titles on the OS lock screen and let hardware media keys skip between them using real action handlers.
Progressive web app media
Keep playback controls reachable when the app is backgrounded, with seekto support letting the lock screen scrub the position.
Radio and streaming embeds
Show live show metadata in the notification shade of a radio or streaming embed, updated whenever the track or programme changes.
Keep-awake players
Combine with a screen wake lock toggle so long listening or viewing sessions are not interrupted by the display sleeping.
Related: Share Menu with Copy Link and Social Options
See the Share Menu with Copy Link and Social Options for a related buttons pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Because the Media Session API's entire visible effect lives outside the page — on the device's lock screen, in the notification shade's media widget, or triggered via hardware media keys. There is no way for any web page, including this snippet inside a preview iframe, to render or simulate that OS-owned chrome. The API is wired for real; its payoff simply isn't observable from inside the page itself.

Yes, identically. The entire play/pause/skip/progress system runs on ordinary page JavaScript (setInterval and DOM updates) gated by a hasMediaSession check only around the OS-integration calls (MediaMetadata assignment and setActionHandler registration). Removing navigator.mediaSession support changes nothing about how the on-page buttons behave.

It registers this page's own JavaScript functions (play, pause, prevTrack, nextTrack, and a seek handler) as the targets the operating system calls when the user interacts with OS-level media controls — a lock-screen button, a Bluetooth headset's play button, a car head unit, or a keyboard's media keys. Pressing one of those calls the exact same function the matching on-page button calls.

Assigning navigator.mediaSession.metadata = new MediaMetadata({ title, artist, album, artwork }) tells the OS what to display for the currently "playing" content on surfaces the page can't draw into directly, like a lock screen's now-playing card or a notification shade's media widget. This snippet updates it every time the track or play state changes so those OS surfaces stay in sync automatically.

Keep the metadata/action-handler wiring in an effect that runs once on mount (re-running setActionHandler on every render can churn OS-level registrations unnecessarily), and store playback state (track index, elapsed time, playing) in your framework's own state so on-page renders and MediaMetadata updates both derive from one source of truth. Clear the interval and consider setting mediaSession.playbackState to 'none' on unmount.