-
Notifications
You must be signed in to change notification settings - Fork 7
feat: add +10s seek forward button to admin control bar #511
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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) | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is safer to validate that
Suggested change
|
||||||||||
| 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: | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| import asyncio | ||
| import logging | ||
| import sys | ||
| from datetime import datetime, timezone | ||
| from typing import TYPE_CHECKING, Any | ||
|
|
||
| if sys.version_info >= (3, 11): | ||
|
|
@@ -551,6 +552,40 @@ 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 | ||
| # Adjust for stale cached position — HA only updates | ||
| # media_position at media_position_updated_at | ||
| updated_at = state.attributes.get("media_position_updated_at") | ||
| if updated_at: | ||
| if isinstance(updated_at, str): | ||
| updated_at = datetime.fromisoformat(updated_at) | ||
| elapsed = (datetime.now(timezone.utc) - updated_at).total_seconds() | ||
| if elapsed > 0: | ||
| current_pos += elapsed | ||
| new_pos = current_pos + seconds | ||
|
Comment on lines
+564
to
+574
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In Home Assistant, the current_pos = state.attributes.get("media_position", 0) or 0
if state.state == "playing":
updated_at = state.attributes.get("media_position_updated_at")
if updated_at:
import homeassistant.util.dt as dt_util
current_pos += (dt_util.utcnow() - updated_at).total_seconds()
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. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -514,9 +514,10 @@ <h2 class="section-header"><span class="section-icon" aria-hidden="true">▶️< | |
| <button id="admin-stop-song" class="btn btn-secondary btn-compact"> | ||
| <span aria-hidden="true">⏹</span> Stop | ||
| </button> | ||
| <button id="admin-vol-down" class="btn btn-secondary btn-compact">🔉</button> | ||
| <button id="admin-vol-up" class="btn btn-secondary btn-compact">🔊</button> | ||
| <button id="admin-end-game-playing" class="btn btn-danger btn-compact">End Game</button> | ||
| <button id="admin-seek-forward" class="btn btn-secondary btn-compact" title="Seek forward 10 seconds">⏩ +10s</button> | ||
| <button id="admin-vol-down" class="btn btn-secondary btn-compact" title="Volume down">🔉</button> | ||
| <button id="admin-vol-up" class="btn btn-secondary btn-compact" title="Volume up">🔊</button> | ||
| <button id="admin-end-game-playing" class="btn btn-danger btn-compact" title="End game">End Game</button> | ||
|
Comment on lines
+517
to
+520
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new |
||
| </div> | ||
|
|
||
| <!-- Player game UI (only shown if admin joined as player) --> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
MediaPlayerProtocoldefined incustom_components/beatify/game/protocols.pyis missing theseek_forwardmethod. Since_media_player_serviceis typed asMediaPlayerProtocol, this call will fail static type checking and violates the service contract. Please update the protocol definition to include this new method.