The correct Apple branding is "AirPlay". Also fixed "macOS" a few places.
* Python 3.12 is minimal required, 3.12 recommended (or check the pyproject for current required version)
* [Python venv](https://docs.python.org/3/library/venv.html)
-We recommend developing on a (recent) MacOS or Linux machine.
+We recommend developing on a (recent) macOS or Linux machine.
It is recommended to use Visual Studio Code as your IDE, since launch files to start Music Assistant are provided as part of the repository. Furthermore, the current code base is not verified to work on a native Windows machine. If you would like to develop on a Windows machine, install [WSL2](https://code.visualstudio.com/blogs/2019/09/03/wsl2) to increase your swag-level 🤘.
## 🚀 Setting up your development environment
### Using Devcontainer/Codespace
We removed support for devcontainers because we do not have anyone willing to maintain it.
It also is not very convenient due to all the port requirements, binaries etc.
-If somebody is willing to create and maintain a devcontainer with host networking and based on our base alpine image, we will add the support back. Until then: Develop with Python venv on a Linux or MacOS machine (see above).
+If somebody is willing to create and maintain a devcontainer with host networking and based on our base alpine image, we will add the support back. Until then: Develop with Python venv on a Linux or macOS machine (see above).
## Note on async Python
The Music Assistant server is fully built in Python. The Python language has no real supported for multi-threading. This is why Music Assistant heavily relies on asyncio to handle blocking IO. It is important to get a good understanding of asynchronous programming before building your first provider. [This](https://www.youtube.com/watch?v=M-UcUs7IMIM) video is an excellent first step in the world of asyncio.
## ▶️ Building your own Player Provider
-A Player Provider is the provider type that adds support for a 'target of playback' to Music Assistant. Sonos, Chromecast and Airplay are examples of a Player Provider.
+A Player Provider is the provider type that adds support for a 'target of playback' to Music Assistant. Sonos, Chromecast and AirPlay are examples of a Player Provider.
All Providers (of all types) can be found in the `music_assistant/providers` folder.
TIP: We have created a template/stub provider in `music_assistant/providers/_template_player_provider` to get you started fast!
but we also support that you specify a material design icon in the manifest.json file.
IMPORTANT NOTE:
-We strongly recommend developing on either MacOS or Linux and start your development
+We strongly recommend developing on either macOS or Linux and start your development
environment by running the setup.sh script in the scripts folder of the repository.
This will create a virtual environment and install all dependencies needed for development.
See also our general DEVELOPMENT.md guide in the repository for more information.
but we also support that you specify a material design icon in the manifest.json file.
IMPORTANT NOTE:
-We strongly recommend developing on either MacOS or Linux and start your development
+We strongly recommend developing on either macOS or Linux and start your development
environment by running the setup.sh scripts in the scripts folder of the repository.
This will create a virtual environment and install all dependencies needed for development.
See also our general DEVELOPMENT.md guide in the repository for more information.
# all songs in the playback queue into a single stream and send that to the player.
# In that case the URI (and metadata) received here is that of the 'flow mode' stream.
- # Examples of player providers that use flow mode for playback by default are Airplay,
+ # Examples of player providers that use flow mode for playback by default are AirPlay,
# SnapCast and Fully Kiosk.
# Examples of player providers that optionally use 'flow mode' are Google Cast and
but we also support that you specify a material design icon in the manifest.json file.
IMPORTANT NOTE:
-We strongly recommend developing on either MacOS or Linux and start your development
+We strongly recommend developing on either macOS or Linux and start your development
environment by running the setup.sh scripts in the scripts folder of the repository.
This will create a virtual environment and install all dependencies needed for development.
See also our general DEVELOPMENT.md guide in the repository for more information.
-"""Airplay Player provider for Music Assistant."""
+"""AirPlay Player provider for Music Assistant."""
from __future__ import annotations
from music_assistant.mass import MusicAssistant
from .const import CONF_BIND_INTERFACE
-from .provider import AirplayProvider
+from .provider import AirPlayProvider
if TYPE_CHECKING:
from music_assistant_models.config_entries import ProviderConfig
type=ConfigEntryType.STRING,
default_value=cast("str", mass.streams.publish_ip),
label="Bind interface",
- description="Interface to bind to for Airplay streaming.",
+ description="Interface to bind to for AirPlay streaming.",
category="advanced",
),
)
mass: MusicAssistant, manifest: ProviderManifest, config: ProviderConfig
) -> ProviderInstanceType:
"""Initialize provider(instance) with given configuration."""
- return AirplayProvider(mass, manifest, config)
+ return AirPlayProvider(mass, manifest, config)
-"""Various helpers/utilities for the Airplay provider."""
+"""Various helpers/utilities for the AirPlay provider."""
from __future__ import annotations
def convert_airplay_volume(value: float) -> int:
- """Remap Airplay Volume to 0..100 scale."""
+ """Remap AirPlay Volume to 0..100 scale."""
airplay_min = -30
airplay_max = 0
normal_min = 0
model = "Apple TV"
manufacturer = "Apple"
- return (manufacturer or "Airplay", model)
+ return (manufacturer or "AirPlay", model)
def get_primary_ip_address(discovery_info: AsyncServiceInfo) -> str | None:
{
"type": "player",
"domain": "airplay",
- "name": "Airplay",
- "description": "Support for players that support the Airplay protocol.",
+ "name": "AirPlay",
+ "description": "Support for players that support the AirPlay protocol.",
"codeowners": ["@music-assistant"],
"requirements": [],
"documentation": "https://music-assistant.io/player-support/airplay/",
if TYPE_CHECKING:
from zeroconf.asyncio import AsyncServiceInfo
- from .provider import AirplayProvider
+ from .provider import AirPlayProvider
from .raop import RaopStream
class AirPlayPlayer:
- """Holds the details of the (discovered) Airplay (RAOP) player."""
+ """Holds the details of the (discovered) AirPlay (RAOP) player."""
def __init__(
- self, prov: AirplayProvider, player_id: str, discovery_info: AsyncServiceInfo, address: str
+ self, prov: AirPlayProvider, player_id: str, discovery_info: AsyncServiceInfo, address: str
) -> None:
"""Initialize AirPlayPlayer."""
self.prov = prov
-"""Airplay Player provider for Music Assistant."""
+"""AirPlay Player provider for Music Assistant."""
from __future__ import annotations
type=ConfigEntryType.BOOLEAN,
default_value=False,
label="Ignore volume reports sent by the device itself",
- description="The Airplay protocol allows devices to report their own volume level. \n"
+ description="The AirPlay protocol allows devices to report their own volume level. \n"
"For some devices this is not reliable and can cause unexpected volume changes. \n"
"Enable this option to ignore these reports.",
category="airplay",
type=ConfigEntryType.ALERT,
default_value=None,
required=False,
- label="This player is known to have broken Airplay 1 (RAOP) support. "
+ label="This player is known to have broken AirPlay 1 (RAOP) support. "
"Playback may fail or simply be silent. There is no workaround for this issue at the moment.",
)
-# TODO: Airplay provider
+# TODO: AirPlay provider
# - Implement authentication for Apple TV
# - Implement volume control for Apple devices using pyatv
# - Implement metadata for Apple Apple devices using pyatv
# - Use pyatv for communicating with original Apple devices (and use cliraop for actual streaming)
-# - Implement Airplay 2 support
+# - Implement AirPlay 2 support
# - Implement late joining to existing stream (instead of restarting it)
-class AirplayProvider(PlayerProvider):
- """Player provider for Airplay based players."""
+class AirPlayProvider(PlayerProvider):
+ """Player provider for AirPlay based players."""
cliraop_bin: str | None
_players: dict[str, AirPlayPlayer]
address = get_primary_ip_address(info)
if address is None:
return
- self.logger.debug("Discovered Airplay device %s on %s", display_name, address)
+ self.logger.debug("Discovered AirPlay device %s on %s", display_name, address)
# prefer airplay mdns info as it has more details
# fallback to raop info if airplay info is not available
# append airplay to the default display name for generic (non-apple) devices
# this makes it easier for users to distinguish between airplay and non-airplay devices
if manufacturer.lower() != "apple" and "airplay" not in display_name.lower():
- display_name += " (Airplay)"
+ display_name += " (AirPlay)"
self._players[player_id] = AirPlayPlayer(self, player_id, info, address)
if not (volume := await self.mass.cache.get(player_id, base_key=CACHE_KEY_PREV_VOLUME)):
-"""Logic for RAOP (AirPlay 1) audio streaming to Airplay devices."""
+"""Logic for RAOP (AirPlay 1) audio streaming to AirPlay devices."""
from __future__ import annotations
from music_assistant_models.player_queue import PlayerQueue
from .player import AirPlayPlayer
- from .provider import AirplayProvider
+ from .provider import AirPlayProvider
class RaopStreamSession:
def __init__(
self,
- airplay_provider: AirplayProvider,
+ airplay_provider: AirPlayProvider,
sync_clients: list[AirPlayPlayer],
input_format: AudioFormat,
audio_source: AsyncGenerator[bytes, None],
class RaopStream:
"""
- RAOP (Airplay 1) Audio Streamer.
+ RAOP (AirPlay 1) Audio Streamer.
Python is not suitable for realtime audio streaming so we do the actual streaming
of (RAOP) audio using a small executable written in C based on libraop to do
}
if platform.system() == "Darwin":
- # NOTE: MacOS does not support special characters in the username/password
+ # NOTE: macOS does not support special characters in the username/password
password_str = f":{password}" if password else ""
mount_cmd = [
"mount",
),
SOURCE_AIRPLAY: PlayerSource(
id=SOURCE_AIRPLAY,
- name="Airplay",
+ name="AirPlay",
passive=True,
can_play_pause=True,
can_next_previous=True,
ConfigEntry(
key=CONF_AIRPLAY_MODE,
type=ConfigEntryType.BOOLEAN,
- label="Enable Airplay mode",
- description="Almost all newer Sonos speakers have Airplay support. "
- "If you have the Airplay provider enabled in Music Assistant, "
- "your Sonos speaker will also be detected as a Airplay speaker, meaning "
- "you can group them with other Airplay speakers.\n\n"
+ label="Enable AirPlay mode",
+ description="Almost all newer Sonos speakers have AirPlay support. "
+ "If you have the AirPlay provider enabled in Music Assistant, "
+ "your Sonos speaker will also be detected as a AirPlay speaker, meaning "
+ "you can group them with other AirPlay speakers.\n\n"
"By default, Music Assistant uses the Sonos protocol for playback but with this "
- "feature enabled, it will use the Airplay protocol instead by redirecting "
- "the playback related commands to the linked Airplay player in Music Assistant, "
- "allowing you to mix and match Sonos speakers with Airplay speakers. \n\n"
- "NOTE: You need to have the Airplay provider enabled as well as "
- "the Airplay version of this player.",
+ "feature enabled, it will use the AirPlay protocol instead by redirecting "
+ "the playback related commands to the linked AirPlay player in Music Assistant, "
+ "allowing you to mix and match Sonos speakers with AirPlay speakers. \n\n"
+ "NOTE: You need to have the AirPlay provider enabled as well as "
+ "the AirPlay version of this player.",
required=False,
default_value=False,
depends_on="airplay_detected",
sonos_player = self.sonos_players[target_player]
if airplay_player := sonos_player.get_linked_airplay_player(False):
# if airplay mode is enabled, we could possibly receive child player id's that are
- # not Sonos players, but Airplay players. We redirect those.
+ # not Sonos players, but AirPlay players. We redirect those.
airplay_child_ids = [x for x in child_player_ids if x.startswith("ap")]
child_player_ids = [x for x in child_player_ids if x not in airplay_child_ids]
if airplay_child_ids:
warn_unused_ignores = true
[tool.ruff.format]
-# Force Linux/MacOS line endings
+# Force Linux/macOS line endings
line-ending = "lf"
[tool.pytest.ini_options]