"""Snapcast group.""" import logging _LOGGER = logging.getLogger(__name__) # pylint: disable=too-many-public-methods class Snapgroup(): """Represents a snapcast group.""" def __init__(self, server, data): """Initialize.""" self._server = server self._snapshot = None self._callback_func = None self.update(data) def update(self, data): """Update group.""" self._group = data @property def identifier(self): """Get group identifier.""" return self._group.get('id') @property def name(self): """Get group name.""" return self._group.get('name') async def set_name(self, name): """Set a group name.""" if not name: name = '' self._group['name'] = name await self._server.group_name(self.identifier, name) @property def stream(self): """Get stream identifier.""" return self._group.get('stream_id') async def set_stream(self, stream_id): """Set group stream.""" self._group['stream_id'] = stream_id await self._server.group_stream(self.identifier, stream_id) _LOGGER.debug('set stream to %s on %s', stream_id, self.friendly_name) @property def stream_status(self): """Get stream status.""" return self._server.stream(self.stream).status @property def muted(self): """Get mute status.""" return self._group.get('muted') async def set_muted(self, status): """Set group mute status.""" self._group['muted'] = status await self._server.group_mute(self.identifier, status) _LOGGER.debug('set muted to %s on %s', status, self.friendly_name) @property def volume(self): """Get volume.""" volume_sum = 0 for client in self._group.get('clients'): volume_sum += self._server.client(client.get('id')).volume return int(volume_sum / len(self._group.get('clients'))) async def set_volume(self, volume): """Set volume.""" if volume not in range(0, 101): raise ValueError('Volume out of range') current_volume = self.volume if volume == current_volume: _LOGGER.debug('left volume at %s on group %s', volume, self.friendly_name) return delta = volume - current_volume if delta < 0: ratio = (current_volume - volume) / current_volume else: ratio = (volume - current_volume) / (100 - current_volume) for data in self._group.get('clients'): client = self._server.client(data.get('id')) client_volume = client.volume if delta < 0: client_volume -= ratio * client_volume else: client_volume += ratio * (100 - client_volume) client_volume = round(client_volume) await client.set_volume(client_volume, update_group=False) client.update_volume({ 'volume': { 'percent': client_volume, 'muted': client.muted } }) _LOGGER.debug('set volume to %s on group %s', volume, self.friendly_name) @property def friendly_name(self): """Get friendly name.""" fname = self.name if self.name != '' else "+".join( sorted([self._server.client(c).friendly_name for c in self.clients if c in [client.identifier for client in self._server.clients]])) return fname if fname != '' else self.identifier @property def clients(self): """Get client identifiers.""" return [client.get('id') for client in self._group.get('clients')] async def add_client(self, client_identifier): """Add a client.""" if client_identifier in self.clients: _LOGGER.error('%s already in group %s', client_identifier, self.identifier) return new_clients = self.clients new_clients.append(client_identifier) await self._server.group_clients(self.identifier, new_clients) _LOGGER.debug('added %s to %s', client_identifier, self.identifier) status = (await self._server.status())[0] self._server.synchronize(status) self._server.client(client_identifier).callback() self.callback() async def remove_client(self, client_identifier): """Remove a client.""" new_clients = self.clients new_clients.remove(client_identifier) await self._server.group_clients(self.identifier, new_clients) _LOGGER.debug('removed %s from %s', client_identifier, self.identifier) status = (await self._server.status())[0] self._server.synchronize(status) self._server.client(client_identifier).callback() self.callback() def streams_by_name(self): """Get available stream objects by name.""" return {stream.friendly_name: stream for stream in self._server.streams} def update_mute(self, data): """Update mute.""" self._group['muted'] = data['mute'] self.callback() _LOGGER.debug('updated mute on %s', self.friendly_name) def update_name(self, data): """Update name.""" self._group['name'] = data['name'] _LOGGER.debug('updated name on %s', self.name) self.callback() def update_stream(self, data): """Update stream.""" self._group['stream_id'] = data['stream_id'] self.callback() _LOGGER.debug('updated stream to %s on %s', self.stream, self.friendly_name) def snapshot(self): """Snapshot current state.""" self._snapshot = { 'muted': self.muted, 'volume': self.volume, 'stream': self.stream } _LOGGER.debug('took snapshot of current state of %s', self.friendly_name) async def restore(self): """Restore snapshotted state.""" if not self._snapshot: return await self.set_muted(self._snapshot['muted']) await self.set_volume(self._snapshot['volume']) await self.set_stream(self._snapshot['stream']) self.callback() _LOGGER.debug('restored snapshot of state of %s', self.friendly_name) def callback(self): """Run callback.""" if self._callback_func and callable(self._callback_func): self._callback_func(self) def set_callback(self, func): """Set callback.""" self._callback_func = func def __repr__(self): """Return string representation.""" return f'Snapgroup ({self.friendly_name}, {self.identifier})'