]> git.ipfire.org Git - thirdparty/shairport-sync.git/commitdiff
Add SetAirplayVolume to the D-Bus RemoteControl interface.
authorMike Brady <mikebrady@eircom.net>
Fri, 20 Dec 2019 17:17:21 +0000 (17:17 +0000)
committerMike Brady <mikebrady@eircom.net>
Fri, 20 Dec 2019 17:17:21 +0000 (17:17 +0000)
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.

dacp.c
dacp.h
dbus-service.c
documents/sample dbus commands
metadata_hub.h
mpris-service.c
org.gnome.ShairportSync.xml
org.mpris.MediaPlayer2.xml

diff --git a/dacp.c b/dacp.c
index 79447afb0e9fcb6dd0a9a6b45767263ced9d0d0c..7797f7947e3e52692e145ac362599316be7c7b46 100644 (file)
--- 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 ce1e127842311755871cdf3a22b3e0a8fb4cfaa8..ea44aacb8431a00e00c6a6218c95dfa6d4365700 100644 (file)
--- 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);
index 0c85a98171a3f909bc60a3efd147cab835beaba9..3ebd88f7be1ffa7dccdbe8abbddc7c2d14b5aca9 100644 (file)
@@ -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);
index 5b173d4a97463188092600f023c0ca495628c069..96219c195d0d0f25d20e2c6872eb0128e7c579cc 100644 (file)
@@ -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
index 5b565bbad53fb64fbdbc0ac991c55d4e03a97313..ed7d947d2a1fa804ba423d17b1ce29db5df6ad19 100644 (file)
@@ -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
index 37e7bf8a5ae5abf92c2c00fe3eed84d48a252128..707c063cf7293865b9ef70107d41774b03bdfa83 100644 (file)
 #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);
 
index 15317faa02ff663435fa91ce59ca425f45fe2607..91c3167168b43affb281f9a741bf804938ea60db 100644 (file)
@@ -13,6 +13,8 @@
     <property name="DriftTolerance" type="d" access="readwrite" />
     <method name="RemoteCommand">
       <arg name="command" type="s" direction="in" />
+      <arg name="reply" type="i" direction="out" />      
+      <arg name="response" type="s" direction="out" />
     </method>
     <property name="VolumeControlProfile" type="s" access="readwrite" />
     <property name="Interpolation" type="s" access="readwrite" />
@@ -45,6 +47,9 @@
                <property name='ProgressString' type='s' access='read'/>
                <property name='Server' type='s' access='read'/>
                <property name='AirplayVolume' type='d' access='read'/>
+    <method name="SetAirplayVolume">
+      <arg name="volume" type="d" direction="in" />
+    </method>
                <property name='Metadata' type='a{sv}' access='read'>
                        <annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
                </property>
index ae34e1e51f8b3f0a21f28caa006bc6d2f9e73347..64ee2cca1242a983fce5136bf2f6490d4820a369 100755 (executable)
@@ -41,6 +41,9 @@
                        <annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
                </property>
                <property name='Volume' type='d' access='read'/>
+               <method name='SetVolume'>
+      <arg name='volume' type='d' direction='in' />
+    </method>
                <property name='Position' type='x' access='read'/>
                <property name='MinimumRate' type='d' access='read'/>
                <property name='MaximumRate' type='d' access='read'/>