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)
|
final coverPath = (t.albumId.isNotEmpty && _coverCache != null)
|
||||||
? _coverCache!.peekCached(t.albumId)
|
? _coverCache!.peekCached(t.albumId)
|
||||||
: null;
|
: null;
|
||||||
// MediaItem.rating reflects the user's like state on this track
|
// MediaItem.rating intentionally NOT set: audio_service propagates
|
||||||
// so external surfaces (Wear's heart button, lock-screen
|
// it to MediaSession.setRating(), but the Android session also
|
||||||
// favorites) render the right filled/outlined icon. Updated on
|
// needs setRatingType(RATING_HEART) configured to expose that to
|
||||||
// every track change via _likeBridge.isTrackLiked.
|
// controllers — audio_service doesn't surface that config knob,
|
||||||
final liked = _likeBridge?.isTrackLiked(t.id) ?? false;
|
// 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(
|
return MediaItem(
|
||||||
id: t.id,
|
id: t.id,
|
||||||
title: t.title,
|
title: t.title,
|
||||||
@@ -327,7 +330,6 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
|||||||
album: t.albumTitle,
|
album: t.albumTitle,
|
||||||
duration: Duration(seconds: t.durationSec),
|
duration: Duration(seconds: t.durationSec),
|
||||||
artUri: coverPath != null ? Uri.file(coverPath) : null,
|
artUri: coverPath != null ? Uri.file(coverPath) : null,
|
||||||
rating: Rating.newHeartRating(liked),
|
|
||||||
extras: extras.isEmpty ? null : extras,
|
extras: extras.isEmpty ? null : extras,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -412,19 +414,6 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
|||||||
@override
|
@override
|
||||||
Future<void> skipToPrevious() => _player.seekToPrevious();
|
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,
|
/// Heart rating from external surfaces (Wear's favorite button,
|
||||||
/// lock-screen like) → LikesController.toggle(track). We only
|
/// lock-screen like) → LikesController.toggle(track). We only
|
||||||
/// route through the bridge when the rating actually flips relative
|
/// route through the bridge when the rating actually flips relative
|
||||||
@@ -498,33 +487,32 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
|||||||
MediaControl.skipToPrevious,
|
MediaControl.skipToPrevious,
|
||||||
if (playing) MediaControl.pause else MediaControl.play,
|
if (playing) MediaControl.pause else MediaControl.play,
|
||||||
MediaControl.skipToNext,
|
MediaControl.skipToNext,
|
||||||
MediaControl.stop,
|
|
||||||
],
|
],
|
||||||
// androidCompactActionIndices tells the system which controls
|
// androidCompactActionIndices tells the system which controls
|
||||||
// appear in the collapsed/lock-screen view. Without this, some
|
// appear in the collapsed/lock-screen view. Without this, some
|
||||||
// Android versions render the player without working buttons.
|
// 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],
|
androidCompactActionIndices: const [0, 1, 2],
|
||||||
// systemActions enumerates which actions the system can invoke
|
// systemActions enumerates which actions the system can invoke
|
||||||
// on us. Anything not listed here doesn't route back to the
|
// on us — without play/pause/skip in here, taps on lock-screen
|
||||||
// handler from external surfaces (Wear, Auto, Bluetooth, lock
|
// controls don't route back to the handler on Android 13+.
|
||||||
// screen) — Android 13+ silently drops those events. Keep this
|
//
|
||||||
// in sync with the override methods so each implemented action
|
// v2026.05.13.3 added stop, skipToQueueItem, setShuffleMode,
|
||||||
// is actually advertised.
|
// 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 {
|
systemActions: const {
|
||||||
MediaAction.play,
|
MediaAction.play,
|
||||||
MediaAction.pause,
|
MediaAction.pause,
|
||||||
MediaAction.stop,
|
|
||||||
MediaAction.skipToNext,
|
MediaAction.skipToNext,
|
||||||
MediaAction.skipToPrevious,
|
MediaAction.skipToPrevious,
|
||||||
MediaAction.skipToQueueItem,
|
|
||||||
MediaAction.seek,
|
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: switch (_player.processingState) {
|
||||||
ProcessingState.idle => AudioProcessingState.idle,
|
ProcessingState.idle => AudioProcessingState.idle,
|
||||||
|
|||||||
Reference in New Issue
Block a user