[player/outputs] New metadata handling (wip)

This commit is contained in:
ejurgensen 2019-03-18 23:04:34 +01:00
parent ab0a6055b9
commit 4fb45e84f2
5 changed files with 258 additions and 475 deletions

View File

@ -37,6 +37,7 @@
#include "listener.h"
#include "db.h"
#include "player.h" //TODO remove me when player_pmap is removed again
#include "worker.h"
#include "outputs.h"
extern struct output_definition output_raop;
@ -418,6 +419,60 @@ device_list_sort(void)
while (swaps > 0);
}
static void
metadata_cb_send(int fd, short what, void *arg)
{
struct output_metadata *metadata = arg;
int ret;
event_free(metadata->ev);
metadata->ev = NULL;
ret = metadata->finalize_cb(metadata);
if (ret < 0)
return;
outputs[metadata->type]->metadata_send(metadata);
}
// *** Worker thread ***
static void
metadata_cb_prepare(void *arg)
{
struct output_metadata *metadata = *((struct output_metadata **)arg);
metadata->priv = outputs[metadata->type]->metadata_prepare(metadata);
if (!metadata->priv)
{
event_free(metadata->ev);
free(metadata);
return;
}
// Metadata is prepared, let the player thread do the actual sending
event_active(metadata->ev, 0, 0);
}
static void
metadata_send(enum output_types type, uint32_t item_id, bool startup, output_metadata_finalize_cb cb)
{
struct output_metadata *metadata;
CHECK_NULL(L_PLAYER, metadata = calloc(1, sizeof(struct output_metadata)));
metadata->type = type;
metadata->item_id = item_id;
metadata->startup = startup;
metadata->finalize_cb = cb;
metadata->ev = event_new(evbase_player, -1, 0, metadata_cb_send, metadata);
if (outputs[type]->metadata_prepare)
worker_execute(metadata_cb_prepare, &metadata, sizeof(struct output_metadata *), 0);
else
outputs[type]->metadata_send(metadata);
}
/* ----------------------------------- API ---------------------------------- */
@ -729,6 +784,12 @@ outputs_device_stop_delayed(struct output_device *device, output_status_cb cb)
if (outputs[device->type]->disabled || !outputs[device->type]->device_stop)
return -1;
if (!device->session)
{
DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_stop_delayed() called for a device that has no session\n");
return -1;
}
outputs[device->type]->device_cb_set(device, callback_add(device, cb));
event_add(device->stop_timer, &outputs_stop_timeout);
@ -899,64 +960,17 @@ outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *qua
buffer_drain(&output_buffer);
}
struct output_metadata *
outputs_metadata_prepare(int id)
{
struct output_metadata *omd;
struct output_metadata *new;
void *metadata;
int i;
omd = NULL;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
continue;
if (!outputs[i]->metadata_prepare)
continue;
metadata = outputs[i]->metadata_prepare(id);
if (!metadata)
continue;
new = calloc(1, sizeof(struct output_metadata));
if (!new)
return omd;
if (omd)
new->next = omd;
omd = new;
omd->type = i;
omd->metadata = metadata;
}
return omd;
}
void
outputs_metadata_send(struct output_metadata *omd, uint64_t rtptime, uint64_t offset, int startup)
outputs_metadata_send(uint32_t item_id, bool startup, output_metadata_finalize_cb cb)
{
struct output_metadata *ptr;
int i;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
if (outputs[i]->disabled || !outputs[i]->metadata_send)
continue;
if (!outputs[i]->metadata_send)
continue;
// Go through linked list to find appropriate metadata for type
for (ptr = omd; ptr; ptr = ptr->next)
if (ptr->type == i)
break;
if (!ptr)
continue;
outputs[i]->metadata_send(ptr->metadata, rtptime, offset, startup);
metadata_send(i, item_id, startup, cb);
}
}
@ -967,41 +981,10 @@ outputs_metadata_purge(void)
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
if (outputs[i]->disabled || !outputs[i]->metadata_purge)
continue;
if (outputs[i]->metadata_purge)
outputs[i]->metadata_purge();
}
}
void
outputs_metadata_prune(uint64_t rtptime)
{
int i;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
continue;
if (outputs[i]->metadata_prune)
outputs[i]->metadata_prune(rtptime);
}
}
void
outputs_metadata_free(struct output_metadata *omd)
{
struct output_metadata *ptr;
if (!omd)
return;
for (ptr = omd; omd; ptr = omd)
{
omd = ptr->next;
free(ptr);
outputs[i]->metadata_purge();
}
}

