From 1b5ee03834092c85e3a45c7fba57212c22d5b158 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 28 May 2019 15:48:53 +0200 Subject: [PATCH] stability fixes --- music_assistant/modules/http_streamer.py | 24 +- .../modules/musicproviders/qobuz.py | 2 +- .../modules/musicproviders/spotify.py | 2 +- music_assistant/modules/player.py | 38 +-- .../modules/playerproviders/chromecast.py | 2 +- .../modules/playerproviders/pylms.py | 269 +++++++++--------- music_assistant/modules/web.py | 25 +- music_assistant/utils.py | 9 +- music_assistant/web/components/player.vue.js | 6 +- 9 files changed, 195 insertions(+), 182 deletions(-) diff --git a/music_assistant/modules/http_streamer.py b/music_assistant/modules/http_streamer.py index 751d4aed..a1ced4e6 100755 --- a/music_assistant/modules/http_streamer.py +++ b/music_assistant/modules/http_streamer.py @@ -9,7 +9,6 @@ import shutil import xml.etree.ElementTree as ET import random - AUDIO_TEMP_DIR = "/tmp/audio_tmp" AUDIO_CACHE_DIR = "/tmp/audio_cache" @@ -42,23 +41,7 @@ class HTTPStreamer(): if not key in self.mass.config['base']['http_streamer']: self.mass.config['base']['http_streamer'][key] = def_value - async def get_audio_stream(self, track_id, provider, player_id=None): - ''' get audio stream for a track ''' - queue = asyncio.Queue() - run_async_background_task( - self.mass.bg_executor, self.__get_audio_stream, queue, track_id, provider, player_id) - while True: - chunk = await queue.get() - if not chunk: - queue.task_done() - break - yield chunk - queue.task_done() - await queue.join() - # TODO: handle disconnects ? - LOGGER.info("Finished streaming %s" % track_id) - - async def __get_audio_stream(self, audioqueue, track_id, provider, player_id=None): + async def get_audio_stream(self, audioqueue, track_id, provider, player_id=None): ''' get audio stream from provider and apply additional effects/processing where/if needed''' input_content_type = await self.mass.music.providers[provider].get_stream_content_type(track_id) cachefile = self.__get_track_cache_filename(track_id, provider) @@ -86,11 +69,12 @@ class HTTPStreamer(): self.__fill_audio_buffer(process.stdin, track_id, provider, input_content_type)) # put chunks from stdout into queue while not process.stdout.at_eof(): - chunk = await process.stdout.read(10240000) + chunk = await process.stdout.read(256000) if not chunk: break await audioqueue.put(chunk) - # TODO: cooldown if the queue can't catch up to prevent memory being filled up with entire track + if audioqueue.qsize() > 10: + await asyncio.sleep(0.1) # cooldown a bit await process.wait() await audioqueue.put('') # indicate EOF LOGGER.info("streaming of track_id %s completed" % track_id) diff --git a/music_assistant/modules/musicproviders/qobuz.py b/music_assistant/modules/musicproviders/qobuz.py index 7ba28a95..1e0b9993 100644 --- a/music_assistant/modules/musicproviders/qobuz.py +++ b/music_assistant/modules/musicproviders/qobuz.py @@ -268,7 +268,7 @@ class QobuzProvider(MusicProvider): async with aiohttp.ClientSession(loop=asyncio.get_event_loop(), connector=aiohttp.TCPConnector(verify_ssl=False)) as session: async with session.get(streamdetails['url']) as resp: while True: - chunk = await resp.content.read(512000) + chunk = await resp.content.read(64000) if not chunk: break yield chunk diff --git a/music_assistant/modules/musicproviders/spotify.py b/music_assistant/modules/musicproviders/spotify.py index 0fc19698..6742a5e0 100644 --- a/music_assistant/modules/musicproviders/spotify.py +++ b/music_assistant/modules/musicproviders/spotify.py @@ -253,7 +253,7 @@ class SpotifyProvider(MusicProvider): args = ['-n', 'temp', '-u', self._username, '-p', self._password, '--pass-through', '--single-track', track_id] process = await asyncio.create_subprocess_exec(spotty, *args, stdout=asyncio.subprocess.PIPE) while not process.stdout.at_eof(): - chunk = await process.stdout.read(128000) + chunk = await process.stdout.read(32000) if not chunk: break yield chunk diff --git a/music_assistant/modules/player.py b/music_assistant/modules/player.py index 0a512010..0ca694c6 100755 --- a/music_assistant/modules/player.py +++ b/music_assistant/modules/player.py @@ -49,13 +49,25 @@ class Player(): cmd = 'pause' if player.state == PlayerState.Playing else 'play' if cmd == 'power' and (cmd_args == 'toggle' or not cmd_args): cmd_args = 'off' if player.powered else 'on' + if cmd == 'volume' and cmd_args == 'up': + cmd_args = player.volume_level + 2 + elif cmd == 'volume' and cmd_args == 'down': + cmd_args = player.volume_level - 2 + elif cmd == 'volume' and '+' in str(cmd_args): + cmd_args = player.volume_level + try_parse_int(cmd_args.replace('+','')) + elif cmd == 'volume' and '-' in str(cmd_args): + cmd_args = player.volume_level - try_parse_int(cmd_args.replace('-','')) + if cmd == 'mute' and (cmd_args == 'toggle' or not cmd_args): + cmd_args = 'off' if player.muted else 'on' + if cmd == 'volume' and cmd_args: + if try_parse_int(cmd_args) > 100: + cmd_args = 100 + elif try_parse_int(cmd_args) < 0: + cmd_args = 0 if cmd == 'volume' and player.is_group and player.settings['apply_group_volume']: # group volume return await self.__player_command_group_volume(player, cmd, cmd_args) - if cmd == 'volume' and (cmd_args == 'up' or '+' in str(cmd_args)): - cmd_args = player.volume_level + 2 - elif cmd == 'volume' and (cmd_args == 'down' or '-' in str(cmd_args)): - cmd_args = player.volume_level - 2 + # redirect playlist related commands to parent player if player.group_parent and cmd not in ['power', 'volume', 'mute']: return await self.player_command(player.group_parent, cmd, cmd_args) @@ -75,7 +87,6 @@ class Player(): # normal execution of command on player await prov.player_command(player_id, cmd, cmd_args) - async def __player_command_hass_integration(self, player, cmd, cmd_args): ''' handle hass integration in player command ''' if not self.mass.hass: @@ -99,19 +110,12 @@ class Player(): await self.mass.hass.call_service('media_player', 'volume_set', service_data) cmd_args = 100 # just force full volume on actual player if volume is outsourced to hass - async def __player_command_group_volume(self, player, cmd, cmd_args): + async def __player_command_group_volume(self, player, new_volume): ''' handle group volume ''' - if cmd_args == 'up': - volume_dif = 2 - volume_dif_percent = 1.02 - elif cmd_args == 'up': - volume_dif = -2 - volume_dif_percent = 0.98 - else: - cur_volume = player.volume_level - new_volume = try_parse_int(cmd_args) - volume_dif = new_volume - cur_volume - volume_dif_percent = volume_dif/cur_volume + cur_volume = player.volume_level + new_volume = try_parse_int(new_volume) + volume_dif = new_volume - cur_volume + volume_dif_percent = volume_dif/cur_volume player_childs = [item for item in self._players.values() if item.group_parent == player.player_id] for child_player in player_childs: if child_player.enabled and child_player.powered: diff --git a/music_assistant/modules/playerproviders/chromecast.py b/music_assistant/modules/playerproviders/chromecast.py index 5259f53d..006eefa9 100644 --- a/music_assistant/modules/playerproviders/chromecast.py +++ b/music_assistant/modules/playerproviders/chromecast.py @@ -124,7 +124,7 @@ class ChromecastProvider(PlayerProvider): await self.__queue_load(player_id, self._player_queue[player_id], cur_queue_index) elif queue_opt == 'next': # insert new items at current index +1 - if len(self._player_queue[player_id]) > cur_queue_index: + if len(self._player_queue[player_id]) > cur_queue_index+1: old_next_uri = self._player_queue[player_id][cur_queue_index+1].uri else: old_next_uri = None diff --git a/music_assistant/modules/playerproviders/pylms.py b/music_assistant/modules/playerproviders/pylms.py index 65da48f3..0e5dbc18 100644 --- a/music_assistant/modules/playerproviders/pylms.py +++ b/music_assistant/modules/playerproviders/pylms.py @@ -11,7 +11,7 @@ from typing import List import random import sys import socket -from utils import run_periodic, LOGGER, parse_track_title, try_parse_int, get_ip +from utils import run_periodic, LOGGER, parse_track_title, try_parse_int, get_ip, get_hostname from models import PlayerProvider, MusicPlayer, PlayerState, MediaType, TrackQuality, AlbumType, Artist, Album, Track, Playlist from constants import CONF_ENABLED @@ -30,6 +30,7 @@ def config_entries(): (CONF_ENABLED, True, CONF_ENABLED) ] + class PyLMSServer(PlayerProvider): ''' Python implementation of SlimProto server ''' @@ -49,14 +50,20 @@ class PyLMSServer(PlayerProvider): # start slimproto server mass.event_loop.create_task(asyncio.start_server(self.__handle_socket_client, '0.0.0.0', 3483)) # setup discovery - listen = mass.event_loop.create_datagram_endpoint( - DiscoveryProtocol, local_addr=('0.0.0.0', 3483), - family=socket.AF_INET, reuse_address=True, reuse_port=True, - allow_broadcast=True) - mass.event_loop.create_task(listen) + mass.event_loop.create_task(self.start_discovery()) ### Provider specific implementation ##### + async def start_discovery(self): + transport, protocol = await self.mass.event_loop.create_datagram_endpoint( + lambda: DiscoveryProtocol(self.mass.web._http_port), + local_addr=('0.0.0.0', 3483)) + try: + while True: + await asyncio.sleep(60) # serve forever + finally: + transport.close() + async def player_command(self, player_id, cmd:str, cmd_args=None): ''' issue command on player (play, pause, next, previous, stop, power, volume, mute) ''' if cmd == 'play': @@ -112,9 +119,11 @@ class PyLMSServer(PlayerProvider): async def __queue_play(self, player_id, index, send_flush=False): ''' send play command to player ''' + if not player_id in self._player_queue: + return if index == None: index = self._player_queue_index[player_id] - if len(self._player_queue[player_id]) >= index-1: + if len(self._player_queue[player_id]) >= index: track = self._player_queue[player_id][index] if send_flush: self._lmsplayers[player_id].flush() @@ -173,7 +182,7 @@ class PyLMSServer(PlayerProvider): player.volume_level = lms_player.volume_level player.cur_item_time = lms_player._elapsed_seconds if event == "disconnected": - player.enabled = False + return await self.mass.player.remove_player(player_id) elif event == "power": player.powered = event_data elif event == "state": @@ -220,8 +229,9 @@ class PyLMSServer(PlayerProvider): lms_player.dataReceived(data) else: break - except RuntimeError: - LOGGER.warning("connection lost") + except Exception as exc: + # connection lost ? + LOGGER.warning(exc) # disconnect heartbeat_task.cancel() asyncio.create_task(self.__handle_player_event(lms_player.player_id, 'disconnected')) @@ -292,7 +302,7 @@ class PyLMSPlayer(object): self.send_frame(b"strm", data) def flush(self): - data = self.pack_stream(b"f", autostart=b"1", flags=0) + data = self.pack_stream(b"f", autostart=b"0", flags=0) self.send_frame(b"strm", data) def pause(self): @@ -546,6 +556,107 @@ class PyLMSPlayer(object): LOGGER.info("UREQ received") + +# from http://wiki.slimdevices.com/index.php/SlimProtoTCPProtocol#HELO +devices = { + 2: 'squeezebox', + 3: 'softsqueeze', + 4: 'squeezebox2', + 5: 'transporter', + 6: 'softsqueeze3', + 7: 'receiver', + 8: 'squeezeslave', + 9: 'controller', + 10: 'boom', + 11: 'softboom', + 12: 'squeezeplay', + } + + +class PyLMSVolume(object): + + """ Represents a sound volume. This is an awful lot more complex than it + sounds. """ + + minimum = 0 + maximum = 100 + step = 1 + + # this map is taken from Slim::Player::Squeezebox2 in the squeezecenter source + # i don't know how much magic it contains, or any way I can test it + old_map = [ + 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, + 5, 5, 6, 6, 7, 8, 9, 9, 10, 11, + 12, 13, 14, 15, 16, 16, 17, 18, 19, 20, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, + 33, 34, 35, 37, 38, 39, 40, 42, 43, 44, + 46, 47, 48, 50, 51, 53, 54, 56, 57, 59, + 60, 61, 63, 65, 66, 68, 69, 71, 72, 74, + 75, 77, 79, 80, 82, 84, 85, 87, 89, 90, + 92, 94, 96, 97, 99, 101, 103, 104, 106, 108, 110, + 112, 113, 115, 117, 119, 121, 123, 125, 127, 128 + ]; + + # new gain parameters, from the same place + total_volume_range = -50 # dB + step_point = -1 # Number of steps, up from the bottom, where a 2nd volume ramp kicks in. + step_fraction = 1 # fraction of totalVolumeRange where alternate volume ramp kicks in. + + def __init__(self): + self.volume = 50 + + def increment(self): + """ Increment the volume """ + self.volume += self.step + if self.volume > self.maximum: + self.volume = self.maximum + + def decrement(self): + """ Decrement the volume """ + self.volume -= self.step + if self.volume < self.minimum: + self.volume = self.minimum + + def old_gain(self): + """ Return the "Old" gain value as required by the squeezebox """ + return self.old_map[self.volume] + + def decibels(self): + """ Return the "new" gain value. """ + + step_db = self.total_volume_range * self.step_fraction + max_volume_db = 0 # different on the boom? + + # Equation for a line: + # y = mx+b + # y1 = mx1+b, y2 = mx2+b. + # y2-y1 = m(x2 - x1) + # y2 = m(x2 - x1) + y1 + slope_high = max_volume_db - step_db / (100.0 - self.step_point) + slope_low = step_db - self.total_volume_range / (self.step_point - 0.0) + x2 = self.volume + if (x2 > self.step_point): + m = slope_high + x1 = 100 + y1 = max_volume_db + else: + m = slope_low + x1 = 0 + y1 = self.total_volume_range + return m * (x2 - x1) + y1 + + def new_gain(self): + db = self.decibels() + floatmult = 10 ** (db/20.0) + # avoid rounding errors somehow + if -30 <= db <= 0: + return int(floatmult * (1 << 8) + 0.5) * (1<<8) + else: + return int((floatmult * (1<<16)) + 0.5) + + +##### UDP DISCOVERY STUFF ############# + class Datagram(object): @classmethod @@ -572,7 +683,7 @@ class ClientDiscoveryDatagram(Datagram): client = None def __init__(self, data): - s = struct.unpack('!cxBB8x6B', data) + s = struct.unpack('!cxBB8x6B', data.encode()) assert s[0] == 'd' self.device = s[1] self.firmware = hex(s[2]) @@ -586,7 +697,7 @@ class DiscoveryResponseDatagram(Datagram): def __init__(self, hostname, port): hostname = hostname[:16].encode("UTF-8") hostname += (16 - len(hostname)) * '\x00' - self.packet = struct.pack('!c16s', 'D', hostname) + self.packet = struct.pack('!c16s', 'D', hostname).decode() class TLVDiscoveryRequestDatagram(Datagram): @@ -596,13 +707,14 @@ class TLVDiscoveryRequestDatagram(Datagram): idx = 1 length = len(data)-5 while idx <= length: - typ, l = struct.unpack_from("4sB", data, idx) + typ, l = struct.unpack_from("4sB", data.encode(), idx) if l: val = data[idx+5:idx+5+l] idx += 5+l else: val = None idx += 5 + typ = typ.decode() requestdata[typ] = val self.data = requestdata @@ -624,6 +736,9 @@ class TLVDiscoveryResponseDatagram(Datagram): class DiscoveryProtocol(): + def __init__(self, web_port): + self.web_port = web_port + def connection_made(self, transport): self.transport = transport # Allow receiving multicast broadcasts @@ -637,31 +752,26 @@ class DiscoveryProtocol(): for typ, value in requestdata.items(): if typ == 'NAME': # send full host name - no truncation - value = 'macbook-marcel' # TODO + value = get_hostname() elif typ == 'IPAD': # send ipaddress as a string only if it is set - value = '192.168.1.145' # TODO + value = get_ip() # :todo: IPv6 if value == '0.0.0.0': # do not send back an ip address typ = None elif typ == 'JSON': # send port as a string - json_port = 9000 # todo: web.service.port + json_port = self.web_port value = str(json_port) elif typ == 'VERS': # send server version value = '7.9' elif typ == 'UUID': # send server uuid - value = 'test' - # elif typ == 'JVID': - # # not handle, just log the information - # typ = None - # log.msg("Jive: %x:%x:%x:%x:%x:%x:" % struct.unpack('>6B', value), - # logLevel=logging.INFO) + value = 'musicassistant' else: - LOGGER.error('Unexpected information request: %r', typ) + LOGGER.debug('Unexpected information request: %r', typ) typ = None if typ: responsedata[typ] = value @@ -670,9 +780,8 @@ class DiscoveryProtocol(): def datagram_received(self, data, addr): try: data = data.decode() - LOGGER.info('Received %r from %s' % (data, addr)) dgram = Datagram.decode(data) - LOGGER.info("Data received from %s: %s" % (addr, dgram)) + LOGGER.debug("Data received from %s: %s" % (addr, dgram)) if isinstance(dgram, ClientDiscoveryDatagram): self.sendDiscoveryResponse(addr) elif isinstance(dgram, TLVDiscoveryRequestDatagram): @@ -682,114 +791,12 @@ class DiscoveryProtocol(): LOGGER.exception(exc) def sendDiscoveryResponse(self, addr): - dgram = DiscoveryResponseDatagram('macbook-marcel', 3483) - LOGGER.info("Sending discovery response %r" % (dgram.packet,)) + dgram = DiscoveryResponseDatagram(get_hostname(), 3483) + LOGGER.debug("Sending discovery response %r" % (dgram.packet,)) self.transport.sendto(dgram.packet.encode(), addr) def sendTLVDiscoveryResponse(self, resonsedata, addr): dgram = TLVDiscoveryResponseDatagram(resonsedata) - LOGGER.info("Sending discovery response %r" % (dgram.packet,)) + LOGGER.debug("Sending discovery response %r" % (dgram.packet,)) self.transport.sendto(dgram.packet.encode(), addr) - - - - - - -# from http://wiki.slimdevices.com/index.php/SlimProtoTCPProtocol#HELO -devices = { - 2: 'squeezebox', - 3: 'softsqueeze', - 4: 'squeezebox2', - 5: 'transporter', - 6: 'softsqueeze3', - 7: 'receiver', - 8: 'squeezeslave', - 9: 'controller', - 10: 'boom', - 11: 'softboom', - 12: 'squeezeplay', - } - - -class PyLMSVolume(object): - - """ Represents a sound volume. This is an awful lot more complex than it - sounds. """ - - minimum = 0 - maximum = 100 - step = 1 - - # this map is taken from Slim::Player::Squeezebox2 in the squeezecenter source - # i don't know how much magic it contains, or any way I can test it - old_map = [ - 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, - 5, 5, 6, 6, 7, 8, 9, 9, 10, 11, - 12, 13, 14, 15, 16, 16, 17, 18, 19, 20, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, - 33, 34, 35, 37, 38, 39, 40, 42, 43, 44, - 46, 47, 48, 50, 51, 53, 54, 56, 57, 59, - 60, 61, 63, 65, 66, 68, 69, 71, 72, 74, - 75, 77, 79, 80, 82, 84, 85, 87, 89, 90, - 92, 94, 96, 97, 99, 101, 103, 104, 106, 108, 110, - 112, 113, 115, 117, 119, 121, 123, 125, 127, 128 - ]; - - # new gain parameters, from the same place - total_volume_range = -50 # dB - step_point = -1 # Number of steps, up from the bottom, where a 2nd volume ramp kicks in. - step_fraction = 1 # fraction of totalVolumeRange where alternate volume ramp kicks in. - - def __init__(self): - self.volume = 50 - - def increment(self): - """ Increment the volume """ - self.volume += self.step - if self.volume > self.maximum: - self.volume = self.maximum - - def decrement(self): - """ Decrement the volume """ - self.volume -= self.step - if self.volume < self.minimum: - self.volume = self.minimum - - def old_gain(self): - """ Return the "Old" gain value as required by the squeezebox """ - return self.old_map[self.volume] - - def decibels(self): - """ Return the "new" gain value. """ - - step_db = self.total_volume_range * self.step_fraction - max_volume_db = 0 # different on the boom? - - # Equation for a line: - # y = mx+b - # y1 = mx1+b, y2 = mx2+b. - # y2-y1 = m(x2 - x1) - # y2 = m(x2 - x1) + y1 - slope_high = max_volume_db - step_db / (100.0 - self.step_point) - slope_low = step_db - self.total_volume_range / (self.step_point - 0.0) - x2 = self.volume - if (x2 > self.step_point): - m = slope_high - x1 = 100 - y1 = max_volume_db - else: - m = slope_low - x1 = 0 - y1 = self.total_volume_range - return m * (x2 - x1) + y1 - - def new_gain(self): - db = self.decibels() - floatmult = 10 ** (db/20.0) - # avoid rounding errors somehow - if -30 <= db <= 0: - return int(floatmult * (1 << 8) + 0.5) * (1<<8) - else: - return int((floatmult * (1<<16)) + 0.5) \ No newline at end of file diff --git a/music_assistant/modules/web.py b/music_assistant/modules/web.py index 3ceb4ac3..c5fc2cb7 100755 --- a/music_assistant/modules/web.py +++ b/music_assistant/modules/web.py @@ -3,7 +3,7 @@ import asyncio import os -from utils import run_periodic, LOGGER +from utils import run_periodic, LOGGER, run_async_background_task import json import aiohttp from aiohttp import web @@ -219,7 +219,8 @@ class Web(): ws_msg = {"message": msg, "message_details": msg_details } try: await ws.send_json(ws_msg, dumps=json_serializer) - except ConnectionResetError: + except Exception as exc: + LOGGER.error(exc) await self.mass.remove_event_listener(cb_id) cb_id = await self.mass.add_event_listener(send_event) @@ -273,17 +274,28 @@ class Web(): headers={'Content-Type': 'audio/flac'}) await resp.prepare(request) if request.method.upper() != 'HEAD': - async for chunk in self.mass.http_streamer.get_audio_stream(track_id, provider, player_id): + # stream audio + queue = asyncio.Queue() + run_async_background_task( + self.mass.bg_executor, self.mass.http_streamer.get_audio_stream, queue, track_id, provider, player_id) + while True: + chunk = await queue.get() + if not chunk: + queue.task_done() + break await resp.write(chunk) + queue.task_done() + LOGGER.info("Finished streaming %s" % track_id) return resp async def json_rpc(self, request): ''' - implement fake LMS jsonrpc interface + implement LMS jsonrpc interface for some compatability with tools that talk to lms only support for basic commands ''' data = await request.json() + LOGGER.info("jsonrpc: %s" % data) params = data['params'] player_id = params[0] cmds = params[1] @@ -291,13 +303,14 @@ class Web(): if cmd_str in ['play', 'pause', 'stop']: await self.mass.player.player_command(player_id, cmd_str) elif 'power' in cmd_str: - await self.mass.player.player_command(player_id, cmd_str, cmd_str[1]) + args = cmds[1] if len(cmds) > 1 else None + await self.mass.player.player_command(player_id, cmd_str, args) elif cmd_str == 'playlist index +1': await self.mass.player.player_command(player_id, 'next') elif cmd_str == 'playlist index -1': await self.mass.player.player_command(player_id, 'previous') elif 'mixer volume' in cmd_str: - await self.mass.player.player_command(player_id, 'volume', cmd_str[2]) + await self.mass.player.player_command(player_id, 'volume', cmds[2]) elif cmd_str == 'mixer muting 1': await self.mass.player.player_command(player_id, 'mute', 'on') elif cmd_str == 'mixer muting 0': diff --git a/music_assistant/utils.py b/music_assistant/utils.py index 6e56c615..75b90cb1 100755 --- a/music_assistant/utils.py +++ b/music_assistant/utils.py @@ -30,12 +30,12 @@ def run_background_task(executor, corofn, *args): def run_async_background_task(executor, corofn, *args): ''' run async task in background ''' def run_task(corofn, *args): - LOGGER.info('running %s in background task' % corofn.__name__) + LOGGER.debug('running %s in background task' % corofn.__name__) new_loop = asyncio.new_event_loop() coro = corofn(*args) res = new_loop.run_until_complete(coro) new_loop.close() - LOGGER.info('completed %s in background task' % corofn.__name__) + LOGGER.debug('completed %s in background task' % corofn.__name__) return res return asyncio.get_event_loop().run_in_executor(executor, run_task, corofn, *args) @@ -103,4 +103,7 @@ def get_ip(): IP = '127.0.0.1' finally: s.close() - return IP \ No newline at end of file + return IP + +def get_hostname(): + return socket.gethostname() \ No newline at end of file diff --git a/music_assistant/web/components/player.vue.js b/music_assistant/web/components/player.vue.js index ba8e71f9..cf49828f 100755 --- a/music_assistant/web/components/player.vue.js +++ b/music_assistant/web/components/player.vue.js @@ -251,6 +251,8 @@ Vue.component("player", { var players = []; if (msg.message == 'player updated') players = [msg.message_details]; + else if (msg.message == 'player removed') + this.players[msg.message_details].enabled = false; else if (msg.message == 'players') players = msg.message_details; @@ -262,14 +264,14 @@ Vue.component("player", { // select new active player // TODO: store previous player in local storage - if (!this.active_player_id) + if (!this.active_player_id || !this.players[this.active_player_id].enabled) for (var player_id in this.players) if (this.players[player_id].state == 'playing' && this.players[player_id].enabled && !this.players[player_id].group_parent) { // prefer the first playing player this.active_player_id = player_id; break; } - if (!this.active_player_id) + if (!this.active_player_id || !this.players[this.active_player_id].enabled) for (var player_id in this.players) { // fallback to just the first player if (this.players[player_id].enabled && !this.players[player_id].group_parent) -- 2.34.1