Squashed commit of the following:

Fix Issue #890 -- https://github.com/mikebrady/shairport-sync/issues/890
    Recognise situation where a track has no artwork properly.
    Less likely to drop the 'availabile' property.
    Always output the full metadata whenever any part of it has changed.
    Metadata coming from dacp is now incorporated,
This commit is contained in:
Mike Brady
2019-08-29 11:47:49 +01:00
parent d846bc53be
commit 86b6d7bfff
7 changed files with 435 additions and 439 deletions
+36
View File
@@ -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;
}
+2
View File
@@ -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
+50 -63
View File
@@ -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;
/*
+39 -43
View File
@@ -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;
+170 -233
View File
@@ -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);
}
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': {
metadata_hub_modify_prolog();
int changed = (metadata_store.active_state != AM_ACTIVE);
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;
case 'abeg':
metadata_hub_modify_prolog();
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: {
+67 -24
View File
@@ -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; // if non-zero, it will have been assigned with malloc.
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;
// end
enum play_status_type
player_state; // this is the state of the actual player itself, which can be a bit noisy.
+71 -76
View File
@@ -82,56 +82,90 @@ 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;
*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)) {
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);
}
// 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;
// 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 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 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 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);
@@ -139,48 +173,9 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength);
}
// 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 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 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 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);
}
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,