Add move queue item HEOS entity service (#142301)

This commit is contained in:
Andrew Sayre 2025-04-05 15:05:01 -05:00 committed by GitHub
parent 52143155e7
commit 660cbc136f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 106 additions and 0 deletions

View File

@ -2,6 +2,7 @@
ATTR_PASSWORD = "password"
ATTR_USERNAME = "username"
ATTR_DESTINATION_POSITION = "destination_position"
ATTR_QUEUE_IDS = "queue_ids"
DOMAIN = "heos"
ENTRY_TITLE = "HEOS System"
@ -9,6 +10,7 @@ SERVICE_GET_QUEUE = "get_queue"
SERVICE_GROUP_VOLUME_SET = "group_volume_set"
SERVICE_GROUP_VOLUME_DOWN = "group_volume_down"
SERVICE_GROUP_VOLUME_UP = "group_volume_up"
SERVICE_MOVE_QUEUE_ITEM = "move_queue_item"
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
SERVICE_SIGN_IN = "sign_in"
SERVICE_SIGN_OUT = "sign_out"

View File

@ -6,6 +6,9 @@
"remove_from_queue": {
"service": "mdi:playlist-remove"
},
"move_queue_item": {
"service": "mdi:playlist-edit"
},
"group_volume_set": {
"service": "mdi:volume-medium"
},

View File

@ -479,6 +479,13 @@ class HeosMediaPlayer(CoordinatorEntity[HeosCoordinator], MediaPlayerEntity):
"""Remove items from the queue."""
await self._player.remove_from_queue(queue_ids)
@catch_action_error("move queue item")
async def async_move_queue_item(
self, queue_ids: list[int], destination_position: int
) -> None:
"""Move items in the queue."""
await self._player.move_queue_item(queue_ids, destination_position)
@property
def available(self) -> bool:
"""Return True if the device is available."""

View File

@ -19,6 +19,7 @@ from homeassistant.helpers import (
from homeassistant.helpers.typing import VolDictType, VolSchemaType
from .const import (
ATTR_DESTINATION_POSITION,
ATTR_PASSWORD,
ATTR_QUEUE_IDS,
ATTR_USERNAME,
@ -27,6 +28,7 @@ from .const import (
SERVICE_GROUP_VOLUME_DOWN,
SERVICE_GROUP_VOLUME_SET,
SERVICE_GROUP_VOLUME_UP,
SERVICE_MOVE_QUEUE_ITEM,
SERVICE_REMOVE_FROM_QUEUE,
SERVICE_SIGN_IN,
SERVICE_SIGN_OUT,
@ -87,6 +89,16 @@ REMOVE_FROM_QUEUE_SCHEMA: Final[VolDictType] = {
GROUP_VOLUME_SET_SCHEMA: Final[VolDictType] = {
vol.Required(ATTR_MEDIA_VOLUME_LEVEL): cv.small_float
}
MOVE_QEUEUE_ITEM_SCHEMA: Final[VolDictType] = {
vol.Required(ATTR_QUEUE_IDS): vol.All(
cv.ensure_list,
[vol.All(vol.Coerce(int), vol.Range(min=1, max=1000))],
vol.Unique(),
),
vol.Required(ATTR_DESTINATION_POSITION): vol.All(
vol.Coerce(int), vol.Range(min=1, max=1000)
),
}
MEDIA_PLAYER_ENTITY_SERVICES: Final = (
# Player queue services
@ -96,6 +108,9 @@ MEDIA_PLAYER_ENTITY_SERVICES: Final = (
EntityServiceDescription(
SERVICE_REMOVE_FROM_QUEUE, "async_remove_from_queue", REMOVE_FROM_QUEUE_SCHEMA
),
EntityServiceDescription(
SERVICE_MOVE_QUEUE_ITEM, "async_move_queue_item", MOVE_QEUEUE_ITEM_SCHEMA
),
# Group volume services
EntityServiceDescription(
SERVICE_GROUP_VOLUME_SET,

View File

@ -17,6 +17,26 @@ remove_from_queue:
multiple: true
type: number
move_queue_item:
target:
entity:
integration: heos
domain: media_player
fields:
queue_ids:
required: true
selector:
text:
multiple: true
type: number
destination_position:
required: true
selector:
number:
min: 1
max: 1000
step: 1
group_volume_set:
target:
entity:

View File

@ -100,6 +100,20 @@
}
}
},
"move_queue_item": {
"name": "Move queue item",
"description": "Move one or more items within the play queue.",
"fields": {
"queue_ids": {
"name": "Queue IDs",
"description": "The IDs (indexes) of the items in the queue to move."
},
"destination_position": {
"name": "Destination position",
"description": "The position index in the queue to move the items to."
}
}
},
"group_volume_down": {
"name": "Turn down group volume",
"description": "Turns down the group volume."

View File

@ -39,6 +39,7 @@ class MockHeos(Heos):
self.player_clear_queue: AsyncMock = AsyncMock()
self.player_get_queue: AsyncMock = AsyncMock()
self.player_get_quick_selects: AsyncMock = AsyncMock()
self.player_move_queue_item: AsyncMock = AsyncMock()
self.player_play_next: AsyncMock = AsyncMock()
self.player_play_previous: AsyncMock = AsyncMock()
self.player_play_queue: AsyncMock = AsyncMock()

View File

@ -27,12 +27,14 @@ from syrupy.assertion import SnapshotAssertion
from syrupy.filters import props
from homeassistant.components.heos.const import (
ATTR_DESTINATION_POSITION,
ATTR_QUEUE_IDS,
DOMAIN,
SERVICE_GET_QUEUE,
SERVICE_GROUP_VOLUME_DOWN,
SERVICE_GROUP_VOLUME_SET,
SERVICE_GROUP_VOLUME_UP,
SERVICE_MOVE_QUEUE_ITEM,
SERVICE_REMOVE_FROM_QUEUE,
)
from homeassistant.components.media_player import (
@ -1784,3 +1786,45 @@ async def test_remove_from_queue(
blocking=True,
)
controller.player_remove_from_queue.assert_called_once_with(1, [1, 2])
async def test_move_queue_item_queue(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None:
"""Test the move queue service."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.services.async_call(
DOMAIN,
SERVICE_MOVE_QUEUE_ITEM,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_QUEUE_IDS: [1, "2"],
ATTR_DESTINATION_POSITION: 10,
},
blocking=True,
)
controller.player_move_queue_item.assert_called_once_with(1, [1, 2], 10)
async def test_move_queue_item_queue_error_raises(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None:
"""Test move queue raises error when failed."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
controller.player_move_queue_item.side_effect = HeosError("error")
with pytest.raises(
HomeAssistantError,
match=re.escape("Unable to move queue item: error"),
):
await hass.services.async_call(
DOMAIN,
SERVICE_MOVE_QUEUE_ITEM,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_QUEUE_IDS: [1, "2"],
ATTR_DESTINATION_POSITION: 10,
},
blocking=True,
)