fixes for squeezebox implementation
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 7 Sep 2020 09:55:37 +0000 (11:55 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 7 Sep 2020 09:55:37 +0000 (11:55 +0200)
music_assistant/providers/squeezebox/__init__.py
music_assistant/providers/squeezebox/socket_client.py
music_assistant/web.py

index d7176f59cf2b8c3b41ec7e468b72f233bd9d2ec0..9873261f95118b3219515d9252c2d83a415a11c0 100644 (file)
@@ -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
index 7bfa3f6d62aca5eae7395e7acec12db96305567d..a136c6fe4429d6e488b134bca668e8f3196e88e4 100644 (file)
@@ -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
index 637c9f90ca5a044af73ce47676154341228a220b..563e52bc0fa5e057dc095defb9d609c4a0d30819 100755 (executable)
@@ -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")