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
from collections import OrderedDict
from dataclasses import dataclass, field
import datetime
from functools import partial
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:
"""Storage class for platform global data."""
@ -89,6 +98,7 @@ class SonosData:
self.boot_counts: dict[str, int] = {}
self.mdns_names: dict[str, str] = {}
self.entity_id_mappings: dict[str, SonosSpeaker] = {}
self.unjoin_data: dict[str, UnjoinData] = {}
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.dispatcher import async_dispatcher_connect
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 (
DATA_SONOS,
DOMAIN as SONOS_DOMAIN,
@ -777,6 +778,27 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
await self.hass.async_add_executor_job(self.speaker.join, speakers)
async def async_unjoin_player(self):
"""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)
"""Remove this player from any group.
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()
def unjoin(self) -> None:
"""Unjoin the player from a group."""
if self.sonos_group == [self]:
return
self.soco.unjoin()
self.coordinator = None