[input/pipe] Parse basic Shairport metadata using mxml

This commit is contained in:
ejurgensen 2017-01-27 11:05:24 +01:00
parent ea874154b2
commit dc84294348
5 changed files with 182 additions and 21 deletions

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -45,6 +45,7 @@
#include <event2/event.h>
#include <event2/buffer.h>
#include <mxml.h>
#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, "</item>", strlen("</item>"), 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,
};

View File

@ -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;