revert(player): MediaSession surface back to 5 advertised actions
Pixel Watch 2 stopped showing controls entirely after v2026.05.13.3's MediaSession expansion. Reverting the additive pieces: * systemActions back to the original 5 (play / pause / skipPrev / skipNext / seek). stop, skipToQueueItem, setShuffleMode, setRepeatMode, setRating removed. * controls list back to skipPrev / play|pause / skipNext (no stop). * stop() override removed — let BaseAudioHandler default apply (probably needs to be a no-op for the MediaSession to stay alive through certain lifecycle events that audio_service triggers internally; the override was actually halting the session). * MediaItem.rating no longer set in _toMediaItem. The Android MediaSession.setRating() path requires setRatingType(RATING_HEART) to actually expose to controllers, and audio_service doesn't surface that config knob — broadcasting an unanchored rating appears to make Wear OS reject the session entirely. Kept in place: * skipToQueueItem override — still needed for QueueScreen's direct handler call (not routed through MediaSession actions). * setRating override + LikeBridge wiring — harmless if never invoked, and lights up automatically if we figure out how to configure the rating type later. * AlbumCoverCache.peekCached for sync artUri seed — that part worked, and the failure mode would be a missing cover, not a rejected session. Watch should come back to its previous "sometimes works" state from v2026.05.13.2 (basic controls only). Getting past that needs proper MediaSession config that audio_service either doesn't expose or requires platform-channel work.
This commit is contained in:
@@ -315,11 +315,14 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
final coverPath = (t.albumId.isNotEmpty && _coverCache != null)
|
||||
? _coverCache!.peekCached(t.albumId)
|
||||
: null;
|
||||
// MediaItem.rating reflects the user's like state on this track
|
||||
// so external surfaces (Wear's heart button, lock-screen
|
||||
// favorites) render the right filled/outlined icon. Updated on
|
||||
// every track change via _likeBridge.isTrackLiked.
|
||||
final liked = _likeBridge?.isTrackLiked(t.id) ?? false;
|
||||
// MediaItem.rating intentionally NOT set: audio_service propagates
|
||||
// it to MediaSession.setRating(), but the Android session also
|
||||
// needs setRatingType(RATING_HEART) configured to expose that to
|
||||
// controllers — audio_service doesn't surface that config knob,
|
||||
// and broadcasting an unanchored rating made Wear OS reject the
|
||||
// session entirely. The LikeBridge wiring stays in place so
|
||||
// setRating can still fire from any surface that DOES route it,
|
||||
// we just don't advertise it.
|
||||
return MediaItem(
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
@@ -327,7 +330,6 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
album: t.albumTitle,
|
||||
duration: Duration(seconds: t.durationSec),
|
||||
artUri: coverPath != null ? Uri.file(coverPath) : null,
|
||||
rating: Rating.newHeartRating(liked),
|
||||
extras: extras.isEmpty ? null : extras,
|
||||
);
|
||||
}
|
||||
@@ -412,19 +414,6 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
@override
|
||||
Future<void> skipToPrevious() => _player.seekToPrevious();
|
||||
|
||||
/// Stops playback and dismisses the system notification. BaseAudio
|
||||
/// Handler's default just sets the processing state to idle without
|
||||
/// touching the player, so the just_audio engine kept its audio
|
||||
/// session and external surfaces (Wear, Auto, BT) saw a "paused
|
||||
/// forever" rather than a clean stop. Halting the player releases
|
||||
/// the session and lets super.stop() tear down the foreground
|
||||
/// notification.
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
await _player.stop();
|
||||
await super.stop();
|
||||
}
|
||||
|
||||
/// Heart rating from external surfaces (Wear's favorite button,
|
||||
/// lock-screen like) → LikesController.toggle(track). We only
|
||||
/// route through the bridge when the rating actually flips relative
|
||||
@@ -498,33 +487,32 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
MediaControl.skipToPrevious,
|
||||
if (playing) MediaControl.pause else MediaControl.play,
|
||||
MediaControl.skipToNext,
|
||||
MediaControl.stop,
|
||||
],
|
||||
// androidCompactActionIndices tells the system which controls
|
||||
// appear in the collapsed/lock-screen view. Without this, some
|
||||
// Android versions render the player without working buttons.
|
||||
// Keep this at 3 actions (prev/play-pause/next) — stop lives
|
||||
// in the expanded view only.
|
||||
androidCompactActionIndices: const [0, 1, 2],
|
||||
// systemActions enumerates which actions the system can invoke
|
||||
// on us. Anything not listed here doesn't route back to the
|
||||
// handler from external surfaces (Wear, Auto, Bluetooth, lock
|
||||
// screen) — Android 13+ silently drops those events. Keep this
|
||||
// in sync with the override methods so each implemented action
|
||||
// is actually advertised.
|
||||
// on us — without play/pause/skip in here, taps on lock-screen
|
||||
// controls don't route back to the handler on Android 13+.
|
||||
//
|
||||
// v2026.05.13.3 added stop, skipToQueueItem, setShuffleMode,
|
||||
// setRepeatMode, and setRating to this set; reverted because
|
||||
// Pixel Watch 2 stopped showing controls entirely after that
|
||||
// change. The MediaSession contract on Wear OS requires more
|
||||
// setup than just advertising the action (e.g. setRatingType
|
||||
// for setRating) and audio_service doesn't expose those knobs.
|
||||
// Keep the override methods themselves (skipToQueueItem is
|
||||
// still routed via QueueScreen's direct handler call;
|
||||
// setRating is harmless if never invoked) so we don't lose
|
||||
// the underlying functionality — just don't tell the system
|
||||
// we support them.
|
||||
systemActions: const {
|
||||
MediaAction.play,
|
||||
MediaAction.pause,
|
||||
MediaAction.stop,
|
||||
MediaAction.skipToNext,
|
||||
MediaAction.skipToPrevious,
|
||||
MediaAction.skipToQueueItem,
|
||||
MediaAction.seek,
|
||||
MediaAction.setShuffleMode,
|
||||
MediaAction.setRepeatMode,
|
||||
// Heart rating — Wear and lock screen render this as a like
|
||||
// button. Routed to LikesController via _likeBridge.
|
||||
MediaAction.setRating,
|
||||
},
|
||||
processingState: switch (_player.processingState) {
|
||||
ProcessingState.idle => AudioProcessingState.idle,
|
||||
|
||||
Reference in New Issue
Block a user