Loading nixos/modules/services/audio/music-assistant.nix +4 −2 Original line number Diff line number Diff line Loading @@ -82,7 +82,8 @@ in allowedTCPPorts = lib.optional cfg.enable 8097 # Music Assistant stream port ++ lib.optional (lib.elem "airplay" cfg.providers) 7000 ++ lib.optional (lib.elem "sendspin" cfg.providers) 8927; ++ lib.optional (lib.elem "sendspin" cfg.providers) 8927 ++ lib.optional (lib.elem "snapcast" cfg.providers) 1780; # The information published by Apple 1 seem to not apply to libraop. # The closest we could find that represents the port range being used as observed by tcpdump is the ephemeral port range. # 1: https://support.apple.com/en-us/103229#:~:text=49152%E2%80%93-,65535,-TCP%2C%20UDP Loading @@ -97,6 +98,7 @@ in services.avahi = lib.mkIf (lib.elem "airplay_receiver" cfg.providers) { enable = true; openFirewall = lib.mkIf cfg.openFirewall true; publish = { enable = true; userServices = true; Loading Loading @@ -129,7 +131,7 @@ in ++ lib.optionals (lib.elem "airplay_receiver" cfg.providers) [ shairport-sync ] ++ lib.optionals (lib.elem "spotify" cfg.providers) [ ++ lib.optionals (lib.elem "spotify" cfg.providers || lib.elem "spotify_connect" cfg.providers) [ librespot-ma ] ++ lib.optionals (lib.elem "snapcast" cfg.providers) [ Loading pkgs/by-name/cl/cliairplay/package.nix +3 −12 Original line number Diff line number Diff line Loading @@ -4,7 +4,6 @@ bison, curl, fetchFromGitHub, fetchpatch, ffmpeg-headless, flex, gperf, Loading @@ -26,26 +25,18 @@ stdenv.mkDerivation { pname = "cliairplay"; # see the beginning of configure.ac for the upstream version number version = "0.2-unstable-2025-12-30"; version = "1.1-unstable-2026-03-16"; src = fetchFromGitHub { owner = "music-assistant"; repo = "cliairplay"; # we try to closely match the commit used in the last music-assistant release from # https://github.com/music-assistant/server/tree/stable/music_assistant/providers/airplay/bin rev = "4660d886585d6bf8f32e889feec2a0e8975c51dc"; rev = "991c65acc2afa17ffe32e279dbc585b0b7f530f8"; fetchSubmodules = true; hash = "sha256-oDStn9LdLYWKhZNm7Qfdibs4qsct8gE3RZbTKooQeOM="; hash = "sha256-m1O4l6gFEGNAyskYcRHcA15cubZnNgkaYjdVThRRX7w="; }; patches = [ # Support gettext 0.25 (fetchpatch { url = "https://github.com/music-assistant/cliairplay/commit/92a2445d64c476d740feba1f31c7e5bc768701b7.patch"; hash = "sha256-/YnopvAGHHnQhfr2X1OenLMoF4ZlUq9x8tQZha/XfbQ="; }) ]; nativeBuildInputs = [ autoreconfHook bison Loading pkgs/by-name/li/libraop/package.nix +3 −3 Original line number Diff line number Diff line Loading @@ -16,16 +16,16 @@ let in stdenv.mkDerivation { pname = "libraop"; version = "0-unstable-2026-02-09"; version = "0-unstable-2026-02-20"; src = fetchFromGitHub { owner = "music-assistant"; repo = "libraop"; # we try to closely match the commit used in the last music-assistant release from # https://github.com/music-assistant/server/tree/stable/music_assistant/providers/airplay/bin rev = "f49284282ea4ea740d07fabc230b4182f8c69a74"; rev = "df3c055674c147eeaa9307b7d554b9d46ed6418a"; fetchSubmodules = true; hash = "sha256-m1ll5vRZx4d/5IWCG24yY/SWEIIz2k/iU84vQKHlCdo="; hash = "sha256-zD1DggBQjbiD7B/u0hmogXj8NhrzYgVXMyzvHkaM4Hg="; }; patches = [ Loading pkgs/by-name/mu/music-assistant/builtin-snapcast-server.patch +15 −24 Original line number Diff line number Diff line diff --git a/music_assistant/providers/snapcast/constants.py b/music_assistant/providers/snapcast/constants.py index eb5ecb1a..92f03212 100644 index 8c897c72..d89a0cd2 100644 --- a/music_assistant/providers/snapcast/constants.py +++ b/music_assistant/providers/snapcast/constants.py @@ -43,7 +43,8 @@ CONTROL_SOCKET_PATH_TEMPLATE = "/tmp/ma-snapcast-{queue_id}.sock" # noqa: S108 @@ -46,7 +46,8 @@ MASS_STREAM_PREFIX = "Music Assistant - " MASS_ANNOUNCEMENT_POSTFIX = " (announcement)" SNAPWEB_DIR = pathlib.Path(__file__).parent.resolve().joinpath("snapweb") Loading @@ -13,18 +13,18 @@ index eb5ecb1a..92f03212 100644 DEFAULT_SNAPCAST_FORMAT = AudioFormat( content_type=ContentType.PCM_S16LE, diff --git a/music_assistant/providers/snapcast/provider.py b/music_assistant/providers/snapcast/provider.py index 94c197fc..9a344f53 100644 index 049fb934..316b6cf8 100644 --- a/music_assistant/providers/snapcast/provider.py +++ b/music_assistant/providers/snapcast/provider.py @@ -32,6 +32,7 @@ from music_assistant.providers.snapcast.constants import ( @@ -35,6 +35,7 @@ CONF_STREAM_IDLE_THRESHOLD, CONF_USE_EXTERNAL_SERVER, CONTROL_SCRIPT, + CONTROL_SCRIPT_DIR, CONTROL_SOCKET_PATH_TEMPLATE, DEFAULT_SNAPSERVER_CONFIG_FILE, DEFAULT_SNAPSERVER_PLUGIN_DIR, DEFAULT_SNAPSERVER_PORT, SNAPWEB_DIR, @@ -215,6 +216,10 @@ class SnapCastProvider(PlayerProvider): @@ -270,6 +271,10 @@ async def _builtin_server_runner(self) -> None: args = [ "snapserver", Loading @@ -34,23 +34,14 @@ index 94c197fc..9a344f53 100644 + "--config=/dev/null", # config settings taken from # https://raw.githubusercontent.com/badaix/snapcast/86cd4b2b63e750a72e0dfe6a46d47caf01426c8d/server/etc/snapserver.conf f"--server.datadir={self.mass.storage_path}", @@ -227,6 +232,7 @@ class SnapCastProvider(PlayerProvider): 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.plugin_dir={CONTROL_SCRIPT_DIR}", f"--config={self._snapcast_server_config_file}", @@ -286,8 +291,7 @@ async def _builtin_server_runner(self) -> None: f"--stream.send_to_muted={str(self._snapcast_server_send_to_muted).lower()}", f"--streaming_client.initial_volume={self._snapcast_server_initial_volume}", ] @@ -243,9 +249,7 @@ class SnapCastProvider(PlayerProvider): # Copy control script after snapserver starts # (run in executor to avoid blocking) loop = asyncio.get_running_loop() - self._controlscript_available = await loop.run_in_executor( - None, self._setup_controlscript - ) + self._controlscript_available = True def _get_ma_id(self, snap_client_id: str) -> str: search_dict = self._ids_map.inverse - loop = asyncio.get_running_loop() - plugin_dir = await loop.run_in_executor(None, self._setup_controlscript) + plugin_dir = CONTROL_SCRIPT_DIR if plugin_dir is not None: args.append(f"--stream.plugin_dir={plugin_dir}") self._controlscript_available = True pkgs/by-name/mu/music-assistant/cliraop-cliap2.patch +7 −7 Original line number Diff line number Diff line diff --git a/music_assistant/providers/airplay/helpers.py b/music_assistant/providers/airplay/helpers.py index c5fef7eb..a628622d 100644 index 0d03b2cd..9cd59cf0 100644 --- a/music_assistant/providers/airplay/helpers.py +++ b/music_assistant/providers/airplay/helpers.py @@ -6,6 +6,7 @@ import os import platform @@ -9,6 +9,7 @@ import socket import time from ipaddress import ip_address +from shutil import which from typing import TYPE_CHECKING from zeroconf import IPVersion @@ -176,7 +177,6 @@ async def check_binary(cli_path: str) -> str | None: from music_assistant_models.enums import ContentType @@ -226,7 +227,6 @@ async def check_binary(cli_path: str) -> str | None: pass return None Loading @@ -18,7 +18,7 @@ index c5fef7eb..a628622d 100644 system = platform.system().lower().replace("darwin", "macos") architecture = platform.machine().lower() @@ -188,7 +188,7 @@ async def check_binary(cli_path: str) -> str | None: @@ -238,7 +238,7 @@ async def check_binary(cli_path: str) -> str | None: raise RuntimeError(f"Unsupported streaming protocol requested: {protocol}") if bridge_binary := await check_binary( Loading Loading
nixos/modules/services/audio/music-assistant.nix +4 −2 Original line number Diff line number Diff line Loading @@ -82,7 +82,8 @@ in allowedTCPPorts = lib.optional cfg.enable 8097 # Music Assistant stream port ++ lib.optional (lib.elem "airplay" cfg.providers) 7000 ++ lib.optional (lib.elem "sendspin" cfg.providers) 8927; ++ lib.optional (lib.elem "sendspin" cfg.providers) 8927 ++ lib.optional (lib.elem "snapcast" cfg.providers) 1780; # The information published by Apple 1 seem to not apply to libraop. # The closest we could find that represents the port range being used as observed by tcpdump is the ephemeral port range. # 1: https://support.apple.com/en-us/103229#:~:text=49152%E2%80%93-,65535,-TCP%2C%20UDP Loading @@ -97,6 +98,7 @@ in services.avahi = lib.mkIf (lib.elem "airplay_receiver" cfg.providers) { enable = true; openFirewall = lib.mkIf cfg.openFirewall true; publish = { enable = true; userServices = true; Loading Loading @@ -129,7 +131,7 @@ in ++ lib.optionals (lib.elem "airplay_receiver" cfg.providers) [ shairport-sync ] ++ lib.optionals (lib.elem "spotify" cfg.providers) [ ++ lib.optionals (lib.elem "spotify" cfg.providers || lib.elem "spotify_connect" cfg.providers) [ librespot-ma ] ++ lib.optionals (lib.elem "snapcast" cfg.providers) [ Loading
pkgs/by-name/cl/cliairplay/package.nix +3 −12 Original line number Diff line number Diff line Loading @@ -4,7 +4,6 @@ bison, curl, fetchFromGitHub, fetchpatch, ffmpeg-headless, flex, gperf, Loading @@ -26,26 +25,18 @@ stdenv.mkDerivation { pname = "cliairplay"; # see the beginning of configure.ac for the upstream version number version = "0.2-unstable-2025-12-30"; version = "1.1-unstable-2026-03-16"; src = fetchFromGitHub { owner = "music-assistant"; repo = "cliairplay"; # we try to closely match the commit used in the last music-assistant release from # https://github.com/music-assistant/server/tree/stable/music_assistant/providers/airplay/bin rev = "4660d886585d6bf8f32e889feec2a0e8975c51dc"; rev = "991c65acc2afa17ffe32e279dbc585b0b7f530f8"; fetchSubmodules = true; hash = "sha256-oDStn9LdLYWKhZNm7Qfdibs4qsct8gE3RZbTKooQeOM="; hash = "sha256-m1O4l6gFEGNAyskYcRHcA15cubZnNgkaYjdVThRRX7w="; }; patches = [ # Support gettext 0.25 (fetchpatch { url = "https://github.com/music-assistant/cliairplay/commit/92a2445d64c476d740feba1f31c7e5bc768701b7.patch"; hash = "sha256-/YnopvAGHHnQhfr2X1OenLMoF4ZlUq9x8tQZha/XfbQ="; }) ]; nativeBuildInputs = [ autoreconfHook bison Loading
pkgs/by-name/li/libraop/package.nix +3 −3 Original line number Diff line number Diff line Loading @@ -16,16 +16,16 @@ let in stdenv.mkDerivation { pname = "libraop"; version = "0-unstable-2026-02-09"; version = "0-unstable-2026-02-20"; src = fetchFromGitHub { owner = "music-assistant"; repo = "libraop"; # we try to closely match the commit used in the last music-assistant release from # https://github.com/music-assistant/server/tree/stable/music_assistant/providers/airplay/bin rev = "f49284282ea4ea740d07fabc230b4182f8c69a74"; rev = "df3c055674c147eeaa9307b7d554b9d46ed6418a"; fetchSubmodules = true; hash = "sha256-m1ll5vRZx4d/5IWCG24yY/SWEIIz2k/iU84vQKHlCdo="; hash = "sha256-zD1DggBQjbiD7B/u0hmogXj8NhrzYgVXMyzvHkaM4Hg="; }; patches = [ Loading
pkgs/by-name/mu/music-assistant/builtin-snapcast-server.patch +15 −24 Original line number Diff line number Diff line diff --git a/music_assistant/providers/snapcast/constants.py b/music_assistant/providers/snapcast/constants.py index eb5ecb1a..92f03212 100644 index 8c897c72..d89a0cd2 100644 --- a/music_assistant/providers/snapcast/constants.py +++ b/music_assistant/providers/snapcast/constants.py @@ -43,7 +43,8 @@ CONTROL_SOCKET_PATH_TEMPLATE = "/tmp/ma-snapcast-{queue_id}.sock" # noqa: S108 @@ -46,7 +46,8 @@ MASS_STREAM_PREFIX = "Music Assistant - " MASS_ANNOUNCEMENT_POSTFIX = " (announcement)" SNAPWEB_DIR = pathlib.Path(__file__).parent.resolve().joinpath("snapweb") Loading @@ -13,18 +13,18 @@ index eb5ecb1a..92f03212 100644 DEFAULT_SNAPCAST_FORMAT = AudioFormat( content_type=ContentType.PCM_S16LE, diff --git a/music_assistant/providers/snapcast/provider.py b/music_assistant/providers/snapcast/provider.py index 94c197fc..9a344f53 100644 index 049fb934..316b6cf8 100644 --- a/music_assistant/providers/snapcast/provider.py +++ b/music_assistant/providers/snapcast/provider.py @@ -32,6 +32,7 @@ from music_assistant.providers.snapcast.constants import ( @@ -35,6 +35,7 @@ CONF_STREAM_IDLE_THRESHOLD, CONF_USE_EXTERNAL_SERVER, CONTROL_SCRIPT, + CONTROL_SCRIPT_DIR, CONTROL_SOCKET_PATH_TEMPLATE, DEFAULT_SNAPSERVER_CONFIG_FILE, DEFAULT_SNAPSERVER_PLUGIN_DIR, DEFAULT_SNAPSERVER_PORT, SNAPWEB_DIR, @@ -215,6 +216,10 @@ class SnapCastProvider(PlayerProvider): @@ -270,6 +271,10 @@ async def _builtin_server_runner(self) -> None: args = [ "snapserver", Loading @@ -34,23 +34,14 @@ index 94c197fc..9a344f53 100644 + "--config=/dev/null", # config settings taken from # https://raw.githubusercontent.com/badaix/snapcast/86cd4b2b63e750a72e0dfe6a46d47caf01426c8d/server/etc/snapserver.conf f"--server.datadir={self.mass.storage_path}", @@ -227,6 +232,7 @@ class SnapCastProvider(PlayerProvider): 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.plugin_dir={CONTROL_SCRIPT_DIR}", f"--config={self._snapcast_server_config_file}", @@ -286,8 +291,7 @@ async def _builtin_server_runner(self) -> None: f"--stream.send_to_muted={str(self._snapcast_server_send_to_muted).lower()}", f"--streaming_client.initial_volume={self._snapcast_server_initial_volume}", ] @@ -243,9 +249,7 @@ class SnapCastProvider(PlayerProvider): # Copy control script after snapserver starts # (run in executor to avoid blocking) loop = asyncio.get_running_loop() - self._controlscript_available = await loop.run_in_executor( - None, self._setup_controlscript - ) + self._controlscript_available = True def _get_ma_id(self, snap_client_id: str) -> str: search_dict = self._ids_map.inverse - loop = asyncio.get_running_loop() - plugin_dir = await loop.run_in_executor(None, self._setup_controlscript) + plugin_dir = CONTROL_SCRIPT_DIR if plugin_dir is not None: args.append(f"--stream.plugin_dir={plugin_dir}") self._controlscript_available = True
pkgs/by-name/mu/music-assistant/cliraop-cliap2.patch +7 −7 Original line number Diff line number Diff line diff --git a/music_assistant/providers/airplay/helpers.py b/music_assistant/providers/airplay/helpers.py index c5fef7eb..a628622d 100644 index 0d03b2cd..9cd59cf0 100644 --- a/music_assistant/providers/airplay/helpers.py +++ b/music_assistant/providers/airplay/helpers.py @@ -6,6 +6,7 @@ import os import platform @@ -9,6 +9,7 @@ import socket import time from ipaddress import ip_address +from shutil import which from typing import TYPE_CHECKING from zeroconf import IPVersion @@ -176,7 +177,6 @@ async def check_binary(cli_path: str) -> str | None: from music_assistant_models.enums import ContentType @@ -226,7 +227,6 @@ async def check_binary(cli_path: str) -> str | None: pass return None Loading @@ -18,7 +18,7 @@ index c5fef7eb..a628622d 100644 system = platform.system().lower().replace("darwin", "macos") architecture = platform.machine().lower() @@ -188,7 +188,7 @@ async def check_binary(cli_path: str) -> str | None: @@ -238,7 +238,7 @@ async def check_binary(cli_path: str) -> str | None: raise RuntimeError(f"Unsupported streaming protocol requested: {protocol}") if bridge_binary := await check_binary( Loading