View File

@ -2,6 +2,7 @@
#ifndef __OUTPUTS_H__
#define __OUTPUTS_H__
#include <stdbool.h>
#include <time.h>
#include <event2/event.h>
#include <event2/buffer.h>
@ -62,6 +63,14 @@
// different values can only do so within a limited range (maybe max 3 secs)
#define OUTPUTS_BUFFER_DURATION 2
// Forward declarations
struct output_device;
struct output_metadata;
enum output_device_state;
typedef void (*output_status_cb)(struct output_device *device, enum output_device_state status);
typedef int (*output_metadata_finalize_cb)(struct output_metadata *metadata);
// Must be in sync with outputs[] in outputs.c
enum output_types
{
@ -147,13 +156,24 @@ struct output_device
struct output_device *next;
};
// Linked list of metadata prepared by each output backend
struct output_metadata
{
enum output_types type;
void *metadata;
uint32_t item_id;
struct output_metadata *next;
// Progress data, filled out by finalize_cb()
uint32_t pos_ms;
uint32_t len_ms;
struct timespec pts;
bool startup;
// Private output data made by the metadata_prepare()
void *priv;
struct event *ev;
// Finalize before right before sending, e.g. set playback position
output_metadata_finalize_cb finalize_cb;
};
struct output_data
@ -172,8 +192,6 @@ struct output_buffer
struct output_data data[OUTPUTS_MAX_QUALITY_SUBSCRIPTIONS + 1];
} output_buffer;
typedef void (*output_status_cb)(struct output_device *device, enum output_device_state status);
struct output_definition
{
// Name of the output
@ -229,11 +247,16 @@ struct output_definition
// Authorize an output with a pin-code (probably coming from the filescanner)
void (*authorize)(const char *pin);
// Metadata
void *(*metadata_prepare)(int id);
void (*metadata_send)(void *metadata, uint64_t rtptime, uint64_t offset, int startup);
// Called from worker thread for async preparation of metadata (e.g. getting
// artwork, which might involce downloading image data). The prepared data is
// saved to metadata->data, which metadata_send() can use.
void *(*metadata_prepare)(struct output_metadata *metadata);
// Send metadata to outputs. Ownership of *metadata is transferred.
void (*metadata_send)(struct output_metadata *metadata);
// Output will cleanup all metadata (so basically like flush but for metadata)
void (*metadata_purge)(void);
void (*metadata_prune)(uint64_t rtptime);
};
// Our main list of devices, not for use by backend modules
@ -317,21 +340,12 @@ outputs_stop_delayed_cancel(void);
void
outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts);
struct output_metadata *
outputs_metadata_prepare(int id);
void
outputs_metadata_send(struct output_metadata *omd, uint64_t rtptime, uint64_t offset, int startup);
outputs_metadata_send(uint32_t item_id, bool startup, output_metadata_finalize_cb cb);
void
outputs_metadata_purge(void);
void
outputs_metadata_prune(uint64_t rtptime);
void
outputs_metadata_free(struct output_metadata *omd);
void
outputs_authorize(enum output_types type, const char *pin);

View File

