Overview
Beat App integrates with the browser’s Media Session API, allowing you to control playback from:
Operating system media controls
Hardware media keys (keyboard, headphones)
Lock screen controls (mobile devices)
Notification center widgets
Browser toolbar media buttons
Media Session API support varies by browser. Works best in Chrome, Edge, and Safari on desktop and mobile.
When a track plays, Beat App sends metadata to the system for display in media controls:
const updateMediaSessionMetadata = ( track ) => {
if ( "mediaSession" in navigator ) {
navigator . mediaSession . metadata = new MediaMetadata ({
title: track . title ,
artist: track . artists ?.[ 0 ]?. name || "" ,
album: track . album ?. name || "" ,
artwork: track . thumbnailUrl
? [
{
src: ` ${ PROXY_URL }${ track . thumbnailUrl } ` ,
sizes: "512x512" ,
type: "image/jpeg" ,
},
]
: [],
});
}
};
This displays:
Track title
Artist name
Album name
Album artwork (512x512px)
Metadata is automatically updated whenever you play a new track via playerActions.playTrack(), playNext(), or playPrevious().
Playback State
The Media Session API receives real-time playback state updates:
Playing State
Paused State
audioEl . onplay = () => {
playerStore . setKey ( "isPlaying" , true );
if ( "mediaSession" in navigator ) {
navigator . mediaSession . playbackState = "playing" ;
}
};
audioEl . onpause = () => {
playerStore . setKey ( "isPlaying" , false );
if ( "mediaSession" in navigator ) {
navigator . mediaSession . playbackState = "paused" ;
}
};
This ensures system controls show the correct play/pause state.
Position State
Beat App reports playback position for accurate progress display in system controls:
audioEl . onloadedmetadata = () => {
playerStore . setKey ( "duration" , audioEl . duration );
if ( "mediaSession" in navigator && ! isNaN ( audioEl . duration ) && audioEl . duration !== Infinity ) {
navigator . mediaSession . setPositionState ({
duration: audioEl . duration ,
playbackRate: audioEl . playbackRate ,
position: audioEl . currentTime ,
});
}
};
Seeking Updates
Position state is also updated when seeking:
seekTo : ( time ) => {
if ( ! isNaN ( time ) && isFinite ( time )) {
audioEl . currentTime = time ;
playerStore . setKey ( "currentTime" , time );
if ( "mediaSession" in navigator && ! isNaN ( audioEl . duration ) && audioEl . duration !== Infinity ) {
navigator . mediaSession . setPositionState ({
duration: audioEl . duration ,
playbackRate: audioEl . playbackRate ,
position: audioEl . currentTime ,
});
}
}
}
Position state is only set when duration is a valid number (not NaN or Infinity) to avoid browser errors.
Action Handlers
Beat App registers handlers for media control actions (src/stores/playerStore.js:209):
Play/Pause
Handle play and pause button presses: navigator . mediaSession . setActionHandler ( "play" , () => playerActions . togglePause ());
navigator . mediaSession . setActionHandler ( "pause" , () => playerActions . togglePause ());
Track Navigation
Handle previous and next track buttons: navigator . mediaSession . setActionHandler ( "previoustrack" , () => playerActions . playPrevious ());
navigator . mediaSession . setActionHandler ( "nexttrack" , () => playerActions . playNext ());
Seeking
Handle seek bar interactions: navigator . mediaSession . setActionHandler ( "seekto" , ( details ) => {
if ( details . fastSeek && "fastSeek" in audioEl ) {
audioEl . fastSeek ( details . seekTime );
return ;
}
playerActions . seekTo ( details . seekTime );
});
Fast Seek Support
When available, Beat App uses the browser’s fast seek capability for smoother scrubbing:
if ( details . fastSeek && "fastSeek" in audioEl ) {
audioEl . fastSeek ( details . seekTime );
return ;
}
playerActions . seekTo ( details . seekTime );
fastSeek() allows quicker seeking by approximating the target position instead of loading precise frame data.
Automatic Updates
Media metadata updates automatically when tracks change:
Playing Track
Next Track
Previous Track
playTrack : async ( track , newQueue = null ) => {
playerStore . setKey ( "currentTrack" , track );
audioEl . pause ();
updateMediaSessionMetadata ( track ); // Auto-update
const audioUrl = await getAudioUrl ( track . trackId );
audioEl . src = audioUrl ;
audioEl . play ();
}
playNext : async () => {
const nextTrack = state . queue [ nextIndex ];
playerStore . setKey ( "currentTrack" , nextTrack );
audioEl . pause ();
updateMediaSessionMetadata ( nextTrack ); // Auto-update
const audioUrl = await getAudioUrl ( nextTrack . trackId );
audioEl . src = audioUrl ;
audioEl . play ();
}
playPrevious : async () => {
const prevTrack = state . queue [ prevIndex ];
playerStore . setKey ( "currentTrack" , prevTrack );
audioEl . pause ();
updateMediaSessionMetadata ( prevTrack ); // Auto-update
const audioUrl = await getAudioUrl ( prevTrack . trackId );
audioEl . src = audioUrl ;
audioEl . play ();
}
Browser Compatibility
All Media Session features are wrapped in feature detection:
if ( "mediaSession" in navigator ) {
// Media Session API is available
navigator . mediaSession . metadata = new MediaMetadata ({ ... });
navigator . mediaSession . setActionHandler ( "play" , handler );
}
This ensures Beat App works gracefully in browsers without Media Session support.
Desktop
Keyboard media keys (Play, Pause, Next, Previous)
Browser toolbar controls
System media overlay (Windows, macOS)
Touch Bar controls (MacBook Pro)
Mobile
Lock screen controls
Notification shade widgets
Bluetooth headphone buttons
Car audio system controls
Implementation Details
Media Session setup happens when the player store mounts:
onMount ( playerStore , () => {
// Audio event listeners
audioEl . onplay = () => { /* ... */ };
audioEl . onpause = () => { /* ... */ };
audioEl . onloadedmetadata = () => { /* ... */ };
// Register Media Session handlers
if ( "mediaSession" in navigator ) {
navigator . mediaSession . setActionHandler ( "play" , () => playerActions . togglePause ());
navigator . mediaSession . setActionHandler ( "pause" , () => playerActions . togglePause ());
navigator . mediaSession . setActionHandler ( "previoustrack" , () => playerActions . playPrevious ());
navigator . mediaSession . setActionHandler ( "nexttrack" , () => playerActions . playNext ());
navigator . mediaSession . setActionHandler ( "seekto" , ( details ) => {
playerActions . seekTo ( details . seekTime );
});
}
});
Action handlers are registered once when the app loads and remain active throughout the session.
Desktop Testing
Play a track in Beat App
Minimize or switch tabs
Use keyboard media keys or browser controls
Verify playback responds correctly
Mobile Testing
Play a track on mobile device
Lock screen or switch apps
Access media controls from lock screen/notification
Test play, pause, next, and previous buttons
Metadata Verification
Check that system controls display:
Correct track title
Artist name
Album artwork
Accurate playback position
Common Use Cases
Background Listening
Multitasking
Driving
Listen to music while using other apps. Media controls remain accessible from:
System notification area
Lock screen
External devices
Control playback without switching back to the browser:
Use keyboard shortcuts
Click browser toolbar controls
Use hardware buttons on headphones
Safely control music while driving via:
Car audio system controls
Steering wheel buttons
Voice commands (where supported)
Next Steps
Music Playback Learn about in-app player controls
Queue Management Manage your playback queue