From: Marcel van der Veldt Date: Mon, 7 Sep 2020 09:55:37 +0000 (+0200) Subject: fixes for squeezebox implementation X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=0dfabca3409c316b4ac595bbeb7900771a72550c;p=music-assistant-server.git fixes for squeezebox implementation --- diff --git a/music_assistant/providers/squeezebox/__init__.py b/music_assistant/providers/squeezebox/__init__.py index d7176f59..9873261f 100644 --- a/music_assistant/providers/squeezebox/__init__.py +++ b/music_assistant/providers/squeezebox/__init__.py @@ -28,12 +28,9 @@ CONF_LAST_VOLUME = "last_volume" LOGGER = logging.getLogger(PROV_ID) -CONFIG_ENTRIES = [] +CONFIG_ENTRIES = [] # we don't have any provider config entries (for now) PLAYER_FEATURES = [PlayerFeature.QUEUE, PlayerFeature.CROSSFADE, PlayerFeature.GAPLESS] -PLAYER_CONFIG_ENTRIES = [ - ConfigEntry(entry_key=CONF_LAST_POWER, entry_type=ConfigEntryType.BOOL, hidden=True), - ConfigEntry(entry_key=CONF_LAST_VOLUME, entry_type=ConfigEntryType.INT, hidden=True), -] +PLAYER_CONFIG_ENTRIES = [] # we don't have any player config entries (for now) async def async_setup(mass): @@ -156,8 +153,9 @@ class PySqueezeProvider(PlayerProvider): socket_client = self._socket_clients.get(player_id) if socket_client: await socket_client.async_cmd_power(True) - # store last power state as we need it when the player (re)connects - self.mass.config.player_settings[player_id][CONF_LAST_POWER] = True + # save power and volume state in cache + cache_str = f"squeezebox_player_state_{player_id}" + await self.mass.cache.async_set(cache_str, (True, socket_client.volume_level)) else: LOGGER.warning("Received command for unavailable player: %s", player_id) @@ -170,7 +168,9 @@ class PySqueezeProvider(PlayerProvider): if socket_client: await socket_client.async_cmd_power(False) # store last power state as we need it when the player (re)connects - self.mass.config.player_settings[player_id][CONF_LAST_POWER] = False + # save power and volume state in cache + cache_str = f"squeezebox_player_state_{player_id}" + await self.mass.cache.async_set(cache_str, (False, socket_client.volume_level)) else: LOGGER.warning("Received command for unavailable player: %s", player_id) @@ -183,8 +183,9 @@ class PySqueezeProvider(PlayerProvider): socket_client = self._socket_clients.get(player_id) if socket_client: await socket_client.async_cmd_volume_set(volume_level) - # store last volume state as we need it when the player (re)connects - self.mass.config.player_settings[player_id][CONF_LAST_VOLUME] = volume_level + # save power and volume state in cache + cache_str = f"squeezebox_player_state_{player_id}" + await self.mass.cache.async_set(cache_str, (socket_client.powered, volume_level)) else: LOGGER.warning("Received command for unavailable player: %s", player_id) @@ -293,10 +294,10 @@ class PySqueezeProvider(PlayerProvider): socket_client.features = PLAYER_FEATURES socket_client.config_entries = PLAYER_CONFIG_ENTRIES # restore power/volume states - conf = self.mass.config.player_settings[socket_client.player_id] - last_volume = conf.get(CONF_LAST_VOLUME, 40) + cache_str = f"squeezebox_player_state_{socket_client.player_id}" + cache_data = await self.mass.cache.async_get(cache_str) + last_power, last_volume = cache_data if cache_data else (False, 40) await socket_client.async_cmd_volume_set(last_volume) - last_power = conf.get(CONF_LAST_POWER, False) await socket_client.async_cmd_power(last_power) await self.mass.player_manager.async_add_player(socket_client) self._socket_clients[socket_client.player_id] = socket_client @@ -305,6 +306,7 @@ class PySqueezeProvider(PlayerProvider): elif event == Event.EVENT_DISCONNECTED: await self.mass.player_manager.async_remove_player(socket_client.player_id) self._socket_clients.pop(socket_client.player_id) + del socket_client elif event == Event.EVENT_DECODER_READY: # player is ready for the next track (if any) player_id = socket_client.player_id diff --git a/music_assistant/providers/squeezebox/socket_client.py b/music_assistant/providers/squeezebox/socket_client.py index 7bfa3f6d..a136c6fe 100644 --- a/music_assistant/providers/squeezebox/socket_client.py +++ b/music_assistant/providers/squeezebox/socket_client.py @@ -1,26 +1,14 @@ """Socketclient implementation for Squeezebox emulated player provider.""" import asyncio -import decimal import logging -import os -import random import re -import socket import struct -import sys import time -from collections import OrderedDict from enum import Enum -from typing import Awaitable, List, Tuple +from typing import Awaitable -from music_assistant.utils import ( - callback, - get_hostname, - get_ip, - run_periodic, - try_parse_int, -) +from music_assistant.utils import callback, run_periodic from .constants import PROV_ID @@ -95,6 +83,7 @@ class SqueezeSocketClient: for task in self._tasks: if not task.cancelled(): task.cancel() + asyncio.create_task(self._event_callback(Event.EVENT_DISCONNECTED, self)) @property def player_id(self) -> str: @@ -182,9 +171,11 @@ class SqueezeSocketClient: async def async_cmd_volume_set(self, volume_level: int): """Send new volume level command to player.""" self._volume_control.volume = volume_level - og = self._volume_control.old_gain() - ng = self._volume_control.new_gain() - await self.__async_send_frame(b"audg", struct.pack("!LLBBLL", og, og, 1, 255, ng, ng)) + old_gain = self._volume_control.old_gain() + new_gain = self._volume_control.new_gain() + await self.__async_send_frame( + b"audg", struct.pack("!LLBBLL", old_gain, old_gain, 1, 255, new_gain, new_gain) + ) self._volume_level = volume_level async def async_cmd_mute(self, muted: bool = False): @@ -241,6 +232,9 @@ class SqueezeSocketClient: async def __async_send_frame(self, command, data): """Send command to Squeeze player.""" + if self._reader.at_eof() or self._writer.is_closing(): + LOGGER.debug("Socket is disconnected.") + return self.close() packet = struct.pack("!H", len(data) + 4) + command + data self._writer.write(packet) await self._writer.drain() @@ -249,7 +243,7 @@ class SqueezeSocketClient: """Handle incoming data from socket.""" buffer = b"" # keep reading bytes from the socket - while not self._reader.at_eof(): + while not (self._reader.at_eof() or self._writer.is_closing()): data = await self._reader.read(64) # handle incoming data from socket buffer = buffer + data @@ -263,13 +257,12 @@ class SqueezeSocketClient: operation = operation.strip(b"!").strip().decode().lower() handler = getattr(self, f"_process_{operation}", None) if handler is None: - LOGGER.warning("No handler for %s" % operation) + LOGGER.warning("No handler for %s", operation) else: handler(packet) # EOF reached: socket is disconnected LOGGER.info("Socket disconnected: %s", self._writer.get_extra_info("peername")) self.close() - asyncio.create_task(self._event_callback(Event.EVENT_DISCONNECTED, self)) @callback def __pack_stream( @@ -310,6 +303,7 @@ class SqueezeSocketClient: @callback def _process_helo(self, data): """Process incoming HELO event from player (player connected).""" + # pylint: disable=unused-variable # player connected (dev_id, rev, mac) = struct.unpack("BB6s", data[:8]) device_mac = ":".join("%02x" % x for x in mac) @@ -336,7 +330,6 @@ class SqueezeSocketClient: @callback def _process_stat_aude(self, data): """Process incoming stat AUDe message (power level and mute).""" - LOGGER.debug("AUDe received (spdif_enable, dac_enable): %s", data) (spdif_enable, dac_enable) = struct.unpack("2B", data[:4]) powered = spdif_enable or dac_enable self._powered = powered @@ -354,13 +347,15 @@ class SqueezeSocketClient: @callback def _process_stat_stmd(self, data): """Process incoming stat STMd message (decoder ready).""" - LOGGER.debug("STMu received - Decoder Ready for next track: %s", data) + #pylint: disable=unused-argument + LOGGER.debug("STMu received - Decoder Ready for next track.") asyncio.create_task(self._event_callback(Event.EVENT_DECODER_READY, self)) - + @callback def _process_stat_stmf(self, data): """Process incoming stat STMf message (connection closed).""" - LOGGER.debug("STMf received - connection closed: %s", data) + #pylint: disable=unused-argument + LOGGER.debug("STMf received - connection closed.") self._state = State.Stopped asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self)) @@ -368,27 +363,31 @@ class SqueezeSocketClient: def _process_stat_stmo(self, data): """Process incoming stat STMo message: No more decoded (uncompressed) data to play; triggers rebuffering.""" - LOGGER.debug("STMo received - output underrun: %s", data) + #pylint: disable=unused-argument + LOGGER.debug("STMo received - output underrun.") LOGGER.debug("Output Underrun") @callback def _process_stat_stmp(self, data): """Process incoming stat STMp message: Pause confirmed.""" - LOGGER.debug("STMp received - pause confirmed: %s", data) + #pylint: disable=unused-argument + LOGGER.debug("STMp received - pause confirmed.") self._state = State.Paused asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self)) @callback def _process_stat_stmr(self, data): """Process incoming stat STMr message: Resume confirmed.""" - LOGGER.debug("STMr received - resume confirmed: %s", data) + #pylint: disable=unused-argument + LOGGER.debug("STMr received - resume confirmed.") self._state = State.Playing asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self)) @callback def _process_stat_stms(self, data): """Process incoming stat STMs message: Playback of new track has started.""" - LOGGER.debug("STMs received - playback of new track has started: %s", data) + LOGGER.debug("STMs received - playback of new track has started.") + #pylint: disable=unused-argument self._state = State.Playing asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self)) @@ -423,21 +422,21 @@ class SqueezeSocketClient: @callback def _process_stat_stmu(self, data): """Process incoming stat STMu message: Buffer underrun: Normal end of playback.""" - LOGGER.debug("STMu received - end of playback: %s", data) + #pylint: disable=unused-argument + LOGGER.debug("STMu received - end of playback.") self.state = State.Stopped asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self)) @callback def _process_resp(self, data): """Process incoming RESP message: Response received at player.""" - LOGGER.debug("RESP received: %s", data) + #pylint: disable=unused-argument # send continue asyncio.create_task(self.__async_send_frame(b"cont", b"0")) @callback def _process_setd(self, data): """Process incoming SETD message: Get/set player firmware settings.""" - LOGGER.debug("SETD received %s", data) cmd_id = data[0] if cmd_id == 0: # received player name diff --git a/music_assistant/web.py b/music_assistant/web.py index 637c9f90..563e52bc 100755 --- a/music_assistant/web.py +++ b/music_assistant/web.py @@ -531,44 +531,46 @@ class Web: player_id = params[0] cmds = params[1] cmd_str = " ".join(cmds) - player = self.mass.player_manager.get_player(player_id) - if not player: - return web.Response(status=404) if cmd_str == "play": - await player.async_cmd_play() + await self.mass.player_manager.async_cmd_play(player_id) elif cmd_str == "pause": - await player.async_cmd_pause() + await self.mass.player_manager.async_cmd_pause(player_id) elif cmd_str == "stop": - await player.async_cmd_stop() + await self.mass.player_manager.async_cmd_stop(player_id) elif cmd_str == "next": - await player.async_cmd_next() + await self.mass.player_manager.async_cmd_next(player_id) elif cmd_str == "previous": - await player.async_cmd_previous() + await self.mass.player_manager.async_cmd_previous(player_id) elif "power" in cmd_str: - args = cmds[1] if len(cmds) > 1 else None - await player.async_cmd_power(args) + powered = cmds[1] if len(cmds) > 1 else False + if powered: + await self.mass.player_manager.async_cmd_power_on(player_id) + else: + await self.mass.player_manager.async_cmd_power_off(player_id) elif cmd_str == "playlist index +1": - await player.async_cmd_next() + await self.mass.player_manager.async_cmd_next(player_id) elif cmd_str == "playlist index -1": - await player.async_cmd_previous() + await self.mass.player_manager.async_cmd_previous(player_id) elif "mixer volume" in cmd_str and "+" in cmds[2]: + player = self.mass.player_manager.get_player(player_id) volume_level = player.volume_level + int(cmds[2].split("+")[1]) - await player.async_cmd_volume_set(volume_level) + await self.mass.player_manager.async_cmd_volume_set(player_id, volume_level) elif "mixer volume" in cmd_str and "-" in cmds[2]: + player = self.mass.player_manager.get_player(player_id) volume_level = player.volume_level - int(cmds[2].split("-")[1]) - await player.async_cmd_volume_set(volume_level) + await self.mass.player_manager.async_cmd_volume_set(player_id, volume_level) elif "mixer volume" in cmd_str: - await player.async_cmd_volume_set(cmds[2]) + await self.mass.player_manager.async_cmd_volume_set(player_id, cmds[2]) elif cmd_str == "mixer muting 1": - await player.async_cmd_volume_mute(True) + await self.mass.player_manager.async_cmd_volume_mute(player_id, True) elif cmd_str == "mixer muting 0": - await player.async_cmd_volume_mute(False) + await self.mass.player_manager.async_cmd_volume_mute(player_id, False) elif cmd_str == "button volup": - await player.async_cmd_volume_up() + await self.mass.player_manager.async_cmd_volume_up(player_id) elif cmd_str == "button voldown": - await player.async_cmd_volume_down() + await self.mass.player_manager.async_cmd_volume_down(player_id) elif cmd_str == "button power": - await player.async_cmd_power_toggle() + await self.mass.player_manager.async_cmd_power_toggle(player_id) else: return web.Response(text="command not supported") return web.Response(text="success")