@ -89,7 +89,6 @@
// AirTunes v2 number of samples per packet
// Probably using this value because 44100/352 and 48000/352 has good 32 byte
// alignment, which improves performance of some encoders
// TODO Should probably not be fixed, but vary with quality
#define RAOP_SAMPLES_PER_PACKET 352
// How many RTP packets keep in a buffer for retransmission
@ -98,7 +97,7 @@
#define RAOP_MD_DELAY_STARTUP 15360
#define RAOP_MD_DELAY_SWITCH (RAOP_MD_DELAY_STARTUP * 2)
/* This is an arbitrary value which just needs to be kept in sync with the config */
// This is an arbitrary value which just needs to be kept in sync with the config
#define RAOP_CONFIG_MAX_VOLUME 11
union sockaddr_all
@ -239,15 +238,8 @@ struct raop_session
struct raop_metadata
{
struct evbuffer *metadata;
struct evbuffer *artwork;
int artwork_fmt;
/* Progress data */
uint32_t start;
uint32_t end;
struct raop_metadata *next;
};
struct raop_service
@ -339,8 +331,7 @@ static struct raop_service control_4svc;
static struct raop_service control_6svc;
/* Metadata */
static struct raop_metadata *metadata_head;
static struct raop_metadata *metadata_tail;
static struct output_metadata *raop_cur_metadata;
/* Keep-alive timer - hack for ATV's with tvOS 10 */
static struct event *keep_alive_timer;
@ -2164,138 +2155,70 @@ session_make(struct output_device *rd, int family, int callback_id, bool only_pr
static void
raop_metadata_free(struct raop_metadata *rmd)
{
evbuffer_free(rmd->metadata);
if (!rmd)
return;
if (rmd->metadata)
evbuffer_free(rmd->metadata);
if (rmd->artwork)
evbuffer_free(rmd->artwork);
free(rmd);
}
static void
raop_metadata_purge(void)
{
struct raop_metadata *rmd;
if (!raop_cur_metadata)
return;
for (rmd = metadata_head; rmd; rmd = metadata_head)
{
metadata_head = rmd->next;
raop_metadata_free(rmd);
}
metadata_tail = NULL;
raop_metadata_free(raop_cur_metadata->priv);
free(raop_cur_metadata);
raop_cur_metadata = NULL;
}
static void
raop_metadata_prune(uint64_t rtptime)
{
struct raop_metadata *rmd;
for (rmd = metadata_head; rmd; rmd = metadata_head)
{
if (rmd->end >= rtptime)
break;
if (metadata_tail == metadata_head)
metadata_tail = rmd->next;
metadata_head = rmd->next;
raop_metadata_free(rmd);
}
}
/* Thread: worker */
// *** Thread: worker ***
static void *
raop_metadata_prepare(int id)
raop_metadata_prepare(struct output_metadata *metadata)
{
struct db_queue_item *queue_item;
struct raop_metadata *rmd;
struct evbuffer *tmp;
int ret;
rmd = (struct raop_metadata *)malloc(sizeof(struct raop_metadata));
if (!rmd)
queue_item = db_queue_fetch_byitemid(metadata->item_id);
if (!queue_item)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for RAOP metadata\n");
DPRINTF(E_LOG, L_RAOP, "Could not fetch queue item\n");
return NULL;
}
memset(rmd, 0, sizeof(struct raop_metadata));
queue_item = db_queue_fetch_byitemid(id);
if (!queue_item)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for queue item\n");
goto out_rmd;
}
/* Get artwork */
rmd->artwork = evbuffer_new();
if (!rmd->artwork)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for artwork evbuffer; no artwork will be sent\n");
goto skip_artwork;
}
CHECK_NULL(L_RAOP, rmd = calloc(1, sizeof(struct raop_metadata)));
CHECK_NULL(L_RAOP, rmd->artwork = evbuffer_new());
CHECK_NULL(L_RAOP, rmd->metadata = evbuffer_new());
CHECK_NULL(L_RAOP, tmp = evbuffer_new());
ret = artwork_get_item(rmd->artwork, queue_item->file_id, ART_DEFAULT_WIDTH, ART_DEFAULT_HEIGHT);
if (ret < 0)
{
DPRINTF(E_INFO, L_RAOP, "Failed to retrieve artwork for file id %d; no artwork will be sent\n", id);
DPRINTF(E_INFO, L_RAOP, "Failed to retrieve artwork for file '%s'; no artwork will be sent\n", queue_item->path);
evbuffer_free(rmd->artwork);
rmd->artwork = NULL;
}
rmd->artwork_fmt = ret;
skip_artwork:
/* Turn it into DAAP metadata */
tmp = evbuffer_new();
if (!tmp)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for temporary metadata evbuffer; metadata will not be sent\n");
goto out_qi;
}
rmd->metadata = evbuffer_new();
if (!rmd->metadata)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for metadata evbuffer; metadata will not be sent\n");
evbuffer_free(tmp);
goto out_qi;
}
ret = dmap_encode_queue_metadata(rmd->metadata, tmp, queue_item);
evbuffer_free(tmp);
free_queue_item(queue_item, 0);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not encode file metadata; metadata will not be sent\n");
goto out_metadata;
raop_metadata_free(rmd);
return NULL;
}
/* Progress - raop_metadata_send() will add rtptime to these */
rmd->start = 0;
rmd->end = ((uint64_t)queue_item->song_length * 44100UL) / 1000UL;
free_queue_item(queue_item, 0);
return rmd;
out_metadata:
evbuffer_free(rmd->metadata);
out_qi:
free_queue_item(queue_item, 0);
out_rmd:
free(rmd);
return NULL;
}
static void
@ -2329,37 +2252,62 @@ raop_cb_metadata(struct evrtsp_request *req, void *arg)
session_failure(rs);
}
static int
raop_metadata_send_progress(struct raop_session *rs, struct evbuffer *evbuf, struct raop_metadata *rmd, uint32_t offset, uint32_t delay)
static void
raop_metadata_rtptimes_get(uint32_t *start, uint32_t *display, uint32_t *pos, uint32_t *end, struct raop_master_session *rms, struct output_metadata *metadata)
{
uint32_t display;
int ret;
struct rtp_session *rtp_session = rms->rtp_session;
uint64_t sample_rate;
uint32_t elapsed_ms;
int delay;
/* Here's the deal with progress values:
* - first value, called display, is always start minus a delay
* -> delay x1 if streaming is starting for this device (joining or not)
* -> delay x2 if stream is switching to a new song
* - second value, called start, is the RTP time of the first sample for this
* - second value, called pos, is the RTP time of the first sample for this
* song for this device
* -> start of song
* -> start of song + offset if device is joining in the middle of a song,
* or getting out of a pause or seeking
* - third value, called end, is the RTP time of the last sample for this song
*/
sample_rate = rtp_session->quality.sample_rate;
display = RAOP_RTPTIME(rmd->start - delay);
/* First calculate the rtptime that streaming of this item started:
* - at time metadata->pts the elapsed time was metadata->pos_ms
* - the time is now rtp_session->pts and the position is rtp_session->pos
* -> time since item started is elapsed = metadata->pos_ms + (rtp_session->pts - metadata->pts)
* -> start must then be start = rtp_session->pos - elapsed * sample_rate;
*/
elapsed_ms = metadata->pos_ms;
ret = evbuffer_add_printf(evbuf, "progress: %u/%u/%u\r\n", display, RAOP_RTPTIME(rmd->start + offset), RAOP_RTPTIME(rmd->end));
*start = rtp_session->pos - sample_rate * elapsed_ms / 1000;
if (metadata->startup)
delay = RAOP_MD_DELAY_STARTUP;
else
delay = RAOP_MD_DELAY_SWITCH;
*display = *start - delay;
*pos = MAX(rtp_session->pos, *start); // TODO is this calculation correct? It is not in line with the description above
*end = *start + sample_rate * metadata->len_ms / 1000;
DPRINTF(E_DBG, L_RAOP, "Metadata sr=%lu, pos_ms=%u, len_ms=%u, start=%u, display=%u, pos=%u, end=%u, rtptime=%u\n",
sample_rate, metadata->pos_ms, metadata->len_ms, *start, *display, *pos, *end, rtp_session->pos);
}
static int
raop_metadata_send_progress(struct raop_session *rs, struct evbuffer *evbuf, struct raop_metadata *rmd, uint32_t display, uint32_t pos, uint32_t end)
{
int ret;
ret = evbuffer_add_printf(evbuf, "progress: %u/%u/%u\r\n", display, pos, end);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not build progress string for sending\n");
return -1;
}
DPRINTF(E_DBG, L_PLAYER, "Metadata send is start_time=%" PRIu32 ", start=%" PRIu32 ", display=%" PRIu32 ", current=%" PRIu32 ", end=%" PRIu32 "\n",
rs->start_rtptime, rmd->start, rmd->start - delay, rmd->start + offset, rmd->end);
ret = raop_send_req_set_parameter(rs, evbuf, "text/parameters", NULL, raop_cb_metadata, "send_progress");
if (ret < 0)
DPRINTF(E_LOG, L_RAOP, "Could not send SET_PARAMETER progress request to '%s'\n", rs->devname);
@ -2435,25 +2383,25 @@ raop_metadata_send_metadata(struct raop_session *rs, struct evbuffer *evbuf, str
}
static int
raop_metadata_send_internal(struct raop_session *rs, struct raop_metadata *rmd, uint32_t offset, uint32_t delay)
raop_metadata_send_generic(struct raop_session *rs, struct output_metadata *metadata)
{
char rtptime[32];
struct raop_metadata *rmd = metadata->priv;
struct evbuffer *evbuf;
uint32_t start;
uint32_t display;
uint32_t pos;
uint32_t end;
char rtptime[32];
int ret;
evbuf = evbuffer_new();
if (!evbuf)
{
DPRINTF(E_LOG, L_RAOP, "Could not allocate temp evbuffer for metadata processing\n");
raop_metadata_rtptimes_get(&start, &display, &pos, &end, rs->master_session, metadata);
return -1;
}
CHECK_NULL(L_RAOP, evbuf = evbuffer_new());
ret = snprintf(rtptime, sizeof(rtptime), "rtptime=%u", RAOP_RTPTIME(rmd->start));
ret = snprintf(rtptime, sizeof(rtptime), "rtptime=%u", start);
if ((ret < 0) || (ret >= sizeof(rtptime)))
{
DPRINTF(E_LOG, L_RAOP, "RTP-Info too big for buffer while sending metadata\n");
ret = -1;
goto out;
}
@ -2462,29 +2410,25 @@ raop_metadata_send_internal(struct raop_session *rs, struct raop_metadata *rmd,
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not send metadata to '%s'\n", rs->devname);
ret = -1;
goto out;
}
if (!rmd->artwork)
goto skip_artwork;
ret = raop_metadata_send_artwork(rs, evbuf, rmd, rtptime);
if (ret < 0)
if (rmd->artwork)
{
DPRINTF(E_LOG, L_RAOP, "Could not send artwork to '%s'\n", rs->devname);
ret = -1;
goto out;
ret = raop_metadata_send_artwork(rs, evbuf, rmd, rtptime);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not send artwork to '%s'\n", rs->devname);
ret = -1;
goto out;
}
}
skip_artwork:
ret = raop_metadata_send_progress(rs, evbuf, rmd, offset, delay);
ret = raop_metadata_send_progress(rs, evbuf, rmd, display, pos, end);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not send progress to '%s'\n", rs->devname);
ret = -1;
goto out;
}
@ -2495,90 +2439,44 @@ raop_metadata_send_internal(struct raop_session *rs, struct raop_metadata *rmd,
return ret;
}
static void
static int
raop_metadata_startup_send(struct raop_session *rs)
{
struct raop_metadata *rmd;
uint32_t offset;
int sent;
int ret;
if (!rs->wants_metadata || !raop_cur_metadata)
return 0;
if (!rs->wants_metadata)
return;
// We don't need to preserve the previous value, this function is the only one
// using raop_cur_metadata
raop_cur_metadata->startup = true;
sent = 0;
for (rmd = metadata_head; rmd; rmd = rmd->next)
{
// Current song, rmd->start >= rmd->end if endless stream
if ((rs->start_rtptime >= rmd->start) && ( (rs->start_rtptime < rmd->end) || (rmd->start >= rmd->end) ))
{
offset = rs->start_rtptime - rmd->start;
ret = raop_metadata_send_internal(rs, rmd, offset, RAOP_MD_DELAY_STARTUP);
if (ret < 0)
{
session_failure(rs);
return;
}
sent = 1;
}
// Next song(s)
else if (sent && (rs->start_rtptime < rmd->start))
{
ret = raop_metadata_send_internal(rs, rmd, 0, RAOP_MD_DELAY_SWITCH);
if (ret < 0)
{
session_failure(rs);
return;
}
}
}
return raop_metadata_send_generic(rs, raop_cur_metadata);
}
static void
raop_metadata_send(void *metadata, uint64_t rtptime, uint64_t offset, int startup)
raop_metadata_send(struct output_metadata *metadata)
{
struct raop_metadata *rmd;
struct raop_session *rs;
struct raop_session *next;
uint32_t delay;
int ret;
rmd = metadata;
rmd->start += rtptime;
rmd->end += rtptime;
/* Add the rmd to the metadata list */
if (metadata_tail)
metadata_tail->next = rmd;
else
{
metadata_head = rmd;
metadata_tail = rmd;
}
for (rs = raop_sessions; rs; rs = next)
{
next = rs->next;
if (!(rs->state & RAOP_STATE_F_CONNECTED))
if (!(rs->state & RAOP_STATE_F_CONNECTED) || !rs->wants_metadata)
continue;
if (!rs->wants_metadata)
continue;
delay = (startup) ? RAOP_MD_DELAY_STARTUP : RAOP_MD_DELAY_SWITCH;
ret = raop_metadata_send_internal(rs, rmd, offset, delay);
ret = raop_metadata_send_generic(rs, metadata);
if (ret < 0)
{
session_failure(rs);
continue;
}
}
// Replace current metadata with the new stuff
raop_metadata_purge();
raop_cur_metadata = metadata;
}
@ -3718,15 +3616,13 @@ raop_cb_startup_volume(struct evrtsp_request *req, void *arg)
if (ret < 0)
goto cleanup;
raop_metadata_startup_send(rs);
ret = raop_metadata_startup_send(rs);
if (ret < 0)
goto cleanup;
ret = raop_v2_stream_open(rs);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not open streaming socket\n");
goto cleanup;
}
goto cleanup;
/* Session startup and setup is done, tell our user */
raop_status(rs);
@ -5068,7 +4964,6 @@ struct output_definition output_raop =
.metadata_prepare = raop_metadata_prepare,
.metadata_send = raop_metadata_send,
.metadata_purge = raop_metadata_purge,
.metadata_prune = raop_metadata_prune,
#ifdef RAOP_VERIFICATION
.authorize = raop_verification_setup,
#endif

View File

@ -33,22 +33,6 @@
* not always obeyed, for instance some outputs do their setup in ways that
* could block.
*
*
* About metadata
* --------------
* The player gets metadata from library + inputs and passes it to the outputs
* and other clients (e.g. Remotes).
*
* 1. On playback start, metadata from the library is loaded into the queue
* items, and these items are then the source of metadata for clients.
* 2. During playback, the input may signal new metadata by making a
* input_write() with the INPUT_FLAG_METADATA flag. When the player read
* reaches that data, the player will request the metadata from the input
* with input_metadata_get(). This metadata is then saved to the currently
* playing queue item, and the clients are told to update metadata.
* 3. Artwork works differently than textual metadata. The artwork module will
* look for artwork in the library, and addition also check the artwork_url
* of the queue_item.
*/
#ifdef HAVE_CONFIG_H
@ -162,12 +146,6 @@ struct speaker_get_param
struct player_speaker_info *spk_info;
};
struct metadata_param
{
struct input_metadata *input;
struct output_metadata *output;
};
struct speaker_auth_param
{
enum output_types type;
@ -184,16 +162,16 @@ union player_arg
struct player_source
{
/* Id of the file/item in the files database */
// Id of the file/item in the files database
uint32_t id;
/* Item-Id of the file/item in the queue */
// Item-Id of the file/item in the queue
uint32_t item_id;
/* Length of the file/item in milliseconds */
// Length of the file/item in milliseconds
uint32_t len_ms;
/* Quality of the source (sample rate etc.) */
// Quality of the source (sample rate etc.)
struct media_quality quality;
enum data_kind data_kind;
@ -292,7 +270,7 @@ static struct timespec player_tick_interval;
// Timer resolution
static struct timespec player_timer_res;
static struct timeval player_pause_timeout = { PLAYER_PAUSE_TIMEOUT, 0 };
//static struct timeval player_pause_timeout = { PLAYER_PAUSE_TIMEOUT, 0 };
// PLAYER_WRITE_BEHIND_MAX converted to clock ticks
static int pb_write_deficit_max;
@ -448,87 +426,24 @@ pause_timer_cb(int fd, short what, void *arg)
pb_abort();
}
// Callback from the worker thread. Here the heavy lifting is done: updating the
// db_queue_item, retrieving artwork (through outputs_metadata_prepare) and
// when done, telling the player to send the metadata to the clients
static void
metadata_update_cb(void *arg)
static int
metadata_finalize_cb(struct output_metadata *metadata)
{
struct input_metadata *metadata = arg;
struct output_metadata *o_metadata;
struct db_queue_item *queue_item;
int ret;
ret = input_metadata_get(metadata);
if (ret < 0)
if (metadata->item_id != pb_session.playing_now->item_id)
{
goto out_free_metadata;
DPRINTF(E_WARN, L_PLAYER, "Aborting metadata_send(), item_id changed during metadata preparation (%" PRIu32 " -> %" PRIu32 ")\n",
metadata->item_id, pb_session.playing_now->item_id);
return -1;
}
queue_item = db_queue_fetch_byitemid(metadata->item_id);
if (!queue_item)
{
DPRINTF(E_LOG, L_PLAYER, "Bug! Input metadata item_id does not match anything in queue\n");
goto out_free_metadata;
}
if (!metadata->pos_ms)
metadata->pos_ms = pb_session.playing_now->pos_ms;
if (!metadata->len_ms)
metadata->len_ms = pb_session.playing_now->len_ms;
if (!metadata->pts.tv_sec)
metadata->pts = pb_session.pts;
// Update queue item if metadata changed
if (metadata->artist || metadata->title || metadata->album || metadata->genre || metadata->artwork_url || metadata->song_length)
{
// Since we won't be using the metadata struct values for anything else than
// this we just swap pointers
if (metadata->artist)
swap_pointers(&queue_item->artist, &metadata->artist);
if (metadata->title)
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)
{
DPRINTF(E_LOG, L_PLAYER, "Database error while updating queue with new metadata\n");
goto out_free_queueitem;
}
}
o_metadata = outputs_metadata_prepare(metadata->item_id);
// Actual sending must be done by player, since the worker does not own the outputs
player_metadata_send(metadata, o_metadata);
outputs_metadata_free(o_metadata);
out_free_queueitem:
free_queue_item(queue_item, 0);
out_free_metadata:
input_metadata_free(metadata, 1);
}
// Gets the metadata, but since the actual update requires db writes and
// possibly retrieving artwork we let the worker do the next step
static void
metadata_trigger(int startup)
{
struct input_metadata metadata;
memset(&metadata, 0, sizeof(struct input_metadata));
metadata.item_id = pb_session.playing_now->item_id;
metadata.startup = startup;
metadata.start = pb_session.playing_now->read_start;
metadata.offset = pb_session.playing_now->play_start - pb_session.playing_now->read_start;
metadata.rtptime = pb_session.pos;
worker_execute(metadata_update_cb, &metadata, sizeof(metadata), 0);
return 0;
}
/*
@ -877,9 +792,10 @@ session_update_read_quality(struct media_quality *quality)
int samples_per_read;
if (quality_is_equal(quality, &pb_session.reading_now->quality))
return;
goto out;
pb_session.reading_now->quality = *quality;
samples_per_read = ((uint64_t)quality->sample_rate * (player_tick_interval.tv_nsec / 1000000)) / 1000;
pb_session.reading_now->output_buffer_samples = OUTPUTS_BUFFER_DURATION * quality->sample_rate;
@ -897,6 +813,9 @@ session_update_read_quality(struct media_quality *quality)
CHECK_NULL(L_PLAYER, pb_session.buffer);
pb_session.reading_now->play_start = pb_session.reading_now->read_start + pb_session.reading_now->output_buffer_samples;
out:
free(quality);
}
static void
@ -926,15 +845,11 @@ session_start(struct player_source *ps, uint32_t seek_ms)
/* ------------------------- Playback event handlers ------------------------ */
static void
event_read_quality()
event_read_quality(struct media_quality *quality)
{
DPRINTF(E_DBG, L_PLAYER, "event_read_quality()\n");
struct media_quality quality;
input_quality_get(&quality);
session_update_read_quality(&quality);
session_update_read_quality(quality);
}
// Stuff to do when read of current track ends
@ -969,11 +884,13 @@ event_read_start_next()
}
static void
event_metadata_new()
event_read_metadata(struct input_metadata *metadata)
{
DPRINTF(E_DBG, L_PLAYER, "event_metadata_new()\n");
DPRINTF(E_DBG, L_PLAYER, "event_read_metadata()\n");
metadata_trigger(0);
outputs_metadata_send(pb_session.playing_now->item_id, false, metadata_finalize_cb);
status_update(player_state);
}
static void
@ -1004,7 +921,10 @@ event_play_eof()
if (consume)
db_queue_delete_byitemid(pb_session.playing_now->item_id);
outputs_metadata_prune(pb_session.pos);
// outputs_metadata_prune(pb_session.pos);
if (pb_session.reading_next)
outputs_metadata_send(pb_session.reading_next->item_id, false, metadata_finalize_cb);
session_update_play_eof();
}
@ -1014,8 +934,6 @@ event_play_start()
{
DPRINTF(E_DBG, L_PLAYER, "event_play_start()\n");
event_metadata_new();
status_update(PLAY_PLAYING);
session_update_play_start();
@ -1055,7 +973,8 @@ event_read(int nsamples)
static inline int
source_read(int *nbytes, int *nsamples, struct media_quality *quality, uint8_t *buf, int len)
{
short flags;
short flag;
void *flagdata;
// Nothing to read
if (!pb_session.reading_now)
@ -1080,28 +999,28 @@ source_read(int *nbytes, int *nsamples, struct media_quality *quality, uint8_t *
*quality = pb_session.reading_now->quality;
*nsamples = 0;
*nbytes = input_read(buf, len, &flags);
if ((*nbytes < 0) || (flags & INPUT_FLAG_ERROR))
*nbytes = input_read(buf, len, &flag, &flagdata);
if ((*nbytes < 0) || (flag == INPUT_FLAG_ERROR))
{
DPRINTF(E_LOG, L_PLAYER, "Error reading source '%s' (id=%d)\n", pb_session.reading_now->path, pb_session.reading_now->id);
event_read_error();
return -1;
}
else if (flags & INPUT_FLAG_START_NEXT)
else if (flag == INPUT_FLAG_START_NEXT)
{
event_read_start_next();
}
else if (flags & INPUT_FLAG_EOF)
else if (flag == INPUT_FLAG_EOF)
{
event_read_eof();
}
else if (flags & INPUT_FLAG_METADATA)
else if (flag == INPUT_FLAG_METADATA)
{
event_metadata_new();
event_read_metadata((struct input_metadata *)flagdata);
}
else if (flags & INPUT_FLAG_QUALITY || quality->channels == 0)
else if (flag == INPUT_FLAG_QUALITY)
{
event_read_quality();
event_read_quality((struct media_quality *)flagdata);
}
if (*nbytes == 0 || quality->channels == 0)
@ -1321,25 +1240,6 @@ device_auth_kickoff(void *arg, int *retval)
}
static enum command_state
device_metadata_send(void *arg, int *retval)
{
struct metadata_param *metadata_param = arg;
struct input_metadata *imd;
struct output_metadata *omd;
imd = metadata_param->input;
omd = metadata_param->output;
outputs_metadata_send(omd, imd->rtptime, imd->offset, imd->startup);
status_update(player_state);
*retval = 0;
return COMMAND_END;
}
/* -------- Output device callbacks executed in the player thread ----------- */
static void
@ -1609,7 +1509,7 @@ pb_timer_start(void)
// The stop timers will be active if we have recently paused, but now that the
// playback loop has been kicked off, we deactivate them
event_del(player_pause_timeout_ev);
// event_del(player_pause_timeout_ev);
outputs_stop_delayed_cancel();
ret = event_add(pb_timer_ev, NULL);
@ -1689,21 +1589,30 @@ pb_session_start(struct db_queue_item *queue_item, uint32_t seek_ms)
// The input source is now open and ready, but we might actually be paused. So
// we activate the below event in case the user never starts us again
event_add(player_pause_timeout_ev, &player_pause_timeout);
// event_add(player_pause_timeout_ev, &player_pause_timeout);
return ret;
}
// Stops input source and stops read loop
static void
pb_session_pause(void)
{
pb_timer_stop();
source_stop();
}
// Stops input source and deallocates pb_session content
static void
pb_session_stop(void)
{
pb_timer_stop();
source_stop();
session_stop();
pb_timer_stop();
status_update(PLAY_STOPPED);
}
@ -1867,6 +1776,8 @@ playback_start_bh(void *arg, int *retval)
if (ret < 0)
goto error;
outputs_metadata_send(pb_session.playing_now->item_id, true, metadata_finalize_cb);
status_update(PLAY_PLAYING);
*retval = 0;
@ -1943,8 +1854,6 @@ playback_start_item(void *arg, int *retval)
}
}
metadata_trigger(1);
// Start sessions on selected devices
*retval = 0;
@ -2246,7 +2155,7 @@ playback_pause(void *arg, int *retval)
return COMMAND_END;
}
pb_timer_stop();
pb_session_pause();
*retval = outputs_flush(device_flush_cb);
outputs_metadata_purge();
@ -3184,20 +3093,6 @@ player_raop_verification_kickoff(char **arglist)
}
/* ---------------------------- Thread: worker ------------------------------ */
void
player_metadata_send(void *imd, void *omd)
{
struct metadata_param metadata_param;
metadata_param.input = imd;
metadata_param.output = omd;
commands_exec_sync(cmdbase, device_metadata_send, NULL, &metadata_param);
}
/* ---------------------------- Thread: player ------------------------------ */
static void *

View File

@ -138,7 +138,6 @@ player_shuffle_set(int enable);
int
player_consume_set(int enable);
void
player_queue_clear_history(void);
@ -157,9 +156,6 @@ player_device_remove(void *device);
void
player_raop_verification_kickoff(char **arglist);
void
player_metadata_send(void *imd, void *omd);
const char *
player_pmap(void *p);