diff --git a/src/input.c b/src/input.c index 362d8da0..4bd5aead 100644 --- a/src/input.c +++ b/src/input.c @@ -464,7 +464,7 @@ input_flush(short *flags) } int -input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup) +input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime) { int type; @@ -490,7 +490,7 @@ input_metadata_get(struct input_metadata *metadata, struct player_source *ps, in if (!inputs[type]->metadata_get) return 0; - return inputs[type]->metadata_get(metadata, ps); + return inputs[type]->metadata_get(metadata, ps, rtptime); } void @@ -499,6 +499,7 @@ input_metadata_free(struct input_metadata *metadata, int content_only) free(metadata->artist); free(metadata->title); free(metadata->album); + free(metadata->genre); free(metadata->artwork_url); if (!content_only) diff --git a/src/input.h b/src/input.h index b828a03a..1e81b058 100644 --- a/src/input.h +++ b/src/input.h @@ -82,9 +82,13 @@ struct input_metadata uint64_t rtptime; uint64_t offset; + // The player will update queue_item with the below + uint32_t song_length; + char *artist; char *title; char *album; + char *genre; char *artwork_url; }; @@ -112,7 +116,7 @@ struct input_definition int (*seek)(struct player_source *ps, int seek_ms); // Return metadata - int (*metadata_get)(struct input_metadata *metadata, struct player_source *ps); + int (*metadata_get)(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime); // Initialization function called during startup int (*init)(void); @@ -214,7 +218,7 @@ input_flush(short *flags); * Gets metadata from the input, returns 0 if metadata is set, otherwise -1 */ int -input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup); +input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime); /* * Free the entire struct diff --git a/src/inputs/file_http.c b/src/inputs/file_http.c index f4494e04..5bda057d 100644 --- a/src/inputs/file_http.c +++ b/src/inputs/file_http.c @@ -105,7 +105,7 @@ seek(struct player_source *ps, int seek_ms) } static int -metadata_get_http(struct input_metadata *metadata, struct player_source *ps) +metadata_get_http(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime) { struct http_icy_metadata *m; int changed; diff --git a/src/inputs/pipe.c b/src/inputs/pipe.c index 94dc9fe2..8707fdf4 100644 --- a/src/inputs/pipe.c +++ b/src/inputs/pipe.c @@ -45,6 +45,7 @@ #include #include +#include #include "misc.h" #include "logger.h" @@ -103,6 +104,8 @@ static struct pipe *pipelist; static struct pipe *pipe_metadata; // We read metadata into this evbuffer static struct evbuffer *pipe_metadata_buf; +// Parsed metadata goes here +static struct input_metadata pipe_metadata_parsed; // True if there is new metadata to push to the player static bool pipe_metadata_is_new; @@ -183,22 +186,143 @@ pipe_find(int id) return NULL; } -static int -pipe_metadata_parse(struct evbuffer *evbuf) +// Convert to macro? +static inline uint32_t +dmapval(const char s[4]) { - char *line; - size_t size; + return ((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0)); +} - while ( (line = evbuffer_readln(evbuf, &size, EVBUFFER_EOL_CRLF)) ) +static void +parse_progress(struct input_metadata *m, char *progress) +{ + char *s; + char *ptr; + uint64_t start; + uint64_t pos; + uint64_t end; + + if (!(s = strtok_r(progress, "/", &ptr))) + return; + safe_atou64(s, &start); + + if (!(s = strtok_r(NULL, "/", &ptr))) + return; + safe_atou64(s, &pos); + + if (!(s = strtok_r(NULL, "/", &ptr))) + return; + safe_atou64(s, &end); + + if (!start || !pos || !end) + return; + + m->rtptime = start; // Not actually used - we have our own rtptime + m->offset = (pos > start) ? (pos - start) : 0; + m->song_length = (end - start) * 10 / 441; // Convert to ms based on 44100 +} + +// returns 0 on metadata found, otherwise -1 +static int +parse_item(struct input_metadata *m, mxml_node_t *item) +{ + mxml_node_t *needle; + const char *s; + uint32_t type; + uint32_t code; + char *progress; + char **data; + + type = 0; + if ( (needle = mxmlFindElement(item, item, "type", NULL, NULL, MXML_DESCEND)) && + (s = mxmlGetText(needle, NULL)) ) + sscanf(s, "%8x", &type); + + code = 0; + if ( (needle = mxmlFindElement(item, item, "code", NULL, NULL, MXML_DESCEND)) && + (s = mxmlGetText(needle, NULL)) ) + sscanf(s, "%8x", &code); + + if (!type || !code) { - DPRINTF(E_DBG, L_PLAYER, "Parsing %s\n", line); + DPRINTF(E_WARN, L_PLAYER, "No type (%d) or code (%d) in pipe metadata\n", type, code); + return -1; + } - free(line); + if (code == dmapval("asal")) + data = &m->album; + else if (code == dmapval("asar")) + data = &m->artist; + else if (code == dmapval("minm")) + data = &m->title; + else if (code == dmapval("asgn")) + data = &m->genre; + else if (code == dmapval("prgr")) + data = &progress; + else + return -1; + + if ( (needle = mxmlFindElement(item, item, "data", NULL, NULL, MXML_DESCEND)) && + (s = mxmlGetText(needle, NULL)) ) + { + if (data != &progress) + free(*data); + + *data = b64_decode(s); + + DPRINTF(E_DBG, L_PLAYER, "Read Shairport metadata (type=%8x, code=%8x): '%s'\n", type, code, *data); + + if (data == &progress) + { + parse_progress(m, progress); + free(*data); + } + + return 0; + } + + return -1; +} + +static int +pipe_metadata_parse(struct input_metadata *m, struct evbuffer *evbuf) +{ + mxml_node_t *xml; + mxml_node_t *haystack; + mxml_node_t *item; + const char *s; + int found; + + xml = mxmlNewXML("1.0"); + if (!xml) + return -1; + + s = (char *)evbuffer_pullup(evbuf, -1); + haystack = mxmlLoadString(xml, s, MXML_NO_CALLBACK); + if (!haystack) + { + DPRINTF(E_LOG, L_PLAYER, "Could not parse pipe metadata\n"); + mxmlDelete(xml); + evbuffer_drain(evbuf, evbuffer_get_length(evbuf)); + return -1; } evbuffer_drain(evbuf, evbuffer_get_length(evbuf)); - return 0; +// DPRINTF(E_DBG, L_PLAYER, "Parsing %s\n", s); + + found = 0; + for (item = mxmlGetFirstChild(haystack); item; item = mxmlWalkNext(item, haystack, MXML_NO_DESCEND)) + { + if (mxmlGetType(item) != 0) + continue; + + if (parse_item(m, item) == 0) + found = 1; + } + + mxmlDelete(xml); + return found; } /* ---------------------------- GENERAL PIPE I/O -------------------------- */ @@ -416,8 +540,6 @@ pipe_metadata_read_cb(evutil_socket_t fd, short event, void *arg) struct evbuffer_ptr evptr; int ret; - DPRINTF(E_DBG, L_PLAYER, "BANG\n"); - ret = evbuffer_read(pipe_metadata_buf, pipe_metadata->fd, PIPE_READ_MAX); if (ret < 0) { @@ -442,22 +564,23 @@ pipe_metadata_read_cb(evutil_socket_t fd, short event, void *arg) evptr = evbuffer_search(pipe_metadata_buf, "", strlen(""), NULL); if (evptr.pos < 0) { - DPRINTF(E_DBG, L_PLAYER, "Incomplete\n"); goto readd; } // NULL-terminate the buffer evbuffer_add(pipe_metadata_buf, "", 1); - ret = pipe_metadata_parse(pipe_metadata_buf); + ret = pipe_metadata_parse(&pipe_metadata_parsed, pipe_metadata_buf); if (ret < 0) { pipe_metadata_watch_del(NULL); return; } - - // Trigger notification in playback loop - pipe_metadata_is_new = 1; + else if (ret > 0) + { + // Trigger notification in playback loop + pipe_metadata_is_new = 1; + } readd: if (pipe_metadata && pipe_metadata->ev) @@ -629,6 +752,34 @@ stop(struct player_source *ps) return 0; } +// FIXME Thread safety of pipe_metadata_parsed +static int +metadata_get(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime) +{ + if (pipe_metadata_parsed.artist) + swap_pointers(&metadata->artist, &pipe_metadata_parsed.artist); + if (pipe_metadata_parsed.title) + swap_pointers(&metadata->title, &pipe_metadata_parsed.title); + if (pipe_metadata_parsed.album) + swap_pointers(&metadata->album, &pipe_metadata_parsed.album); + if (pipe_metadata_parsed.genre) + swap_pointers(&metadata->genre, &pipe_metadata_parsed.genre); + if (pipe_metadata_parsed.artwork_url) + swap_pointers(&metadata->artwork_url, &pipe_metadata_parsed.artwork_url); + + if (pipe_metadata_parsed.song_length) + { + if (rtptime > ps->stream_start) + metadata->rtptime = rtptime - pipe_metadata_parsed.offset; + metadata->offset = pipe_metadata_parsed.offset; + metadata->song_length = pipe_metadata_parsed.song_length; + } + + input_metadata_free(&pipe_metadata_parsed, 1); + + return 0; +} + // Thread: main static int init(void) @@ -665,6 +816,7 @@ struct input_definition input_pipe = .setup = setup, .start = start, .stop = stop, + .metadata_get = metadata_get, .init = init, .deinit = deinit, }; diff --git a/src/player.c b/src/player.c index d3e6f3ec..4dac3739 100644 --- a/src/player.c +++ b/src/player.c @@ -488,8 +488,12 @@ metadata_update_cb(void *arg) swap_pointers(&queue_item->title, &metadata->title); if (metadata->album) swap_pointers(&queue_item->album, &metadata->album); + if (metadata->genre) + swap_pointers(&queue_item->genre, &metadata->genre); if (metadata->artwork_url) swap_pointers(&queue_item->artwork_url, &metadata->artwork_url); + if (metadata->song_length) + queue_item->song_length = metadata->song_length; ret = db_queue_update_item(queue_item); if (ret < 0) @@ -520,7 +524,7 @@ metadata_trigger(int startup) struct input_metadata metadata; int ret; - ret = input_metadata_get(&metadata, cur_streaming, startup); + ret = input_metadata_get(&metadata, cur_streaming, startup, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); if (ret < 0) return;