diff --git a/.github/workflows/docker-on-push-tag-or-pr.yaml b/.github/workflows/docker-on-push-tag-or-pr.yaml index 7282c2f0..9ebee0a2 100644 --- a/.github/workflows/docker-on-push-tag-or-pr.yaml +++ b/.github/workflows/docker-on-push-tag-or-pr.yaml @@ -73,7 +73,7 @@ jobs: uses: docker/setup-buildx-action@v3.12.0 - name: Build and push ${{ matrix.name }} - uses: docker/build-push-action@v6.18.0 + uses: docker/build-push-action@v6.19.2 env: registry_and_name: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE_NAME }} with: diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml deleted file mode 100644 index 270d5796..00000000 --- a/.github/workflows/docker.yaml +++ /dev/null @@ -1,89 +0,0 @@ -# Builds & pushes a docker image when a tag is created. -# Tag pattern: '[tag]' & '[tag]-classic' -# 'latest' & 'classic' also, when master tagged. - -# Only pushes the tag when it matches one of the following patterns: -# X, X.Y or X.Y.Z - -name: Build and push Docker images... - -on: - workflow_dispatch: - push: - tags: - - '[0-9]+' # X - - '[0-9]+\.[0-9]+' # X.Y - - '[0-9]+\.[0-9]+\.[0-9]+' # X.Y.Z - -env: - DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 - NQPTP_BRANCH: main - LATEST_TAG: false - -jobs: - main: - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 0 - - - name: Set SHAIRPORT_SYNC_BRANCH env. - run: | - raw=$(git branch -r --contains ${{ github.ref }}) - branch=${raw##*/} - echo "SHAIRPORT_SYNC_BRANCH=${branch}" >> $GITHUB_ENV - - - name: Set tag env - run: echo "GIT_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - - - name: Is branch "development"? - if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }} - run: | - echo "NQPTP_BRANCH=development" >> $GITHUB_ENV - - - name: Is branch "danger-2301"? - if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'danger-2301' }} - run: | - echo "NQPTP_BRANCH=development" >> $GITHUB_ENV - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3.6.0 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.10.0 - - - name: Login to Docker Registry - uses: docker/login-action@v3.4.0 - with: - # registry: docker.io # default - username: ${{ secrets.DOCKER_REGISTRY_USER }} - password: ${{ secrets.DOCKER_REGISTRY_TOKEN }} - - - name: Build and push (classic) - uses: docker/build-push-action@v6.15.0 - with: - context: ./ - file: ./docker/classic/Dockerfile - platforms: ${{ env.DOCKER_PLATFORMS }} - push: true - tags: | - ${{ secrets.DOCKER_IMAGE_NAME }}:${{ env.GIT_TAG }}-classic - ${{ env.LATEST_TAG == 'true' && format('{0}:classic', secrets.DOCKER_IMAGE_NAME) || '' }} - build-args: | - SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }} - - - name: Build and Push AirPlay 2 Version - uses: docker/build-push-action@v6.15.0 - with: - context: ./ - file: ./docker/Dockerfile - platforms: ${{ env.DOCKER_PLATFORMS }} - push: true - tags: | - mikebrady/sps-private:${{ env.GIT_TAG }} - mikebrady/sps-private:latest - build-args: | - SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }} - NQPTP_BRANCH=${{ env.NQPTP_BRANCH }} diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index b03e5005..a74358a1 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -11,7 +11,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v10.1.1 + - uses: actions/stale@v10.2.0 with: stale-issue-message: 'This issue has been inactive for 28 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.' stale-pr-message: 'This PR has been inactive for 28 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.' diff --git a/configure.ac b/configure.ac index 14ea54cd..42f16ac6 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [5.0rc0], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [5.0-post-dev], [4265913+mikebrady@users.noreply.github.com]) : ${CFLAGS="-O3"} : ${CXXFLAGS="-O3"} AM_INIT_AUTOMAKE([subdir-objects]) @@ -389,8 +389,6 @@ AC_ARG_WITH(mqtt-client, [AS_HELP_STRING([--with-mqtt-client],[include a client if test "x$with_mqtt_client" = "xyes" ; then AC_DEFINE([CONFIG_MQTT], 1, [Include a client for MQTT, the Message Queuing Telemetry Transport protocol]) AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], , AC_MSG_ERROR(MQTT support requires the mosquitto library -- libmosquitto-dev suggested!)) - systemd_after_args="${systemd_after_args} mosquitto.service" - systemd_requires_args="${systemd_requires_args} mosquitto.service" fi AM_CONDITIONAL([USE_MQTT], [test "x$with_mqtt_client" = "xyes"]) diff --git a/dacp.c b/dacp.c index a849c8a6..aeb3ca1f 100644 --- a/dacp.c +++ b/dacp.c @@ -474,7 +474,7 @@ void set_dacp_server_information(rtsp_conn_info *conn) { debug_mutex_unlock(&dacp_server_information_lock, 3); } -void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) { +void dacp_monitor_port_update_callback(const char *dacp_id, uint16_t port) { debug_mutex_lock(&dacp_server_information_lock, 500000, 2); debug(3, "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port " diff --git a/dacp.h b/dacp.h index ea44aacb..8b5dc876 100644 --- a/dacp.h +++ b/dacp.h @@ -31,7 +31,7 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn); // tell the DACP // that the player thread is no // longer associated with it. void dacp_monitor_port_update_callback( - char *dacp_id, uint16_t port); // a callback to say the port is no longer in use + const char *dacp_id, uint16_t port); // a callback to say the port is no longer in use int dacp_send_command(const char *command, char **body, ssize_t *bodysize); int send_simple_dacp_command(const char *command); diff --git a/documents/D-Bus.md b/documents/D-Bus.md new file mode 100644 index 00000000..012e4f65 --- /dev/null +++ b/documents/D-Bus.md @@ -0,0 +1,174 @@ +## Shairport Sync D-Bus Interface + +Shairport Sync can have a D-Bus interface, which can be used to control aspects of its operation and get status information from it. + +To include the D-Bus interface at build time, add the `--with-dbus-interface` flag at the `./configure…` stage. When the D-Bus interface is included in Shairport Sync, its Version String will include the term `dbus`, for instance: +``` +$ shairport-sync -V +5.0.0-1-gc956d763-AirPlay2-smi10-OpenSSL-Avahi-ALSA-soxr-metadata-dbus-sysconfdir:/etc + +``` +Once Shairport Sync has been built with the D-Bus interface, it must then be installed correctly to provide a Shairport Sync service either on the D-Bus `system` bus or the D Bus `session` bus. +To become available as a system-wide service on the D-Bus `system` bus, it must be installed as a system service using the `# make install` step of the build process. This installs the appropriate service files and sets required permissions. +Remember to enable Shairport Sync to start as a system service. You may also need to restart the entire system to allow the service to be seen. + +If Shairport Sync is installed as a user service, it will not be able to become a service on the D-Bus `system` bus, but it can be added to the D-Bus `session` bus. Edit the configuration file to select the `session` bus, or add the option `--dbus_default_message_bus=session` to the command line. + +### D-Feet +On desktop Linuxes with a GUI, e.g. Ubuntu, D-Feet is a great tool for examining a D-Bus service: + +![Shairport Sync and DFeet](https://github.com/user-attachments/assets/613dab83-9b02-4312-9ad1-c94db1d0ef49) + +### Sample Test Client + +A simple test client, written in C, can be built when you are building Shairport Sync itself. To build it, simply add the `--with-dbus-test-client` flag at the `./configure…` stage. Along with the `shairport-sync` executable application, you'll get another executable called `shairport-sync-dbus-test-client` which you can run from the command line. + +After attempting to send some commands, it will listen for property changes on the D-Bus interface and report them on the console. + +### Command Line Examples + +The examples below are based on Shairport Sync running as a `system` service and use the standard CLI tool `dbus-send`: +* Get `Active` Status -- `true` when Shairport Sync is playing (and for a short time later); `false` otherwise. +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:Active +``` +* Get Log Verbosity +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.Diagnostics string:Verbosity +``` +* Return Log Verbosity +``` + $ echo `dbus-send --print-reply=literal --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.Diagnostics string:Verbosity` +``` +* Set Log Verbosity to 2 +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync.Diagnostics string:Verbosity variant:int32:2 +``` +* Get Statistics-Requested Status +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.Diagnostics string:Statistics +``` +* Set Statistics-Requested Status to `true` +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync.Diagnostics string:Statistics variant:boolean:true +``` +* Are File Name and Line Number included in Log Entries? +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.Diagnostics string:FileAndLine +``` +* Include File Name and Line Number in Log Entries +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync.Diagnostics string:FileAndLine variant:boolean:true +``` +* Is Elapsed Time included in Log Entries? +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.Diagnostics string:ElapsedTime +``` +* Include Elapsed Time in Log Entries +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync.Diagnostics string:ElapsedTime variant:boolean:true +``` +* Get Drift Tolerance +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:DriftTolerance +``` +* Set Drift Tolerance to 1 millisecond +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:DriftTolerance variant:double:0.001 +``` +* Is Loudness enabled: +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:LoudnessEnabled +``` +* Enable Loudness Filter +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:LoudnessEnabled variant:boolean:true +``` +* Get Loudness Threshold +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:LoudnessThreshold +``` +* Set Loudness Threshold +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:LoudnessThreshold variant:double:-15.0 +``` +* Is Convolution enabled: +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:ConvolutionEnabled +``` +* Enable Convolution +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:ConvolutionEnabled variant:boolean:true +``` +* Get Convolution Gain: +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:ConvolutionGain +``` +* Set Convolution Gain -- the gain applied after convolution is applied -- to -10.0 dB +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:ConvolutionGain variant:double:-10 +``` +* Get Convolution Impulse Response Files: +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:ConvolutionImpulseResponseFiles +``` +* Set Convolution Impulse Response Files: +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:ConvolutionImpulseResponseFiles variant:string:"'/home/pi/filters/Sennheiser HD 205 minimum phase 48000Hz.wav', '/home/pi/filters/Sennheiser HD 205 minimum phase 44100Hz.wav'" +``` +* Get Convolution Impulse Response File Maximum Length: +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:ConvolutionMaximumLengthInSeconds +``` +* Set Convolution Impulse Response File Maximum Length: +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:ConvolutionMaximumLengthInSeconds variant:double:1 +``` +* Get the Protocol that Shairport Sync was built for -- AirPlay or AirPlay 2: +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:Protocol +``` +* Get the current volume level. This returns a value between -30.0 and 0.0, which is linear on the UI volume control. It can also return -144.0 to indicate mute. +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:Volume +``` +* Set the current volume level. This should be a value between -30.0 and 0.0. Set a value of -144.0 to mute. Note that all this is done locally on the Shairport Sync device itself. The volume control of audio source (e.g. iTunes / macOS Music / iOS) is not updated to reflect any changes. +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:Volume variant:double:-10.0 +``` +* Drop the current session immediately. Shairport Sync will immediately stop playing and drop the connection from the audio source. This may show up as an error at the source. +``` + $ dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.DropSession +``` +#### Remote Control +Remote Control commands are sent as requests to the player (iOS, iTunes, macOS Music, etc.). Different versions of the players implement different subsets of the following commands. + +**Note:** Unfortunately, at this time -- early 2026 -- these requests are ignored, so remote control doesn't work. + +* Check if Remote Control is available: +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.RemoteControl string:Available +``` +* Send the `play` command to the player: +``` + $ dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.RemoteControl.Play +``` + Remote Control commands include: `Play`, `Pause`, `PlayPause`, `Resume`, `Stop`, `Next`, `Previous`, `VolumeUp`, `VolumeDown`, `ToggleMute`, `FastForward`, `Rewind`, `ShuffleSongs`. + +* Set Airplay Volume using Remote Control. Airplay Volume is between -30.0 and 0.0 and maps linearly onto the slider, with -30.0 being lowest and 0.0 being highest. +``` + $ dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.RemoteControl.SetAirplayVolume double:-10.0 +``` +#### Advanced Remote Control +Some commands and properties are accessible only through the `AdvancedRemoteControl` interface. + +**Note:** Unfortunately, at this time -- early 2026 -- these requests are ignored, so advanced remote control doesn't work. + +* Check if Advanced Remote Control is available: +``` + $ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.AdvancedRemoteControl string:Available +``` +* Set Volume using Advanced Remote Control: +``` + $ dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50 +``` diff --git a/documents/sample dbus commands b/documents/sample dbus commands index e1941005..c5c435a5 100644 --- a/documents/sample dbus commands +++ b/documents/sample dbus commands @@ -1,10 +1,14 @@ -# For the "Native" Shairport Sync D-Bus Interface support, Shairport Sync must be built with the D-Bus interface support. Add the '--with-dbus-interface' flag at the ./configure stage. +# For the "Native" Shairport Sync D-Bus Interface support, Shairport Sync must be built with the D-Bus interface support -- to do this, add the '--with-dbus-interface' flag at the ./configure stage. +# Secondly, for the Shairport Sync interface to become available on the D-Bus system bus, Shairport Sync must be installed and running as a system service. The example below assume that it is installed and running as a system service. # There is a simple test client that you can build -- add the '--with-dbus-test-client' flag at the ./configure stage. You'll get an executable called shairport-sync-dbus-test-client # After attempting to send some commands, it will listen for property changes on the D-Bus interface and report them on the console. # Additionally, the following are examples of using the CLI command "dbus-send" to send D-Bus commands to Shairport Sync: +# Get "Active" Status -- true when Shairport Sync is playing (and for a short time later); false otherwise. +dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:Active + # Get Log Verbosity dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.Diagnostics string:Verbosity # Return Log Verbosity diff --git a/mdns_avahi.c b/mdns_avahi.c index 29c360f6..cf45ba23 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -109,7 +109,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn // char a[AVAHI_ADDRESS_STR_MAX], *t; debug(3, "resolve_callback: Service '%s' of type '%s' in domain '%s':", name, type, domain); if (dbs->dacp_id) { - char *dacpid = strstr(name, "iTunes_Ctrl_"); + const char *dacpid = strstr(name, "iTunes_Ctrl_"); if (dacpid) { dacpid += strlen("iTunes_Ctrl_"); while (*dacpid == '0') diff --git a/mqtt.c b/mqtt.c index 14178fd3..64cc1aa9 100644 --- a/mqtt.c +++ b/mqtt.c @@ -198,8 +198,10 @@ void send_autodiscovery_messages(struct mosquitto *mosq) { (strcmp(sensors[i], "active") == 0 || strcmp(sensors[i], "playing") == 0); bool is_volume_sensor = strcmp(sensors[i], "volume") == 0; - snprintf(topic, sizeof(topic), "%s/%ssensor/%s_%s/%s/config", autodiscovery_prefix, - is_binary_sensor ? "binary_" : "", model, device_id_no_colons, sensors[i]); + const char* entity_type = is_binary_sensor ? "binary_sensor" : "sensor"; + + snprintf(topic, sizeof(topic), "%s/%s/%s_%s/%s/config", autodiscovery_prefix, + entity_type, model, device_id_no_colons, sensors[i]); snprintf(id_string, sizeof(id_string), "%s_%s_%s", model, device_name, sensors[i]); @@ -211,9 +213,18 @@ void send_autodiscovery_messages(struct mosquitto *mosq) { "\"icon\": \"%s\"," "\"unique_id\": \"%s\"," "\"object_id\": \"%s\"," + // As of Home Assistant 2025.10, `default_entity_id` replaces `object_id`. + // Home Assistant 2026.4 will remove support for `object_id`, + // so we add both for backward compatibility. + "\"default_entity_id\": \"%s.%s\"," "%s%s%s" "}", - sensor_names[i], config.mqtt_topic, sensors[i], icons[i], id_string, id_string, + sensor_names[i], // name + config.mqtt_topic, sensors[i], //state_topic + icons[i], // icon + id_string, // unique_id + id_string, // object_id + entity_type, id_string, // default_entity_id is_binary_sensor ? "\"payload_on\": \"1\",\"payload_off\": \"0\"," : "", is_volume_sensor ? "\"value_template\": \"{{ ((value | regex_findall_index("