]> git.ipfire.org Git - thirdparty/shairport-sync.git/blame - mpris-service.c
Update check_classic_systemd_full.yml
[thirdparty/shairport-sync.git] / mpris-service.c
CommitLineData
1f75fe4e
MB
1/*
2 * This file is part of Shairport Sync.
249a6def 3 * Copyright (c) Mike Brady 2018 -- 2020
1f75fe4e
MB
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 */
54d761ff 26#include <inttypes.h>
df7c48f0
MB
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
aaee366f 40#include "metadata_hub.h"
df7c48f0 41#include "mpris-service.h"
b98e2a08 42
85ba78b6
MB
43MediaPlayer2 *mprisPlayerSkeleton;
44MediaPlayer2Player *mprisPlayerPlayerSkeleton;
45
fe8198a2
MB
46double 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;
54d761ff 51 sp = (sp / 30.0) + 1;
fe8198a2
MB
52 return sp;
53}
54
55double mpris_volume_to_airplay_volume(double sp) {
54d761ff 56 sp = (sp - 1.0) * 30.0;
fe8198a2
MB
57 if (sp < -30.0)
58 sp = -30.0;
59 if (sp > 0.0)
60 sp = 0.0;
61 return sp;
62}
63
8991f342 64void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
3a4e29c5 65 // debug(1, "MPRIS metadata watcher called");
aaee366f 66 char response[100];
54d761ff
MB
67 media_player2_player_set_volume(mprisPlayerPlayerSkeleton,
68 airplay_volume_to_mpris_volume(argc->airplay_volume));
aaee366f 69 switch (argc->repeat_status) {
cbbe84d8
MB
70 case RS_NOT_AVAILABLE:
71 strcpy(response, "Not Available");
72 break;
73 case RS_OFF:
aaee366f
MB
74 strcpy(response, "None");
75 break;
cbbe84d8 76 case RS_ONE:
aaee366f
MB
77 strcpy(response, "Track");
78 break;
79 case RS_ALL:
80 strcpy(response, "Playlist");
81 break;
82 }
83
aaee366f 84 media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, response);
c36a7822 85
64d36693 86 switch (argc->player_state) {
cbbe84d8
MB
87 case PS_NOT_AVAILABLE:
88 strcpy(response, "Not Available");
89 break;
64d36693
MB
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
c36a7822 101 media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, response);
aaee366f 102
cbbe84d8
MB
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
86b6d7bf
MB
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;
4aab0a6f
MB
141 for (it = 0; it < 16; it++) {
142 if (argc->track_metadata->item_composite_id[it])
143 non_zero = 1;
08a7d4ff 144 snprintf(pt, 3, "%02X", argc->track_metadata->item_composite_id[it]);
4aab0a6f
MB
145 pt += 2;
146 }
86b6d7bf
MB
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);
2cfbeca6 174 }
4aab0a6f 175
86b6d7bf 176 // Add in the Track ID based on the 'mper' metadata if it is non-zero
d67909b8 177 if (argc->item_id_is_valid != 0) {
64d36693 178 char trackidstring[128];
54d761ff
MB
179 snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "",
180 argc->item_id);
c36a7822
MB
181 GVariant *trackid = g_variant_new("o", trackidstring);
182 g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
3a4e29c5 183 }
c36a7822 184
86b6d7bf
MB
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);
352f6d5c 189 }
6b5a1b39 190
86b6d7bf
MB
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);
3a4e29c5 195 }
c36a7822 196
86b6d7bf
MB
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);
3a4e29c5 204 }
c36a7822 205
86b6d7bf
MB
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);
3a4e29c5 213 }
c36a7822 214
d67909b8 215 if (argc->songtime_in_milliseconds_is_valid) {
86b6d7bf
MB
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);
3a4e29c5 222 }
c36a7822 223
aaee366f
MB
224 GVariant *dict = g_variant_builder_end(dict_builder);
225 g_variant_builder_unref(dict_builder);
aaee366f
MB
226 media_player2_player_set_metadata(mprisPlayerPlayerSkeleton, dict);
227}
228
a4eaace7
MB
229static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation,
230 __attribute__((unused)) gpointer user_data) {
c8c70b60 231 debug(1, "quit requested (MPRIS interface).");
f775267d
MB
232 type_of_exit_cleanup = TOE_dbus; // request an exit cleanup that is compatible with dbus
233 exit(EXIT_SUCCESS);
a4eaace7
MB
234 media_player2_complete_quit(skeleton, invocation);
235 return TRUE;
236}
237
03321799 238static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
8991f342 239 __attribute__((unused)) gpointer user_data) {
03321799
MB
240 send_simple_dacp_command("nextitem");
241 media_player2_player_complete_next(skeleton, invocation);
242 return TRUE;
243}
244
245static gboolean on_handle_previous(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
8991f342 246 __attribute__((unused)) gpointer user_data) {
03321799
MB
247 send_simple_dacp_command("previtem");
248 media_player2_player_complete_previous(skeleton, invocation);
249 return TRUE;
250}
251
b98e2a08 252static gboolean on_handle_stop(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
8991f342 253 __attribute__((unused)) gpointer user_data) {
b98e2a08 254 send_simple_dacp_command("stop");
df7c48f0
MB
255 media_player2_player_complete_stop(skeleton, invocation);
256 return TRUE;
257}
258
b98e2a08 259static gboolean on_handle_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
8991f342 260 __attribute__((unused)) gpointer user_data) {
b98e2a08
MB
261 send_simple_dacp_command("pause");
262 media_player2_player_complete_pause(skeleton, invocation);
263 return TRUE;
264}
265
bafdd94e 266static gboolean on_handle_play_pause(MediaPlayer2Player *skeleton,
8991f342
MB
267 GDBusMethodInvocation *invocation,
268 __attribute__((unused)) gpointer user_data) {
b98e2a08
MB
269 send_simple_dacp_command("playpause");
270 media_player2_player_complete_play_pause(skeleton, invocation);
271 return TRUE;
272}
273
274static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
8991f342 275 __attribute__((unused)) gpointer user_data) {
b98e2a08
MB
276 send_simple_dacp_command("play");
277 media_player2_player_complete_play(skeleton, invocation);
278 return TRUE;
279}
df7c48f0 280
fe8198a2 281static gboolean on_handle_set_volume(MediaPlayer2Player *skeleton,
54d761ff
MB
282 GDBusMethodInvocation *invocation, const gdouble volume,
283 __attribute__((unused)) gpointer user_data) {
fe8198a2
MB
284 double ap_volume = mpris_volume_to_airplay_volume(volume);
285 debug(2, "Set mpris volume to %.6f, i.e. airplay volume to %.6f.", volume, ap_volume);
286 char command[256] = "";
287 snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", ap_volume);
288 send_simple_dacp_command(command);
289 media_player2_player_complete_play(skeleton, invocation);
290 return TRUE;
291}
292
bafdd94e 293static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *name,
8991f342 294 __attribute__((unused)) gpointer user_data) {
df7c48f0 295
bafdd94e 296 const char *empty_string_array[] = {NULL};
df7c48f0 297
c36a7822
MB
298 // debug(1, "MPRIS well-known interface name \"%s\" acquired on the %s bus.", name,
299 // (config.mpris_service_bus_type == DBT_session) ? "session" : "system");
df7c48f0
MB
300 mprisPlayerSkeleton = media_player2_skeleton_new();
301 mprisPlayerPlayerSkeleton = media_player2_player_skeleton_new();
302
303 g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(mprisPlayerSkeleton), connection,
304 "/org/mpris/MediaPlayer2", NULL);
305 g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(mprisPlayerPlayerSkeleton), connection,
306 "/org/mpris/MediaPlayer2", NULL);
bafdd94e 307
df7c48f0
MB
308 media_player2_set_desktop_entry(mprisPlayerSkeleton, "shairport-sync");
309 media_player2_set_identity(mprisPlayerSkeleton, "Shairport Sync");
a4eaace7 310 media_player2_set_can_quit(mprisPlayerSkeleton, TRUE);
df7c48f0
MB
311 media_player2_set_can_raise(mprisPlayerSkeleton, FALSE);
312 media_player2_set_has_track_list(mprisPlayerSkeleton, FALSE);
bafdd94e
MB
313 media_player2_set_supported_uri_schemes(mprisPlayerSkeleton, empty_string_array);
314 media_player2_set_supported_mime_types(mprisPlayerSkeleton, empty_string_array);
315
aaee366f
MB
316 media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, "Stopped");
317 media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, "None");
bafdd94e
MB
318 media_player2_player_set_minimum_rate(mprisPlayerPlayerSkeleton, 1.0);
319 media_player2_player_set_maximum_rate(mprisPlayerPlayerSkeleton, 1.0);
03321799
MB
320 media_player2_player_set_can_go_next(mprisPlayerPlayerSkeleton, TRUE);
321 media_player2_player_set_can_go_previous(mprisPlayerPlayerSkeleton, TRUE);
bafdd94e
MB
322 media_player2_player_set_can_play(mprisPlayerPlayerSkeleton, TRUE);
323 media_player2_player_set_can_pause(mprisPlayerPlayerSkeleton, TRUE);
324 media_player2_player_set_can_seek(mprisPlayerPlayerSkeleton, FALSE);
325 media_player2_player_set_can_control(mprisPlayerPlayerSkeleton, TRUE);
326
a4eaace7
MB
327 g_signal_connect(mprisPlayerSkeleton, "handle-quit", G_CALLBACK(on_handle_quit), NULL);
328
b98e2a08
MB
329 g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play", G_CALLBACK(on_handle_play), NULL);
330 g_signal_connect(mprisPlayerPlayerSkeleton, "handle-pause", G_CALLBACK(on_handle_pause), NULL);
bafdd94e
MB
331 g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play-pause", G_CALLBACK(on_handle_play_pause),
332 NULL);
df7c48f0 333 g_signal_connect(mprisPlayerPlayerSkeleton, "handle-stop", G_CALLBACK(on_handle_stop), NULL);
03321799 334 g_signal_connect(mprisPlayerPlayerSkeleton, "handle-next", G_CALLBACK(on_handle_next), NULL);
1637a79d
MB
335 g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous),
336 NULL);
fe8198a2
MB
337 g_signal_connect(mprisPlayerPlayerSkeleton, "handle-set-volume", G_CALLBACK(on_handle_set_volume),
338 NULL);
249a6def 339
aaee366f
MB
340 add_metadata_watcher(mpris_metadata_watcher, NULL);
341
c36a7822
MB
342 debug(1, "MPRIS service started at \"%s\" on the %s bus.", name,
343 (config.mpris_service_bus_type == DBT_session) ? "session" : "system");
df7c48f0
MB
344}
345
8991f342
MB
346static void on_mpris_name_lost_again(__attribute__((unused)) GDBusConnection *connection,
347 const gchar *name,
348 __attribute__((unused)) gpointer user_data) {
dfd90bac 349 warn("could not acquire an MPRIS interface named \"%s\" on the %s bus.", name,
c36a7822 350 (config.mpris_service_bus_type == DBT_session) ? "session" : "system");
df7c48f0
MB
351}
352
8991f342
MB
353static void on_mpris_name_lost(__attribute__((unused)) GDBusConnection *connection,
354 __attribute__((unused)) const gchar *name,
355 __attribute__((unused)) gpointer user_data) {
c36a7822
MB
356 // debug(1, "Could not acquire MPRIS interface \"%s\" on the %s bus -- will try adding the process
357 // "
f3ef6b5e
MB
358 // "number to the end of it.",
359 // name,(mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
df7c48f0
MB
360 pid_t pid = getpid();
361 char interface_name[256] = "";
c2e3fa5a 362 snprintf(interface_name, sizeof(interface_name), "org.mpris.MediaPlayer2.ShairportSync.i%d", pid);
df7c48f0 363 GBusType mpris_bus_type = G_BUS_TYPE_SYSTEM;
bafdd94e 364 if (config.mpris_service_bus_type == DBT_session)
df7c48f0 365 mpris_bus_type = G_BUS_TYPE_SESSION;
c36a7822
MB
366 // debug(1, "Looking for an MPRIS interface \"%s\" on the %s bus.",interface_name,
367 // (mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
df7c48f0 368 g_bus_own_name(mpris_bus_type, interface_name, G_BUS_NAME_OWNER_FLAGS_NONE, NULL,
bafdd94e 369 on_mpris_name_acquired, on_mpris_name_lost_again, NULL, NULL);
df7c48f0
MB
370}
371
372int start_mpris_service() {
373 mprisPlayerSkeleton = NULL;
374 mprisPlayerPlayerSkeleton = NULL;
375 GBusType mpris_bus_type = G_BUS_TYPE_SYSTEM;
bafdd94e 376 if (config.mpris_service_bus_type == DBT_session)
df7c48f0 377 mpris_bus_type = G_BUS_TYPE_SESSION;
c36a7822
MB
378 // debug(1, "Looking for an MPRIS interface \"org.mpris.MediaPlayer2.ShairportSync\" on the %s
379 // bus.",(mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
bafdd94e
MB
380 g_bus_own_name(mpris_bus_type, "org.mpris.MediaPlayer2.ShairportSync",
381 G_BUS_NAME_OWNER_FLAGS_NONE, NULL, on_mpris_name_acquired, on_mpris_name_lost,
382 NULL, NULL);
df7c48f0
MB
383 return 0; // this is just to quieten a compiler warning
384}