From 967032014c20687ca5e2883b73030c39c7af8b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Holzh=C3=A4user?= Date: Wed, 8 Apr 2026 22:52:37 +0200 Subject: [PATCH 1/2] feat: add +10s seek forward button to admin control bar (#498) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adds ⏩ +10s button next to volume controls - Reads current media_position from HA state and seeks to pos + 10s - Only visible during PLAYING and REVEAL phases - Adds tooltips to all admin control bar buttons Co-Authored-By: Claude Opus 4.6 (1M context) --- custom_components/beatify/game/state.py | 6 +++++ custom_components/beatify/server/websocket.py | 12 +++++++++ .../beatify/services/media_player.py | 25 +++++++++++++++++++ custom_components/beatify/www/admin.html | 7 +++--- custom_components/beatify/www/js/admin.js | 7 ++++++ 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/custom_components/beatify/game/state.py b/custom_components/beatify/game/state.py index 5de6242..b29665b 100644 --- a/custom_components/beatify/game/state.py +++ b/custom_components/beatify/game/state.py @@ -1849,6 +1849,12 @@ async def set_volume_on_player(self, level: float) -> bool: return await self._media_player_service.set_volume(level) return False + async def seek_forward(self, seconds: int) -> bool: + """Seek media player forward by given seconds (#498).""" + if self._media_player_service: + return await self._media_player_service.seek_forward(seconds) + return False + async def play_deferred_song(self, song: dict) -> bool: """Play a song that was deferred for intro splash (#321). diff --git a/custom_components/beatify/server/websocket.py b/custom_components/beatify/server/websocket.py index 4aec423..54029a1 100644 --- a/custom_components/beatify/server/websocket.py +++ b/custom_components/beatify/server/websocket.py @@ -421,6 +421,7 @@ async def _handle_admin( "next_round": self._admin_next_round, "stop_song": self._admin_stop_song, "set_volume": self._admin_set_volume, + "seek_forward": self._admin_seek_forward, "end_game": self._admin_end_game, "dismiss_game": self._admin_dismiss_game, "rematch_game": self._admin_rematch_game, @@ -646,6 +647,17 @@ async def _admin_set_volume( } ) + async def _admin_seek_forward( + self, ws: web.WebSocketResponse, data: dict, game_state: GameState + ) -> None: + """Handle admin seek_forward action (#498).""" + if game_state.phase not in (GamePhase.PLAYING, GamePhase.REVEAL): + return + seconds = data.get("seconds", 10) + success = await game_state.seek_forward(seconds) + if success: + _LOGGER.info("Media seeked forward %ds", seconds) + async def _admin_end_game( self, ws: web.WebSocketResponse, data: dict, game_state: GameState ) -> None: diff --git a/custom_components/beatify/services/media_player.py b/custom_components/beatify/services/media_player.py index 4effc94..87f6ab7 100644 --- a/custom_components/beatify/services/media_player.py +++ b/custom_components/beatify/services/media_player.py @@ -551,6 +551,31 @@ async def set_volume(self, level: float) -> bool: self._record_error("MEDIA_PLAYER_ERROR", f"Failed to set volume: {err}") return False + async def seek_forward(self, seconds: int) -> bool: + """Seek media forward by given seconds (#498). + + Reads current position from HA state and seeks to position + seconds. + """ + try: + state = self._hass.states.get(self._entity_id) + if not state: + return False + current_pos = state.attributes.get("media_position", 0) or 0 + new_pos = current_pos + seconds + await self._hass.services.async_call( + "media_player", + "media_seek", + { + "entity_id": self._entity_id, + "seek_position": new_pos, + }, + ) + return True # noqa: TRY300 + except Exception as err: # noqa: BLE001 + _LOGGER.error("Failed to seek media: %s", err) # noqa: TRY400 + self._record_error("MEDIA_PLAYER_ERROR", f"Failed to seek: {err}") + return False + def is_available(self) -> bool: """ Check if media player is available. diff --git a/custom_components/beatify/www/admin.html b/custom_components/beatify/www/admin.html index 1ef6347..3b0eb65 100644 --- a/custom_components/beatify/www/admin.html +++ b/custom_components/beatify/www/admin.html @@ -514,9 +514,10 @@