1
0
mirror of https://github.com/natekspencer/hacs-oasis_mini.git synced 2025-11-08 05:03:52 -05:00

Adjust media player to allow adding multiple tracks at a time

This commit is contained in:
Nathan Spencer
2025-01-10 21:48:31 +00:00
parent ddabccc4a8
commit 51c4c8a6a2
4 changed files with 74 additions and 24 deletions

View File

@@ -56,6 +56,20 @@ Alternatively:
After this integration is set up, you can configure the integration to connect to the Kinetic Oasis cloud API. This will allow pulling in certain details (such as track name and image) that are otherwise not available. After this integration is set up, you can configure the integration to connect to the Kinetic Oasis cloud API. This will allow pulling in certain details (such as track name and image) that are otherwise not available.
# Actions
The media player entity supports various actions, including managing the playlist queue. You can specify a track by its ID or name. If using a track name, it must match an entry in the [tracks list](custom_components/oasis_mini/pyoasismini/tracks.json). To specify multiple tracks, separate them with commas. An example is below:
```yaml
action: media_player.play_media
target:
entity_id: media_player.oasis_mini
data:
media_content_id: 63, Turtle
media_content_type: track
enqueue: replace
```
--- ---
## Support Me ## Support Me

View File

@@ -2,11 +2,14 @@
from __future__ import annotations from __future__ import annotations
import logging
from typing import Any from typing import Any
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST
from .pyoasismini import OasisMini from .pyoasismini import TRACKS, OasisMini
_LOGGER = logging.getLogger(__name__)
def create_client(data: dict[str, Any]) -> OasisMini: def create_client(data: dict[str, Any]) -> OasisMini:
@@ -27,3 +30,21 @@ async def add_and_play_track(device: OasisMini, track: int) -> None:
if device.status_code != 4: if device.status_code != 4:
await device.async_play() await device.async_play()
def get_track_id(track: str) -> int | None:
"""Get a track id.
`track` can be either an id or title
"""
track = track.lower().strip()
if track not in map(str, TRACKS):
track = next(
(id for id, info in TRACKS.items() if info["name"].lower() == track), track
)
try:
return int(track)
except ValueError:
_LOGGER.warning("Invalid track: %s", track)
return None

View File

@@ -19,7 +19,9 @@ from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import OasisMiniConfigEntry from . import OasisMiniConfigEntry
from .const import DOMAIN
from .entity import OasisMiniEntity from .entity import OasisMiniEntity
from .helpers import get_track_id
from .pyoasismini.const import TRACKS from .pyoasismini.const import TRACKS
@@ -102,18 +104,30 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
return MediaPlayerState.ON return MediaPlayerState.ON
return MediaPlayerState.IDLE return MediaPlayerState.IDLE
def abort_if_busy(self) -> None:
"""Abort if the device is currently busy."""
if self.device.busy:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="device_busy",
translation_placeholders={"name": self._friendly_name_internal()},
)
async def async_media_pause(self) -> None: async def async_media_pause(self) -> None:
"""Send pause command.""" """Send pause command."""
self.abort_if_busy()
await self.device.async_pause() await self.device.async_pause()
await self.coordinator.async_request_refresh() await self.coordinator.async_request_refresh()
async def async_media_play(self) -> None: async def async_media_play(self) -> None:
"""Send play command.""" """Send play command."""
self.abort_if_busy()
await self.device.async_play() await self.device.async_play()
await self.coordinator.async_request_refresh() await self.coordinator.async_request_refresh()
async def async_media_stop(self) -> None: async def async_media_stop(self) -> None:
"""Send stop command.""" """Send stop command."""
self.abort_if_busy()
await self.device.async_stop() await self.device.async_stop()
await self.coordinator.async_request_refresh() await self.coordinator.async_request_refresh()
@@ -127,6 +141,7 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
async def async_media_previous_track(self) -> None: async def async_media_previous_track(self) -> None:
"""Send previous track command.""" """Send previous track command."""
self.abort_if_busy()
if (index := self.device.playlist_index - 1) < 0: if (index := self.device.playlist_index - 1) < 0:
index = len(self.device.playlist) - 1 index = len(self.device.playlist) - 1
await self.device.async_change_track(index) await self.device.async_change_track(index)
@@ -134,6 +149,7 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
async def async_media_next_track(self) -> None: async def async_media_next_track(self) -> None:
"""Send next track command.""" """Send next track command."""
self.abort_if_busy()
if (index := self.device.playlist_index + 1) >= len(self.device.playlist): if (index := self.device.playlist_index + 1) >= len(self.device.playlist):
index = 0 index = 0
await self.device.async_change_track(index) await self.device.async_change_track(index)
@@ -147,32 +163,29 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
**kwargs: Any, **kwargs: Any,
) -> None: ) -> None:
"""Play a piece of media.""" """Play a piece of media."""
if media_id not in map(str, TRACKS): self.abort_if_busy()
media_id = next( if media_type == MediaType.PLAYLIST:
( raise ServiceValidationError("Playlists are not currently supported")
id else:
for id, info in TRACKS.items() track = list(filter(None, map(get_track_id, media_id.split(","))))
if info["name"].lower() == media_id.lower() if not track:
), raise ServiceValidationError(f"Invalid media: {media_id}")
media_id,
)
try:
track = int(media_id)
except ValueError as err:
raise ServiceValidationError(f"Invalid media: {media_id}") from err
device = self.device device = self.device
enqueue = MediaPlayerEnqueue.NEXT if not enqueue else enqueue enqueue = MediaPlayerEnqueue.NEXT if not enqueue else enqueue
if enqueue == MediaPlayerEnqueue.REPLACE: if enqueue == MediaPlayerEnqueue.REPLACE:
await device.async_set_playlist([track]) await device.async_set_playlist(track)
else: else:
await device.async_add_track_to_playlist(track) await device.async_add_track_to_playlist(track)
if enqueue in (MediaPlayerEnqueue.NEXT, MediaPlayerEnqueue.PLAY): if enqueue in (MediaPlayerEnqueue.NEXT, MediaPlayerEnqueue.PLAY):
# Move track to next item in the playlist # Move track to next item in the playlist
if (index := (len(device.playlist) - 1)) != device.playlist_index: new_tracks = 1 if isinstance(track, int) else len(track)
if (index := (len(device.playlist) - new_tracks)) != device.playlist_index:
if index != ( if index != (
_next := min(device.playlist_index + 1, len(device.playlist) - 1) _next := min(
device.playlist_index + 1, len(device.playlist) - new_tracks
)
): ):
await device.async_move_track(index, _next) await device.async_move_track(index, _next)
if enqueue == MediaPlayerEnqueue.PLAY: if enqueue == MediaPlayerEnqueue.PLAY:
@@ -188,6 +201,7 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
async def async_clear_playlist(self) -> None: async def async_clear_playlist(self) -> None:
"""Clear players playlist.""" """Clear players playlist."""
self.abort_if_busy()
await self.device.async_clear_playlist() await self.device.async_clear_playlist()
await self.coordinator.async_request_refresh() await self.coordinator.async_request_refresh()

