Various fixed and optimizations (#604)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sun, 2 Apr 2023 15:24:14 +0000 (17:24 +0200)
committerGitHub <noreply@github.com>
Sun, 2 Apr 2023 15:24:14 +0000 (17:24 +0200)
suppress annoying BrokenPipe Error
another fix for multi instanced providers
allow override of output codec
update airplay bridge

40 files changed:
README.md
music_assistant/__main__.py
music_assistant/common/models/config_entries.py
music_assistant/common/models/enums.py
music_assistant/constants.py
music_assistant/server/controllers/media/base.py
music_assistant/server/controllers/streams.py
music_assistant/server/helpers/process.py
music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64 [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64-static
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64 [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64-static
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm-static
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6 [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6-static
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips-static
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc-static
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64 [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64-static
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86 [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86-static
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64 [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64-static
music_assistant/server/providers/airplay/bin/squeeze2raop-macos [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64 [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64-static
music_assistant/server/providers/airplay/bin/squeeze2raop-macos-static
music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64 [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64-static
music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64 [deleted file]
music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64-static
music_assistant/server/providers/airplay/bin/squeeze2raop-static.exe
music_assistant/server/providers/airplay/bin/squeeze2raop.exe [deleted file]
music_assistant/server/providers/chromecast/__init__.py
music_assistant/server/providers/dlna/__init__.py
music_assistant/server/providers/sonos/__init__.py
script/profiler.py [new file with mode: 0644]

index a48626c778b79b843fcfce7d2607941339b1d815..7c2ae572d7177c386a1f1a35598aa6cf02e942eb 100644 (file)
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ docker run --network host --privileged ghcr.io/music-assistant/server
 
 You must run the docker container with host network mode and the data volume is `/data`.
 If you want access to your local music files from within MA, make sure to also mount that, e.g. /media.
-Note that accessing remote (SMB) shares can be done from within MA itself using the SMB File provider (but requires the priviliged flag).
+Note that accessing remote (SMB) shares can be done from within MA itself using the SMB File provider (but requires the privileged flag).
 
 ____________
 
index 9d40c04a4395cbc7b9997dcac01e7f11b55b193f..c8c13bcbbaffc6ce632b44d308ab044ac499c71a 100644 (file)
@@ -89,6 +89,7 @@ def main():
         hass_options = {}
 
     log_level = hass_options.get("log_level", args.log_level).upper()
+    dev_mode = bool(os.environ.get("PYTHONDEVMODE", "0"))
 
     # setup logger
     logger = setup_logger(data_dir, log_level)
@@ -100,7 +101,7 @@ def main():
 
     async def start_mass():
         loop = asyncio.get_running_loop()
-        if log_level == "DEBUG":
+        if dev_mode:
             loop.set_debug(True)
         await mass.start()
 
index 8d5bd57b7f934a1424d98b1e12105d288f62c327..c2a3eaac8151116e210648f762ac832d2cfae010 100644 (file)
@@ -17,6 +17,7 @@ from music_assistant.constants import (
     CONF_FLOW_MODE,
     CONF_LOG_LEVEL,
     CONF_OUTPUT_CHANNELS,
+    CONF_OUTPUT_CODEC,
     CONF_VOLUME_NORMALISATION,
     CONF_VOLUME_NORMALISATION_TARGET,
     SECURE_STRING_SUBSTITUTE,
@@ -367,3 +368,21 @@ DEFAULT_PLAYER_CONFIG_ENTRIES = (
         advanced=True,
     ),
 )
+
+CONF_ENTRY_OUTPUT_CODEC = ConfigEntry(
+    key=CONF_OUTPUT_CODEC,
+    type=ConfigEntryType.STRING,
+    label="Output codec",
+    options=[
+        ConfigValueOption("FLAC (lossless, compact file size)", "flac"),
+        ConfigValueOption("M4A AAC (lossy, superior quality)", "aac"),
+        ConfigValueOption("MP3 (lossy, average quality)", "mp3"),
+        ConfigValueOption("WAV (lossless, huge file size)", "wav"),
+    ],
+    default_value="flac",
+    description="Define the codec that is sent to the player when streaming audio. "
+    "By default Music Assistant prefers FLAC because it is lossless, has a "
+    "respectable filesize and is supported by most player devices. "
+    "Change this setting only if needed for your device/environment.",
+    advanced=True,
+)
index 1b2cda524d8598f857fb7353537766de23c51ac4..ff62c45e1150c45c9c1e803e1f4168ea37d2b490 100644 (file)
@@ -250,6 +250,7 @@ class PlayerFeature(StrEnum):
     SEEK = "seek"
     SET_MEMBERS = "set_members"
     QUEUE = "queue"
+    CROSSFADE = "crossfade"
 
 
 class EventType(StrEnum):
index fe84ecd19ebbc07b504847171b67318c0f8a91ad..6c5a3ecf8ccb7b55a691ea5be32286ef892f6a01 100755 (executable)
@@ -45,6 +45,7 @@ CONF_OUTPUT_CHANNELS: Final[str] = "output_channels"
 CONF_FLOW_MODE: Final[str] = "flow_mode"
 CONF_LOG_LEVEL: Final[str] = "log_level"
 CONF_HIDE_GROUP_CHILDS: Final[str] = "hide_group_childs"
+CONF_OUTPUT_CODEC: Final[str] = "output_codec"
 
 # config default values
 DEFAULT_HOST: Final[str] = "0.0.0.0"
index 971f5fcb42ef9a39f9108502ac9ff58a299de571..3c5026624e4608d9dc8610a56192d490b14919f4 100644 (file)
@@ -389,7 +389,20 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
         if not force_refresh and (cache := await self.mass.cache.get(cache_key)):
             return self.item_cls.from_dict(cache)
         if provider := self.mass.get_provider(provider_instance_id_or_domain):  # noqa: SIM102
-            if item := await provider.get_item(self.media_type, item_id):
+            item: MediaItemType = None
+            try:
+                item = await provider.get_item(self.media_type, item_id)
+            except MediaNotFoundError:
+                # fallback to domain matching
+                for provider in self.mass.music.providers:
+                    if not provider.available:
+                        continue
+                    if provider_instance_id_or_domain != provider.domain:
+                        continue
+                    with suppress(MediaNotFoundError):
+                        if item := await provider.get_item(self.media_type, item_id):
+                            break
+            if item:
                 await self.mass.cache.set(cache_key, item.to_dict())
                 return item
         raise MediaNotFoundError(
@@ -449,7 +462,7 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
                 continue
             return await self._get_provider_dynamic_tracks(
                 prov_mapping.item_id,
-                provider_instance_id_or_domain,
+                prov_mapping.provider_instance,
                 limit=limit,
             )
         # Fallback to the default implementation
index a0f7240c15eacac6c8f86aff3b0a2c1fa4392d53..6b52d27a8ef0047667c2ed70032e7b14ed97db3a 100644 (file)
@@ -643,17 +643,25 @@ class StreamsController:
             "-i",
             "-",
         ]
-        # output args
-        output_args = [
-            # output args
-            "-f",
-            output_format.value,
+        input_args += ["-metadata", 'title="Music Assistant"']
+        # select output args
+        if output_format == ContentType.FLAC:
+            output_args = ["-f", "flac", "-compression_level", "3"]
+        elif output_format == ContentType.AAC:
+            output_args = ["-f", "adts", "-c:a", output_format.value, "-b:a", "320k"]
+        elif output_format == ContentType.MP3:
+            output_args = ["-f", "mp3", "-c:a", output_format.value, "-b:a", "320k"]
+        else:
+            output_args = ["-f", output_format.value]
+
+        output_args += [
+            # append channels
             "-ac",
             "1" if conf_channels != "stereo" else "2",
+            # append sample rate
             "-ar",
             str(output_sample_rate),
-            "-compression_level",
-            "0",
+            # output = pipe
             "-",
         ]
         # collect extra and filter args
index c928416b39fd03b2b0983d219b41cf08539bc20d..6d8476fdacd52c3c454ba6fd5a67fcc87ba4b285 100644 (file)
@@ -114,7 +114,8 @@ class AsyncProcess:
         if self.closed or self._proc.stdin.is_closing():
             return
         self._proc.stdin.write(data)
-        await self._proc.stdin.drain()
+        with suppress(BrokenPipeError):
+            await self._proc.stdin.drain()
 
     def write_eof(self) -> None:
         """Write end of file to to process stdin."""
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64
deleted file mode 100755 (executable)
index 337acf6..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64 and /dev/null differ
index 9b79048d5d394ed0bc43b9b9c293912229c39cfb..ca9f28f5bbe460261bc7f87665b26755c2ff73c8 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64
deleted file mode 100755 (executable)
index 668077f..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64 and /dev/null differ
index 95cdc4dd1d2a2007b7e8c1f873e12a61c875142a..b7be884f81ef380fdc96d38d0dd86cb50b6a31de 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm
deleted file mode 100755 (executable)
index 3b240e3..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm and /dev/null differ
index acd8455e28cba2d4d17526f85ed5837374061644..dc0e0d3889b32ee0cf8b806d164291412fcd63ac 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6 b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6
deleted file mode 100755 (executable)
index ccbeb34..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6 and /dev/null differ
index 7867ce6dad888206492a438a9f79ef81d67aca6c..ac3fc9e3287bd5fc2c73c35c314519d1685eea93 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips
deleted file mode 100755 (executable)
index 8570099..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips and /dev/null differ
index 068d1324dba9d6280e94662f9328292aeb9198d4..5184eba451d467ec7cf3701f70c28d40d9dac74e 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc
deleted file mode 100755 (executable)
index 5dbf52a..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc and /dev/null differ
index 2775a988de4d232854582414fe687d20a73e3959..5de83585999753a631bb714bd636ecedf2db9b42 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64
deleted file mode 100755 (executable)
index f8494ef..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64 and /dev/null differ
index 3c5f0226d574eecb558929161a2b15923bcbb609..62c6acb45baa57f426f24c346a64c48181eecbbe 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86 b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86
deleted file mode 100755 (executable)
index 4f85692..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86 and /dev/null differ
index 6397f594b45a9ae4d72dea5c2f45ac73e087130f..54066495d38149a3a7f2d9123f5f9045d6eea887 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64
deleted file mode 100755 (executable)
index 5c511a3..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64 and /dev/null differ
index 27575f9c6cddd69eadaaebe9c7060ac35edfddb9..52aabc00cea6cd4ca1f80e711e2728b505a28dfe 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos
deleted file mode 100755 (executable)
index 2791328..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos and /dev/null differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64
deleted file mode 100755 (executable)
index 67820ec..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64 and /dev/null differ
index c08c362c2b38fa7b7a9568124ff3b01fa8ce0894..b1b834eda8183ee543c897e009e604389a9d8f13 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64-static differ
index c2ce4274ef183bd9ce5cef23bc1e2aa4875ba91c..5aba575435ae41a3c9a0df8e29713bdb17db9caa 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64
deleted file mode 100755 (executable)
index a5489bf..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64 and /dev/null differ
index d7b867639998abe3742b977befa0b21bbf34f039..89b7ed637b531cc93099d849b16088c436c8ade4 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64-static differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64
deleted file mode 100755 (executable)
index 80a3cd5..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64 and /dev/null differ
index 0c72982e26e37ac7e041aaf81652532129b12e61..1893e41761ea325933cfbd1b67ed0587f7524081 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64-static differ
index e955ad6a265612d695a5be6bc35c4f6ed19cce36..74820de3470dba94ad51dc0b08322415a1d25e7e 100755 (executable)
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-static.exe and b/music_assistant/server/providers/airplay/bin/squeeze2raop-static.exe differ
diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop.exe b/music_assistant/server/providers/airplay/bin/squeeze2raop.exe
deleted file mode 100755 (executable)
index d3b2284..0000000
Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop.exe and /dev/null differ
index c1788f749ed2813fea36a911e731f6096907f131..8da64c57906a32dd2dce7cac1500f0ff955db335 100644 (file)
@@ -19,7 +19,11 @@ from pychromecast.discovery import CastBrowser, SimpleCastListener
 from pychromecast.models import CastInfo
 from pychromecast.socket_client import CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED
 
-from music_assistant.common.models.config_entries import ConfigEntry, ConfigValueOption
+from music_assistant.common.models.config_entries import (
+    CONF_ENTRY_OUTPUT_CODEC,
+    ConfigEntry,
+    ConfigValueOption,
+)
 from music_assistant.common.models.enums import (
     ConfigEntryType,
     ContentType,
@@ -31,7 +35,12 @@ from music_assistant.common.models.enums import (
 from music_assistant.common.models.errors import PlayerUnavailableError, QueueEmpty
 from music_assistant.common.models.player import DeviceInfo, Player
 from music_assistant.common.models.queue_item import QueueItem
-from music_assistant.constants import CONF_HIDE_GROUP_CHILDS, CONF_PLAYERS, MASS_LOGO_ONLINE
+from music_assistant.constants import (
+    CONF_HIDE_GROUP_CHILDS,
+    CONF_OUTPUT_CODEC,
+    CONF_PLAYERS,
+    MASS_LOGO_ONLINE,
+)
 from music_assistant.server.models.player_provider import PlayerProvider
 
 from .helpers import CastStatusListener, ChromecastInfo
@@ -49,6 +58,7 @@ if TYPE_CHECKING:
 
 CONF_ALT_APP = "alt_app"
 
+
 BASE_PLAYER_CONFIG_ENTRIES = (
     ConfigEntry(
         key=CONF_ALT_APP,
@@ -59,6 +69,7 @@ BASE_PLAYER_CONFIG_ENTRIES = (
         "the playback experience but may not work on non-Google hardware.",
         advanced=True,
     ),
+    CONF_ENTRY_OUTPUT_CODEC,
 )
 
 
@@ -188,13 +199,13 @@ class ChromecastProvider(PlayerProvider):
     ) -> None:
         """Send PLAY MEDIA command to given player."""
         castplayer = self.castplayers[player_id]
+        output_codec = self.mass.config.get_player_config_value(player_id, CONF_OUTPUT_CODEC).value
         url = await self.mass.streams.resolve_stream_url(
             queue_item=queue_item,
             player_id=player_id,
             seek_position=seek_position,
             fade_in=fade_in,
-            # prefer FLAC as it seems to work on all CC players
-            content_type=ContentType.FLAC,
+            content_type=ContentType(output_codec),
             flow_mode=flow_mode,
         )
         castplayer.flow_mode_active = flow_mode
index 44e0b55d9a3f5870b155295ecd6990ec70b05226..557b3087b30936e19488c28001013eb344e777ee 100644 (file)
@@ -23,12 +23,12 @@ from async_upnp_client.profiles.dlna import DmrDevice, TransportState
 from async_upnp_client.search import async_search
 from async_upnp_client.utils import CaseInsensitiveDict
 
-from music_assistant.common.models.config_entries import ConfigEntry
+from music_assistant.common.models.config_entries import CONF_ENTRY_OUTPUT_CODEC, ConfigEntry
 from music_assistant.common.models.enums import ContentType, PlayerFeature, PlayerState, PlayerType
 from music_assistant.common.models.errors import PlayerUnavailableError, QueueEmpty
 from music_assistant.common.models.player import DeviceInfo, Player
 from music_assistant.common.models.queue_item import QueueItem
-from music_assistant.constants import CONF_PLAYERS
+from music_assistant.constants import CONF_OUTPUT_CODEC, CONF_PLAYERS
 from music_assistant.server.helpers.didl_lite import create_didl_metadata
 from music_assistant.server.models.player_provider import PlayerProvider
 
@@ -46,7 +46,7 @@ PLAYER_FEATURES = (
     PlayerFeature.VOLUME_MUTE,
     PlayerFeature.VOLUME_SET,
 )
-PLAYER_CONFIG_ENTRIES = tuple()  # we don't have any player config entries (for now)
+PLAYER_CONFIG_ENTRIES = (CONF_ENTRY_OUTPUT_CODEC,)
 
 _DLNAPlayerProviderT = TypeVar("_DLNAPlayerProviderT", bound="DLNAPlayerProvider")
 _R = TypeVar("_R")
@@ -220,6 +220,10 @@ class DLNAPlayerProvider(PlayerProvider):
             for dlna_player in self.dlnaplayers.values():
                 tg.create_task(self._device_disconnect(dlna_player))
 
+    def get_player_config_entries(self, player_id: str) -> tuple[ConfigEntry, ...]:  # noqa: ARG002
+        """Return all (provider/player specific) Config Entries for the given player (if any)."""
+        return PLAYER_CONFIG_ENTRIES
+
     def on_player_config_changed(
         self, config: PlayerConfig, changed_keys: set[str]  # noqa: ARG002
     ) -> None:
@@ -258,13 +262,13 @@ class DLNAPlayerProvider(PlayerProvider):
         # always clear queue (by sending stop) first
         if dlna_player.device.can_stop:
             await self.cmd_stop(player_id)
-
+        output_codec = self.mass.config.get_player_config_value(player_id, CONF_OUTPUT_CODEC).value
         url = await self.mass.streams.resolve_stream_url(
             queue_item=queue_item,
             player_id=dlna_player.udn,
             seek_position=seek_position,
             fade_in=fade_in,
-            content_type=ContentType.FLAC,
+            content_type=ContentType(output_codec),
             flow_mode=flow_mode,
         )
 
@@ -548,10 +552,13 @@ class DLNAPlayerProvider(PlayerProvider):
             return
 
         # send queue item to dlna queue
+        output_codec = self.mass.config.get_player_config_value(
+            dlna_player.player.player_id, CONF_OUTPUT_CODEC
+        ).value
         url = await self.mass.streams.resolve_stream_url(
             queue_item=next_item,
             player_id=dlna_player.udn,
-            content_type=ContentType.FLAC,
+            content_type=ContentType(output_codec),
             # DLNA pre-caches pretty aggressively so do not yet start the runner
             auto_start_runner=False,
         )
index 6e60f26d3990c7fd6d0a8cd70622d53a45d8c0a0..a565ad3ce72fa1c5f23c3262f23ebc8fb4016e9c 100644 (file)
@@ -14,7 +14,7 @@ from soco.events_base import Event as SonosEvent
 from soco.events_base import SubscriptionBase
 from soco.groups import ZoneGroup
 
-from music_assistant.common.models.config_entries import ConfigEntry
+from music_assistant.common.models.config_entries import CONF_ENTRY_OUTPUT_CODEC, ConfigEntry
 from music_assistant.common.models.enums import (
     ContentType,
     MediaType,
@@ -42,7 +42,7 @@ PLAYER_FEATURES = (
     PlayerFeature.VOLUME_MUTE,
     PlayerFeature.VOLUME_SET,
 )
-PLAYER_CONFIG_ENTRIES = tuple()  # we don't have any player config entries (for now)
+PLAYER_CONFIG_ENTRIES = (CONF_ENTRY_OUTPUT_CODEC,)
 
 
 async def setup(
@@ -227,6 +227,10 @@ class SonosPlayerProvider(PlayerProvider):
             for player in self.sonosplayers.values():
                 player.soco.end_direct_control_session
 
+    def get_player_config_entries(self, player_id: str) -> tuple[ConfigEntry, ...]:  # noqa: ARG002
+        """Return all (provider/player specific) Config Entries for the given player (if any)."""
+        return PLAYER_CONFIG_ENTRIES
+
     def on_player_config_changed(
         self, config: PlayerConfig, changed_keys: set[str]  # noqa: ARG002
     ) -> None:
diff --git a/script/profiler.py b/script/profiler.py
new file mode 100644 (file)
index 0000000..c5135bc
--- /dev/null
@@ -0,0 +1,60 @@
+"""
+Helper to trace memory usage.
+
+https://www.red-gate.com/simple-talk/development/python/memory-profiling-in-python-with-tracemalloc/
+"""
+import asyncio
+import tracemalloc
+
+# ruff: noqa: D103,E501,E741
+
+# list to store memory snapshots
+snaps = []
+
+
+def _take_snapshot():
+    snaps.append(tracemalloc.take_snapshot())
+
+
+async def take_snapshot():
+    loop = asyncio.get_running_loop()
+    await loop.run_in_executor(None, _take_snapshot)
+
+
+def _display_stats():
+    stats = snaps[0].statistics("filename")
+    print("\n*** top 5 stats grouped by filename ***")
+    for s in stats[:5]:
+        print(s)
+
+
+async def display_stats():
+    loop = asyncio.get_running_loop()
+    await loop.run_in_executor(None, _display_stats)
+
+
+def compare():
+    first = snaps[0]
+    for snapshot in snaps[1:]:
+        stats = snapshot.compare_to(first, "lineno")
+        print("\n*** top 10 stats ***")
+        for s in stats[:10]:
+            print(s)
+
+
+def print_trace():
+    # pick the last saved snapshot, filter noise
+    snapshot = snaps[-1].filter_traces(
+        (
+            tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
+            tracemalloc.Filter(False, "<frozen importlib._bootstrap_external>"),
+            tracemalloc.Filter(False, "<unknown>"),
+        )
+    )
+    largest = snapshot.statistics("traceback")[0]
+
+    print(
+        f"\n*** Trace for largest memory block - ({largest.count} blocks, {largest.size/1024} Kb) ***"
+    )
+    for l in largest.traceback.format():
+        print(l)