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:
jjlawren 2022-06-28 15:19:27 -05:00 committed by GitHub
parent abe44a100f
commit 4bfdb1433e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 38 additions and 4 deletions

View file

@ -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:

View file

@ -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()

View file

@ -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