From 4c85792e41c430fac4a0aa4277f6937c0cfd6412 Mon Sep 17 00:00:00 2001 From: Santiago Soto Date: Tue, 1 Oct 2024 17:27:50 -0300 Subject: [PATCH 01/10] set idle_threshold to 60000 --- music_assistant/server/providers/snapcast/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index 82814e83f..781b94190 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -588,7 +588,7 @@ async def _create_stream(self) -> tuple[Snapstream, int]: result = await self._snapserver.stream_add_stream( # NOTE: setting the sampleformat to something else # (like 24 bits bit depth) does not seem to work at all! - f"tcp://0.0.0.0:{port}?name={name}&sampleformat=48000:16:2", + f"tcp://0.0.0.0:{port}?name={name}&sampleformat=48000:16:2&idle_threshold=60000", ) if "id" not in result: # if the port is already taken, the result will be an error From 201ac4d159725c13c6780b65c0334df74cacca9f Mon Sep 17 00:00:00 2001 From: Santiago Soto Date: Wed, 2 Oct 2024 09:48:58 -0300 Subject: [PATCH 02/10] Add idle_threshold as ui parameter --- .../server/providers/snapcast/__init__.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index 781b94190..300e3d5fe 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -62,11 +62,14 @@ CONF_SERVER_INITIAL_VOLUME = "snapcast_server_built_in_initial_volume" CONF_SERVER_TRANSPORT_CODEC = "snapcast_server_built_in_codec" CONF_SERVER_SEND_AUDIO_TO_MUTED = "snapcast_server_built_in_send_muted" +CONF_STREAM_IDLE_THRESHOLD = "snapcast_stream_idle_threshold" + # airplay has fixed sample rate/bit depth so make this config entry static and hidden CONF_ENTRY_SAMPLE_RATES_SNAPCAST = create_sample_rates_config_entry(48000, 16, 48000, 16, True) DEFAULT_SNAPSERVER_PORT = 1705 +DEFAULT_SNAPSTREAM_IDLE_THRESHOLD = 60000 SNAPWEB_DIR: Final[pathlib.Path] = pathlib.Path(__file__).parent.resolve().joinpath("snapweb") @@ -210,6 +213,14 @@ async def get_config_entries( depends_on=CONF_USE_EXTERNAL_SERVER, category="advanced" if local_snapserver_present else "generic", ), + ConfigEntry( + key=CONF_STREAM_IDLE_THRESHOLD, + type=ConfigEntryType.INTEGER, + default_value=DEFAULT_SNAPSTREAM_IDLE_THRESHOLD, + label="Snapcast idle threshold stream parameter", + required=True, + category="Stream Settings", + ), ) @@ -277,6 +288,7 @@ async def handle_async_init(self) -> None: else: self._snapcast_server_host = self.config.get_value(CONF_SERVER_HOST) self._snapcast_server_control_port = self.config.get_value(CONF_SERVER_CONTROL_PORT) + self._snapcast_stream_idle_threshold = self.config.get_value(CONF_STREAM_IDLE_THRESHOLD) self._stream_tasks = {} self._ids_map = bidict({}) @@ -588,7 +600,7 @@ async def _create_stream(self) -> tuple[Snapstream, int]: result = await self._snapserver.stream_add_stream( # NOTE: setting the sampleformat to something else # (like 24 bits bit depth) does not seem to work at all! - f"tcp://0.0.0.0:{port}?name={name}&sampleformat=48000:16:2&idle_threshold=60000", + f"tcp://0.0.0.0:{port}?name={name}&sampleformat=48000:16:2&idle_threshold={self._snapcast_stream_idle_threshold}", ) if "id" not in result: # if the port is already taken, the result will be an error From ce196dcc7ddfd0155b0918446158f0a3505c49f3 Mon Sep 17 00:00:00 2001 From: whc2001 Date: Wed, 2 Oct 2024 09:33:18 -0400 Subject: [PATCH 03/10] fix: correct the config parameter for buffer size and control port --- music_assistant/server/providers/snapcast/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index 300e3d5fe..f4b3b4475 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -668,8 +668,8 @@ async def _builtin_server_runner(self) -> None: "--http.port=1780", f"--http.doc_root={SNAPWEB_DIR}", "--tcp.enabled=true", - "--tcp.port=1705", - f"--stream.buffer={self._snapcast_server_control_port}", + f"--tcp.port={self._snapcast_server_control_port}", + f"--stream.buffer={self._snapcast_server_buffer_size}", f"--stream.codec={self._snapcast_server_transport_codec}", f"--stream.send_to_muted={str(self._snapcast_server_send_to_muted).lower()}", f"--streaming_client.initial_volume={self._snapcast_server_initial_volume}", From 7c2a67af8603ce8d716a86559ca271158682cc7c Mon Sep 17 00:00:00 2001 From: whc2001 Date: Wed, 2 Oct 2024 09:34:13 -0400 Subject: [PATCH 04/10] feat: enlarge config flow range of buffer size --- music_assistant/server/providers/snapcast/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index f4b3b4475..571ff6116 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -122,7 +122,7 @@ async def get_config_entries( ConfigEntry( key=CONF_SERVER_BUFFER_SIZE, type=ConfigEntryType.INTEGER, - range=(500, 6000), + range=(200, 6000), default_value=1000, label="Snapserver buffer size", description="Buffer[ms]. The end-to-end latency, " From 6a913dc90b700db8b2de9852330766db9cec3fc1 Mon Sep 17 00:00:00 2001 From: whc2001 Date: Wed, 2 Oct 2024 09:34:31 -0400 Subject: [PATCH 05/10] feat: add chunk ms in config flow --- .../server/providers/snapcast/__init__.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index 571ff6116..1f2046bee 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -59,6 +59,7 @@ CONF_SERVER_CONTROL_PORT = "snapcast_server_control_port" CONF_USE_EXTERNAL_SERVER = "snapcast_use_external_server" CONF_SERVER_BUFFER_SIZE = "snapcast_server_built_in_buffer_size" +CONF_SERVER_CHUNK_MS = "snapcast_server_built_in_chunk_ms" CONF_SERVER_INITIAL_VOLUME = "snapcast_server_built_in_initial_volume" CONF_SERVER_TRANSPORT_CODEC = "snapcast_server_built_in_codec" CONF_SERVER_SEND_AUDIO_TO_MUTED = "snapcast_server_built_in_send_muted" @@ -133,6 +134,20 @@ async def get_config_entries( hidden=not local_snapserver_present, help_link="https://raw.githubusercontent.com/badaix/snapcast/86cd4b2b63e750a72e0dfe6a46d47caf01426c8d/server/etc/snapserver.conf", ), + ConfigEntry( + key=CONF_SERVER_CHUNK_MS, + type=ConfigEntryType.INTEGER, + range=(10, 100), + default_value=20, + label="Snapserver chunk size", + description="Default source stream read chunk size [ms]. " + "The server will continously read this number of milliseconds from the source into buffer and pass this buffer to the encoder." + "The encoded buffer is sent to the clients. Some codecs have a higher latency and will need more data, e.g. Flac will need ~26ms chunks", + required=False, + category="Built-in Snapserver Settings", + hidden=not local_snapserver_present, + help_link="https://raw.githubusercontent.com/badaix/snapcast/86cd4b2b63e750a72e0dfe6a46d47caf01426c8d/server/etc/snapserver.conf", + ), ConfigEntry( key=CONF_SERVER_INITIAL_VOLUME, type=ConfigEntryType.INTEGER, @@ -277,6 +292,7 @@ async def handle_async_init(self) -> None: self._snapcast_server_host = "127.0.0.1" self._snapcast_server_control_port = DEFAULT_SNAPSERVER_PORT self._snapcast_server_buffer_size = self.config.get_value(CONF_SERVER_BUFFER_SIZE) + self._snapcast_server_chunk_ms = self.config.get_value(CONF_SERVER_CHUNK_MS) self._snapcast_server_initial_volume = self.config.get_value(CONF_SERVER_INITIAL_VOLUME) self._snapcast_server_send_to_muted = self.config.get_value( CONF_SERVER_SEND_AUDIO_TO_MUTED @@ -670,6 +686,7 @@ async def _builtin_server_runner(self) -> None: "--tcp.enabled=true", f"--tcp.port={self._snapcast_server_control_port}", f"--stream.buffer={self._snapcast_server_buffer_size}", + f"--stream.chunk_ms={self._snapcast_server_chunk_ms}", f"--stream.codec={self._snapcast_server_transport_codec}", f"--stream.send_to_muted={str(self._snapcast_server_send_to_muted).lower()}", f"--streaming_client.initial_volume={self._snapcast_server_initial_volume}", From 42afa25451f33b9ffdf4dd3178c9cf84ba6be7bf Mon Sep 17 00:00:00 2001 From: whc2001 Date: Wed, 2 Oct 2024 11:13:31 -0400 Subject: [PATCH 06/10] fix: increase the default chunk size Co-authored-by: Santiago Soto --- music_assistant/server/providers/snapcast/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index 1f2046bee..7dd149c4b 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -138,7 +138,7 @@ async def get_config_entries( key=CONF_SERVER_CHUNK_MS, type=ConfigEntryType.INTEGER, range=(10, 100), - default_value=20, + default_value=26, label="Snapserver chunk size", description="Default source stream read chunk size [ms]. " "The server will continously read this number of milliseconds from the source into buffer and pass this buffer to the encoder." From 3db0b04071a591b9b89b2b5db9221e2951a5170a Mon Sep 17 00:00:00 2001 From: Santiago Soto Date: Wed, 2 Oct 2024 14:07:18 -0300 Subject: [PATCH 07/10] fix typo --- music_assistant/server/providers/snapcast/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index 7dd149c4b..b58d3ce7a 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -141,8 +141,10 @@ async def get_config_entries( default_value=26, label="Snapserver chunk size", description="Default source stream read chunk size [ms]. " - "The server will continously read this number of milliseconds from the source into buffer and pass this buffer to the encoder." - "The encoded buffer is sent to the clients. Some codecs have a higher latency and will need more data, e.g. Flac will need ~26ms chunks", + "The server will continuously read this number of milliseconds from the source into " + "buffer and pass this buffer to the encoder." + "The encoded buffer is sent to the clients. Some codecs have a higher" + "latency and will need more data, e.g. Flac will need ~26ms chunks", required=False, category="Built-in Snapserver Settings", hidden=not local_snapserver_present, From f491d37269ae1753f1322db34b9650be70eadf70 Mon Sep 17 00:00:00 2001 From: Santiago Soto Date: Wed, 2 Oct 2024 16:30:13 -0300 Subject: [PATCH 08/10] move idle threshold to advanced --- music_assistant/server/providers/snapcast/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index b58d3ce7a..2e6a247bc 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -236,7 +236,7 @@ async def get_config_entries( default_value=DEFAULT_SNAPSTREAM_IDLE_THRESHOLD, label="Snapcast idle threshold stream parameter", required=True, - category="Stream Settings", + category="advanced", ), ) From 565de7237943f9582184b109cc858849d2d102ca Mon Sep 17 00:00:00 2001 From: whc2001 Date: Wed, 2 Oct 2024 16:36:05 -0400 Subject: [PATCH 09/10] chore: strip out hardcoded values --- .../server/providers/snapcast/__init__.py | 80 ++++++++----------- 1 file changed, 33 insertions(+), 47 deletions(-) diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index 2e6a247bc..fa905ce34 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -20,27 +20,19 @@ from music_assistant.common.helpers.util import get_ip_pton from music_assistant.common.models.config_entries import ( - CONF_ENTRY_CROSSFADE, - CONF_ENTRY_CROSSFADE_DURATION, - CONF_ENTRY_FLOW_MODE_ENFORCED, - ConfigEntry, - ConfigValueOption, - ConfigValueType, - create_sample_rates_config_entry, -) -from music_assistant.common.models.enums import ( - ConfigEntryType, - ContentType, - MediaType, - PlayerFeature, - PlayerState, - PlayerType, - ProviderFeature, -) + CONF_ENTRY_CROSSFADE, CONF_ENTRY_CROSSFADE_DURATION, + CONF_ENTRY_FLOW_MODE_ENFORCED, ConfigEntry, ConfigValueOption, + ConfigValueType, create_sample_rates_config_entry) +from music_assistant.common.models.enums import (ConfigEntryType, ContentType, + MediaType, PlayerFeature, + PlayerState, PlayerType, + ProviderFeature) from music_assistant.common.models.errors import SetupFailedError from music_assistant.common.models.media_items import AudioFormat -from music_assistant.common.models.player import DeviceInfo, Player, PlayerMedia -from music_assistant.server.helpers.audio import FFMpeg, get_ffmpeg_stream, get_player_filter_params +from music_assistant.common.models.player import (DeviceInfo, Player, + PlayerMedia) +from music_assistant.server.helpers.audio import (FFMpeg, get_ffmpeg_stream, + get_player_filter_params) from music_assistant.server.helpers.process import AsyncProcess, check_output from music_assistant.server.models.player_provider import PlayerProvider @@ -66,9 +58,16 @@ CONF_STREAM_IDLE_THRESHOLD = "snapcast_stream_idle_threshold" +CONF_CATEGORY_GENERIC = "generic" +CONF_CATEGORY_ADVANCED = "advanced" +CONF_CATEGORY_BUILT_IN = "Built-in Snapserver Settings" + +CONF_HELP_LINK = "https://raw.githubusercontent.com/badaix/snapcast/refs/heads/master/server/etc/snapserver.conf" + # airplay has fixed sample rate/bit depth so make this config entry static and hidden CONF_ENTRY_SAMPLE_RATES_SNAPCAST = create_sample_rates_config_entry(48000, 16, 48000, 16, True) +DEFAULT_SNAPSERVER_IP = "127.0.0.1" DEFAULT_SNAPSERVER_PORT = 1705 DEFAULT_SNAPSTREAM_IDLE_THRESHOLD = 60000 @@ -126,13 +125,10 @@ async def get_config_entries( range=(200, 6000), default_value=1000, label="Snapserver buffer size", - description="Buffer[ms]. The end-to-end latency, " - "from capturing a sample on the snapserver until " - "the sample is played-out on the client ", required=False, - category="Built-in Snapserver Settings", + category=CONF_CATEGORY_BUILT_IN, hidden=not local_snapserver_present, - help_link="https://raw.githubusercontent.com/badaix/snapcast/86cd4b2b63e750a72e0dfe6a46d47caf01426c8d/server/etc/snapserver.conf", + help_link=CONF_HELP_LINK, ), ConfigEntry( key=CONF_SERVER_CHUNK_MS, @@ -140,15 +136,10 @@ async def get_config_entries( range=(10, 100), default_value=26, label="Snapserver chunk size", - description="Default source stream read chunk size [ms]. " - "The server will continuously read this number of milliseconds from the source into " - "buffer and pass this buffer to the encoder." - "The encoded buffer is sent to the clients. Some codecs have a higher" - "latency and will need more data, e.g. Flac will need ~26ms chunks", required=False, - category="Built-in Snapserver Settings", + category=CONF_CATEGORY_BUILT_IN, hidden=not local_snapserver_present, - help_link="https://raw.githubusercontent.com/badaix/snapcast/86cd4b2b63e750a72e0dfe6a46d47caf01426c8d/server/etc/snapserver.conf", + help_link=CONF_HELP_LINK, ), ConfigEntry( key=CONF_SERVER_INITIAL_VOLUME, @@ -156,11 +147,10 @@ async def get_config_entries( range=(0, 100), default_value=25, label="Snapserver initial volume", - description="Volume assigned to new snapclients [percent]", required=False, - category="Built-in Snapserver Settings", + category=CONF_CATEGORY_BUILT_IN, hidden=not local_snapserver_present, - help_link="https://raw.githubusercontent.com/badaix/snapcast/86cd4b2b63e750a72e0dfe6a46d47caf01426c8d/server/etc/snapserver.conf", + help_link=CONF_HELP_LINK, ), ConfigEntry( key=CONF_SERVER_SEND_AUDIO_TO_MUTED, @@ -168,9 +158,9 @@ async def get_config_entries( default_value=False, label="Send audio to muted clients", required=False, - category="Built-in Snapserver Settings", + category=CONF_CATEGORY_BUILT_IN, hidden=not local_snapserver_present, - help_link="https://raw.githubusercontent.com/badaix/snapcast/86cd4b2b63e750a72e0dfe6a46d47caf01426c8d/server/etc/snapserver.conf", + help_link=CONF_HELP_LINK, ), ConfigEntry( key=CONF_SERVER_TRANSPORT_CODEC, @@ -195,11 +185,10 @@ async def get_config_entries( ), default_value="flac", label="Snapserver default transport codec", - description="This is the codec used by snapserver to send audio to clients", required=False, - category="Built-in Snapserver Settings", + category=CONF_CATEGORY_BUILT_IN, hidden=not local_snapserver_present, - help_link="https://raw.githubusercontent.com/badaix/snapcast/86cd4b2b63e750a72e0dfe6a46d47caf01426c8d/server/etc/snapserver.conf", + help_link=CONF_HELP_LINK, ), ConfigEntry( key=CONF_USE_EXTERNAL_SERVER, @@ -207,19 +196,16 @@ async def get_config_entries( default_value=not local_snapserver_present, label="Use existing Snapserver", required=False, - description="Music Assistant by default already includes a Snapserver. \n\n" - "Checking this option allows you to connect to your own/external existing Snapserver " - "and not use the builtin one provided by Music Assistant.", - category="advanced" if local_snapserver_present else "generic", + category=CONF_CATEGORY_ADVANCED if local_snapserver_present else CONF_CATEGORY_GENERIC, ), ConfigEntry( key=CONF_SERVER_HOST, type=ConfigEntryType.STRING, - default_value="127.0.0.1", + default_value=DEFAULT_SNAPSERVER_IP, label="Snapcast server ip", required=False, depends_on=CONF_USE_EXTERNAL_SERVER, - category="advanced" if local_snapserver_present else "generic", + category=CONF_CATEGORY_ADVANCED if local_snapserver_present else CONF_CATEGORY_GENERIC, ), ConfigEntry( key=CONF_SERVER_CONTROL_PORT, @@ -228,7 +214,7 @@ async def get_config_entries( label="Snapcast control port", required=False, depends_on=CONF_USE_EXTERNAL_SERVER, - category="advanced" if local_snapserver_present else "generic", + category=CONF_CATEGORY_ADVANCED if local_snapserver_present else CONF_CATEGORY_GENERIC, ), ConfigEntry( key=CONF_STREAM_IDLE_THRESHOLD, @@ -236,7 +222,7 @@ async def get_config_entries( default_value=DEFAULT_SNAPSTREAM_IDLE_THRESHOLD, label="Snapcast idle threshold stream parameter", required=True, - category="advanced", + category=CONF_CATEGORY_ADVANCED, ), ) From 936b82b3cfc493ccc0ba3b358c0d7c6b6e9036ba Mon Sep 17 00:00:00 2001 From: Santiago Soto Date: Wed, 2 Oct 2024 17:49:39 -0300 Subject: [PATCH 10/10] fix lint --- .../server/providers/snapcast/__init__.py | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index fa905ce34..9f3960dcb 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -20,19 +20,27 @@ from music_assistant.common.helpers.util import get_ip_pton from music_assistant.common.models.config_entries import ( - CONF_ENTRY_CROSSFADE, CONF_ENTRY_CROSSFADE_DURATION, - CONF_ENTRY_FLOW_MODE_ENFORCED, ConfigEntry, ConfigValueOption, - ConfigValueType, create_sample_rates_config_entry) -from music_assistant.common.models.enums import (ConfigEntryType, ContentType, - MediaType, PlayerFeature, - PlayerState, PlayerType, - ProviderFeature) + CONF_ENTRY_CROSSFADE, + CONF_ENTRY_CROSSFADE_DURATION, + CONF_ENTRY_FLOW_MODE_ENFORCED, + ConfigEntry, + ConfigValueOption, + ConfigValueType, + create_sample_rates_config_entry, +) +from music_assistant.common.models.enums import ( + ConfigEntryType, + ContentType, + MediaType, + PlayerFeature, + PlayerState, + PlayerType, + ProviderFeature, +) from music_assistant.common.models.errors import SetupFailedError from music_assistant.common.models.media_items import AudioFormat -from music_assistant.common.models.player import (DeviceInfo, Player, - PlayerMedia) -from music_assistant.server.helpers.audio import (FFMpeg, get_ffmpeg_stream, - get_player_filter_params) +from music_assistant.common.models.player import DeviceInfo, Player, PlayerMedia +from music_assistant.server.helpers.audio import FFMpeg, get_ffmpeg_stream, get_player_filter_params from music_assistant.server.helpers.process import AsyncProcess, check_output from music_assistant.server.models.player_provider import PlayerProvider @@ -62,7 +70,9 @@ CONF_CATEGORY_ADVANCED = "advanced" CONF_CATEGORY_BUILT_IN = "Built-in Snapserver Settings" -CONF_HELP_LINK = "https://raw.githubusercontent.com/badaix/snapcast/refs/heads/master/server/etc/snapserver.conf" +CONF_HELP_LINK = ( + "https://raw.githubusercontent.com/badaix/snapcast/refs/heads/master/server/etc/snapserver.conf" +) # airplay has fixed sample rate/bit depth so make this config entry static and hidden CONF_ENTRY_SAMPLE_RATES_SNAPCAST = create_sample_rates_config_entry(48000, 16, 48000, 16, True)