Optimize Sonos unjoin behavior when using media_player.unjoin
(#74086)
* Coalesce Sonos unjoins to process together * Refactor for readability * Skip unjoin call if already ungrouped * Store unjoin data in a dedicated dataclass * Revert import adjustment
This commit is contained in:
parent
abe44a100f
commit
4bfdb1433e
3 changed files with 38 additions and 4 deletions
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from dataclasses import dataclass, field
|
||||||
import datetime
|
import datetime
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
@ -74,6 +75,14 @@ CONFIG_SCHEMA = vol.Schema(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UnjoinData:
|
||||||
|
"""Class to track data necessary for unjoin coalescing."""
|
||||||
|
|
||||||
|
speakers: list[SonosSpeaker]
|
||||||
|
event: asyncio.Event = field(default_factory=asyncio.Event)
|
||||||
|
|
||||||
|
|
||||||
class SonosData:
|
class SonosData:
|
||||||
"""Storage class for platform global data."""
|
"""Storage class for platform global data."""
|
||||||
|
|
||||||
|
@ -89,6 +98,7 @@ class SonosData:
|
||||||
self.boot_counts: dict[str, int] = {}
|
self.boot_counts: dict[str, int] = {}
|
||||||
self.mdns_names: dict[str, str] = {}
|
self.mdns_names: dict[str, str] = {}
|
||||||
self.entity_id_mappings: dict[str, SonosSpeaker] = {}
|
self.entity_id_mappings: dict[str, SonosSpeaker] = {}
|
||||||
|
self.unjoin_data: dict[str, UnjoinData] = {}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
|
|
|
@ -44,8 +44,9 @@ from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import config_validation as cv, entity_platform, service
|
from homeassistant.helpers import config_validation as cv, entity_platform, service
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.event import async_call_later
|
||||||
|
|
||||||
from . import media_browser
|
from . import UnjoinData, media_browser
|
||||||
from .const import (
|
from .const import (
|
||||||
DATA_SONOS,
|
DATA_SONOS,
|
||||||
DOMAIN as SONOS_DOMAIN,
|
DOMAIN as SONOS_DOMAIN,
|
||||||
|
@ -777,6 +778,27 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
||||||
await self.hass.async_add_executor_job(self.speaker.join, speakers)
|
await self.hass.async_add_executor_job(self.speaker.join, speakers)
|
||||||
|
|
||||||
async def async_unjoin_player(self):
|
async def async_unjoin_player(self):
|
||||||
"""Remove this player from any group."""
|
"""Remove this player from any group.
|
||||||
async with self.hass.data[DATA_SONOS].topology_condition:
|
|
||||||
await self.hass.async_add_executor_job(self.speaker.unjoin)
|
Coalesces all calls within 0.5s to allow use of SonosSpeaker.unjoin_multi()
|
||||||
|
which optimizes the order in which speakers are removed from their groups.
|
||||||
|
Removing coordinators last better preserves playqueues on the speakers.
|
||||||
|
"""
|
||||||
|
sonos_data = self.hass.data[DATA_SONOS]
|
||||||
|
household_id = self.speaker.household_id
|
||||||
|
|
||||||
|
async def async_process_unjoin(now: datetime.datetime) -> None:
|
||||||
|
"""Process the unjoin with all remove requests within the coalescing period."""
|
||||||
|
unjoin_data = sonos_data.unjoin_data.pop(household_id)
|
||||||
|
await SonosSpeaker.unjoin_multi(self.hass, unjoin_data.speakers)
|
||||||
|
unjoin_data.event.set()
|
||||||
|
|
||||||
|
if unjoin_data := sonos_data.unjoin_data.get(household_id):
|
||||||
|
unjoin_data.speakers.append(self.speaker)
|
||||||
|
else:
|
||||||
|
unjoin_data = sonos_data.unjoin_data[household_id] = UnjoinData(
|
||||||
|
speakers=[self.speaker]
|
||||||
|
)
|
||||||
|
async_call_later(self.hass, 0.5, async_process_unjoin)
|
||||||
|
|
||||||
|
await unjoin_data.event.wait()
|
||||||
|
|
|
@ -906,6 +906,8 @@ class SonosSpeaker:
|
||||||
@soco_error()
|
@soco_error()
|
||||||
def unjoin(self) -> None:
|
def unjoin(self) -> None:
|
||||||
"""Unjoin the player from a group."""
|
"""Unjoin the player from a group."""
|
||||||
|
if self.sonos_group == [self]:
|
||||||
|
return
|
||||||
self.soco.unjoin()
|
self.soco.unjoin()
|
||||||
self.coordinator = None
|
self.coordinator = None
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue