# Buggy code
async def _handle_event(self, event):
    """Handle events.

    This will update last_called and player_state events.
    Each MediaClient reports if it's the last_called MediaClient and will
    listen for HA events to determine it is the last_called.
    When polling instead of websockets, all devices on same account will
    update to handle starting music with other devices. If websocket is on
    only the updated alexa will update.
    Last_called events are only sent if it's a new device or timestamp.
    Without polling, we must schedule the HA update manually.
    https://developers.home-assistant.io/docs/en/entity_index.html#subscribing-to-updates
    The difference between self.update and self.schedule_update_ha_state
    is self.update will pull data from Amazon, while schedule_update
    assumes the MediaClient state is already updated.
    """

    async def _refresh_if_no_audiopush(already_refreshed=False):
        email = self._login.email
        seen_commands = (
            self.hass.data[DATA_ALEXAMEDIA]["accounts"][email][
                "websocket_commands"
            ].keys()
            if "websocket_commands"
            in (self.hass.data[DATA_ALEXAMEDIA]["accounts"][email])
            else None
        )
        if (
            not already_refreshed
            and seen_commands
            and not (
                "PUSH_AUDIO_PLAYER_STATE" in seen_commands
                or "PUSH_MEDIA_CHANGE" in seen_commands
                or "PUSH_MEDIA_PROGRESS_CHANGE" in seen_commands
            )
        ):
            # force refresh if player_state update not found, see #397
            _LOGGER.debug(
                "%s: No PUSH_AUDIO_PLAYER_STATE/"
                "PUSH_MEDIA_CHANGE/PUSH_MEDIA_PROGRESS_CHANGE in %s;"
                "forcing refresh",
                hide_email(email),
                seen_commands,
            )
            await self.async_update()

    try:
        if not self.enabled:
            return
    except AttributeError:
        pass
    already_refreshed = False
    event_serial = None
    if "last_called_change" in event:
        event_serial = (
            event["last_called_change"]["serialNumber"]
            if event["last_called_change"]
            else None
        )
    elif "bluetooth_change" in event:
        event_serial = (
            event["bluetooth_change"]["deviceSerialNumber"]
            if event["bluetooth_change"]
            else None
        )
    elif "player_state" in event:
        event_serial = (
            event["player_state"]["dopplerId"]["deviceSerialNumber"]
            if event["player_state"]
            else None
        )
    elif "queue_state" in event:
        event_serial = (
            event["queue_state"]["dopplerId"]["deviceSerialNumber"]
            if event["queue_state"]
            else None
        )
    elif "push_activity" in event:
        event_serial = event.get("push_activity", {}).get("key", {}).get("serialNumber")
    if not event_serial:
        return
    self.available = True
    self.async_schedule_update_ha_state()
    if "last_called_change" in event:
        if event_serial == self.device_serial_number or any(
            item["serialNumber"] == event_serial for item in self._app_device_list
        ):
            _LOGGER.debug(
                "%s is last_called: %s",
                self.name,
                hide_serial(self.device_serial_number),
            )
            self._last_called = True
            self._last_called_timestamp = event["last_called_change"]["timestamp"]
        else:
            self._last_called = False
        if self.hass and self.async_schedule_update_ha_state:
            email = self._login.email
            force_refresh = not (
                self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]
            )
            self.async_schedule_update_ha_state(force_refresh=force_refresh)
    elif "bluetooth_change" in event:
        if event_serial == self.device_serial_number:
            _LOGGER.debug(
                "%s bluetooth_state update: %s",
                self.name,
                hide_serial(event["bluetooth_change"]),
            )
            self._bluetooth_state = event["bluetooth_change"]
            # the setting of bluetooth_state is not consistent as this
            # takes from the event instead of the hass storage. We're
            # setting the value twice. Architectually we should have a
            # single authorative source of truth.
            self._source = self._get_source()
            self._source_list = self._get_source_list()
            self._connected_bluetooth = self._get_connected_bluetooth()
            self._bluetooth_list = self._get_bluetooth_list()
            if self.hass and self.async_schedule_update_ha_state:
                self.async_schedule_update_ha_state()
    elif "player_state" in event:
        player_state = event["player_state"]
        if event_serial == self.device_serial_number:
            if "audioPlayerState" in player_state:
                _LOGGER.debug(
                    "%s state update: %s",
                    self.name,
                    player_state["audioPlayerState"],
                )
                # allow delay before trying to refresh to avoid http 400 errors
                await asyncio.sleep(2)
                await self.async_update()
                already_refreshed = True
            elif "mediaReferenceId" in player_state:
                _LOGGER.debug(
                    "%s media update: %s",
                    self.name,
                    player_state["mediaReferenceId"],
                )
                await self.async_update()
                already_refreshed = True
            elif "volumeSetting" in player_state:
                _LOGGER.debug(
                    "%s volume updated: %s",
                    self.name,
                    player_state["volumeSetting"],
                )
                self._media_vol_level = player_state["volumeSetting"] / 100
                if self.hass and self.async_schedule_update_ha_state:
                    self.async_schedule_update_ha_state()
            elif "dopplerConnectionState" in player_state:
                self.available = player_state["dopplerConnectionState"] == "ONLINE"
                if self.hass and self.async_schedule_update_ha_state:
                    self.async_schedule_update_ha_state()
            await _refresh_if_no_audiopush(already_refreshed)
    elif "push_activity" in event:
        if self.state in {STATE_IDLE, STATE_PAUSED, STATE_PLAYING}:
            _LOGGER.debug(
                "%s checking for potential state update due to push activity on %s",
                self.name,
                hide_serial(event_serial),
            )
            # allow delay before trying to refresh to avoid http 400 errors
            await asyncio.sleep(2)
            await self.async_update()
            already_refreshed = True
    if "queue_state" in event:
        queue_state = event["queue_state"]
        if event_serial == self.device_serial_number:
            if (
                "trackOrderChanged" in queue_state
                and not queue_state["trackOrderChanged"]
                and "loopMode" in queue_state
            ):
                self._repeat = queue_state["loopMode"] == "LOOP_QUEUE"
                _LOGGER.debug(
                    "%s repeat updated to: %s %s",
                    self.name,
                    self._repeat,
                    queue_state["loopMode"],
                )
            elif "playBackOrder" in queue_state:
                self._shuffle = queue_state["playBackOrder"] == "SHUFFLE_ALL"
                _LOGGER.debug(
                    "%s shuffle updated to: %s %s",
                    self.name,
                    self._shuffle,
                    queue_state["playBackOrder"],
                )
            await _refresh_if_no_audiopush(already_refreshed)


