From: Mike Brady Date: Fri, 20 Dec 2019 17:17:21 +0000 (+0000) Subject: Add SetAirplayVolume to the D-Bus RemoteControl interface. X-Git-Tag: 3.3.7d12~172 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fe8198a23b1083272dbf3dbf3e06b41e7b3ed123;p=thirdparty%2Fshairport-sync.git Add SetAirplayVolume to the D-Bus RemoteControl interface. Add SetVolume to the MPRIS interface. Hook up the Volume property in the MPRIS interface. Modify RemoteCommand in the D-Bus interface to return the HTTP status and response. Change the type of airplay_volume from int to double in the metadata hub. Add a few sample commands in the D-Bus document. --- diff --git a/dacp.c b/dacp.c index 79447afb..7797f794 100644 --- a/dacp.c +++ b/dacp.c @@ -592,6 +592,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command); result = dacp_send_command(command, &response, &le); // debug(1,"Response to \"%s\" is %d.",command,result); + // remember: unless the revision_number you pass in is 1, + // response will be 200 only if there's something new to report. if (result == 200) { // if (0) { char *sp = response; diff --git a/dacp.h b/dacp.h index ce1e1278..ea44aacb 100644 --- a/dacp.h +++ b/dacp.h @@ -32,6 +32,8 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn); // tell the DACP // 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 + +int dacp_send_command(const char *command, char **body, ssize_t *bodysize); int send_simple_dacp_command(const char *command); int dacp_set_include_speaker_volume(int64_t machine_number, int32_t vo); diff --git a/dbus-service.c b/dbus-service.c index 0c85a981..3ebd88f7 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -337,6 +337,19 @@ static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton, return TRUE; } +static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleton, + GDBusMethodInvocation *invocation, const gdouble volume, + __attribute__((unused)) gpointer user_data) { + debug(2, "Set airplay volume to %.6f.", volume); + char command[256] = ""; + snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", volume); + send_simple_dacp_command(command); + shairport_sync_remote_control_complete_set_airplay_volume(skeleton, invocation); + return TRUE; +} + + + gboolean notify_elapsed_time_callback(ShairportSyncDiagnostics *skeleton, __attribute__((unused)) gpointer user_data) { // debug(1, "\"notify_elapsed_time_callback\" called."); @@ -714,12 +727,30 @@ static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *i static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation, const gchar *command, __attribute__((unused)) gpointer user_data) { - debug(1, "RemoteCommand with command \"%s\".", command); - send_simple_dacp_command((const char *)command); - shairport_sync_complete_remote_command(skeleton, invocation); + debug(1, "RemoteCommand with command \"%s\".", command); + int reply = 0; + char *server_reply = NULL; + ssize_t reply_size = 0; + reply = dacp_send_command((const char *)command, &server_reply, &reply_size); + char *server_reply_hex = alloca(reply_size * 2 + 1); + if (server_reply_hex) { + char *p = server_reply_hex; + if (server_reply) { + char *q = server_reply; + int i; + for (i = 0; i < reply_size; i++) { + snprintf(p, 3, "%02X", *q); + p += 2; + q++; + } + } + *p = '\0'; + } + shairport_sync_complete_remote_command(skeleton, invocation, reply, server_reply_hex); return TRUE; } + static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name, __attribute__((unused)) gpointer user_data) { @@ -814,6 +845,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name G_CALLBACK(on_handle_volume_up), NULL); g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-volume-down", G_CALLBACK(on_handle_volume_down), NULL); + g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-set-airplay-volume", + G_CALLBACK(on_handle_set_airplay_volume), NULL); + g_signal_connect(shairportSyncAdvancedRemoteControlSkeleton, "handle-set-volume", G_CALLBACK(on_handle_set_volume), NULL); diff --git a/documents/sample dbus commands b/documents/sample dbus commands index 5b173d4a..96219c19 100644 --- a/documents/sample dbus commands +++ b/documents/sample dbus commands @@ -1,3 +1,6 @@ +# 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. +# There is a simple test client that you can have built -- add the '--with-dbus-test-client' flag at the ./configure stage. You'll get an executable called shairport-sync-dbus-test-client + # 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 @@ -59,6 +62,11 @@ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/Shair # Set Convolution Impulse Response File: dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:ConvolutionImpulseResponseFile variant:string:"/etc/shairport-sync/boom.wav" +# 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 + + +# AdvancedRemoteControl interface. # Some commands and properties are accessible only through the AdvancedRemoteControl interface. # However, only iTunes or the macOS Music app from 10.15.2 onwards # provide the functionality needed for the AdvancedRemoteControl interface to work. @@ -70,3 +78,10 @@ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/Shair # Set Volume using Advanced Remote Control -- only works if the org.gnome.ShairportSync.AdvancedRemoteControl is available. dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50 + +# MPRIS interface commands. +# For MPRIS support, Shairport Sync must be built with the MPRIS interface support. Add the '--with-mpris-interface' flag at the ./configure stage. +# There is a simple test client that you can have built -- add the '--with-mpris-test-client' flag at the ./configure stage. You'll get an executable called shairport-sync-mpris-test-client. +# This is mostly compatible with the MPRIS standard, except that Volume is read-only, with a separate SetVolume method. +# Set Volume, which must be between 0.0 and 1.0 and maps linearly onto the slider. +dbus-send --system --print-reply --type=method_call --dest=org.mpris.MediaPlayer2.ShairportSync '/org/mpris/MediaPlayer2' org.mpris.MediaPlayer2.Player.SetVolume double:0.3 diff --git a/metadata_hub.h b/metadata_hub.h index 5b565bba..ed7d947d 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -129,7 +129,7 @@ typedef struct metadata_bundle { int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the // speaker volume control - int airplay_volume; + double airplay_volume; metadata_watcher watchers[number_of_watchers]; // functions to call if the metadata is changed. void *watchers_data[number_of_watchers]; // their individual data diff --git a/mpris-service.c b/mpris-service.c index 37e7bf8a..707c063c 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -14,10 +14,28 @@ #include "metadata_hub.h" #include "mpris-service.h" +double airplay_volume_to_mpris_volume(double sp) { + if (sp < -30.0) + sp = -30.0; + if (sp > 0.0) + sp = 0.0; + sp = (sp/30.0)+1; + return sp; +} + +double mpris_volume_to_airplay_volume(double sp) { + sp = (sp-1.0)*30.0; + if (sp < -30.0) + sp = -30.0; + if (sp > 0.0) + sp = 0.0; + return sp; +} + void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) { // debug(1, "MPRIS metadata watcher called"); char response[100]; - + media_player2_player_set_volume(mprisPlayerPlayerSkeleton, airplay_volume_to_mpris_volume(argc->airplay_volume)); switch (argc->repeat_status) { case RS_NOT_AVAILABLE: strcpy(response, "Not Available"); @@ -229,6 +247,18 @@ static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocati return TRUE; } +static gboolean on_handle_set_volume(MediaPlayer2Player *skeleton, + GDBusMethodInvocation *invocation, const gdouble volume, + __attribute__((unused)) gpointer user_data) { + double ap_volume = mpris_volume_to_airplay_volume(volume); + debug(2, "Set mpris volume to %.6f, i.e. airplay volume to %.6f.", volume, ap_volume); + char command[256] = ""; + snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", ap_volume); + send_simple_dacp_command(command); + media_player2_player_complete_play(skeleton, invocation); + return TRUE; +} + static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *name, __attribute__((unused)) gpointer user_data) { @@ -254,7 +284,6 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, "Stopped"); media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, "None"); - media_player2_player_set_volume(mprisPlayerPlayerSkeleton, 0.5); media_player2_player_set_minimum_rate(mprisPlayerPlayerSkeleton, 1.0); media_player2_player_set_maximum_rate(mprisPlayerPlayerSkeleton, 1.0); media_player2_player_set_can_go_next(mprisPlayerPlayerSkeleton, TRUE); @@ -274,6 +303,9 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam g_signal_connect(mprisPlayerPlayerSkeleton, "handle-next", G_CALLBACK(on_handle_next), NULL); g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous), NULL); + g_signal_connect(mprisPlayerPlayerSkeleton, "handle-set-volume", G_CALLBACK(on_handle_set_volume), + NULL); + add_metadata_watcher(mpris_metadata_watcher, NULL); diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml index 15317faa..91c31671 100644 --- a/org.gnome.ShairportSync.xml +++ b/org.gnome.ShairportSync.xml @@ -13,6 +13,8 @@ + + @@ -45,6 +47,9 @@ + + + diff --git a/org.mpris.MediaPlayer2.xml b/org.mpris.MediaPlayer2.xml index ae34e1e5..64ee2cca 100755 --- a/org.mpris.MediaPlayer2.xml +++ b/org.mpris.MediaPlayer2.xml @@ -41,6 +41,9 @@ + + +