View File

@@ -8,7 +8,6 @@ from typing import Any, Awaitable, Final
from urllib.parse import urljoin from urllib.parse import urljoin
from aiohttp import ClientResponseError, ClientSession from aiohttp import ClientResponseError, ClientSession
import async_timeout
from .const import TRACKS from .const import TRACKS
from .utils import _bit_to_bool, decrypt_svg_content from .utils import _bit_to_bool, decrypt_svg_content
@@ -159,17 +158,17 @@ class OasisMini:
"""Return the url.""" """Return the url."""
return f"http://{self._host}/" return f"http://{self._host}/"
async def async_add_track_to_playlist(self, track: int) -> None: async def async_add_track_to_playlist(self, track: int | list[int]) -> None:
"""Add track to playlist.""" """Add track to playlist."""
if not track: if not track:
return return
if isinstance(track, int):
track = [track]
if 0 in self.playlist: if 0 in self.playlist:
playlist = [t for t in self.playlist if t] + [track] playlist = [t for t in self.playlist if t] + track
return await self.async_set_playlist(playlist) return await self.async_set_playlist(playlist)
await self._async_command(params={"ADDJOBLIST": track}) await self._async_command(params={"ADDJOBLIST": track})
self.playlist.append(track) self.playlist.extend(track)
async def async_change_track(self, index: int) -> None: async def async_change_track(self, index: int) -> None:
"""Change the track.""" """Change the track."""
@@ -312,8 +311,10 @@ class OasisMini:
raise ValueError("Invalid pause option specified") raise ValueError("Invalid pause option specified")
await self._async_command(params={"WRIWAITAFTER": option}) await self._async_command(params={"WRIWAITAFTER": option})
async def async_set_playlist(self, playlist: list[int]) -> None: async def async_set_playlist(self, playlist: list[int] | int) -> None:
"""Set the playlist.""" """Set the playlist."""
if isinstance(playlist, int):
playlist = [playlist]
if is_playing := (self.status_code == 4): if is_playing := (self.status_code == 4):
await self.async_stop() await self.async_stop()
await self._async_command(params={"WRIJOBLIST": ",".join(map(str, playlist))}) await self._async_command(params={"WRIJOBLIST": ",".join(map(str, playlist))})