diff --git a/common.c b/common.c index 396481612..5221e0eec 100644 --- a/common.c +++ b/common.c @@ -1519,3 +1519,39 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma } return previous_random_number; } + +// This will check the incoming string "s" of length "len" with the existing NUL-terminated string "str" and update "flag" accordingly. + +// Note: if the incoming string length is zero, then the a NULL is used; i.e. no zero-length strings are stored. + +// If the strings are different, the str is free'd and replaced by a pointer +// to a newly strdup'd string and the flag is set +// If they are the same, the flag is cleared + +int string_update_with_size(char **str, int *flag, char *s, size_t len) { + if (*str) { + if ((s) && (len)) { + if (strncmp(*str, s, len) != 0) { + free(*str); + *str = strndup(s, len); + *flag = 1; + } else { + *flag = 0; + } + } else { + // old string is non-NULL, new string is NULL or length 0 + free(*str); + *str = NULL; + *flag = 1; + } + } else { // old string is NULL + if ((s) && (len)) { + *str = strndup(s, len); + *flag = 1; + } else { + // old string is NULL and new string is NULL or length 0 + *flag = 0; // so no change + } + } + return *flag; +} diff --git a/common.h b/common.h index e1b18d88d..cc8c93c02 100644 --- a/common.h +++ b/common.h @@ -393,4 +393,6 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma void malloc_cleanup(void *arg); +int string_update_with_size(char **str, int *flag, char *s, size_t len); + #endif // _COMMON_H diff --git a/dacp.c b/dacp.c index 0b6db70ed..1c5271e19 100644 --- a/dacp.c +++ b/dacp.c @@ -112,7 +112,10 @@ static void response_code(void *opaque, int code) { } static const struct http_funcs responseFuncs = { - response_realloc, response_body, response_header, response_code, + response_realloc, + response_body, + response_header, + response_code, }; // static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER; @@ -320,9 +323,10 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size); // debug(1,"dacp_conversation_lock released."); } else { - debug(3, "dacp_send_command: could not acquire a lock on the dacp transmit/receive section " - "when attempting to " - "send the command \"%s\". Possible timeout?", + debug(3, + "dacp_send_command: could not acquire a lock on the dacp transmit/receive section " + "when attempting to " + "send the command \"%s\". Possible timeout?", command); response.code = 494; // This client is already busy } @@ -419,8 +423,9 @@ void set_dacp_server_information(rtsp_conn_info *conn) { void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) { debug_mutex_lock(&dacp_server_information_lock, 500000, 2); - debug(3, "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port " - "number %d.", + debug(3, + "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port " + "number %d.", dacp_id, dacp_server.dacp_id, port); if (strcmp(dacp_id, dacp_server.dacp_id) == 0) { dacp_server.port = port; @@ -468,8 +473,9 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { (metadata_store.advanced_dacp_server_active != 0); metadata_store.dacp_server_active = 0; metadata_store.advanced_dacp_server_active = 0; - debug(2, "setting dacp_server_active and advanced_dacp_server_active to 0 with an update " - "flag value of %d", + debug(2, + "setting dacp_server_active and advanced_dacp_server_active to 0 with an update " + "flag value of %d", ch); metadata_hub_modify_epilog(ch); while (dacp_server.scan_enable == 0) { @@ -483,7 +489,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { scan_index++; result = dacp_get_volume(&the_volume); // just want the http code - if ((result == 496) || (result == 403) || (result == 501)) { + if ((result == 403) || (result == 501)) { bad_result_count++; // debug(1,"Bad Scan : %d.",result); } else @@ -511,8 +517,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { if (dacp_server.scan_enable == 1) { // if it hasn't been turned off, continue looking for information. int transient_problem = - (result == 494) || - (result == 495); // this just means that it couldn't send the query because something + (result == 494) || (result == 495) || + (result == 496); // this just means that it couldn't send the query because something // else // was sending a command or something if ((!transient_problem) && (bad_result_count == 0) && (idle_scan_count == 0) && @@ -525,12 +531,12 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { int inactive = metadata_store.dacp_server_active == 0; if (inactive) { metadata_store.dacp_server_active = 1; - debug(2, "Setting dacp_server_active to active because of a response of %d.", result); + debug(1, "Setting dacp_server_active to active because of a response of %d.", result); } int same = metadata_store.advanced_dacp_server_active == (result == 200); if (!same) { metadata_store.advanced_dacp_server_active = (result == 200); - debug(2, "Setting dacp_advanced_server_active to %d because of a response of %d.", + debug(1, "Setting dacp_advanced_server_active to %d because of a response of %d.", (result == 200), result); } metadata_hub_modify_epilog(inactive + (!same)); @@ -574,6 +580,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { // char u; // char *st; int32_t r; + uint32_t ui; // uint64_t v; // int i; @@ -658,64 +665,42 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { break; } break; - /* case 'cann': // track name - t = sp - item_size; - if ((metadata_store.track_name == NULL) || - (strncmp(metadata_store.track_name, t, item_size) != 0)) { - if (metadata_store.track_name) - free(metadata_store.track_name); - metadata_store.track_name = strndup(t, item_size); - debug(1, "Track name changed to: \"%s\"", metadata_store.track_name); - metadata_store.track_name_changed = 1; - metadata_store.changed = 1; + debug(2, "DACP Track Name seen"); + if (string_update_with_size(&metadata_store.track_name, &metadata_store.track_name_changed, + sp - item_size, item_size)) { + debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name); } break; case 'cana': // artist name - t = sp - item_size; - if ((metadata_store.artist_name == NULL) || - (strncmp(metadata_store.artist_name, t, item_size) != 0)) { - if (metadata_store.artist_name) - free(metadata_store.artist_name); - metadata_store.artist_name = strndup(t, item_size); - debug(1, "Artist name changed to: \"%s\"", metadata_store.artist_name); - metadata_store.artist_name_changed = 1; - metadata_store.changed = 1; + debug(2, "DACP Artist Name seen"); + if (string_update_with_size(&metadata_store.artist_name, + &metadata_store.artist_name_changed, sp - item_size, + item_size)) { + debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name); } break; case 'canl': // album name - t = sp - item_size; - if ((metadata_store.album_name == NULL) || - (strncmp(metadata_store.album_name, t, item_size) != 0)) { - if (metadata_store.album_name) - free(metadata_store.album_name); - metadata_store.album_name = strndup(t, item_size); - debug(1, "Album name changed to: \"%s\"", metadata_store.album_name); - metadata_store.album_name_changed = 1; - metadata_store.changed = 1; + debug(2, "DACP Album Name seen"); + if (string_update_with_size(&metadata_store.album_name, &metadata_store.album_name_changed, + sp - item_size, item_size)) { + debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name); } 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) - free(metadata_store.genre); - metadata_store.genre = strndup(t, item_size); - debug(1, "Genre changed to: \"%s\"", metadata_store.genre); - metadata_store.genre_changed = 1; - metadata_store.changed = 1; + debug(2, "DACP Genre seen"); + if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed, + sp - item_size, item_size)) { + debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre); } break; 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, + debug(2, "DACP Composite ID seen"); + if (memcmp(metadata_store.item_composite_id, sp - item_size, sizeof(metadata_store.item_composite_id)) != 0) { - memcpy(metadata_store.item_composite_id, t, + memcpy(metadata_store.item_composite_id, sp - item_size, sizeof(metadata_store.item_composite_id)); - char st[33]; char *pt = st; int it; @@ -724,18 +709,20 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { pt += 2; } *pt = 0; - // debug(1, "Item composite ID set to 0x%s.", st); - metadata_store.item_id_changed = 1; - metadata_store.changed = 1; + debug(2, "Item composite ID changed to 0x%s.", st); + metadata_store.item_composite_id_changed = 1; } break; - */ case 'astm': t = sp - item_size; - r = ntohl(*(uint32_t *)(t)); - if (metadata_store.track_metadata) - metadata_store.track_metadata->songtime_in_milliseconds = - ntohl(*(uint32_t *)(t)); + ui = ntohl(*(uint32_t *)(t)); + debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size); + if (ui != metadata_store.songtime_in_milliseconds) { + metadata_store.songtime_in_milliseconds = ui; + metadata_store.songtime_in_milliseconds_changed = 1; + debug(2, "DACP Song Time set to: \"%u\"", + metadata_store.songtime_in_milliseconds); + } break; /* diff --git a/dbus-service.c b/dbus-service.c index 8ab4cd2c0..2294a706d 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -146,77 +146,73 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) debug(1, "This should never happen."); } - GVariantBuilder *dict_builder, *aa; + // Build the metadata array + debug(2, "Build metadata"); + GVariantBuilder *dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); - /* Build the metadata array */ - // debug(1,"Build metadata"); - dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); - - // Make up the artwork URI if we have one + // Add in the artwork URI if it exists. if (argc->cover_art_pathname) { - char artURIstring[1024]; - snprintf(artURIstring, sizeof(artURIstring), "file://%s", argc->cover_art_pathname); - // debug(1,"artURI String: \"%s\".",artURIstring); - GVariant *artUrl = g_variant_new("s", artURIstring); + GVariant *artUrl = g_variant_new("s", argc->cover_art_pathname); g_variant_builder_add(dict_builder, "{sv}", "mpris:artUrl", artUrl); } - // Add the TrackID if we have one - if ((argc->track_metadata) && (argc->track_metadata->item_id)) { + // Add in the Track ID based on the 'mper' metadata if it is non-zero + if (argc->item_id != 0) { char trackidstring[128]; - // debug(1, "Set ID using mper ID: \"%u\".",argc->item_id); snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u", - argc->track_metadata->item_id); + argc->item_id); GVariant *trackid = g_variant_new("o", trackidstring); g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid); } - // Add the track name if there is one - if ((argc->track_metadata) && (argc->track_metadata->track_name)) { - // debug(1, "Track name set to \"%s\".", argc->track_name); - GVariant *trackname = g_variant_new("s", argc->track_metadata->track_name); - g_variant_builder_add(dict_builder, "{sv}", "xesam:title", trackname); + // Add the track name if it exists + if (argc->track_name) { + GVariant *track_name = g_variant_new("s", argc->track_name); + g_variant_builder_add(dict_builder, "{sv}", "xesam:title", track_name); } - // Add the album name if there is one - if ((argc->track_metadata) && (argc->track_metadata->album_name)) { - // debug(1, "Album name set to \"%s\".", argc->album_name); - GVariant *albumname = g_variant_new("s", argc->track_metadata->album_name); - g_variant_builder_add(dict_builder, "{sv}", "xesam:album", albumname); + // Add the album name if it exists + if (argc->album_name) { + GVariant *album_name = g_variant_new("s", argc->album_name); + g_variant_builder_add(dict_builder, "{sv}", "xesam:album", album_name); } - // Add the artists if there are any (actually there will be at most one, but put it in an array) - if ((argc->track_metadata) && (argc->track_metadata->artist_name)) { - /* Build the artists array */ - // debug(1,"Build artist array"); - aa = g_variant_builder_new(G_VARIANT_TYPE("as")); - g_variant_builder_add(aa, "s", argc->track_metadata->artist_name); - GVariant *artists = g_variant_builder_end(aa); - g_variant_builder_unref(aa); + // Add the artist name if it exists + if (argc->artist_name) { + GVariantBuilder *artist_as = g_variant_builder_new(G_VARIANT_TYPE("as")); + g_variant_builder_add(artist_as, "s", argc->artist_name); + GVariant *artists = g_variant_builder_end(artist_as); + g_variant_builder_unref(artist_as); g_variant_builder_add(dict_builder, "{sv}", "xesam:artist", artists); } - // Add the genres if there are any (actually there will be at most one, but put it in an array) - if ((argc->track_metadata) && (argc->track_metadata->genre)) { - // debug(1,"Build genre"); - aa = g_variant_builder_new(G_VARIANT_TYPE("as")); - g_variant_builder_add(aa, "s", argc->track_metadata->genre); - GVariant *genres = g_variant_builder_end(aa); - g_variant_builder_unref(aa); - g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genres); + // Add the genre if it exists + if (argc->genre) { + GVariantBuilder *genre_as = g_variant_builder_new(G_VARIANT_TYPE("as")); + g_variant_builder_add(genre_as, "s", argc->genre); + GVariant *genre = g_variant_builder_end(genre_as); + g_variant_builder_unref(genre_as); + g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genre); } + if (argc->songtime_in_milliseconds) { + uint64_t track_length_in_microseconds = argc->songtime_in_milliseconds; + track_length_in_microseconds *= 1000; // to microseconds in 64-bit precision + // Make up the track name and album name + // debug(1, "Set tracklength to %lu.", track_length_in_microseconds); + GVariant *tracklength = g_variant_new("x", track_length_in_microseconds); + g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength); + } + GVariant *dict = g_variant_builder_end(dict_builder); g_variant_builder_unref(dict_builder); - - // debug(1,"Set metadata"); shairport_sync_remote_control_set_metadata(shairportSyncRemoteControlSkeleton, dict); } static gboolean on_handle_set_volume(ShairportSyncAdvancedRemoteControl *skeleton, GDBusMethodInvocation *invocation, const gint volume, __attribute__((unused)) gpointer user_data) { - debug(1, "Set volume to %d.", volume); + debug(2, "Set volume to %d.", volume); dacp_set_volume(volume); shairport_sync_advanced_remote_control_complete_set_volume(skeleton, invocation); return TRUE; diff --git a/metadata_hub.c b/metadata_hub.c index 400c1e55a..dc2f34336 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -61,64 +61,21 @@ int metadata_hub_initialised = 0; pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER; -struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store -void release_char_string(char **str) { - if (*str) { - free(*str); - *str = NULL; - } -} - -void metadata_hub_release_track_metadata(struct track_metadata_bundle *track_metadata) { - // debug(1,"release track metadata"); - if (track_metadata) { - release_char_string(&track_metadata->track_name); - release_char_string(&track_metadata->artist_name); - release_char_string(&track_metadata->album_artist_name); - release_char_string(&track_metadata->album_name); - release_char_string(&track_metadata->genre); - release_char_string(&track_metadata->comment); - release_char_string(&track_metadata->composer); - release_char_string(&track_metadata->file_kind); - release_char_string(&track_metadata->song_description); - release_char_string(&track_metadata->song_album_artist); - release_char_string(&track_metadata->sort_name); - release_char_string(&track_metadata->sort_artist); - release_char_string(&track_metadata->sort_album); - release_char_string(&track_metadata->sort_composer); - free((char *)track_metadata); - } else { - debug(3, "Asked to release non-existent track metadata"); - } -} - -void metadata_hub_release_track_artwork(void) { - // debug(1,"release track artwork"); - release_char_string(&metadata_store.cover_art_pathname); +int string_update(char **str, int *flag, char *s) { + if (s) + return string_update_with_size(str, flag, s, strlen(s)); + else + return string_update_with_size(str, flag, NULL, 0); } void metadata_hub_init(void) { // debug(1, "Metadata bundle initialisation."); memset(&metadata_store, 0, sizeof(metadata_store)); - track_metadata = NULL; metadata_hub_initialised = 1; } -void metadata_hub_stop(void) { - if (metadata_hub_initialised) { - debug(2, "metadata_hub_stop."); - metadata_hub_release_track_artwork(); - if (metadata_store.track_metadata) { - metadata_hub_release_track_metadata(metadata_store.track_metadata); - metadata_store.track_metadata = NULL; - } - if (track_metadata) { - metadata_hub_release_track_metadata(track_metadata); - track_metadata = NULL; - } - } -} +void metadata_hub_stop(void) {} void add_metadata_watcher(metadata_watcher fn, void *userdata) { int i; @@ -132,24 +89,41 @@ void add_metadata_watcher(metadata_watcher fn, void *userdata) { } } +/* void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) { // debug(1, "metadata_hub_unlock_hub_mutex_cleanup called."); pthread_rwlock_unlock(&metadata_hub_re_lock); } +*/ void run_metadata_watchers(void) { int i; - // debug(1, "locking metadata hub for reading"); - pthread_rwlock_rdlock(&metadata_hub_re_lock); - pthread_cleanup_push(metadata_hub_unlock_hub_mutex_cleanup, NULL); for (i = 0; i < number_of_watchers; i++) { if (metadata_store.watchers[i]) { metadata_store.watchers[i](&metadata_store, metadata_store.watchers_data[i]); } } - // debug(1, "unlocking metadata hub for reading"); - // pthread_rwlock_unlock(&metadata_hub_re_lock); - pthread_cleanup_pop(1); + // turn off changed flags + metadata_store.cover_art_pathname_changed = 0; + metadata_store.client_ip_changed = 0; + metadata_store.server_ip_changed = 0; + metadata_store.progress_string_changed = 0; + metadata_store.item_id_changed = 0; + metadata_store.item_composite_id_changed = 0; + metadata_store.artist_name_changed = 0; + metadata_store.album_artist_name_changed = 0; + metadata_store.album_name_changed = 0; + metadata_store.track_name_changed = 0; + metadata_store.genre_changed = 0; + metadata_store.comment_changed = 0; + metadata_store.composer_changed = 0; + metadata_store.file_kind_changed = 0; + metadata_store.song_description_changed = 0; + metadata_store.song_album_artist_changed = 0; + metadata_store.sort_artist_changed = 0; + metadata_store.sort_album_changed = 0; + metadata_store.sort_composer_changed = 0; + metadata_store.songtime_in_milliseconds_changed = 0; } void metadata_hub_modify_prolog(void) { @@ -163,48 +137,21 @@ void metadata_hub_modify_prolog(void) { } void metadata_hub_modify_epilog(int modified) { - // always run this after changing an entry or a sequence of entries in the metadata_hub - // debug(1, "unlocking metadata hub for writing"); - - // Here, we check to see if the dacp_server is transitioning between active and inactive - // If it's going off, we will release track metadata and image stuff - // If it's already off, we do nothing - // If it's transitioning to on, we will record it for use later. - - int m = 0; - int tm = modified; - - if ((metadata_store.dacp_server_active == 0) && - (metadata_store.dacp_server_has_been_active != 0)) { - debug(2, "dacp_scanner going inactive -- release track metadata and artwork"); - if (metadata_store.track_metadata) { - m = 1; - metadata_hub_release_track_metadata(metadata_store.track_metadata); - metadata_store.track_metadata = NULL; - } - if (metadata_store.cover_art_pathname) { - m = 1; - metadata_hub_release_track_artwork(); - } - if (m) - debug(2, "Release track metadata after dacp server goes inactive."); - tm += m; - } metadata_store.dacp_server_has_been_active = metadata_store.dacp_server_active; // set the scanner_has_been_active now. - pthread_rwlock_unlock(&metadata_hub_re_lock); - if (tm) { + if (modified) { run_metadata_watchers(); } + pthread_rwlock_unlock(&metadata_hub_re_lock); } void metadata_hub_read_prolog(void) { // always run this before reading an entry or a sequence of entries in the metadata_hub // debug(1, "locking metadata hub for reading"); if (pthread_rwlock_tryrdlock(&metadata_hub_re_lock) != 0) { - debug(1, "Metadata_hub read lock is already taken -- must wait."); + debug(2, "Metadata_hub read lock is already taken -- must wait."); pthread_rwlock_rdlock(&metadata_hub_re_lock); - debug(1, "Okay -- acquired the metadata_hub read lock."); + debug(2, "Okay -- acquired the metadata_hub read lock."); } } @@ -223,7 +170,7 @@ char *metadata_write_image_file(const char *buf, int len) { char *path = NULL; // this will be what is returned uint8_t img_md5[16]; -// uint8_t ap_md5[16]; + // uint8_t ap_md5[16]; #ifdef CONFIG_OPENSSL MD5_CTX ctx; @@ -345,124 +292,125 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin // https://code.google.com/p/ytrack/wiki/DMAP // all the following items of metadata are contained in one metadata packet - // they are preseded by an 'ssnc' 'mdst' item and followed by an 'ssnc 'mden' item. + // they are preceded by an 'ssnc' 'mdst' item and followed by an 'ssnc 'mden' item. + uint32_t ui; + char *cs; + int changed = 0; if (type == 'core') { switch (code) { case 'mper': - if (track_metadata) { - track_metadata->item_id = ntohl(*(uint32_t *)data); - track_metadata->item_id_received = 1; - debug(2, "MH Item ID set to: \"%u\"", track_metadata->item_id); - } else { - debug(1, "No track metadata memory allocated when item id received!"); + ui = ntohl(*(uint32_t *)data); + debug(2, "MH Item ID seen: \"%u\" of length %u.", ui, length); + if (ui != metadata_store.item_id) { + metadata_store.item_id = ui; + metadata_store.item_id_changed = 1; + metadata_store.item_id_received = 1; + debug(2, "MH Item ID set to: \"%u\"", metadata_store.item_id); + } + break; + case 'astm': + ui = ntohl(*(uint32_t *)data); + debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length); + if (ui != metadata_store.songtime_in_milliseconds) { + metadata_store.songtime_in_milliseconds = ui; + metadata_store.songtime_in_milliseconds_changed = 1; + debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds); } break; case 'asal': - if (track_metadata) { - track_metadata->album_name = strndup(data, length); - debug(2, "MH Album name set to: \"%s\"", track_metadata->album_name); - } else { - debug(1, "No track metadata memory allocated when album name received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.album_name, &metadata_store.album_name_changed, cs)) { + debug(2, "MH Album name set to: \"%s\"", metadata_store.album_name); } + free(cs); break; case 'asar': - if (track_metadata) { - track_metadata->artist_name = strndup(data, length); - debug(2, "MH Artist name set to: \"%s\"", track_metadata->artist_name); - } else { - debug(1, "No track metadata memory allocated when artist name received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.artist_name, &metadata_store.artist_name_changed, cs)) { + debug(2, "MH Artist name set to: \"%s\"", metadata_store.artist_name); } + free(cs); break; case 'assl': - if (track_metadata) { - track_metadata->album_artist_name = strndup(data, length); - debug(2, "MH Album Artist name set to: \"%s\"", track_metadata->album_artist_name); - } else { - debug(1, "No track metadata memory allocated when album artist name received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.album_artist_name, + &metadata_store.album_artist_name_changed, cs)) { + debug(2, "MH Album Artist name set to: \"%s\"", metadata_store.album_artist_name); } + free(cs); break; case 'ascm': - if (track_metadata) { - track_metadata->comment = strndup(data, length); - debug(2, "MH Comment set to: \"%s\"", track_metadata->comment); - } else { - debug(1, "No track metadata memory allocated when comment received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.comment, &metadata_store.comment_changed, cs)) { + debug(2, "MH Comment set to: \"%s\"", metadata_store.comment); } + free(cs); break; case 'asgn': - if (track_metadata) { - track_metadata->genre = strndup(data, length); - debug(2, "MH Genre set to: \"%s\"", track_metadata->genre); - } else { - debug(1, "No track metadata memory allocated when genre received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.genre, &metadata_store.genre_changed, cs)) { + debug(2, "MH Genre set to: \"%s\"", metadata_store.genre); } + free(cs); break; case 'minm': - if (track_metadata) { - track_metadata->track_name = strndup(data, length); - debug(2, "MH Track name set to: \"%s\"", track_metadata->track_name); - } else { - debug(1, "No track metadata memory allocated when track name received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.track_name, &metadata_store.track_name_changed, cs)) { + debug(2, "MH Track Name set to: \"%s\"", metadata_store.track_name); } + free(cs); break; case 'ascp': - if (track_metadata) { - track_metadata->composer = strndup(data, length); - debug(2, "MH Composer set to: \"%s\"", track_metadata->composer); - } else { - debug(1, "No track metadata memory allocated when track name received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.composer, &metadata_store.composer_changed, cs)) { + debug(2, "MH Composer set to: \"%s\"", metadata_store.composer); } + free(cs); break; case 'asdt': - if (track_metadata) { - track_metadata->song_description = strndup(data, length); - debug(2, "MH Song Description set to: \"%s\"", track_metadata->song_description); - } else { - debug(1, "No track metadata memory allocated when song description received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.song_description, &metadata_store.song_description_changed, + cs)) { + debug(2, "MH Song Description set to: \"%s\"", metadata_store.song_description); } + free(cs); break; case 'asaa': - if (track_metadata) { - track_metadata->song_album_artist = strndup(data, length); - debug(2, "MH Song Album Artist set to: \"%s\"", track_metadata->song_album_artist); - } else { - debug(1, "No track metadata memory allocated when song artist received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.song_album_artist, + &metadata_store.song_album_artist_changed, cs)) { + debug(2, "MH Song Album Artist set to: \"%s\"", metadata_store.song_album_artist); } + free(cs); break; case 'assn': - if (track_metadata) { - track_metadata->sort_name = strndup(data, length); - debug(2, "MH Sort Name set to: \"%s\"", track_metadata->sort_name); - } else { - debug(1, "No track metadata memory allocated when sort name description received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.sort_name, &metadata_store.sort_name_changed, cs)) { + debug(2, "MH Sort Name set to: \"%s\"", metadata_store.sort_name); } + free(cs); break; case 'assa': - if (track_metadata) { - track_metadata->sort_artist = strndup(data, length); - debug(2, "MH Sort Artist set to: \"%s\"", track_metadata->sort_artist); - } else { - debug(1, "No track metadata memory allocated when sort artist description received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.sort_artist, &metadata_store.sort_artist_changed, cs)) { + debug(2, "MH Sort Artist set to: \"%s\"", metadata_store.sort_artist); } + free(cs); break; case 'assu': - if (track_metadata) { - track_metadata->sort_album = strndup(data, length); - debug(2, "MH Sort Album set to: \"%s\"", track_metadata->sort_album); - } else { - debug(1, "No track metadata memory allocated when sort album description received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.sort_album, &metadata_store.sort_album_changed, cs)) { + debug(2, "MH Sort Album set to: \"%s\"", metadata_store.sort_album); } + free(cs); break; case 'assc': - if (track_metadata) { - track_metadata->sort_composer = strndup(data, length); - debug(2, "MH Sort Composer set to: \"%s\"", track_metadata->sort_composer); - } else { - debug(1, "No track metadata memory allocated when sort composer description received!"); + cs = strndup(data, length); + if (string_update(&metadata_store.sort_composer, &metadata_store.sort_composer_changed, cs)) { + debug(2, "MH Sort Composer set to: \"%s\"", metadata_store.sort_composer); } - break; - + free(cs); default: /* { @@ -487,132 +435,121 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin } } else if (type == 'ssnc') { switch (code) { - // ignore the following case 'pcst': case 'pcen': break; - case 'mdst': debug(2, "MH Metadata stream processing start."); - if (track_metadata) { - debug(1, "This track metadata bundle still seems to exist -- releasing it"); - metadata_hub_release_track_metadata(track_metadata); - } - track_metadata = (struct track_metadata_bundle *)malloc(sizeof(struct track_metadata_bundle)); - if (track_metadata == NULL) - die("Could not allocate memory for track metadata."); - memset(track_metadata, 0, sizeof(struct track_metadata_bundle)); + metadata_hub_modify_prolog(); break; case 'mden': - if (track_metadata) { - metadata_hub_modify_prolog(); - metadata_hub_release_track_metadata(metadata_store.track_metadata); - metadata_store.track_metadata = track_metadata; - track_metadata = NULL; - metadata_hub_modify_epilog(1); - } debug(2, "MH Metadata stream processing end."); + metadata_hub_modify_epilog(1); + debug(2, "MH Metadata stream processing epilog complete."); break; case 'PICT': + metadata_hub_modify_prolog(); + debug(2, "MH Picture received, length %u bytes.", length); + char uri[2048]; if (length > 16) { - metadata_hub_modify_prolog(); - debug(2, "MH Picture received, length %u bytes.", length); - release_char_string(&metadata_store.cover_art_pathname); - metadata_store.cover_art_pathname = metadata_write_image_file(data, length); - metadata_hub_modify_epilog(1); + char *pathname = metadata_write_image_file(data, length); + snprintf(uri, sizeof(uri), "file://%s", pathname); + free(pathname); + } else { + uri[0] = '\0'; } + if (string_update(&metadata_store.cover_art_pathname, + &metadata_store.cover_art_pathname_changed, + uri)) // if the picture's file path is different from the stored one... + metadata_hub_modify_epilog(1); + else + metadata_hub_modify_epilog(0); break; - /* case 'clip': - if ((metadata_store.client_ip == NULL) || - (strncmp(metadata_store.client_ip, data, length) != 0)) { - metadata_hub_modify_prolog(); - if (metadata_store.client_ip) - free(metadata_store.client_ip); - metadata_store.client_ip = strndup(data, length); - debug(1, "MH Client IP set to: \"%s\"", metadata_store.client_ip); - metadata_store.client_ip_changed = 1; - metadata_store.changed = 1; - metadata_hub_modify_epilog(1); + metadata_hub_modify_prolog(); + cs = strndup(data, length); + if (string_update(&metadata_store.client_ip, &metadata_store.client_ip_changed, cs)) { + changed = 1; + debug(2, "MH Client IP set to: \"%s\"", metadata_store.client_ip); } + free(cs); + metadata_hub_modify_epilog(changed); break; - */ case 'prgr': - if ((metadata_store.progress_string == NULL) || - (strncmp(metadata_store.progress_string, data, length) != 0)) { - metadata_hub_modify_prolog(); - release_char_string(&metadata_store.progress_string); - metadata_store.progress_string = strndup(data, length); + metadata_hub_modify_prolog(); + cs = strndup(data, length); + if (string_update(&metadata_store.progress_string, &metadata_store.progress_string_changed, + cs)) { + changed = 1; debug(2, "MH Progress String set to: \"%s\"", metadata_store.progress_string); - metadata_hub_modify_epilog(1); } + free(cs); + metadata_hub_modify_epilog(changed); break; case 'svip': - if ((metadata_store.server_ip == NULL) || - (strncmp(metadata_store.server_ip, data, length) != 0)) { - metadata_hub_modify_prolog(); - release_char_string(&metadata_store.server_ip); - metadata_store.server_ip = strndup(data, length); - // debug(1, "MH Server IP set to: \"%s\"", metadata_store.server_ip); - metadata_hub_modify_epilog(1); + metadata_hub_modify_prolog(); + cs = strndup(data, length); + if (string_update(&metadata_store.server_ip, &metadata_store.server_ip_changed, cs)) { + changed = 1; + debug(2, "MH Server IP set to: \"%s\"", metadata_store.server_ip); } + free(cs); + metadata_hub_modify_epilog(changed); break; - // these could tell us about play / pause etc. but will only occur if metadata is enabled, so - // we'll just ignore them - case 'abeg': { + case 'abeg': metadata_hub_modify_prolog(); - int changed = (metadata_store.active_state != AM_ACTIVE); + changed = (metadata_store.active_state != AM_ACTIVE); metadata_store.active_state = AM_ACTIVE; metadata_hub_modify_epilog(changed); - } break; - case 'aend': { + break; + case 'aend': metadata_hub_modify_prolog(); - int changed = (metadata_store.active_state != AM_INACTIVE); + changed = (metadata_store.active_state != AM_INACTIVE); metadata_store.active_state = AM_INACTIVE; metadata_hub_modify_epilog(changed); - } break; - case 'pbeg': { + break; + case 'pbeg': metadata_hub_modify_prolog(); - int changed = (metadata_store.player_state != PS_PLAYING); + changed = (metadata_store.player_state != PS_PLAYING); metadata_store.player_state = PS_PLAYING; metadata_store.player_thread_active = 1; metadata_hub_modify_epilog(changed); - } break; - case 'pend': { + break; + case 'pend': metadata_hub_modify_prolog(); + changed = (metadata_store.player_state != PS_STOPPED); metadata_store.player_thread_active = 0; metadata_store.player_state = PS_STOPPED; - metadata_hub_modify_epilog(1); - } break; - case 'pfls': { + metadata_hub_modify_epilog(changed); + break; + case 'pfls': metadata_hub_modify_prolog(); - int changed = (metadata_store.player_state != PS_PAUSED); + changed = (metadata_store.player_state != PS_PAUSED); metadata_store.player_state = PS_PAUSED; metadata_hub_modify_epilog(changed); - } break; + break; case 'pffr': // this is sent when the first frame has been received - case 'prsm': { + case 'prsm': metadata_hub_modify_prolog(); int changed = (metadata_store.player_state != PS_PLAYING); metadata_store.player_state = PS_PLAYING; metadata_hub_modify_epilog(changed); - } break; + break; case 'pvol': { // Note: it's assumed that the config.airplay volume has already been correctly set. - int modified = 0; int32_t actual_volume; int gv = dacp_get_volume(&actual_volume); metadata_hub_modify_prolog(); if ((gv == 200) && (metadata_store.speaker_volume != actual_volume)) { metadata_store.speaker_volume = actual_volume; - modified = 1; + changed = 1; } if (metadata_store.airplay_volume != config.airplay_volume) { metadata_store.airplay_volume = config.airplay_volume; - modified = 1; + changed = 1; } - metadata_hub_modify_epilog(modified); // change + metadata_hub_modify_epilog(changed); // change } break; default: { diff --git a/metadata_hub.h b/metadata_hub.h index 47965d5d8..5b565bbad 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -30,27 +30,8 @@ enum repeat_status_type { RS_ALL, } repeat_status_type; -typedef struct track_metadata_bundle { - uint32_t item_id; // seems to be a track ID -- see itemid in DACP.c - int item_id_received; // important for deciding if the track information should be ignored. - unsigned char - item_composite_id[16]; // seems to be nowplaying 4 ids: dbid, plid, playlistItem, itemid - char *track_name; // a malloced string -- if non-zero, free it before replacing it - char *artist_name; // a malloced string -- if non-zero, free it before replacing it - char *album_artist_name; // a malloced string -- if non-zero, free it before replacing it - char *album_name; // a malloced string -- if non-zero, free it before replacing it - char *genre; // a malloced string -- if non-zero, free it before replacing it - char *comment; // a malloced string -- if non-zero, free it before replacing it - char *composer; // a malloced string -- if non-zero, free it before replacing it - char *file_kind; // a malloced string -- if non-zero, free it before replacing it - char *song_description; // a malloced string -- if non-zero, free it before replacing it - char *song_album_artist; // a malloced string -- if non-zero, free it before replacing it - char *sort_name; // a malloced string -- if non-zero, free it before replacing it - char *sort_artist; // a malloced string -- if non-zero, free it before replacing it - char *sort_album; // a malloced string -- if non-zero, free it before replacing it - char *sort_composer; // a malloced string -- if non-zero, free it before replacing it - uint32_t songtime_in_milliseconds; -} track_metadata_bundle; +int string_update(char **str, int *changed, char *s); +int int_update(int *receptacle, int *changed, int value); struct metadata_bundle; @@ -60,8 +41,14 @@ typedef struct metadata_bundle { char *client_ip; // IP number used by the audio source (i.e. the "client"), which is also the DACP // server + int client_ip_changed; + char *server_ip; // IP number used by Shairport Sync - char *progress_string; // progress string, emitted by the source from time to time + int server_ip_changed; + + char *progress_string; // progress string, emitted by the source from time to time + int progress_string_changed; + int player_thread_active; // true if a play thread is running int dacp_server_active; // true if there's a reachable DACP server (assumed to be the Airplay // client) ; false otherwise @@ -76,9 +63,65 @@ typedef struct metadata_bundle { enum shuffle_status_type shuffle_status; enum repeat_status_type repeat_status; - struct track_metadata_bundle *track_metadata; + // the following pertain to the track playing + + char *cover_art_pathname; + int cover_art_pathname_changed; + + uint32_t item_id; // seems to be a track ID -- see itemid in DACP.c + int item_id_changed; + int item_id_received; // important for deciding if the track information should be ignored. + + unsigned char + item_composite_id[16]; // seems to be nowplaying 4 ids: dbid, plid, playlistItem, itemid + int item_composite_id_changed; + + char *track_name; + int track_name_changed; + + char *artist_name; + int artist_name_changed; + + char *album_artist_name; + int album_artist_name_changed; + + char *album_name; + int album_name_changed; + + char *genre; + int genre_changed; + + char *comment; + int comment_changed; + + char *composer; + int composer_changed; + + char *file_kind; + int file_kind_changed; + + char *song_description; + int song_description_changed; + + char *song_album_artist; + int song_album_artist_changed; + + char *sort_name; + int sort_name_changed; + + char *sort_artist; + int sort_artist_changed; + + char *sort_album; + int sort_album_changed; + + char *sort_composer; + int sort_composer_changed; + + uint32_t songtime_in_milliseconds; + int songtime_in_milliseconds_changed; - char *cover_art_pathname; // if non-zero, it will have been assigned with malloc. + // end enum play_status_type player_state; // this is the state of the actual player itself, which can be a bit noisy. diff --git a/mpris-service.c b/mpris-service.c index 668d776e2..37e7bf8a5 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -82,105 +82,100 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused) debug(1, "This should never happen."); } - GVariantBuilder *dict_builder, *aa; - - /* Build the metadata array */ - // debug(1,"Build metadata"); - dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); - - // Make up the artwork URI if we have one - if (argc->cover_art_pathname) { - char artURIstring[1024]; - snprintf(artURIstring, sizeof(artURIstring), "file://%s", argc->cover_art_pathname); - // debug(1,"artURI String: \"%s\".",artURIstring); - GVariant *artUrl = g_variant_new("s", artURIstring); - g_variant_builder_add(dict_builder, "{sv}", "mpris:artUrl", artUrl); - } - - // Add the TrackID if we have one - // 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; - int non_zero = 0; - if (argc->track_metadata) { + /* + // Add the TrackID if we have one + // 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; + int non_zero = 0; for (it = 0; it < 16; it++) { if (argc->track_metadata->item_composite_id[it]) non_zero = 1; snprintf(pt, 3, "%02X", argc->track_metadata->item_composite_id[it]); pt += 2; } + *pt = 0; + + if (non_zero) { + // debug(1, "Set ID using composite ID: \"0x%s\".", st); + char trackidstring[1024]; + snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%s", st); + GVariant *trackid = g_variant_new("o", trackidstring); + g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid); + } else if ((argc->track_metadata) && (argc->track_metadata->item_id)) { + char trackidstring[128]; + // debug(1, "Set ID using mper ID: \"%u\".",argc->item_id); + snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u", + argc->track_metadata->item_id); + GVariant *trackid = g_variant_new("o", trackidstring); + g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid); + } + + */ + + // Build the metadata array + debug(2, "Build metadata"); + GVariantBuilder *dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + // Add in the artwork URI if it exists. + if (argc->cover_art_pathname) { + GVariant *artUrl = g_variant_new("s", argc->cover_art_pathname); + g_variant_builder_add(dict_builder, "{sv}", "mpris:artUrl", artUrl); } - *pt = 0; - if (non_zero) { - // debug(1, "Set ID using composite ID: \"0x%s\".", st); - char trackidstring[1024]; - snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%s", st); - GVariant *trackid = g_variant_new("o", trackidstring); - g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid); - } else if ((argc->track_metadata) && (argc->track_metadata->item_id)) { + // Add in the Track ID based on the 'mper' metadata if it is non-zero + if (argc->item_id != 0) { char trackidstring[128]; - // debug(1, "Set ID using mper ID: \"%u\".",argc->item_id); snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u", - argc->track_metadata->item_id); + argc->item_id); GVariant *trackid = g_variant_new("o", trackidstring); g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid); } - // Add the track length if it's non-zero - if ((argc->track_metadata) && (argc->track_metadata->songtime_in_milliseconds)) { - uint64_t track_length_in_microseconds = argc->track_metadata->songtime_in_milliseconds; - track_length_in_microseconds *= 1000; // to microseconds in 64-bit precision - // Make up the track name and album name - // debug(1, "Set tracklength to %lu.", track_length_in_microseconds); - GVariant *tracklength = g_variant_new("x", track_length_in_microseconds); - g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength); + // Add the track name if it exists + if (argc->track_name) { + GVariant *track_name = g_variant_new("s", argc->track_name); + g_variant_builder_add(dict_builder, "{sv}", "xesam:title", track_name); } - // Add the track name if there is one - if ((argc->track_metadata) && (argc->track_metadata->track_name)) { - // debug(1, "Track name set to \"%s\".", argc->track_name); - GVariant *trackname = g_variant_new("s", argc->track_metadata->track_name); - g_variant_builder_add(dict_builder, "{sv}", "xesam:title", trackname); + // Add the album name if it exists + if (argc->album_name) { + GVariant *album_name = g_variant_new("s", argc->album_name); + g_variant_builder_add(dict_builder, "{sv}", "xesam:album", album_name); } - // Add the album name if there is one - if ((argc->track_metadata) && (argc->track_metadata->album_name)) { - // debug(1, "Album name set to \"%s\".", argc->album_name); - GVariant *albumname = g_variant_new("s", argc->track_metadata->album_name); - g_variant_builder_add(dict_builder, "{sv}", "xesam:album", albumname); + // Add the artist name if it exists + if (argc->artist_name) { + GVariantBuilder *artist_as = g_variant_builder_new(G_VARIANT_TYPE("as")); + g_variant_builder_add(artist_as, "s", argc->artist_name); + GVariant *artists = g_variant_builder_end(artist_as); + g_variant_builder_unref(artist_as); + g_variant_builder_add(dict_builder, "{sv}", "xesam:artist", artists); } - // Add the artists if there are any (actually there will be at most one, but put it in an array) - if ((argc->track_metadata) && (argc->track_metadata->artist_name)) { - /* Build the artists array */ - // debug(1,"Build artist array"); - aa = g_variant_builder_new(G_VARIANT_TYPE("as")); - g_variant_builder_add(aa, "s", argc->track_metadata->artist_name); - GVariant *artists = g_variant_builder_end(aa); - g_variant_builder_unref(aa); - g_variant_builder_add(dict_builder, "{sv}", "xesam:artist", artists); + // Add the genre if it exists + if (argc->genre) { + GVariantBuilder *genre_as = g_variant_builder_new(G_VARIANT_TYPE("as")); + g_variant_builder_add(genre_as, "s", argc->genre); + GVariant *genre = g_variant_builder_end(genre_as); + g_variant_builder_unref(genre_as); + g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genre); } - // Add the genres if there are any (actually there will be at most one, but put it in an array) - if ((argc->track_metadata) && (argc->track_metadata->genre)) { - // debug(1,"Build genre"); - aa = g_variant_builder_new(G_VARIANT_TYPE("as")); - g_variant_builder_add(aa, "s", argc->track_metadata->genre); - GVariant *genres = g_variant_builder_end(aa); - g_variant_builder_unref(aa); - g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genres); + if (argc->songtime_in_milliseconds) { + uint64_t track_length_in_microseconds = argc->songtime_in_milliseconds; + track_length_in_microseconds *= 1000; // to microseconds in 64-bit precision + // Make up the track name and album name + // debug(1, "Set tracklength to %lu.", track_length_in_microseconds); + GVariant *tracklength = g_variant_new("x", track_length_in_microseconds); + g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength); } GVariant *dict = g_variant_builder_end(dict_builder); g_variant_builder_unref(dict_builder); - - // debug(1,"Set metadata"); media_player2_player_set_metadata(mprisPlayerPlayerSkeleton, dict); - - // media_player2_player_set_volume(mprisPlayerPlayerSkeleton, metadata_store.speaker_volume); } static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation,