media_player: handle STOP command correctly (fixes #128)

Home Assistant sends MEDIA_PLAYER_COMMAND_STOP when the queue is cleared.
The MediaPlayer entity did not handle this command, causing playback to
continue without media metadata and leaving the UI in an inconsistent state.

This change adds explicit handling for the STOP command:
- stop playback via the music player
- reset the media player state to IDLE

This aligns the behavior with the original player and Home Assistant’s
expected media player semantics.
This commit is contained in:
Philipp Grafe committed 2026-02-10 18:58:55 +01:00
1 parent 7a92d69864
commit 72c7d4dff4
1 file changed
+30 -1
+30 -1
View File
@@ -1,6 +1,7 @@
from abc import abstractmethod from abc import abstractmethod
from collections.abc import Iterable from collections.abc import Iterable
from typing import Callable, List, Optional, Union from typing import Callable, List, Optional, Union
import logging
# pylint: disable=no-name-in-module # pylint: disable=no-name-in-module
from aioesphomeapi.api_pb2 import ( # type: ignore[attr-defined] from aioesphomeapi.api_pb2 import ( # type: ignore[attr-defined]
@@ -68,6 +69,7 @@ class MediaPlayerEntity(ESPHomeEntity):
self.previous_volume = 1.0 self.previous_volume = 1.0
self.music_player = music_player self.music_player = music_player
self.announce_player = announce_player self.announce_player = announce_player
self._log = logging.getLogger(f"{self.__class__.__name__}[{self.key}]")
def play( def play(
self, self,
@@ -111,19 +113,37 @@ class MediaPlayerEntity(ESPHomeEntity):
yield self._update_state(MediaPlayerState.PLAYING) yield self._update_state(MediaPlayerState.PLAYING)
def handle_message(self, msg: message.Message) -> Iterable[message.Message]: def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
self._log.debug("handle_message called with msg: %s", msg)
if isinstance(msg, MediaPlayerCommandRequest) and (msg.key == self.key): if isinstance(msg, MediaPlayerCommandRequest) and (msg.key == self.key):
self._log.debug("MediaPlayerCommandRequest matched for this key")
if msg.has_media_url: if msg.has_media_url:
self._log.debug("Message has media URL: %s", msg.media_url)
announcement = msg.has_announcement and msg.announcement announcement = msg.has_announcement and msg.announcement
yield from self.play(msg.media_url, announcement=announcement) yield from self.play(msg.media_url, announcement=announcement)
elif msg.has_command: elif msg.has_command:
self._log.debug("Message has command: %s", msg.command)
command = MediaPlayerCommand(msg.command) command = MediaPlayerCommand(msg.command)
if msg.command == MediaPlayerCommand.PAUSE: if msg.command == MediaPlayerCommand.PAUSE:
self._log.debug("Executing PAUSE")
self.music_player.pause() self.music_player.pause()
yield self._update_state(MediaPlayerState.PAUSED) yield self._update_state(MediaPlayerState.PAUSED)
elif msg.command == MediaPlayerCommand.PLAY: elif msg.command == MediaPlayerCommand.PLAY:
self._log.debug("Executing PLAY / RESUME")
self.music_player.resume() self.music_player.resume()
yield self._update_state(MediaPlayerState.PLAYING) yield self._update_state(MediaPlayerState.PLAYING)
elif command == MediaPlayerCommand.STOP:
self._log.debug("Executing STOP")
self.music_player.stop()
yield self._update_state(MediaPlayerState.IDLE)
elif command == MediaPlayerCommand.MUTE: elif command == MediaPlayerCommand.MUTE:
self._log.debug("Executing MUTE")
if not self.muted: if not self.muted:
self.previous_volume = self.volume self.previous_volume = self.volume
self.volume = 0 self.volume = 0
@@ -131,20 +151,26 @@ class MediaPlayerEntity(ESPHomeEntity):
self.announce_player.set_volume(0) self.announce_player.set_volume(0)
self.muted = True self.muted = True
yield self._update_state(self.state) yield self._update_state(self.state)
elif command == MediaPlayerCommand.UNMUTE: elif command == MediaPlayerCommand.UNMUTE:
self._log.debug("Executing UNMUTE")
if self.muted: if self.muted:
self.volume = self.previous_volume self.volume = self.previous_volume
self.music_player.set_volume(int(self.volume * 100)) self.music_player.set_volume(int(self.volume * 100))
self.announce_player.set_volume(int(self.volume * 100)) self.announce_player.set_volume(int(self.volume * 100))
self.muted = False self.muted = False
yield self._update_state(self.state) yield self._update_state(self.state)
elif msg.has_volume: elif msg.has_volume:
self._log.debug("Message has volume: %.2f", msg.volume)
volume = int(msg.volume * 100) volume = int(msg.volume * 100)
self.music_player.set_volume(volume) self.music_player.set_volume(volume)
self.announce_player.set_volume(volume) self.announce_player.set_volume(volume)
self.volume = msg.volume self.volume = msg.volume
yield self._update_state(self.state) yield self._update_state(self.state)
elif isinstance(msg, ListEntitiesRequest): elif isinstance(msg, ListEntitiesRequest):
self._log.debug("ListEntitiesRequest received")
yield ListEntitiesMediaPlayerResponse( yield ListEntitiesMediaPlayerResponse(
object_id=self.object_id, object_id=self.object_id,
key=self.key, key=self.key,
@@ -153,7 +179,10 @@ class MediaPlayerEntity(ESPHomeEntity):
feature_flags=SUPPORTED_MEDIA_PLAYER_FEATURES, feature_flags=SUPPORTED_MEDIA_PLAYER_FEATURES,
) )
elif isinstance(msg, SubscribeHomeAssistantStatesRequest): elif isinstance(msg, SubscribeHomeAssistantStatesRequest):
self._log.debug("SubscribeHomeAssistantStatesRequest received")
yield self._get_state_message() yield self._get_state_message()
else:
self._log.warning("Unknown message type received: %s", type(msg))
def _update_state(self, new_state: MediaPlayerState) -> MediaPlayerStateResponse: def _update_state(self, new_state: MediaPlayerState) -> MediaPlayerStateResponse:
self.state = new_state self.state = new_state