# Traceback (RuntimeError)
2020-06-21 12:36:29 ERROR (MainThread) [alexapy.helpers] alexaapi.get_state((<alexapy.alexaapi.AlexaAPI object at 0xa8cddbd0>,), {}): An error occured accessing AlexaAPI: An exception of type RuntimeError occurred. Arguments:
('Session is closed',)
2020-06-21 12:36:29 ERROR (MainThread) [homeassistant.util.logging] Exception in _handle_event when dispatching 'alexa_media_a******e@g*******m': ({'player_state': {'destinationUserId': 'XXX', 'dopplerId': {'deviceType': 'AB72C64C86AW2', 'deviceSerialNumber': 'XXX'}, 'audioPlayerState': 'PLAYING', 'quality': None, 'error': False, 'errorMessage': None, 'mediaReferenceId': '78e56ac3-4ab4-4c85-86b4-3d9bb3f5ae60:6'}},)
Traceback (most recent call last):
File "/config/custom_components/alexa_media/media_player.py", line 369, in _handle_event
await self.async_update()
File "/config/custom_components/alexa_media/helpers.py", line 148, in wrapper
result = await func(*args, **kwargs)
File "/config/custom_components/alexa_media/media_player.py", line 802, in async_update
device, no_throttle=True
File "/config/custom_components/alexa_media/helpers.py", line 148, in wrapper
result = await func(*args, **kwargs)
File "/config/custom_components/alexa_media/media_player.py", line 545, in refresh
session = await self.alexa_api.get_state()
File "/usr/local/lib/python3.7/site-packages/alexapy/helpers.py", line 69, in wrapper
return await func(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/alexapy/alexaapi.py", line 761, in get_state
+ "&amp;screenWidth=2560"
File "/usr/local/lib/python3.7/site-packages/alexapy/alexaapi.py", line 122, in _get_request
return await self._request("get", uri, data)
File "/usr/local/lib/python3.7/site-packages/backoff/_async.py", line 133, in retry
ret = await target(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/alexapy/alexaapi.py", line 93, in _request
ssl=self._login._ssl,
File "/usr/local/lib/python3.7/site-packages/alexapy/aiohttp/client.py", line 377, in _request
raise RuntimeError("Session is closed")
RuntimeError: Session is closed

# Fixed code
async def _handle_event(self, event):
    """Handle events.

    This will update last_called and player_state events.
    Each MediaClient reports if it's the last_called MediaClient and will
    listen for HA events to determine it is the last_called.
    When polling instead of websockets, all devices on same account will
    update to handle starting music with other devices. If websocket is on
    only the updated alexa will update.
    Last_called events are only sent if it's a new device or timestamp.
    Without polling, we must schedule the HA update manually.
    https://developers.home-assistant.io/docs/en/entity_index.html#subscribing-to-updates
    The difference between self.update and self.schedule_update_ha_state
    is self.update will pull data from Amazon, while schedule_update
    assumes the MediaClient state is already updated.
    """

    async def _refresh_if_no_audiopush(already_refreshed=False):
        email = self._login.email
        seen_commands = (
            self.hass.data[DATA_ALEXAMEDIA]["accounts"][email][
                "websocket_commands"
            ].keys()
            if "websocket_commands"
            in (self.hass.data[DATA_ALEXAMEDIA]["accounts"][email])
            else None
        )
        if (
            not already_refreshed
            and seen_commands
            and not (
                "PUSH_AUDIO_PLAYER_STATE" in seen_commands
                or "PUSH_MEDIA_CHANGE" in seen_commands
                or "PUSH_MEDIA_PROGRESS_CHANGE" in seen_commands
            )
        ):
            # force refresh if player_state update not found, see #397
            _LOGGER.debug(
                "%s: No PUSH_AUDIO_PLAYER_STATE/"
                "PUSH_MEDIA_CHANGE/PUSH_MEDIA_PROGRESS_CHANGE in %s;"
                "forcing refresh",
                hide_email(email),
                seen_commands,
            )
            await self.async_update()

    try:
        if not self.enabled:
            return
    except AttributeError:
        pass
    self.check_login_changes()
    already_refreshed = False
    event_serial = None
    if "last_called_change" in event:
        event_serial = (
            event["last_called_change"]["serialNumber"]
            if event["last_called_change"]
            else None
        )
    elif "bluetooth_change" in event:
        event_serial = (
            event["bluetooth_change"]["deviceSerialNumber"]
            if event["bluetooth_change"]
            else None
        )
    elif "player_state" in event:
        event_serial = (
            event["player_state"]["dopplerId"]["deviceSerialNumber"]
            if event["player_state"]
            else None
        )
    elif "queue_state" in event:
        event_serial = (
            event["queue_state"]["dopplerId"]["deviceSerialNumber"]
            if event["queue_state"]
            else None
        )
    elif "push_activity" in event:
        event_serial = event.get("push_activity", {}).get("key", {}).get("serialNumber")
    if not event_serial:
        return
    self.available = True
    self.async_schedule_update_ha_state()
    if "last_called_change" in event:
        if event_serial == self.device_serial_number or any(
            item["serialNumber"] == event_serial for item in self._app_device_list
        ):
            _LOGGER.debug(
                "%s is last_called: %s",
                self.name,
                hide_serial(self.device_serial_number),
            )
            self._last_called = True
            self._last_called_timestamp = event["last_called_change"]["timestamp"]
        else:
            self._last_called = False
        if self.hass and self.async_schedule_update_ha_state:
            email = self._login.email
            force_refresh = not (
                self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]
            )
            self.async_schedule_update_ha_state(force_refresh=force_refresh)
    elif "bluetooth_change" in event:
        if event_serial == self.device_serial_number:
            _LOGGER.debug(
                "%s bluetooth_state update: %s",
                self.name,
                hide_serial(event["bluetooth_change"]),
            )
            self._bluetooth_state = event["bluetooth_change"]
            # the setting of bluetooth_state is not consistent as this
            # takes from the event instead of the hass storage. We're
            # setting the value twice. Architectually we should have a
            # single authorative source of truth.
            self._source = self._get_source()
            self._source_list = self._get_source_list()
            self._connected_bluetooth = self._get_connected_bluetooth()
            self._bluetooth_list = self._get_bluetooth_list()
            if self.hass and self.async_schedule_update_ha_state:
                self.async_schedule_update_ha_state()
    elif "player_state" in event:
        player_state = event["player_state"]
        if event_serial == self.device_serial_number:
            if "audioPlayerState" in player_state:
                _LOGGER.debug(
                    "%s state update: %s",
                    self.name,
                    player_state["audioPlayerState"],
                )
                # allow delay before trying to refresh to avoid http 400 errors
                await asyncio.sleep(2)
                await self.async_update()
                already_refreshed = True
            elif "mediaReferenceId" in player_state:
                _LOGGER.debug(
                    "%s media update: %s",
                    self.name,
                    player_state["mediaReferenceId"],
                )
                await self.async_update()
                already_refreshed = True
            elif "volumeSetting" in player_state:
                _LOGGER.debug(
                    "%s volume updated: %s",
                    self.name,
                    player_state["volumeSetting"],
                )
                self._media_vol_level = player_state["volumeSetting"] / 100
                if self.hass and self.async_schedule_update_ha_state:
                    self.async_schedule_update_ha_state()
            elif "dopplerConnectionState" in player_state:
                self.available = player_state["dopplerConnectionState"] == "ONLINE"
                if self.hass and self.async_schedule_update_ha_state:
                    self.async_schedule_update_ha_state()
            await _refresh_if_no_audiopush(already_refreshed)
    elif "push_activity" in event:
        if self.state in {STATE_IDLE, STATE_PAUSED, STATE_PLAYING}:
            _LOGGER.debug(
                "%s checking for potential state update due to push activity on %s",
                self.name,
                hide_serial(event_serial),
            )
            # allow delay before trying to refresh to avoid http 400 errors
            await asyncio.sleep(2)
            await self.async_update()
            already_refreshed = True
    if "queue_state" in event:
        queue_state = event["queue_state"]
        if event_serial == self.device_serial_number:
            if (
                "trackOrderChanged" in queue_state
                and not queue_state["trackOrderChanged"]
                and "loopMode" in queue_state
            ):
                self._repeat = queue_state["loopMode"] == "LOOP_QUEUE"
                _LOGGER.debug(
                    "%s repeat updated to: %s %s",
                    self.name,
                    self._repeat,
                    queue_state["loopMode"],
                )
            elif "playBackOrder" in queue_state:
                self._shuffle = queue_state["playBackOrder"] == "SHUFFLE_ALL"
                _LOGGER.debug(
                    "%s shuffle updated to: %s %s",
                    self.name,
                    self._shuffle,
                    queue_state["playBackOrder"],
                )
            await _refresh_if_no_audiopush(already_refreshed)