Rework roon media player grouping to use media_player base services (#49667)
* Add group/join status attributes to roon player. * Rework join/unjoin code to use base media_player services. * Switch join and unjoin to be sync.pull/49794/head
parent
3fda66d9e2
commit
cd84595429
|
@ -3,7 +3,7 @@
|
||||||
"name": "RoonLabs music player",
|
"name": "RoonLabs music player",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/roon",
|
"documentation": "https://www.home-assistant.io/integrations/roon",
|
||||||
"requirements": ["roonapi==0.0.32"],
|
"requirements": ["roonapi==0.0.36"],
|
||||||
"codeowners": ["@pavoni"],
|
"codeowners": ["@pavoni"],
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import voluptuous as vol
|
||||||
from homeassistant.components.media_player import MediaPlayerEntity
|
from homeassistant.components.media_player import MediaPlayerEntity
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
SUPPORT_BROWSE_MEDIA,
|
SUPPORT_BROWSE_MEDIA,
|
||||||
|
SUPPORT_GROUPING,
|
||||||
SUPPORT_NEXT_TRACK,
|
SUPPORT_NEXT_TRACK,
|
||||||
SUPPORT_PAUSE,
|
SUPPORT_PAUSE,
|
||||||
SUPPORT_PLAY,
|
SUPPORT_PLAY,
|
||||||
|
@ -42,6 +43,7 @@ from .media_browser import browse_media
|
||||||
|
|
||||||
SUPPORT_ROON = (
|
SUPPORT_ROON = (
|
||||||
SUPPORT_BROWSE_MEDIA
|
SUPPORT_BROWSE_MEDIA
|
||||||
|
| SUPPORT_GROUPING
|
||||||
| SUPPORT_PAUSE
|
| SUPPORT_PAUSE
|
||||||
| SUPPORT_VOLUME_SET
|
| SUPPORT_VOLUME_SET
|
||||||
| SUPPORT_STOP
|
| SUPPORT_STOP
|
||||||
|
@ -59,12 +61,8 @@ SUPPORT_ROON = (
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SERVICE_JOIN = "join"
|
|
||||||
SERVICE_UNJOIN = "unjoin"
|
|
||||||
SERVICE_TRANSFER = "transfer"
|
SERVICE_TRANSFER = "transfer"
|
||||||
|
|
||||||
ATTR_JOIN = "join_ids"
|
|
||||||
ATTR_UNJOIN = "unjoin_ids"
|
|
||||||
ATTR_TRANSFER = "transfer_id"
|
ATTR_TRANSFER = "transfer_id"
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,16 +73,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
|
||||||
# Register entity services
|
# Register entity services
|
||||||
platform = entity_platform.current_platform.get()
|
platform = entity_platform.current_platform.get()
|
||||||
platform.async_register_entity_service(
|
|
||||||
SERVICE_JOIN,
|
|
||||||
{vol.Required(ATTR_JOIN): vol.All(cv.ensure_list, [cv.entity_id])},
|
|
||||||
"join",
|
|
||||||
)
|
|
||||||
platform.async_register_entity_service(
|
|
||||||
SERVICE_UNJOIN,
|
|
||||||
{vol.Optional(ATTR_UNJOIN): vol.All(cv.ensure_list, [cv.entity_id])},
|
|
||||||
"unjoin",
|
|
||||||
)
|
|
||||||
platform.async_register_entity_service(
|
platform.async_register_entity_service(
|
||||||
SERVICE_TRANSFER,
|
SERVICE_TRANSFER,
|
||||||
{vol.Required(ATTR_TRANSFER): cv.entity_id},
|
{vol.Required(ATTR_TRANSFER): cv.entity_id},
|
||||||
|
@ -164,6 +152,13 @@ class RoonDevice(MediaPlayerEntity):
|
||||||
"""Flag media player features that are supported."""
|
"""Flag media player features that are supported."""
|
||||||
return SUPPORT_ROON
|
return SUPPORT_ROON
|
||||||
|
|
||||||
|
@property
|
||||||
|
def group_members(self):
|
||||||
|
"""Return the grouped players."""
|
||||||
|
|
||||||
|
roon_names = self._server.roonapi.grouped_zone_names(self._output_id)
|
||||||
|
return [self._server.entity_id(roon_name) for roon_name in roon_names]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self):
|
def device_info(self):
|
||||||
"""Return the device info."""
|
"""Return the device info."""
|
||||||
|
@ -491,8 +486,8 @@ class RoonDevice(MediaPlayerEntity):
|
||||||
path_list,
|
path_list,
|
||||||
)
|
)
|
||||||
|
|
||||||
def join(self, join_ids):
|
def join_players(self, group_members):
|
||||||
"""Add another Roon player to this player's join group."""
|
"""Join `group_members` as a player group with the current player."""
|
||||||
|
|
||||||
zone_data = self._server.roonapi.zone_by_output_id(self._output_id)
|
zone_data = self._server.roonapi.zone_by_output_id(self._output_id)
|
||||||
if zone_data is None:
|
if zone_data is None:
|
||||||
|
@ -511,7 +506,7 @@ class RoonDevice(MediaPlayerEntity):
|
||||||
sync_available[zone["display_name"]] = output["output_id"]
|
sync_available[zone["display_name"]] = output["output_id"]
|
||||||
|
|
||||||
names = []
|
names = []
|
||||||
for entity_id in join_ids:
|
for entity_id in group_members:
|
||||||
name = self._server.roon_name(entity_id)
|
name = self._server.roon_name(entity_id)
|
||||||
if name is None:
|
if name is None:
|
||||||
_LOGGER.error("No roon player found for %s", entity_id)
|
_LOGGER.error("No roon player found for %s", entity_id)
|
||||||
|
@ -531,43 +526,17 @@ class RoonDevice(MediaPlayerEntity):
|
||||||
[self._output_id] + [sync_available[name] for name in names]
|
[self._output_id] + [sync_available[name] for name in names]
|
||||||
)
|
)
|
||||||
|
|
||||||
def unjoin(self, unjoin_ids=None):
|
def unjoin_player(self):
|
||||||
"""Remove a Roon player to this player's join group."""
|
"""Remove this player from any group."""
|
||||||
|
|
||||||
zone_data = self._server.roonapi.zone_by_output_id(self._output_id)
|
if not self._server.roonapi.is_grouped(self._output_id):
|
||||||
if zone_data is None:
|
_LOGGER.error(
|
||||||
_LOGGER.error("No zone data for %s", self.name)
|
"Can't unjoin player %s because it's not in a group",
|
||||||
|
self.name,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
join_group = {
|
self._server.roonapi.ungroup_outputs([self._output_id])
|
||||||
output["display_name"]: output["output_id"]
|
|
||||||
for output in zone_data["outputs"]
|
|
||||||
if output["display_name"] != self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
if unjoin_ids is None:
|
|
||||||
# unjoin everything
|
|
||||||
names = list(join_group)
|
|
||||||
else:
|
|
||||||
names = []
|
|
||||||
for entity_id in unjoin_ids:
|
|
||||||
name = self._server.roon_name(entity_id)
|
|
||||||
if name is None:
|
|
||||||
_LOGGER.error("No roon player found for %s", entity_id)
|
|
||||||
return
|
|
||||||
|
|
||||||
if name not in join_group:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Can't unjoin player %s from %s because it's not in the joined group %s",
|
|
||||||
name,
|
|
||||||
self.name,
|
|
||||||
list(join_group),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
names.append(name)
|
|
||||||
|
|
||||||
_LOGGER.debug("Unjoining %s from %s", names, self.name)
|
|
||||||
self._server.roonapi.ungroup_outputs([join_group[name] for name in names])
|
|
||||||
|
|
||||||
async def async_transfer(self, transfer_id):
|
async def async_transfer(self, transfer_id):
|
||||||
"""Transfer playback from this roon player to another."""
|
"""Transfer playback from this roon player to another."""
|
||||||
|
|
|
@ -28,6 +28,7 @@ class RoonServer:
|
||||||
self.offline_devices = set()
|
self.offline_devices = set()
|
||||||
self._exit = False
|
self._exit = False
|
||||||
self._roon_name_by_id = {}
|
self._roon_name_by_id = {}
|
||||||
|
self._id_by_roon_name = {}
|
||||||
|
|
||||||
async def async_setup(self, tries=0):
|
async def async_setup(self, tries=0):
|
||||||
"""Set up a roon server based on config parameters."""
|
"""Set up a roon server based on config parameters."""
|
||||||
|
@ -78,11 +79,16 @@ class RoonServer:
|
||||||
def add_player_id(self, entity_id, roon_name):
|
def add_player_id(self, entity_id, roon_name):
|
||||||
"""Register a roon player."""
|
"""Register a roon player."""
|
||||||
self._roon_name_by_id[entity_id] = roon_name
|
self._roon_name_by_id[entity_id] = roon_name
|
||||||
|
self._id_by_roon_name[roon_name] = entity_id
|
||||||
|
|
||||||
def roon_name(self, entity_id):
|
def roon_name(self, entity_id):
|
||||||
"""Get the name of the roon player from entity_id."""
|
"""Get the name of the roon player from entity_id."""
|
||||||
return self._roon_name_by_id.get(entity_id)
|
return self._roon_name_by_id.get(entity_id)
|
||||||
|
|
||||||
|
def entity_id(self, roon_name):
|
||||||
|
"""Get the id of the roon player from the roon name."""
|
||||||
|
return self._id_by_roon_name.get(roon_name)
|
||||||
|
|
||||||
def stop_roon(self):
|
def stop_roon(self):
|
||||||
"""Stop background worker."""
|
"""Stop background worker."""
|
||||||
self.roonapi.stop()
|
self.roonapi.stop()
|
||||||
|
|
|
@ -1,23 +1,3 @@
|
||||||
join:
|
|
||||||
description: Group players together.
|
|
||||||
fields:
|
|
||||||
entity_id:
|
|
||||||
description: id of the player that will be the master of the group.
|
|
||||||
example: "media_player.study"
|
|
||||||
join_ids:
|
|
||||||
description: id(s) of the players that will join the master.
|
|
||||||
example: "['media_player.bedroom', 'media_player.kitchen']"
|
|
||||||
|
|
||||||
unjoin:
|
|
||||||
description: Remove players from a group.
|
|
||||||
fields:
|
|
||||||
entity_id:
|
|
||||||
description: id of the player that is the master of the group..
|
|
||||||
example: "media_player.study"
|
|
||||||
unjoin_ids:
|
|
||||||
description: Optional id(s) of the players that will be unjoined from the group. If not specified, all players will be unjoined from the master.
|
|
||||||
example: "['media_player.bedroom', 'media_player.kitchen']"
|
|
||||||
|
|
||||||
transfer:
|
transfer:
|
||||||
description: Transfer playback from one player to another.
|
description: Transfer playback from one player to another.
|
||||||
fields:
|
fields:
|
||||||
|
|
|
@ -2000,7 +2000,7 @@ rokuecp==0.8.1
|
||||||
roombapy==1.6.3
|
roombapy==1.6.3
|
||||||
|
|
||||||
# homeassistant.components.roon
|
# homeassistant.components.roon
|
||||||
roonapi==0.0.32
|
roonapi==0.0.36
|
||||||
|
|
||||||
# homeassistant.components.rova
|
# homeassistant.components.rova
|
||||||
rova==0.2.1
|
rova==0.2.1
|
||||||
|
|
|
@ -1067,7 +1067,7 @@ rokuecp==0.8.1
|
||||||
roombapy==1.6.3
|
roombapy==1.6.3
|
||||||
|
|
||||||
# homeassistant.components.roon
|
# homeassistant.components.roon
|
||||||
roonapi==0.0.32
|
roonapi==0.0.36
|
||||||
|
|
||||||
# homeassistant.components.rpi_power
|
# homeassistant.components.rpi_power
|
||||||
rpi-bad-power==0.1.0
|
rpi-bad-power==0.1.0
|
||||||
|
|
Loading…
Reference in New Issue