From a926a2825b0d9eb13063179f172e6674abc0414d Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 30 Aug 2026 17:04:41 +0700 Subject: [PATCH] fix(player): handle headset media controls --- lib/services/music_player_service.dart | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/services/music_player_service.dart b/lib/services/music_player_service.dart index 2d4682dc..39520310 100644 --- a/lib/services/music_player_service.dart +++ b/lib/services/music_player_service.dart @@ -436,6 +436,16 @@ class MusicPlayerHandler extends BaseAudioHandler } } + // Required on some Android builds because audio_session manages focus. + Future _claimHardwareMediaButtons() async { + if (!Platform.isAndroid) return; + try { + await AudioService.androidForceEnableMediaButtons(); + } catch (e) { + _log.w('Failed to claim Android media-button routing: $e'); + } + } + AudioProcessingState _mapProcessingState(PlayerState state) { switch (state) { case PlayerState.playing: @@ -957,6 +967,8 @@ class MusicPlayerHandler extends BaseAudioHandler // foreground window) so audio_service can start its foreground service // before the async source resolve below. _broadcastState(playerState: PlayerState.playing, loading: true); + await _claimHardwareMediaButtons(); + if (!_isCurrentPlayRequest(generation, media)) return; final resolved = await _resolveSource(media); if (!_isCurrentPlayRequest(generation, media)) return; @@ -1137,10 +1149,31 @@ class MusicPlayerHandler extends BaseAudioHandler return; } await _activateAudioSession(); + await _claimHardwareMediaButtons(); await _player.resume(); _broadcastState(playerState: PlayerState.playing); } + @override + Future click([MediaButton button = MediaButton.media]) async { + _log.d('Hardware media button: ${button.name}'); + switch (button) { + case MediaButton.media: + if (playbackState.value.playing) { + await pause(); + } else { + await play(); + } + break; + case MediaButton.next: + await skipToNext(); + break; + case MediaButton.previous: + await skipToPrevious(); + break; + } + } + @override Future pause() async { _log.i('Pausing internal player by user/control request');