small fixes
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 10 Oct 2020 23:39:06 +0000 (01:39 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 10 Oct 2020 23:39:06 +0000 (01:39 +0200)
music_assistant/mass.py
music_assistant/providers/builtin_player/__init__.py [new file with mode: 0644]
music_assistant/providers/builtin_player/icon.png [new file with mode: 0644]
music_assistant/providers/mass/__init__.py [deleted file]
music_assistant/providers/mass/icon.png [deleted file]
music_assistant/web/endpoints/websocket.py

index 0c0b589149e3ea1d309b4c26cffe21a81e46abb9..cfdab9dc603b8fb488747daa0cee1b8b7b639cf7 100644 (file)
@@ -30,6 +30,14 @@ from zeroconf import NonUniqueNameException, ServiceInfo, Zeroconf
 LOGGER = logging.getLogger("mass")
 
 
+def global_exception_handler(loop: asyncio.AbstractEventLoop, context: Dict) -> None:
+    """Global exception handler."""
+    LOGGER.exception(
+        "Caught exception: %s", context.get("exception", context["message"])
+    )
+    loop.default_exception_handler(context)
+
+
 class MusicAssistant:
     """Main MusicAssistant object."""
 
@@ -63,7 +71,7 @@ class MusicAssistant:
         """Start running the music assistant server."""
         # initialize loop
         self._loop = asyncio.get_event_loop()
-        self._loop.set_exception_handler(__handle_exception)
+        self._loop.set_exception_handler(global_exception_handler)
         self._loop.set_debug(self._debug)
         # create shared aiohttp ClientSession
         self._http_session = aiohttp.ClientSession(
@@ -334,11 +342,3 @@ class MusicAssistant:
                     LOGGER.exception("Error preloading module %s: %s", module_name, exc)
                 else:
                     LOGGER.debug("Successfully preloaded module %s", module_name)
-
-
-def __handle_exception(loop: asyncio.AbstractEventLoop, context: Dict) -> None:
-    """Global exception handler."""
-    LOGGER.exception(
-        "Caught exception: %s", context.get("exception", context["message"])
-    )
-    loop.default_exception_handler(context)
diff --git a/music_assistant/providers/builtin_player/__init__.py b/music_assistant/providers/builtin_player/__init__.py
new file mode 100644 (file)
index 0000000..dacc85f
--- /dev/null
@@ -0,0 +1,260 @@
+"""Builtin player provider."""
+import logging
+import time
+from typing import List
+
+from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.util import run_periodic
+from music_assistant.models.config_entry import ConfigEntry
+from music_assistant.models.player import (
+    DeviceInfo,
+    PlaybackState,
+    Player,
+    PlayerFeature,
+)
+from music_assistant.models.provider import PlayerProvider
+
+PROV_ID = "builtin_player"
+PROV_NAME = "Music Assistant"
+LOGGER = logging.getLogger(PROV_ID)
+
+CONFIG_ENTRIES = []
+PLAYER_CONFIG_ENTRIES = []
+PLAYER_FEATURES = []
+
+EVENT_WEBPLAYER_CMD = "webplayer command"
+EVENT_WEBPLAYER_STATE = "webplayer state"
+EVENT_WEBPLAYER_REGISTER = "webplayer register"
+
+
+async def async_setup(mass):
+    """Perform async setup of this Plugin/Provider."""
+    prov = MassPlayerProvider()
+    await mass.async_register_provider(prov)
+
+
+class MassPlayerProvider(PlayerProvider):
+    """
+    Built-in PlayerProvider.
+
+    Provides virtual players in the frontend using websockets.
+    """
+
+    @property
+    def id(self) -> str:
+        """Return provider ID for this provider."""
+        return PROV_ID
+
+    @property
+    def name(self) -> str:
+        """Return provider Name for this provider."""
+        return PROV_NAME
+
+    @property
+    def config_entries(self) -> List[ConfigEntry]:
+        """Return Config Entries for this provider."""
+        return []
+
+    async def async_on_start(self) -> bool:
+        """Handle initialization of the provider based on config."""
+        # listen for websockets events to dynamically create players
+        self.mass.add_event_listener(
+            self.async_handle_mass_event,
+            [EVENT_WEBPLAYER_STATE, EVENT_WEBPLAYER_REGISTER],
+        )
+        self.mass.add_job(self.async_check_players())
+        return True
+
+    async def async_on_stop(self):
+        """Handle correct close/cleanup of the provider on exit."""
+        for player in self.players:
+            await player.async_cmd_stop()
+
+    async def async_handle_mass_event(self, msg, msg_details):
+        """Handle received event for the webplayer component."""
+        player = self.mass.players.get_player(msg_details["player_id"])
+        if not player:
+            # register new player
+            player = WebsocketsPlayer(
+                self.mass, msg_details["player_id"], msg_details["name"]
+            )
+            await self.mass.players.async_add_player(player)
+        await player.handle_player_state(msg_details)
+
+    @run_periodic(30)
+    async def async_check_players(self) -> None:
+        """Invalidate players that did not send a heartbeat message in a while."""
+        cur_time = time.time()
+        offline_players = []
+        for player in self.players:
+            if not isinstance(player, WebsocketsPlayer):
+                continue
+            if cur_time - player.last_message > 30:
+                offline_players.append(player.player_id)
+        for player_id in offline_players:
+            await self.mass.players.async_remove_player(player_id)
+
+
+class WebsocketsPlayer(Player):
+    """
+    Implementation of a player using pure HTML/javascript.
+
+    Used in the front-end.
+    Communication is handled through the websocket connection
+    and our internal event bus.
+    """
+
+    def __init__(self, mass: MusicAssistantType, player_id: str, player_name: str):
+        """Initialize the webplayer."""
+        self._player_id = player_id
+        self._player_name = player_name
+        self._powered = True
+        self._elapsed_time = 0
+        self._state = PlaybackState.Stopped
+        self._current_uri = ""
+        self._volume_level = 100
+        self._muted = False
+        self._device_info = DeviceInfo()
+        self.last_message = time.time()
+
+    async def handle_player_state(self, data: dict):
+        """Handle state event from player."""
+        if "volume_level" in data:
+            self._volume_level = data["volume_level"]
+        if "muted" in data:
+            self._muted = data["muted"]
+        if "state" in data:
+            self._state = PlaybackState(data["state"])
+        if "elapsed_time" in data:
+            self._elapsed_time = data["elapsed_time"]
+        if "current_uri" in data:
+            self._current_uri = data["current_uri"]
+        if "name" in data:
+            self._player_name = data["name"]
+        if "device_info" in data:
+            for key, value in data["device_info"].items():
+                setattr(self._device_info, key, value)
+        self.last_message = time.time()
+        self.update_state()
+
+    @property
+    def player_id(self) -> str:
+        """Return player id of this player."""
+        return self._player_id
+
+    @property
+    def provider_id(self) -> str:
+        """Return provider id of this player."""
+        return PROV_ID
+
+    @property
+    def name(self) -> str:
+        """Return name of the player."""
+        return self._player_name
+
+    @property
+    def powered(self) -> bool:
+        """Return current power state of player."""
+        return self._powered
+
+    @property
+    def elapsed_time(self) -> int:
+        """Return elapsed time of current playing media in seconds."""
+        return self._elapsed_time
+
+    @property
+    def state(self) -> PlaybackState:
+        """Return current PlaybackState of player."""
+        return self._state
+
+    @property
+    def current_uri(self) -> str:
+        """Return currently loaded uri of player (if any)."""
+        return self._current_uri
+
+    @property
+    def volume_level(self) -> int:
+        """Return current volume level of player (scale 0..100)."""
+        return self._volume_level
+
+    @property
+    def muted(self) -> bool:
+        """Return current mute state of player."""
+        return self._muted
+
+    @property
+    def device_info(self) -> DeviceInfo:
+        """Return the device info for this player."""
+        return self._device_info
+
+    @property
+    def should_poll(self) -> bool:
+        """Return True if this player should be polled for state updates."""
+        return False
+
+    @property
+    def features(self) -> List[PlayerFeature]:
+        """Return list of features this player supports."""
+        return PLAYER_FEATURES
+
+    @property
+    def config_entries(self) -> List[ConfigEntry]:
+        """Return player specific config entries (if any)."""
+        return PLAYER_CONFIG_ENTRIES
+
+    async def async_cmd_play_uri(self, uri: str) -> None:
+        """
+        Play the specified uri/url on the player.
+
+            :param uri: uri/url to send to the player.
+        """
+        data = {"player_id": self.player_id, "cmd": "play_uri", "uri": uri}
+        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
+
+    async def async_cmd_stop(self) -> None:
+        """Send STOP command to player."""
+        data = {"player_id": self.player_id, "cmd": "stop"}
+        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
+
+    async def async_cmd_play(self) -> None:
+        """Send PLAY command to player."""
+        data = {"player_id": self.player_id, "cmd": "play"}
+        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
+
+    async def async_cmd_pause(self) -> None:
+        """Send PAUSE command to player."""
+        data = {"player_id": self.player_id, "cmd": "pause"}
+        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
+
+    async def async_cmd_power_on(self) -> None:
+        """Send POWER ON command to player."""
+        self._powered = True
+        self.update_state()
+
+    async def async_cmd_power_off(self) -> None:
+        """Send POWER OFF command to player."""
+        await self.async_cmd_stop()
+        self._powered = False
+        self.update_state()
+
+    async def async_cmd_volume_set(self, volume_level: int) -> None:
+        """
+        Send volume level command to player.
+
+            :param volume_level: volume level to set (0..100).
+        """
+        data = {
+            "player_id": self.player_id,
+            "cmd": "volume_set",
+            "volume_level": volume_level,
+        }
+        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
+
+    async def async_cmd_volume_mute(self, is_muted: bool = False) -> None:
+        """
+        Send volume MUTE command to given player.
+
+            :param is_muted: bool with new mute state.
+        """
+        data = {"player_id": self.player_id, "cmd": "volume_mute", "is_muted": is_muted}
+        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
diff --git a/music_assistant/providers/builtin_player/icon.png b/music_assistant/providers/builtin_player/icon.png
new file mode 100644 (file)
index 0000000..092121e
Binary files /dev/null and b/music_assistant/providers/builtin_player/icon.png differ
diff --git a/music_assistant/providers/mass/__init__.py b/music_assistant/providers/mass/__init__.py
deleted file mode 100644 (file)
index 383bcc1..0000000
+++ /dev/null
@@ -1,444 +0,0 @@
-"""Builtin player provider."""
-import asyncio
-import logging
-import signal
-import subprocess
-import time
-from typing import List
-
-from music_assistant.helpers.typing import MusicAssistantType
-from music_assistant.helpers.util import get_hostname, run_periodic
-from music_assistant.models.config_entry import ConfigEntry
-from music_assistant.models.player import (
-    DeviceInfo,
-    PlaybackState,
-    Player,
-    PlayerFeature,
-)
-from music_assistant.models.provider import PlayerProvider
-
-PROV_ID = "mass"
-PROV_NAME = "Music Assistant"
-LOGGER = logging.getLogger("mass_provider")
-
-CONFIG_ENTRIES = []
-PLAYER_CONFIG_ENTRIES = []
-PLAYER_FEATURES = []
-
-EVENT_WEBPLAYER_CMD = "webplayer command"
-EVENT_WEBPLAYER_STATE = "webplayer state"
-EVENT_WEBPLAYER_REGISTER = "webplayer register"
-
-
-async def async_setup(mass):
-    """Perform async setup of this Plugin/Provider."""
-    prov = MassPlayerProvider()
-    await mass.async_register_provider(prov)
-
-
-class MassPlayerProvider(PlayerProvider):
-    """
-    Built-in PlayerProvider.
-
-    Provides a single headless local player on the server using SoX.
-    Provides virtual players in the frontend using websockets.
-    """
-
-    @property
-    def id(self) -> str:
-        """Return provider ID for this provider."""
-        return PROV_ID
-
-    @property
-    def name(self) -> str:
-        """Return provider Name for this provider."""
-        return PROV_NAME
-
-    @property
-    def config_entries(self) -> List[ConfigEntry]:
-        """Return Config Entries for this provider."""
-        return []
-
-    async def async_on_start(self) -> bool:
-        """Handle initialization of the provider based on config."""
-        # add local sox player on the server
-        player = BuiltinLocalPlayer("server_player", f"Server: {get_hostname()}")
-        self.mass.add_job(self.mass.players.async_add_player(player))
-        # listen for websockets events to dynamically create players
-        self.mass.add_event_listener(
-            self.async_handle_mass_event,
-            [EVENT_WEBPLAYER_STATE, EVENT_WEBPLAYER_REGISTER],
-        )
-        self.mass.add_job(self.async_check_players())
-        return True
-
-    async def async_on_stop(self):
-        """Handle correct close/cleanup of the provider on exit."""
-        for player in self.players:
-            await player.async_cmd_stop()
-
-    async def async_handle_mass_event(self, msg, msg_details):
-        """Handle received event for the webplayer component."""
-        player = self.mass.players.get_player(msg_details["player_id"])
-        if not player:
-            # register new player
-            player = WebsocketsPlayer(
-                self.mass, msg_details["player_id"], msg_details["name"]
-            )
-            await self.mass.players.async_add_player(player)
-        await player.handle_player_state(msg_details)
-
-    @run_periodic(30)
-    async def async_check_players(self) -> None:
-        """Invalidate players that did not send a heartbeat message in a while."""
-        cur_time = time.time()
-        offline_players = []
-        for player in self.players:
-            if not isinstance(player, WebsocketsPlayer):
-                continue
-            if cur_time - player.last_message > 30:
-                offline_players.append(player.player_id)
-        for player_id in offline_players:
-            await self.mass.players.async_remove_player(player_id)
-
-    async def __async_handle_player_state(self, data):
-        """Handle state event from player."""
-        player_id = data["player_id"]
-        player = self.mass.players.get_player(player_id)
-        if "volume_level" in data:
-            player.volume_level = data["volume_level"]
-        if "muted" in data:
-            player.muted = data["muted"]
-        if "state" in data:
-            player.state = PlaybackState(data["state"])
-        if "cur_time" in data:
-            player.elapsed_time = data["elapsed_time"]
-        if "current_uri" in data:
-            player.current_uri = data["current_uri"]
-        if "powered" in data:
-            player.powered = data["powered"]
-        if "name" in data:
-            player.name = data["name"]
-        player.last_message = time.time()
-        player.update_state()
-
-
-class BuiltinLocalPlayer(Player):
-    """Representation of a local player on the server using SoX."""
-
-    def __init__(self, player_id: str, name: str) -> None:
-        """Initialize the built-in player."""
-        self._player_id = player_id
-        self._name = name
-        self._powered = False
-        self._elapsed_time = 0
-        self._state = PlaybackState.Stopped
-        self._current_uri = ""
-        self._volume_level = 100
-        self._muted = False
-        self._sox = None
-        self._progress_task = None
-
-    @property
-    def player_id(self) -> str:
-        """Return player id of this player."""
-        return self._player_id
-
-    @property
-    def provider_id(self) -> str:
-        """Return provider id of this player."""
-        return PROV_ID
-
-    @property
-    def name(self) -> str:
-        """Return name of the player."""
-        return self._name
-
-    @property
-    def powered(self) -> bool:
-        """Return current power state of player."""
-        return self._powered
-
-    @property
-    def elapsed_time(self) -> float:
-        """Return elapsed_time of current playing uri in seconds."""
-        return self._elapsed_time
-
-    @property
-    def state(self) -> PlaybackState:
-        """Return current PlaybackState of player."""
-        return self._state
-
-    @property
-    def available(self) -> bool:
-        """Return current availablity of player."""
-        return True
-
-    @property
-    def current_uri(self) -> str:
-        """Return currently loaded uri of player (if any)."""
-        return self._current_uri
-
-    @property
-    def volume_level(self) -> int:
-        """Return current volume level of player (scale 0..100)."""
-        return self._volume_level
-
-    @property
-    def muted(self) -> bool:
-        """Return current mute state of player."""
-        return self._muted
-
-    @property
-    def is_group_player(self) -> bool:
-        """Return True if this player is a group player."""
-        return False
-
-    @property
-    def device_info(self) -> DeviceInfo:
-        """Return the device info for this player."""
-        return DeviceInfo(
-            model="Demo", address="http://demo:12345", manufacturer=PROV_NAME
-        )
-
-    # SERVICE CALLS / PLAYER COMMANDS
-
-    async def async_cmd_play_uri(self, uri: str):
-        """Play the specified uri/url on the player."""
-        if self._sox:
-            await self.async_cmd_stop()
-        self._current_uri = uri
-        self._sox = subprocess.Popen(["play", "-t", "flac", "-q", uri])
-        self._state = PlaybackState.Playing
-        self._powered = True
-        self.update_state()
-
-        async def report_progress():
-            """Report fake progress while sox is playing."""
-            LOGGER.info("Playback started on player %s", self.name)
-            self._elapsed_time = 0
-            while self._sox and not self._sox.poll():
-                await asyncio.sleep(1)
-                self._elapsed_time += 1
-                self.update_state()
-            LOGGER.info("Playback stopped on player %s", self.name)
-            self._elapsed_time = 0
-            self._state = PlaybackState.Stopped
-            self.update_state()
-
-        if self._progress_task:
-            self._progress_task.cancel()
-        self._progress_task = self.mass.add_job(report_progress)
-
-    async def async_cmd_stop(self) -> None:
-        """Send STOP command to player."""
-        if self._sox:
-            self._sox.terminate()
-            self._sox = None
-        self._state = PlaybackState.Stopped
-        self.update_state()
-
-    async def async_cmd_play(self) -> None:
-        """Send PLAY command to player."""
-        if self._sox:
-            self._sox.send_signal(signal.SIGCONT)
-            self._state = PlaybackState.Playing
-            self.update_state()
-
-    async def async_cmd_pause(self):
-        """Send PAUSE command to given player."""
-        if self._sox:
-            self._sox.send_signal(signal.SIGSTOP)
-        self._state = PlaybackState.Paused
-        self.update_state()
-
-    async def async_cmd_power_on(self) -> None:
-        """Send POWER ON command to player."""
-        self._powered = True
-        self.update_state()
-
-    async def async_cmd_power_off(self) -> None:
-        """Send POWER OFF command to player."""
-        await self.async_cmd_stop()
-        self._powered = False
-        self.update_state()
-
-    async def async_cmd_volume_set(self, volume_level: int) -> None:
-        """
-        Send volume level command to given player.
-
-            :param volume_level: volume level to set (0..100).
-        """
-        self._volume_level = volume_level
-        self.update_state()
-
-    async def async_cmd_volume_mute(self, is_muted=False):
-        """
-        Send volume MUTE command to given player.
-
-            :param is_muted: bool with new mute state.
-        """
-        self._muted = is_muted
-        self.update_state()
-
-
-class WebsocketsPlayer(Player):
-    """
-    Implementation of a player using pure HTML/javascript.
-
-    Used in the front-end.
-    Communication is handled through the websocket connection
-    and our internal event bus.
-    """
-
-    def __init__(self, mass: MusicAssistantType, player_id: str, player_name: str):
-        """Initialize the webplayer."""
-        self._player_id = player_id
-        self._player_name = player_name
-        self._powered = True
-        self._elapsed_time = 0
-        self._state = PlaybackState.Stopped
-        self._current_uri = ""
-        self._volume_level = 100
-        self._muted = False
-        self.last_message = time.time()
-
-    async def handle_player_state(self, data: dict):
-        """Handle state event from player."""
-        if "volume_level" in data:
-            self._volume_level = data["volume_level"]
-        if "muted" in data:
-            self._muted = data["muted"]
-        if "state" in data:
-            self._state = PlaybackState(data["state"])
-        if "elapsed_time" in data:
-            self._elapsed_time = data["elapsed_time"]
-        if "current_uri" in data:
-            self._current_uri = data["current_uri"]
-        if "powered" in data:
-            self._powered = data["powered"]
-        if "name" in data:
-            self._player_name = data["name"]
-        self.last_message = time.time()
-        self.update_state()
-
-    @property
-    def player_id(self) -> str:
-        """Return player id of this player."""
-        return self._player_id
-
-    @property
-    def provider_id(self) -> str:
-        """Return provider id of this player."""
-        return PROV_ID
-
-    @property
-    def name(self) -> str:
-        """Return name of the player."""
-        return self._player_name
-
-    @property
-    def powered(self) -> bool:
-        """Return current power state of player."""
-        return self._powered
-
-    @property
-    def elapsed_time(self) -> int:
-        """Return elapsed time of current playing media in seconds."""
-        return self._elapsed_time
-
-    @property
-    def state(self) -> PlaybackState:
-        """Return current PlaybackState of player."""
-        return self._state
-
-    @property
-    def current_uri(self) -> str:
-        """Return currently loaded uri of player (if any)."""
-        return self._current_uri
-
-    @property
-    def volume_level(self) -> int:
-        """Return current volume level of player (scale 0..100)."""
-        return self._volume_level
-
-    @property
-    def muted(self) -> bool:
-        """Return current mute state of player."""
-        return self._muted
-
-    @property
-    def device_info(self) -> DeviceInfo:
-        """Return the device info for this player."""
-        return DeviceInfo()
-
-    @property
-    def should_poll(self) -> bool:
-        """Return True if this player should be polled for state updates."""
-        return False
-
-    @property
-    def features(self) -> List[PlayerFeature]:
-        """Return list of features this player supports."""
-        return PLAYER_FEATURES
-
-    @property
-    def config_entries(self) -> List[ConfigEntry]:
-        """Return player specific config entries (if any)."""
-        return PLAYER_CONFIG_ENTRIES
-
-    async def async_cmd_play_uri(self, uri: str) -> None:
-        """
-        Play the specified uri/url on the player.
-
-            :param uri: uri/url to send to the player.
-        """
-        data = {"player_id": self.player_id, "cmd": "play_uri", "uri": uri}
-        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
-
-    async def async_cmd_stop(self) -> None:
-        """Send STOP command to player."""
-        data = {"player_id": self.player_id, "cmd": "stop"}
-        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
-
-    async def async_cmd_play(self) -> None:
-        """Send PLAY command to player."""
-        data = {"player_id": self.player_id, "cmd": "play"}
-        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
-
-    async def async_cmd_pause(self) -> None:
-        """Send PAUSE command to player."""
-        data = {"player_id": self.player_id, "cmd": "pause"}
-        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
-
-    async def async_cmd_power_on(self) -> None:
-        """Send POWER ON command to player."""
-        data = {"player_id": self.player_id, "cmd": "power_on"}
-        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
-
-    async def async_cmd_power_off(self) -> None:
-        """Send POWER OFF command to player."""
-        data = {"player_id": self.player_id, "cmd": "power_off"}
-        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
-
-    async def async_cmd_volume_set(self, volume_level: int) -> None:
-        """
-        Send volume level command to player.
-
-            :param volume_level: volume level to set (0..100).
-        """
-        data = {
-            "player_id": self.player_id,
-            "cmd": "volume_set",
-            "volume_level": volume_level,
-        }
-        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
-
-    async def async_cmd_volume_mute(self, is_muted: bool = False) -> None:
-        """
-        Send volume MUTE command to given player.
-
-            :param is_muted: bool with new mute state.
-        """
-        data = {"player_id": self.player_id, "cmd": "volume_mute", "is_muted": is_muted}
-        self.mass.signal_event(EVENT_WEBPLAYER_CMD, data)
diff --git a/music_assistant/providers/mass/icon.png b/music_assistant/providers/mass/icon.png
deleted file mode 100644 (file)
index 092121e..0000000
Binary files a/music_assistant/providers/mass/icon.png and /dev/null differ
index ead069d97d17724be70941af951f062c7c9e7d81..9b40d64f2e2c35c1c511b16a791c07ef5fdb724a 100644 (file)
@@ -1,7 +1,6 @@
 """Websocket API endpoint."""
 
 import logging
-from asyncio import CancelledError
 
 import jwt
 import orjson
@@ -29,7 +28,14 @@ async def async_websocket_handler(request: Request):
             if hasattr(msg_details, "to_dict"):
                 msg_details = msg_details.to_dict()
             ws_msg = {"message": msg, "message_details": msg_details}
-            await ws_response.send_str(json_serializer(ws_msg).decode())
+            try:
+                await ws_response.send_str(json_serializer(ws_msg).decode())
+            # pylint: disable=broad-except
+            except Exception as exc:
+                LOGGER.debug(
+                    "Error while trying to send message to websocket (probably disconnected): %s",
+                    str(exc),
+                )
 
         # process incoming messages
         async for msg in ws_response:
@@ -87,9 +93,8 @@ async def async_websocket_handler(request: Request):
             else:
                 # simply echo the message on the eventbus
                 request.app["mass"].signal_event(msg, msg_details)
-    except (AssertionError, CancelledError):
-        LOGGER.debug("Websocket disconnected")
     finally:
+        LOGGER.debug("Websocket disconnected")
         for remove_callback in remove_callbacks:
             remove_callback()
     return ws_response