notification_media_session
A cross-platform Flutter bridge that synchronizes your app's playback state with system media sessions, notification cards, and lock-screen controls.
- 📱 Android: Full support for Android Media Notification, lock screen controls, and Android 13+ media card actions (including custom native actions like favorite/repeat).
- 💻 Windows Desktop: Integrates with Windows System Media Transport Controls (SMTC) for native volume overlay and taskbar controls.
- 🌐 Web (Browser & Windows SMTC): Supports the W3C Media Session API (
navigator.mediaSession), seamlessly connecting with browser media controls and Windows SMTC / lock-screen cards on desktop browsers (Edge, Chrome). - 🎯 Clean Architecture: Decoupled from any specific player library through the Gateway & Snapshot pattern.
- 🎨 Customizable & Built-in Resources: Default vector drawables are pre-bundled, and buttons/actions are fully customizable.
1. Installation
Add the dependency to your pubspec.yaml:
dependencies:
notification_media_session: ^0.2.2
2. Platform Setup
Android Setup
A. Declare Permissions & Services (android/app/src/main/AndroidManifest.xml)
Add the required permissions and service declarations inside your AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Permissions required for foreground playback and notification -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application ...>
<!-- AudioService declaration -->
<service
android:name="com.ryanheise.audioservice.AudioService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<!-- Media button receiver for hardware keys and headsets -->
<receiver
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>
B. Built-in Android Drawables & Custom Icons
The package automatically bundles default vector icons for Android media notification cards:
drawable/ic_notification_favorite(Heart filled)drawable/ic_notification_favorite_border(Heart outline)drawable/ic_notif_prev_outline(Previous track)drawable/ic_notif_play_outline(Play)drawable/ic_notif_pause_outline(Pause)drawable/ic_notif_next_outline(Next track)drawable/ic_notif_placeholder(Placeholder/Stop)drawable/ic_default_artwork(Default album cover)
Adding New / Custom Icons: You can add any custom vector drawable (
.xml) or PNG file into your app'sandroid/app/src/main/res/drawable/directory (e.g.drawable/ic_repeat), and reference it directly in custom media controls.
C. Request Notification Permission (Android 13+)
On Android 13 (API 33) and above, request the notification permission at app startup:
import 'dart:io';
import 'package:permission_handler/permission_handler.dart';
Future<void> requestNotificationPermission() async {
if (Platform.isAndroid) {
final status = await Permission.notification.status;
if (!status.isGranted) {
await Permission.notification.request();
}
}
}
Web Setup & Windows SMTC Integration
Zero configuration required for Flutter Web!
When running on the Web:
- The package leverages the W3C Media Session API (
navigator.mediaSession) under the hood. - When running in modern desktop browsers (e.g., Google Chrome, Microsoft Edge on Windows 10/11), the browser automatically hooks into Windows SMTC (System Media Transport Controls).
- This allows web playback to display the native Windows volume overlay media card, lock-screen controls, hardware media keys, and browser global media controls with album artwork and track metadata.
Windows Desktop Setup
No additional manifest or XML configuration is required on Windows Desktop. The C++/WinRT plugin communicates directly with Windows SMTC.
3. Quick Start
Step 1: Implement NotificationPlaybackGateway
Implement NotificationPlaybackGateway to bridge your audio player / ViewModel with the notification session:
import 'dart:async';
import 'package:notification_media_session/notification_media_session.dart';
class MyPlayerNotificationGateway extends NotificationPlaybackGateway {
final _snapshots = StreamController<NotificationPlaybackSnapshot>.broadcast();
NotificationPlaybackSnapshot _current = const NotificationPlaybackSnapshot(
track: null,
phase: NotificationPlaybackPhase.idle,
isPlaying: false,
isFavorite: false,
);
@override
Stream<NotificationPlaybackSnapshot> get snapshots => _snapshots.stream;
@override
NotificationPlaybackSnapshot get currentSnapshot => _current;
/// Call this method whenever player state, track, or position changes.
void updateState({
required NotificationTrack? track,
required bool isPlaying,
required bool isFavorite,
required Duration position,
required Duration bufferedPosition,
}) {
_current = NotificationPlaybackSnapshot(
track: track,
phase: isPlaying ? NotificationPlaybackPhase.ready : NotificationPlaybackPhase.idle,
isPlaying: isPlaying,
isFavorite: isFavorite,
position: position,
bufferedPosition: bufferedPosition,
);
_snapshots.add(_current);
}
// Handle standard platform control commands
@override
Future<void> play() async => myPlayer.play();
@override
Future<void> pause() async => myPlayer.pause();
@override
Future<void> skipToNext() async => myPlayer.next();
@override
Future<void> skipToPrevious() async => myPlayer.previous();
@override
Future<void> seek(Duration position) async => myPlayer.seek(position);
@override
Future<void> changeLike() async => toggleFavorite();
@override
Future<void> stop() async => myPlayer.stop();
// Optional: handle custom actions
@override
Future<dynamic> onCustomAction(String name, [Map<String, dynamic>? extras]) async {
if (name == 'my_custom_action') {
// Execute custom action logic
return true;
}
return null;
}
}
Step 2: Initialize the Notification Media Session
Initialize the bridge during application startup (e.g., in main()):
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final gateway = MyPlayerNotificationGateway();
final handler = await initializeNotificationMediaSession(
gateway,
config: const NotificationMediaSessionConfig(
androidNotificationChannelId: 'com.example.app.playback',
androidNotificationChannelName: 'Media Playback',
androidStopForegroundOnPause: false,
),
);
runApp(const MyApp());
}
4. Customizing Media Card Buttons & Actions
Button Slot Ordering (Left-to-Right)
On Android and desktop media cards (which typically feature up to 5 action slots), the physical order of buttons from left to right strictly matches the order of elements in the controlsBuilder list:
- Slot 1 (leftmost):
list[0] - Slot 2:
list[1] - Slot 3 (center):
list[2] - Slot 4:
list[3] - Slot 5 (rightmost):
list[4]
You can freely reorder, swap, or insert any built-in controls or custom actions by arranging the list returned from controlsBuilder.
Example 1: Custom Button Order (e.g. Play/Pause -> Favorite -> Next -> Previous)
final handler = await initializeNotificationMediaSession(
gateway,
config: NotificationMediaSessionConfig(
androidNotificationChannelId: 'com.example.app.playback',
androidNotificationChannelName: 'Media Playback',
// Custom button order: Play/Pause, Favorite, Next, Previous
controlsBuilder: (snapshot) => [
// 1. Play / Pause
snapshot.isPlaying ? NotificationControls.pause : NotificationControls.play,
// 2. Favorite / Unfavorite (dynamically switches filled / outline heart)
snapshot.isFavorite ? NotificationControls.favorite : NotificationControls.favoriteBorder,
// 3. Next track
NotificationControls.skipNext,
// 4. Previous track
NotificationControls.skipPrevious,
],
),
);
Example 2: Custom Controls with Rewind, Fast Forward, and Custom Repeat Action
final handler = await initializeNotificationMediaSession(
gateway,
config: NotificationMediaSessionConfig(
androidNotificationChannelId: 'com.example.app.playback',
androidNotificationChannelName: 'Media Playback',
// Custom button layout:
controlsBuilder: (snapshot) => [
NotificationControls.rewind,
snapshot.isPlaying ? NotificationControls.pause : NotificationControls.play,
NotificationControls.fastForward,
// Custom action with custom/built-in Android drawable
NotificationControls.custom(
name: 'toggle_repeat',
label: 'Repeat Mode',
androidIcon: 'drawable/ic_repeat', // Your custom drawable in android/app/src/main/res/drawable/
),
],
// Handle custom action:
onCustomAction: (name, extras) async {
if (name == 'toggle_repeat') {
// Toggle player repeat mode
return true;
}
return null;
},
),
);
Pre-built Controls & Layout Generators
The package provides standard controls ready to use:
NotificationControls.favorite&NotificationControls.favoriteBorderNotificationControls.skipPrevious&NotificationControls.skipNextNotificationControls.play&NotificationControls.pauseNotificationControls.stopNotificationControls.rewind&NotificationControls.fastForwardNotificationControls.spacer(Transparent placeholder icondrawable/ic_notif_spacerused to balance Android 5-slot layouts)NotificationControls.custom(...)(Factory to construct custom action buttons with native Android 13+ support)
Built-in Layout Generators:
NotificationControls.defaultControls:[Previous, Spacer, Play/Pause, Spacer, Next](Default layout whencontrolsBuilderis omitted; evenly distributes the 3 buttons across all 5 slots on Android).NotificationControls.spacedControls:[Previous, Spacer, Play/Pause, Spacer, Next](Alias fordefaultControls; spreads buttons across slots 1, 3, 5).NotificationControls.centeredControls:[Spacer, Previous, Play/Pause, Next, Spacer](Centers the 3 buttons in slots 2, 3, 4 with left/right transparent margins).NotificationControls.favoriteControls:[Favorite, Previous, Play/Pause, Next](4-button player layout with favorite button).NotificationControls.standardControls:[Previous, Play/Pause, Next](Classic left-aligned 3-button layout without favorite or spacers).
Note on Android Compact (Collapsed) View: When using
spacedControlsorcenteredControls, the package automatically excludesspacerplaceholders fromandroidCompactActionIndices, ensuring the collapsed notification cleanly displays only the 3 actual playback buttons[Previous, Play/Pause, Next].
5. API Reference
NotificationTrack
id: Stable track identifier.title: Track title.duration: Track duration.artist: Artist name (optional).album: Album title (optional).artworkUri: Remote URL or localfile:///android.resource://URI for artwork.artworkHeaders: HTTP headers for authenticated artwork image loading.
NotificationPlaybackSnapshot
track: ActiveNotificationTrack?.phase: Playback lifecycle (NotificationPlaybackPhase.ready,loading,buffering, etc.).isPlaying: Boolean indicating playback state.isFavorite: Boolean indicating favorite state.position: Current playback positionDuration.bufferedPosition: Buffered playback positionDuration.speed: Playback rate (defaults to1.0).
License
MIT. See LICENSE.