Updated Artwork handling on AriaCast Receiver (#3226)
authorLorenzo Imbastari <lorenzoimbastari@gmail.com>
Tue, 24 Feb 2026 08:25:31 +0000 (09:25 +0100)
committerGitHub <noreply@github.com>
Tue, 24 Feb 2026 08:25:31 +0000 (09:25 +0100)
* Add AriaCast Receiver plugin for Music Assistant

- Implemented AriaCast Receiver plugin to stream audio from Android devices to Music Assistant players.
- Added README.md with features, installation, configuration, and usage instructions.
- Created configuration classes for audio and server settings.
- Developed metadata handling for AriaCast streams.
- Implemented UDP discovery and WebSocket server for audio and metadata streaming.
- Added helper functions for local IP retrieval and artwork downloading.
- Included SVG icon for the plugin.
- Updated manifest.json with documentation link and requirements.

* Update README to simplify installation instructions

Removed installation instructions and updated configuration step for clarity.

* fixed mypy and pre-commit problems for my provider

* Delete music_assistant/providers/ariacast_receiver/README.md

* fixed pre-commit problems x2

* fixed icon

* Merge remote-tracking branch 'origin/dev' into dev

* Fixed Metadata and media controls after refactoring

* copilot notes addressing

* Addressing more copilot notes

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Addressing last notes

* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix: address code review feedback for ariacast_receiver

* fix(ariacast): add playback ready event for audio/player sync

- Add _playback_ready event to coordinate audio handler and player stream
- Audio handler waits up to 2s for player to become ready after starting playback
- get_audio_stream signals ready when it starts consuming frames
- Clear ready state on cleanup in _clear_active_player and stream end

This reduces audio frame loss during player setup by synchronizing
the audio receiver with the player's readiness to consume frames.

* Add multi-platform AriaCast receiver binaries (darwin/linux amd64/arm64/arm)

* renamed receiver name

* no redownload if same artwork

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/manifest.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix: use shared http_session for ariacast websocket connection

* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix in previous copilot commit messing up the way pipe was read (no loop)

* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fixel last comments

* Change multi_instance setting to false

* Addressed latest comments after review

- Switched from pipe to stdout
- Dropped linux arm
- Moved _get_binary_path() to helpers.py

* Implemented stderr logging, robust WebSocket connection retries, improved artwork handling, and player switching control.

Updated Manifest

* Fix after PlayerController renamed all to all_players and players.get to get_player()

* Update music_assistant/providers/ariacast_receiver/__init__.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update music_assistant/providers/ariacast_receiver/__init__.py

Added suggested track change comments

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update documentation URL in manifest.json

* Added README for the binary of AriaCast Receiver

* updated receiver for artwork

* final fixes

* addressed copilot comment

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
music_assistant/providers/ariacast_receiver/__init__.py
music_assistant/providers/ariacast_receiver/bin/ariacast_darwin_amd64
music_assistant/providers/ariacast_receiver/bin/ariacast_darwin_arm64
music_assistant/providers/ariacast_receiver/bin/ariacast_linux_amd64
music_assistant/providers/ariacast_receiver/bin/ariacast_linux_arm64

index 3ef17ac6f76dfafbfff884fed941e3a235ff9885..7544710673ba8e5af9f53cb08142689dd9904f8a 100644 (file)
@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 import asyncio
+import hashlib
 import time
 from collections import deque
 from collections.abc import AsyncGenerator
@@ -262,13 +263,17 @@ class AriaCastBridge(PluginProvider):
         if not artwork_url:
             return
 
+        # The binary often sends a static local URL (like http://127.0.0.1/image/artwork).
+        # We combine it with the track title to detect actual song/artwork changes.
+        current_identifier = f"{artwork_url}_{meta.title}_{meta.artist}"
+
         last_artwork_identifier = getattr(self, "_last_artwork_identifier", None)
-        if artwork_url != last_artwork_identifier:
+        if current_identifier != last_artwork_identifier:
             # New artwork detected
             self.logger.debug(
-                "New artwork detected: %s (was: %s)", artwork_url, last_artwork_identifier
+                "New artwork detected for track: %s (was: %s)", meta.title, last_artwork_identifier
             )
-            self._last_artwork_identifier = artwork_url
+            self._last_artwork_identifier = current_identifier
             # Clear old artwork data to prevent serving stale image
             self._artwork_bytes = None
             if meta:
@@ -402,18 +407,18 @@ class AriaCastBridge(PluginProvider):
                         self.logger.info(
                             "Artwork downloaded successfully, size: %d bytes", len(img_data)
                         )
-
+                        # Use a content-derived hash to prevent unbounded cache growth
+                        img_hash = hashlib.md5(img_data).hexdigest()[:8]
                         image = MediaItemImage(
                             type=ImageType.THUMB,
-                            path="artwork",
+                            path=f"artwork_{img_hash}",
                             provider=self.instance_id,
                             remotely_accessible=False,
                         )
-                        base_url = self.mass.metadata.get_image_url(image)
 
                         if self._source_details.metadata:
                             self._source_details.metadata.image_url = (
-                                f"{base_url}&t={self._artwork_timestamp}"
+                                self.mass.metadata.get_image_url(image)
                             )
 
                         if self._source_details.in_use_by:
@@ -425,7 +430,7 @@ class AriaCastBridge(PluginProvider):
 
     async def resolve_image(self, path: str) -> bytes:
         """Return raw image bytes to Music Assistant."""
-        if path == "artwork" and self._artwork_bytes:
+        if path.startswith("artwork") and self._artwork_bytes:
             return self._artwork_bytes
         return b""
 
index cccbda90536cb4663ac25b8e553785048ede490b..575651e61aba1fa5d6d88ef7a29fc31986909c2c 100755 (executable)
Binary files a/music_assistant/providers/ariacast_receiver/bin/ariacast_darwin_amd64 and b/music_assistant/providers/ariacast_receiver/bin/ariacast_darwin_amd64 differ
index 89541d077d43ae2258e4cc50eb0768605f5a04f1..833380f5c52d05b499438b5bdf8389b636d8c29e 100755 (executable)
Binary files a/music_assistant/providers/ariacast_receiver/bin/ariacast_darwin_arm64 and b/music_assistant/providers/ariacast_receiver/bin/ariacast_darwin_arm64 differ
index 26df90a58f0e793ea4fba623c3ca0a0c3f21e6c0..a32e72e32a0eeced581cf490aa4f44d7f3a25b0e 100755 (executable)
Binary files a/music_assistant/providers/ariacast_receiver/bin/ariacast_linux_amd64 and b/music_assistant/providers/ariacast_receiver/bin/ariacast_linux_amd64 differ
index f8f7d2ec5e6252db9449eb4c55ae0cc7e9de057e..ec86be0279412b37a177bf8918ebf1f8dbb6ee81 100755 (executable)
Binary files a/music_assistant/providers/ariacast_receiver/bin/ariacast_linux_arm64 and b/music_assistant/providers/ariacast_receiver/bin/ariacast_linux_arm64 differ