1 /*
2  * This file is part of Shairport Sync.
3  * Copyright (c) Mike Brady 2018 -- 2020
4  * All rights reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person
7  * obtaining a copy of this software and associated documentation
8  * files (the "Software"), to deal in the Software without
9  * restriction, including without limitation the rights to use,
10  * copy, modify, merge, publish, distribute, sublicense, and/or
11  * sell copies of the Software, and to permit persons to whom the
12  * Software is furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24  * OTHER DEALINGS IN THE SOFTWARE.
25  */
26 #include <inttypes.h>
27 #include <stdio.h>
28 #include <string.h>
29 
30 #include "config.h"
31 
32 #include "common.h"
33 #include "player.h"
34 #include "rtsp.h"
35 
36 #include "rtp.h"
37 
38 #include "dacp.h"
39 
40 #include "metadata_hub.h"
41 #include "mpris-service.h"
42 
43 MediaPlayer2 *mprisPlayerSkeleton;
44 MediaPlayer2Player *mprisPlayerPlayerSkeleton;
45 
airplay_volume_to_mpris_volume(double sp)46 double airplay_volume_to_mpris_volume(double sp) {
47   if (sp < -30.0)
48     sp = -30.0;
49   if (sp > 0.0)
50     sp = 0.0;
51   sp = (sp / 30.0) + 1;
52   return sp;
53 }
54 
mpris_volume_to_airplay_volume(double sp)55 double mpris_volume_to_airplay_volume(double sp) {
56   sp = (sp - 1.0) * 30.0;
57   if (sp < -30.0)
58     sp = -30.0;
59   if (sp > 0.0)
60     sp = 0.0;
61   return sp;
62 }
63 
mpris_metadata_watcher(struct metadata_bundle * argc,void * userdata)64 void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
65   // debug(1, "MPRIS metadata watcher called");
66   char response[100];
67   media_player2_player_set_volume(mprisPlayerPlayerSkeleton,
68                                   airplay_volume_to_mpris_volume(argc->airplay_volume));
69   switch (argc->repeat_status) {
70   case RS_NOT_AVAILABLE:
71     strcpy(response, "Not Available");
72     break;
73   case RS_OFF:
74     strcpy(response, "None");
75     break;
76   case RS_ONE:
77     strcpy(response, "Track");
78     break;
79   case RS_ALL:
80     strcpy(response, "Playlist");
81     break;
82   }
83 
84   media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, response);
85 
86   switch (argc->player_state) {
87   case PS_NOT_AVAILABLE:
88     strcpy(response, "Not Available");
89     break;
90   case PS_STOPPED:
91     strcpy(response, "Stopped");
92     break;
93   case PS_PAUSED:
94     strcpy(response, "Paused");
95     break;
96   case PS_PLAYING:
97     strcpy(response, "Playing");
98     break;
99   }
100 
101   media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, response);
102 
103   /*
104     switch (argc->shuffle_state) {
105     case SS_NOT_AVAILABLE:
106       strcpy(response, "Not Available");
107       break;
108     case SS_OFF:
109       strcpy(response, "Off");
110       break;
111     case SS_ON:
112       strcpy(response, "On");
113       break;
114     }
115 
116      media_player2_player_set_shuffle_status(mprisPlayerPlayerSkeleton, response);
117   */
118 
119   switch (argc->shuffle_status) {
120   case SS_NOT_AVAILABLE:
121     media_player2_player_set_shuffle(mprisPlayerPlayerSkeleton, FALSE);
122     break;
123   case SS_OFF:
124     media_player2_player_set_shuffle(mprisPlayerPlayerSkeleton, FALSE);
125     break;
126   case SS_ON:
127     media_player2_player_set_shuffle(mprisPlayerPlayerSkeleton, TRUE);
128     break;
129   default:
130     debug(1, "This should never happen.");
131   }
132 
133   /*
134     // Add the TrackID if we have one
135     // Build the Track ID from the 16-byte item_composite_id in hex prefixed by
136     // /org/gnome/ShairportSync
137     char st[33];
138     char *pt = st;
139     int it;
140     int non_zero = 0;
141     for (it = 0; it < 16; it++) {
142       if (argc->track_metadata->item_composite_id[it])
143         non_zero = 1;
144       snprintf(pt, 3, "%02X", argc->track_metadata->item_composite_id[it]);
145       pt += 2;
146     }
147     *pt = 0;
148 
149     if (non_zero) {
150       // debug(1, "Set ID using composite ID: \"0x%s\".", st);
151       char trackidstring[1024];
152       snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%s", st);
153       GVariant *trackid = g_variant_new("o", trackidstring);
154       g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
155     } else if ((argc->track_metadata) && (argc->track_metadata->item_id)) {
156       char trackidstring[128];
157       // debug(1, "Set ID using mper ID: \"%u\".",argc->item_id);
158       snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
159                argc->track_metadata->item_id);
160       GVariant *trackid = g_variant_new("o", trackidstring);
161       g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
162     }
163 
164   */
165 
166   // Build the metadata array
167   debug(2, "Build metadata");
168   GVariantBuilder *dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
169 
170   // Add in the artwork URI if it exists.
171   if (argc->cover_art_pathname) {
172     GVariant *artUrl = g_variant_new("s", argc->cover_art_pathname);
173     g_variant_builder_add(dict_builder, "{sv}", "mpris:artUrl", artUrl);
174   }
175 
176   // Add in the Track ID based on the 'mper' metadata if it is non-zero
177   if (argc->item_id != 0) {
178     char trackidstring[128];
179     snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "",
180              argc->item_id);
181     GVariant *trackid = g_variant_new("o", trackidstring);
182     g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
183   }
184 
185   // Add the track name if it exists
186   if (argc->track_name) {
187     GVariant *track_name = g_variant_new("s", argc->track_name);
188     g_variant_builder_add(dict_builder, "{sv}", "xesam:title", track_name);
189   }
190 
191   // Add the album name if it exists
192   if (argc->album_name) {
193     GVariant *album_name = g_variant_new("s", argc->album_name);
194     g_variant_builder_add(dict_builder, "{sv}", "xesam:album", album_name);
195   }
196 
197   // Add the artist name if it exists
198   if (argc->artist_name) {
199     GVariantBuilder *artist_as = g_variant_builder_new(G_VARIANT_TYPE("as"));
200     g_variant_builder_add(artist_as, "s", argc->artist_name);
201     GVariant *artists = g_variant_builder_end(artist_as);
202     g_variant_builder_unref(artist_as);
203     g_variant_builder_add(dict_builder, "{sv}", "xesam:artist", artists);
204   }
205 
206   // Add the genre if it exists
207   if (argc->genre) {
208     GVariantBuilder *genre_as = g_variant_builder_new(G_VARIANT_TYPE("as"));
209     g_variant_builder_add(genre_as, "s", argc->genre);
210     GVariant *genre = g_variant_builder_end(genre_as);
211     g_variant_builder_unref(genre_as);
212     g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genre);
213   }
214 
215   if (argc->songtime_in_milliseconds) {
216     uint64_t track_length_in_microseconds = argc->songtime_in_milliseconds;
217     track_length_in_microseconds *= 1000; // to microseconds in 64-bit precision
218                                           // Make up the track name and album name
219     // debug(1, "Set tracklength to %lu.", track_length_in_microseconds);
220     GVariant *tracklength = g_variant_new("x", track_length_in_microseconds);
221     g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength);
222   }
223 
224   GVariant *dict = g_variant_builder_end(dict_builder);
225   g_variant_builder_unref(dict_builder);
226   media_player2_player_set_metadata(mprisPlayerPlayerSkeleton, dict);
227 }
228 
on_handle_quit(MediaPlayer2 * skeleton,GDBusMethodInvocation * invocation,gpointer user_data)229 static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation,
230                                __attribute__((unused)) gpointer user_data) {
231   debug(1, "quit requested (MPRIS interface).");
232   pthread_cancel(main_thread_id);
233   media_player2_complete_quit(skeleton, invocation);
234   return TRUE;
235 }
236 
on_handle_next(MediaPlayer2Player * skeleton,GDBusMethodInvocation * invocation,gpointer user_data)237 static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
238                                __attribute__((unused)) gpointer user_data) {
239   send_simple_dacp_command("nextitem");
240   media_player2_player_complete_next(skeleton, invocation);
241   return TRUE;
242 }
243 
on_handle_previous(MediaPlayer2Player * skeleton,GDBusMethodInvocation * invocation,gpointer user_data)244 static gboolean on_handle_previous(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
245                                    __attribute__((unused)) gpointer user_data) {
246   send_simple_dacp_command("previtem");
247   media_player2_player_complete_previous(skeleton, invocation);
248   return TRUE;
249 }
250 
on_handle_stop(MediaPlayer2Player * skeleton,GDBusMethodInvocation * invocation,gpointer user_data)251 static gboolean on_handle_stop(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
252                                __attribute__((unused)) gpointer user_data) {
253   send_simple_dacp_command("stop");
254   media_player2_player_complete_stop(skeleton, invocation);
255   return TRUE;
256 }
257 
on_handle_pause(MediaPlayer2Player * skeleton,GDBusMethodInvocation * invocation,gpointer user_data)258 static gboolean on_handle_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
259                                 __attribute__((unused)) gpointer user_data) {
260   send_simple_dacp_command("pause");
261   media_player2_player_complete_pause(skeleton, invocation);
262   return TRUE;
263 }
264 
on_handle_play_pause(MediaPlayer2Player * skeleton,GDBusMethodInvocation * invocation,gpointer user_data)265 static gboolean on_handle_play_pause(MediaPlayer2Player *skeleton,
266                                      GDBusMethodInvocation *invocation,
267                                      __attribute__((unused)) gpointer user_data) {
268   send_simple_dacp_command("playpause");
269   media_player2_player_complete_play_pause(skeleton, invocation);
270   return TRUE;
271 }
272 
on_handle_play(MediaPlayer2Player * skeleton,GDBusMethodInvocation * invocation,gpointer user_data)273 static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
274                                __attribute__((unused)) gpointer user_data) {
275   send_simple_dacp_command("play");
276   media_player2_player_complete_play(skeleton, invocation);
277   return TRUE;
278 }
279 
on_handle_set_volume(MediaPlayer2Player * skeleton,GDBusMethodInvocation * invocation,const gdouble volume,gpointer user_data)280 static gboolean on_handle_set_volume(MediaPlayer2Player *skeleton,
281                                      GDBusMethodInvocation *invocation, const gdouble volume,
282                                      __attribute__((unused)) gpointer user_data) {
283   double ap_volume = mpris_volume_to_airplay_volume(volume);
284   debug(2, "Set mpris volume to %.6f, i.e. airplay volume to %.6f.", volume, ap_volume);
285   char command[256] = "";
286   snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", ap_volume);
287   send_simple_dacp_command(command);
288   media_player2_player_complete_play(skeleton, invocation);
289   return TRUE;
290 }
291 
on_mpris_name_acquired(GDBusConnection * connection,const gchar * name,gpointer user_data)292 static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *name,
293                                    __attribute__((unused)) gpointer user_data) {
294 
295   const char *empty_string_array[] = {NULL};
296 
297   // debug(1, "MPRIS well-known interface name \"%s\" acquired on the %s bus.", name,
298   // (config.mpris_service_bus_type == DBT_session) ? "session" : "system");
299   mprisPlayerSkeleton = media_player2_skeleton_new();
300   mprisPlayerPlayerSkeleton = media_player2_player_skeleton_new();
301 
302   g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(mprisPlayerSkeleton), connection,
303                                    "/org/mpris/MediaPlayer2", NULL);
304   g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(mprisPlayerPlayerSkeleton), connection,
305                                    "/org/mpris/MediaPlayer2", NULL);
306 
307   media_player2_set_desktop_entry(mprisPlayerSkeleton, "shairport-sync");
308   media_player2_set_identity(mprisPlayerSkeleton, "Shairport Sync");
309   media_player2_set_can_quit(mprisPlayerSkeleton, TRUE);
310   media_player2_set_can_raise(mprisPlayerSkeleton, FALSE);
311   media_player2_set_has_track_list(mprisPlayerSkeleton, FALSE);
312   media_player2_set_supported_uri_schemes(mprisPlayerSkeleton, empty_string_array);
313   media_player2_set_supported_mime_types(mprisPlayerSkeleton, empty_string_array);
314 
315   media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, "Stopped");
316   media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, "None");
317   media_player2_player_set_minimum_rate(mprisPlayerPlayerSkeleton, 1.0);
318   media_player2_player_set_maximum_rate(mprisPlayerPlayerSkeleton, 1.0);
319   media_player2_player_set_can_go_next(mprisPlayerPlayerSkeleton, TRUE);
320   media_player2_player_set_can_go_previous(mprisPlayerPlayerSkeleton, TRUE);
321   media_player2_player_set_can_play(mprisPlayerPlayerSkeleton, TRUE);
322   media_player2_player_set_can_pause(mprisPlayerPlayerSkeleton, TRUE);
323   media_player2_player_set_can_seek(mprisPlayerPlayerSkeleton, FALSE);
324   media_player2_player_set_can_control(mprisPlayerPlayerSkeleton, TRUE);
325 
326   g_signal_connect(mprisPlayerSkeleton, "handle-quit", G_CALLBACK(on_handle_quit), NULL);
327 
328   g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play", G_CALLBACK(on_handle_play), NULL);
329   g_signal_connect(mprisPlayerPlayerSkeleton, "handle-pause", G_CALLBACK(on_handle_pause), NULL);
330   g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play-pause", G_CALLBACK(on_handle_play_pause),
331                    NULL);
332   g_signal_connect(mprisPlayerPlayerSkeleton, "handle-stop", G_CALLBACK(on_handle_stop), NULL);
333   g_signal_connect(mprisPlayerPlayerSkeleton, "handle-next", G_CALLBACK(on_handle_next), NULL);
334   g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous),
335                    NULL);
336   g_signal_connect(mprisPlayerPlayerSkeleton, "handle-set-volume", G_CALLBACK(on_handle_set_volume),
337                    NULL);
338 
339   add_metadata_watcher(mpris_metadata_watcher, NULL);
340 
341   debug(1, "MPRIS service started at \"%s\" on the %s bus.", name,
342         (config.mpris_service_bus_type == DBT_session) ? "session" : "system");
343 }
344 
on_mpris_name_lost_again(GDBusConnection * connection,const gchar * name,gpointer user_data)345 static void on_mpris_name_lost_again(__attribute__((unused)) GDBusConnection *connection,
346                                      const gchar *name,
347                                      __attribute__((unused)) gpointer user_data) {
348   warn("could not acquire an MPRIS interface named \"%s\" on the %s bus.", name,
349        (config.mpris_service_bus_type == DBT_session) ? "session" : "system");
350 }
351 
on_mpris_name_lost(GDBusConnection * connection,const gchar * name,gpointer user_data)352 static void on_mpris_name_lost(__attribute__((unused)) GDBusConnection *connection,
353                                __attribute__((unused)) const gchar *name,
354                                __attribute__((unused)) gpointer user_data) {
355   // debug(1, "Could not acquire MPRIS interface \"%s\" on the %s bus -- will try adding the process
356   // "
357   //         "number to the end of it.",
358   //      name,(mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
359   pid_t pid = getpid();
360   char interface_name[256] = "";
361   snprintf(interface_name, sizeof(interface_name), "org.mpris.MediaPlayer2.ShairportSync.i%d", pid);
362   GBusType mpris_bus_type = G_BUS_TYPE_SYSTEM;
363   if (config.mpris_service_bus_type == DBT_session)
364     mpris_bus_type = G_BUS_TYPE_SESSION;
365   // debug(1, "Looking for an MPRIS interface \"%s\" on the %s bus.",interface_name,
366   // (mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
367   g_bus_own_name(mpris_bus_type, interface_name, G_BUS_NAME_OWNER_FLAGS_NONE, NULL,
368                  on_mpris_name_acquired, on_mpris_name_lost_again, NULL, NULL);
369 }
370 
start_mpris_service()371 int start_mpris_service() {
372   mprisPlayerSkeleton = NULL;
373   mprisPlayerPlayerSkeleton = NULL;
374   GBusType mpris_bus_type = G_BUS_TYPE_SYSTEM;
375   if (config.mpris_service_bus_type == DBT_session)
376     mpris_bus_type = G_BUS_TYPE_SESSION;
377   // debug(1, "Looking for an MPRIS interface \"org.mpris.MediaPlayer2.ShairportSync\" on the %s
378   // bus.",(mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
379   g_bus_own_name(mpris_bus_type, "org.mpris.MediaPlayer2.ShairportSync",
380                  G_BUS_NAME_OWNER_FLAGS_NONE, NULL, on_mpris_name_acquired, on_mpris_name_lost,
381                  NULL, NULL);
382   return 0; // this is just to quieten a compiler warning
383 }
384