diff --git a/dacp.c b/dacp.c index 8865bd49b..bd170b4dd 100644 --- a/dacp.c +++ b/dacp.c @@ -457,7 +457,8 @@ void *dacp_monitor_thread_code(void *na) { break; case 'cang': // genre t = sp - item_size; - if ((metadata_store.genre == NULL) || (strncmp(metadata_store.genre, t, item_size) != 0)) { + if ((metadata_store.genre == NULL) || + (strncmp(metadata_store.genre, t, item_size) != 0)) { if (metadata_store.genre) free(metadata_store.genre); metadata_store.genre = strndup(t, item_size); @@ -469,8 +470,10 @@ void *dacp_monitor_thread_code(void *na) { case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware -- // see reference above) t = sp - item_size; - if (memcmp(metadata_store.item_composite_id, t, sizeof(metadata_store.item_composite_id)) != 0) { - memcpy(metadata_store.item_composite_id, t, sizeof(metadata_store.item_composite_id)); + if (memcmp(metadata_store.item_composite_id, t, + sizeof(metadata_store.item_composite_id)) != 0) { + memcpy(metadata_store.item_composite_id, t, + sizeof(metadata_store.item_composite_id)); char st[33]; char *pt = st; @@ -485,6 +488,12 @@ void *dacp_monitor_thread_code(void *na) { metadata_store.changed = 1; } break; + case 'astm': + t = sp - item_size; + r = ntohl(*(int32_t *)(t)); + metadata_store.songtime_in_milliseconds = ntohl(*(uint32_t *)(t)); + break; + /* case 'mstt': case 'cant': @@ -492,7 +501,6 @@ void *dacp_monitor_thread_code(void *na) { case 'cmmk': case 'caas': case 'caar': - case 'astm': t = sp - item_size; r = ntohl(*(int32_t *)(t)); printf(" %d", r); @@ -534,7 +542,9 @@ void *dacp_monitor_thread_code(void *na) { } // printf("\n"); } + // now, if the metadata is changed, send a signal + run_metadata_watchers(); } else { printf("Status Update not found.\n"); @@ -571,7 +581,7 @@ void *dacp_monitor_thread_code(void *na) { response = NULL; } */ - sleep(2); + sleep(2); } debug(1, "DACP monitor thread exiting."); pthread_exit(NULL); diff --git a/metadata_hub.c b/metadata_hub.c index 3dd1ed81c..3f09fc5d0 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -3,7 +3,7 @@ * Basically, if you need to store metadata * (e.g. for use with the dbus interfaces), * then you need a metadata hub, - * where everything is stored + * where everything is stored * This file is part of Shairport Sync. * Copyright (c) Mike Brady 2017 * All rights reserved. @@ -38,3 +38,24 @@ void metadata_hub_init(void) { debug(1, "Metadata bundle initialisation."); memset(&metadata_store, 0, sizeof(metadata_store)); } + +void add_metadata_watcher(metadata_watcher fn, void *userdata) { + int i; + for (i = 0; i < number_of_watchers; i++) { + if (metadata_store.watchers[i] == NULL) { + metadata_store.watchers[i] = fn; + metadata_store.watchers_data[i] = userdata; + debug(1, "Added a metadata watcher into slot %d", i); + break; + } + } +} + +void run_metadata_watchers(void) { + int i; + for (i = 0; i < number_of_watchers; i++) { + if (metadata_store.watchers[i]) { + metadata_store.watchers[i](&metadata_store, metadata_store.watchers_data[i]); + } + } +} diff --git a/metadata_hub.h b/metadata_hub.h index 0d4e49985..cb116f054 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -3,6 +3,8 @@ #include "config.h" #include +#define number_of_watchers 2 + enum play_status_type { PS_PLAYING = 0, PS_PAUSED, @@ -20,6 +22,10 @@ enum repeat_status_type { RS_ALL, } repeat_status_type; +struct metadata_bundle; + +typedef void (*metadata_watcher)(struct metadata_bundle *argc, void *userdata); + typedef struct metadata_bundle { int changed; // normally 0, nonzero if a field has been changed int playerstatusupdates_are_received; // false if it's "traditional" metadata @@ -48,11 +54,19 @@ typedef struct metadata_bundle { uint32_t item_id; // seems to be a track ID -- see itemid in DACP.c int item_id_changed; + uint32_t songtime_in_milliseconds; + unsigned char item_composite_id[16]; // seems to be nowplaying 4 ids: dbid, plid, playlistItem, itemid + metadata_watcher watchers[number_of_watchers]; // functions to call if the metadata is changed. + void *watchers_data[number_of_watchers]; // their individual data + } metadata_bundle; struct metadata_bundle metadata_store; +void add_metadata_watcher(metadata_watcher fn, void *userdata); +void run_metadata_watchers(void); + void metadata_hub_init(void); diff --git a/mpris-service.c b/mpris-service.c index 4b1a0cffd..393de0d3b 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -11,8 +11,90 @@ #include "dacp.h" +#include "metadata_hub.h" #include "mpris-service.h" +void mpris_metadata_watcher(struct metadata_bundle *argc, void *userdata) { + debug(1, "MPRIS metadata watcher called"); + char response[100]; + switch (argc->repeat_status) { + case RS_NONE: + strcpy(response, "None"); + break; + case RS_SINGLE: + strcpy(response, "Track"); + break; + case RS_ALL: + strcpy(response, "Playlist"); + break; + } + + // debug(1,"Set loop status to \"%s\"",response); + media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, response); + + GVariantBuilder *dict_builder, *aa; + GVariant *trackname, *albumname, *trackid, *tracklength; + + // Build the Track ID from the 16-byte item_composite_id in hex prefixed by + // /org/gnome/ShairportSync + + char st[33]; + char *pt = st; + int it; + for (it = 0; it < 16; it++) { + sprintf(pt, "%02X", argc->item_composite_id[it]); + pt += 2; + } + *pt = 0; + debug(1, "Item composite ID set to 0x%s.", st); + + char trackidstring[1024]; + sprintf(trackidstring, "/org/gnome/ShairportSync/%s", st); + + trackid = g_variant_new("o", trackidstring); + + // Make up the track name and album name + trackname = g_variant_new("s", argc->track_name); + albumname = g_variant_new("s", argc->album_name); + + // Make up the track length in microseconds as an int64 + + uint64_t track_length_in_microseconds = argc->songtime_in_milliseconds; + + track_length_in_microseconds *= 1000; // to micorseconds in 64-bit precision + + // Make up the track name and album name + tracklength = g_variant_new("x", track_length_in_microseconds); + + /* Build the artists array */ + // debug(1,"Build artists"); + aa = g_variant_builder_new(G_VARIANT_TYPE("as")); + g_variant_builder_add(aa, "s", argc->artist_name); + GVariant *artists = g_variant_builder_end(aa); + g_variant_builder_unref(aa); + + /* Build the genre array */ + // debug(1,"Build genre"); + aa = g_variant_builder_new(G_VARIANT_TYPE("as")); + g_variant_builder_add(aa, "s", argc->genre); + GVariant *genres = g_variant_builder_end(aa); + g_variant_builder_unref(aa); + + /* Build the metadata array */ + // debug(1,"Build metadata"); + dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(dict_builder, "{sv}", "xesam:title", trackname); + g_variant_builder_add(dict_builder, "{sv}", "xesam:album", albumname); + g_variant_builder_add(dict_builder, "{sv}", "xesam:artist", artists); + g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genres); + g_variant_builder_add(dict_builder, "{sv}", "xesam:trackid", trackid); + g_variant_builder_add(dict_builder, "{sv}", "xesam:length", tracklength); + GVariant *dict = g_variant_builder_end(dict_builder); + g_variant_builder_unref(dict_builder); + + media_player2_player_set_metadata(mprisPlayerPlayerSkeleton, dict); +} + static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, gpointer user_data) { send_simple_dacp_command("nextitem"); @@ -77,8 +159,8 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam media_player2_set_supported_uri_schemes(mprisPlayerSkeleton, empty_string_array); media_player2_set_supported_mime_types(mprisPlayerSkeleton, empty_string_array); - media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, "stop"); - media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, "off"); + 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); @@ -98,6 +180,8 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous), NULL); + add_metadata_watcher(mpris_metadata_watcher, NULL); + debug(1, "Shairport Sync D-BUS service started on interface \"%s\".", name); debug(1, "MPRIS service started on interface \"%s\".", name);