MediaSession
Tell the operating system what your app is playing, so the lock screen, notification shade, media keys and headset buttons show your track and control it - without your page being in the foreground.
@inject Bit.Butil.MediaSession mediaSessionMDN reference
<audio> element, WebAudio, a
MediaStream - and most platforms only surface the session once audio is genuinely
playing, so setting metadata on a silent page usually shows nothing.
Returns true when the runtime exposes navigator.mediaSession. During prerender/SSR the check returns false rather than throwing, so defer it to OnAfterRenderAsync.
@inject Bit.Butil.MediaSession mediaSession
var supported = await mediaSession.IsSupported();SetMetadata is what the platform shows: title, artist, album and artwork. Offering several artwork sizes lets each surface pick what fits. SetPlaybackState keeps the system's play/pause icon in step with reality - set it on every play and pause, or the lock screen will disagree with your page.
await mediaSession.SetMetadata(new MediaMetadata
{
Title = "A test tone",
Artist = "Bit.Butil",
Album = "MediaSession demo",
Artwork =
[
new MediaArtwork { Src = "images/cover-512.png", Sizes = "512x512", Type = "image/png" },
],
});
await mediaSession.SetPlaybackState(MediaSessionPlaybackState.Playing);Registering a handler is also what makes the corresponding control appear on the platform. Engines implement different subsets, so SetActionHandler returns false for the ones this one doesn't know - which is the normal way to discover that SeekTo or the skip actions are unavailable. Try a media key on your keyboard, or the transport controls in your OS.
var ok = await mediaSession.SetActionHandler(MediaSessionAction.Play, _ => Play());
await mediaSession.SetActionHandler(MediaSessionAction.Pause, _ => Pause());
await mediaSession.SetActionHandler(MediaSessionAction.NextTrack, _ => Next());
// seek actions carry their offset or target in the details:
await mediaSession.SetActionHandler(MediaSessionAction.SeekTo,
details => SeekTo(details.SeekTime ?? 0));
await mediaSession.SetActionHandler(MediaSessionAction.SeekForward,
details => SeekBy(details.SeekOffset ?? 10));
// remove a handler (and hide its control):
await mediaSession.ClearActionHandler(MediaSessionAction.NextTrack);The same mechanism drives more than a music player. A call gets microphone, camera and hang-up buttons, and VoiceActivity when the user starts talking while muted (voice detection only runs while the page holds the microphone). A presentation gets previous/next slide, and EnterPictureInPicture is offered when the user switches away from a playing video - the one moment a page may open a picture-in-picture window without a click. Support is patchier than for the playback actions, so the false return is the answer to read here.
await mediaSession.SetActionHandler(MediaSessionAction.ToggleMicrophone, _ => ToggleMute());
await mediaSession.SetActionHandler(MediaSessionAction.HangUp, _ => EndCall());
await mediaSession.SetActionHandler(MediaSessionAction.VoiceActivity, _ => SuggestUnmute());
await mediaSession.SetActionHandler(MediaSessionAction.NextSlide, _ => Next());
await mediaSession.SetActionHandler(MediaSessionAction.PreviousSlide, _ => Previous());
var ok = await mediaSession.SetActionHandler(MediaSessionAction.EnterPictureInPicture,
_ => OpenPictureInPicture());Publishes duration, position and rate so the platform can draw an accurate progress bar and let the user seek from outside your page. It extrapolates between updates, so this needs calling when you seek or change rate - not once per frame. The position is clamped into the track's range here, because the spec throws when it overshoots the duration.
await mediaSession.SetPositionState(
durationSeconds: 240,
positionSeconds: 37.5,
playbackRate: 1);
// when playback ends entirely:
await mediaSession.ClearPositionState();API reference
ValueTask<bool> IsSupported()ValueTask<bool> SetMetadata(MediaMetadata? metadata)ValueTask SetPlaybackState(MediaSessionPlaybackState state)ValueTask<bool> SetPositionState(double durationSeconds, double positionSeconds, double playbackRate = 1)ValueTask ClearPositionState()ValueTask<bool> SetActionHandler(MediaSessionAction action, Action<MediaSessionActionDetails> handler)ValueTask ClearActionHandler(MediaSessionAction action)ValueTask DisposeAsync()Play, Pause, Stop, SeekBackward, SeekForward, SeekTo, PreviousTrack, NextTrack, SkipAd, ToggleMicrophone, ToggleCamera, HangUp, PreviousSlide, NextSlide, EnterPictureInPicture, VoiceActivityAction, SeekTime, SeekOffset, FastSeek