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.
This commit is contained in:
@@ -592,6 +592,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
|
|||||||
// debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
|
// debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
|
||||||
result = dacp_send_command(command, &response, &le);
|
result = dacp_send_command(command, &response, &le);
|
||||||
// debug(1,"Response to \"%s\" is %d.",command,result);
|
// 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 (result == 200) {
|
||||||
// if (0) {
|
// if (0) {
|
||||||
char *sp = response;
|
char *sp = response;
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn); // tell the DACP
|
|||||||
// longer associated with it.
|
// longer associated with it.
|
||||||
void dacp_monitor_port_update_callback(
|
void dacp_monitor_port_update_callback(
|
||||||
char *dacp_id, uint16_t port); // a callback to say the port is no longer in use
|
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 send_simple_dacp_command(const char *command);
|
||||||
|
|
||||||
int dacp_set_include_speaker_volume(int64_t machine_number, int32_t vo);
|
int dacp_set_include_speaker_volume(int64_t machine_number, int32_t vo);
|
||||||
|
|||||||
+36
-2
@@ -337,6 +337,19 @@ static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton,
|
|||||||
return TRUE;
|
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,
|
gboolean notify_elapsed_time_callback(ShairportSyncDiagnostics *skeleton,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
// debug(1, "\"notify_elapsed_time_callback\" called.");
|
// debug(1, "\"notify_elapsed_time_callback\" called.");
|
||||||
@@ -715,11 +728,29 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
|
|||||||
const gchar *command,
|
const gchar *command,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
debug(1, "RemoteCommand with command \"%s\".", command);
|
debug(1, "RemoteCommand with command \"%s\".", command);
|
||||||
send_simple_dacp_command((const char *)command);
|
int reply = 0;
|
||||||
shairport_sync_complete_remote_command(skeleton, invocation);
|
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;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name,
|
static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__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_CALLBACK(on_handle_volume_up), NULL);
|
||||||
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-volume-down",
|
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-volume-down",
|
||||||
G_CALLBACK(on_handle_volume_down), NULL);
|
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_signal_connect(shairportSyncAdvancedRemoteControlSkeleton, "handle-set-volume",
|
||||||
G_CALLBACK(on_handle_set_volume), NULL);
|
G_CALLBACK(on_handle_set_volume), NULL);
|
||||||
|
|||||||
@@ -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
|
# 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
|
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
|
# Return Log Verbosity
|
||||||
@@ -59,6 +62,11 @@ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/Shair
|
|||||||
# Set Convolution Impulse Response File:
|
# 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"
|
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.
|
# Some commands and properties are accessible only through the AdvancedRemoteControl interface.
|
||||||
# However, only iTunes or the macOS Music app from 10.15.2 onwards
|
# However, only iTunes or the macOS Music app from 10.15.2 onwards
|
||||||
# provide the functionality needed for the AdvancedRemoteControl interface to work.
|
# 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.
|
# 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
|
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
|
||||||
|
|||||||
+1
-1
@@ -129,7 +129,7 @@ typedef struct metadata_bundle {
|
|||||||
|
|
||||||
int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the
|
int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the
|
||||||
// speaker volume control
|
// speaker volume control
|
||||||
int airplay_volume;
|
double airplay_volume;
|
||||||
|
|
||||||
metadata_watcher watchers[number_of_watchers]; // functions to call if the metadata is changed.
|
metadata_watcher watchers[number_of_watchers]; // functions to call if the metadata is changed.
|
||||||
void *watchers_data[number_of_watchers]; // their individual data
|
void *watchers_data[number_of_watchers]; // their individual data
|
||||||
|
|||||||
+34
-2
@@ -14,10 +14,28 @@
|
|||||||
#include "metadata_hub.h"
|
#include "metadata_hub.h"
|
||||||
#include "mpris-service.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) {
|
void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
|
||||||
// debug(1, "MPRIS metadata watcher called");
|
// debug(1, "MPRIS metadata watcher called");
|
||||||
char response[100];
|
char response[100];
|
||||||
|
media_player2_player_set_volume(mprisPlayerPlayerSkeleton, airplay_volume_to_mpris_volume(argc->airplay_volume));
|
||||||
switch (argc->repeat_status) {
|
switch (argc->repeat_status) {
|
||||||
case RS_NOT_AVAILABLE:
|
case RS_NOT_AVAILABLE:
|
||||||
strcpy(response, "Not Available");
|
strcpy(response, "Not Available");
|
||||||
@@ -229,6 +247,18 @@ static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocati
|
|||||||
return TRUE;
|
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,
|
static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *name,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__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_playback_status(mprisPlayerPlayerSkeleton, "Stopped");
|
||||||
media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, "None");
|
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_minimum_rate(mprisPlayerPlayerSkeleton, 1.0);
|
||||||
media_player2_player_set_maximum_rate(mprisPlayerPlayerSkeleton, 1.0);
|
media_player2_player_set_maximum_rate(mprisPlayerPlayerSkeleton, 1.0);
|
||||||
media_player2_player_set_can_go_next(mprisPlayerPlayerSkeleton, TRUE);
|
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-next", G_CALLBACK(on_handle_next), NULL);
|
||||||
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous),
|
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous),
|
||||||
NULL);
|
NULL);
|
||||||
|
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-set-volume", G_CALLBACK(on_handle_set_volume),
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
|
||||||
add_metadata_watcher(mpris_metadata_watcher, NULL);
|
add_metadata_watcher(mpris_metadata_watcher, NULL);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
<property name="DriftTolerance" type="d" access="readwrite" />
|
<property name="DriftTolerance" type="d" access="readwrite" />
|
||||||
<method name="RemoteCommand">
|
<method name="RemoteCommand">
|
||||||
<arg name="command" type="s" direction="in" />
|
<arg name="command" type="s" direction="in" />
|
||||||
|
<arg name="reply" type="i" direction="out" />
|
||||||
|
<arg name="response" type="s" direction="out" />
|
||||||
</method>
|
</method>
|
||||||
<property name="VolumeControlProfile" type="s" access="readwrite" />
|
<property name="VolumeControlProfile" type="s" access="readwrite" />
|
||||||
<property name="Interpolation" type="s" access="readwrite" />
|
<property name="Interpolation" type="s" access="readwrite" />
|
||||||
@@ -45,6 +47,9 @@
|
|||||||
<property name='ProgressString' type='s' access='read'/>
|
<property name='ProgressString' type='s' access='read'/>
|
||||||
<property name='Server' type='s' access='read'/>
|
<property name='Server' type='s' access='read'/>
|
||||||
<property name='AirplayVolume' type='d' 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'>
|
<property name='Metadata' type='a{sv}' access='read'>
|
||||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
|
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
|
||||||
</property>
|
</property>
|
||||||
|
|||||||
@@ -41,6 +41,9 @@
|
|||||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
|
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
|
||||||
</property>
|
</property>
|
||||||
<property name='Volume' type='d' access='read'/>
|
<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='Position' type='x' access='read'/>
|
||||||
<property name='MinimumRate' type='d' access='read'/>
|
<property name='MinimumRate' type='d' access='read'/>
|
||||||
<property name='MaximumRate' type='d' access='read'/>
|
<property name='MaximumRate' type='d' access='read'/>
|
||||||
|
|||||||
Reference in New Issue
Block a user