Merge branch 'input_progress2'
This commit is contained in:
commit
c1fad30df0
|
@ -174,7 +174,13 @@ dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_
|
|||
*/
|
||||
if (queue_item->data_kind == DATA_KIND_HTTP || queue_item->data_kind == DATA_KIND_PIPE)
|
||||
{
|
||||
id = djb_hash(queue_item->album, strlen(queue_item->album));
|
||||
// Could also use queue_item->queue_version, but it changes a bit too much
|
||||
// leading to Remote reloading too much
|
||||
if (queue_item->artwork_url)
|
||||
id = djb_hash(queue_item->artwork_url, strlen(queue_item->artwork_url));
|
||||
else
|
||||
id = djb_hash(queue_item->title, strlen(queue_item->title));
|
||||
|
||||
songalbumid = (int64_t)id;
|
||||
}
|
||||
else
|
||||
|
|
37
src/input.c
37
src/input.c
|
@ -177,6 +177,9 @@ map_data_kind(int data_kind)
|
|||
static void
|
||||
metadata_free(struct input_metadata *metadata, int content_only)
|
||||
{
|
||||
if (!metadata)
|
||||
return;
|
||||
|
||||
free(metadata->artist);
|
||||
free(metadata->title);
|
||||
free(metadata->album);
|
||||
|
@ -193,7 +196,6 @@ static struct input_metadata *
|
|||
metadata_get(struct input_source *source)
|
||||
{
|
||||
struct input_metadata *metadata;
|
||||
struct db_queue_item *queue_item;
|
||||
int ret;
|
||||
|
||||
if (!inputs[source->type]->metadata_get)
|
||||
|
@ -205,37 +207,7 @@ metadata_get(struct input_source *source)
|
|||
if (ret < 0)
|
||||
goto out_free_metadata;
|
||||
|
||||
queue_item = db_queue_fetch_byitemid(source->item_id);
|
||||
if (!queue_item)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Bug! Input source item_id does not match anything in queue\n");
|
||||
goto out_free_metadata;
|
||||
}
|
||||
|
||||
// Update queue item if metadata changed
|
||||
if (metadata->artist || metadata->title || metadata->album || metadata->genre || metadata->artwork_url || metadata->len_ms)
|
||||
{
|
||||
// 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->len_ms)
|
||||
queue_item->song_length = metadata->len_ms;
|
||||
|
||||
ret = db_queue_update_item(queue_item);
|
||||
if (ret < 0)
|
||||
DPRINTF(E_LOG, L_PLAYER, "Database error while updating queue with new metadata\n");
|
||||
}
|
||||
|
||||
free_queue_item(queue_item, 0);
|
||||
metadata->item_id = source->item_id;
|
||||
|
||||
return metadata;
|
||||
|
||||
|
@ -867,7 +839,6 @@ input_flush(short *flags)
|
|||
flush(flags);
|
||||
}
|
||||
|
||||
// Not currently used, perhaps remove?
|
||||
void
|
||||
input_metadata_free(struct input_metadata *metadata, int content_only)
|
||||
{
|
||||
|
|
14
src/input.h
14
src/input.h
|
@ -74,9 +74,12 @@ struct input_metadata
|
|||
// queue_item id
|
||||
uint32_t item_id;
|
||||
|
||||
// Input can override the default player progress by setting this
|
||||
// FIXME only implemented for Airplay speakers currently
|
||||
uint32_t pos_ms;
|
||||
// Input can override the default player progress by setting this. For the
|
||||
// other fields the receiver can check whether an update happened by checking
|
||||
// if it is non-zero/null, but not for pos_ms since 0 and even negative values
|
||||
// are valid.
|
||||
bool pos_is_updated;
|
||||
int32_t pos_ms;
|
||||
|
||||
// Sets new song length (input will also update queue_item)
|
||||
uint32_t len_ms;
|
||||
|
@ -87,9 +90,6 @@ struct input_metadata
|
|||
char *album;
|
||||
char *genre;
|
||||
char *artwork_url;
|
||||
|
||||
// Indicates whether we are starting playback. Just passed on to output.
|
||||
int startup;
|
||||
};
|
||||
|
||||
struct input_definition
|
||||
|
@ -219,7 +219,7 @@ void
|
|||
input_flush(short *flags);
|
||||
|
||||
/*
|
||||
* Free the entire struct
|
||||
* Free input_metadata
|
||||
*/
|
||||
void
|
||||
input_metadata_free(struct input_metadata *metadata, int content_only);
|
||||
|
|
|
@ -340,29 +340,33 @@ handle_progress(struct input_metadata *m, char *progress)
|
|||
{
|
||||
char *s;
|
||||
char *ptr;
|
||||
uint64_t start;
|
||||
uint64_t pos;
|
||||
uint64_t end;
|
||||
// Below must be signed to avoid casting in the calculations of pos_ms/len_ms
|
||||
int64_t start;
|
||||
int64_t pos;
|
||||
int64_t end;
|
||||
|
||||
if (!(s = strtok_r(progress, "/", &ptr)))
|
||||
return;
|
||||
safe_atou64(s, &start);
|
||||
safe_atoi64(s, &start);
|
||||
|
||||
if (!(s = strtok_r(NULL, "/", &ptr)))
|
||||
return;
|
||||
safe_atou64(s, &pos);
|
||||
safe_atoi64(s, &pos);
|
||||
|
||||
if (!(s = strtok_r(NULL, "/", &ptr)))
|
||||
return;
|
||||
safe_atou64(s, &end);
|
||||
safe_atoi64(s, &end);
|
||||
|
||||
if (!start || !pos || !end)
|
||||
return;
|
||||
|
||||
if (pos > start)
|
||||
m->pos_ms = (pos - start) * 1000 / pipe_sample_rate;
|
||||
if (end > start)
|
||||
m->len_ms = (end - start) * 1000 / pipe_sample_rate;
|
||||
// Note that negative positions are allowed and supported. A negative position
|
||||
// of e.g. -1000 means that the track will start in one second.
|
||||
m->pos_is_updated = true;
|
||||
m->pos_ms = (pos - start) * 1000 / pipe_sample_rate;
|
||||
m->len_ms = (end > start) ? (end - start) * 1000 / pipe_sample_rate : 0;
|
||||
|
||||
DPRINTF(E_DBG, L_PLAYER, "Received Shairport metadata progress: %ld/%ld/%ld => %d/%u ms\n", start, pos, end, m->pos_ms, m->len_ms);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
280
src/player.c
280
src/player.c
|
@ -175,9 +175,14 @@ struct player_source
|
|||
// 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, 0 for endless (unless the input
|
||||
// has given us a track length)
|
||||
uint32_t len_ms;
|
||||
|
||||
// Set when opening the item based on initial track length (so is not changed
|
||||
// by later input track length metadata)
|
||||
bool is_seekable;
|
||||
|
||||
// Quality of the source (sample rate etc.)
|
||||
struct media_quality quality;
|
||||
|
||||
|
@ -199,8 +204,14 @@ struct player_source
|
|||
uint64_t play_start;
|
||||
uint64_t play_end;
|
||||
|
||||
// When we receive a metadata update from the input it shouldn't be pushed to
|
||||
// clients until the speakers have reached the position that matches the
|
||||
// position the player was reading at when it got INPUT_FLAG_METADATA.
|
||||
uint64_t metadata_update;
|
||||
|
||||
// The number of milliseconds into the media that we started
|
||||
uint32_t seek_ms;
|
||||
|
||||
// This should at any time match the millisecond position of the media that is
|
||||
// coming out of your device. Will be 0 during initial buffering.
|
||||
uint32_t pos_ms;
|
||||
|
@ -308,6 +319,14 @@ static uint32_t cur_plversion;
|
|||
// Play history
|
||||
static struct player_history *history;
|
||||
|
||||
// When we receive track metadata from the input we have to wait until playback
|
||||
// has reached the position before using it. We use this to record the update.
|
||||
struct metadata_pending_register
|
||||
{
|
||||
uint64_t pos;
|
||||
struct input_metadata *metadata;
|
||||
} metadata_pending[16];
|
||||
|
||||
|
||||
/* -------------------------------- Forwards -------------------------------- */
|
||||
|
||||
|
@ -348,31 +367,6 @@ scrobble_cb(void *arg)
|
|||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
metadata_finalize_cb(struct output_metadata *metadata)
|
||||
{
|
||||
if (!pb_session.playing_now)
|
||||
{
|
||||
DPRINTF(E_WARN, L_PLAYER, "Aborting metadata_send(), playback stopped during metadata preparation\n");
|
||||
return -1;
|
||||
}
|
||||
else if (metadata->item_id != pb_session.playing_now->item_id)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the song with the given id to the list of previously played songs
|
||||
*/
|
||||
|
@ -470,6 +464,132 @@ status_update(enum play_status status)
|
|||
listener_notify(LISTENER_PLAYER);
|
||||
}
|
||||
|
||||
/* ------ All this is for dealing with metadata received from the input ----- */
|
||||
|
||||
static int
|
||||
metadata_pending_add(struct input_metadata *metadata, uint64_t pos)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (pos == 0)
|
||||
return -1; // Invalid position
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(metadata_pending); i++)
|
||||
{
|
||||
if (metadata_pending[i].metadata == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(metadata_pending))
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Error, too many pending metadata updates\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
metadata_pending[i].pos = pos;
|
||||
metadata_pending[i].metadata = metadata;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
metadata_pending_next_pos(void)
|
||||
{
|
||||
uint64_t next_pos;
|
||||
int i;
|
||||
|
||||
for (i = 0, next_pos = 0; i < ARRAY_SIZE(metadata_pending); i++)
|
||||
{
|
||||
if (metadata_pending[i].metadata && (metadata_pending[i].pos < next_pos || !next_pos))
|
||||
next_pos = metadata_pending[i].pos;
|
||||
}
|
||||
|
||||
return next_pos;
|
||||
}
|
||||
|
||||
static int
|
||||
metadata_finalize_cb(struct output_metadata *metadata)
|
||||
{
|
||||
if (!pb_session.playing_now)
|
||||
{
|
||||
DPRINTF(E_WARN, L_PLAYER, "Aborting metadata_send(), playback stopped during metadata preparation\n");
|
||||
return -1;
|
||||
}
|
||||
else if (metadata->item_id != pb_session.playing_now->item_id)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum command_state
|
||||
metadata_finalize(void *arg, int *retval)
|
||||
{
|
||||
if (!pb_session.playing_now)
|
||||
return COMMAND_END; // Playback ended while we doing the metadata update
|
||||
|
||||
outputs_metadata_send(pb_session.playing_now->item_id, false, metadata_finalize_cb);
|
||||
|
||||
status_update(player_state);
|
||||
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
// Done in worker thread because we avoid blocking db updates in the player
|
||||
static void
|
||||
metadata_update_queue_cb(void *arg)
|
||||
{
|
||||
struct input_metadata *metadata = *(struct input_metadata **)arg;
|
||||
struct db_queue_item *queue_item;
|
||||
int ret;
|
||||
|
||||
queue_item = db_queue_fetch_byitemid(metadata->item_id);
|
||||
if (!queue_item)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Bug! Could not update queue metadata, the item_id is unknown (%u)\n", metadata->item_id);
|
||||
input_metadata_free(metadata, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update queue item if metadata changed
|
||||
if (metadata->artist || metadata->title || metadata->album || metadata->genre || metadata->artwork_url || metadata->len_ms)
|
||||
{
|
||||
// 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->len_ms)
|
||||
queue_item->song_length = metadata->len_ms;
|
||||
|
||||
ret = db_queue_update_item(queue_item);
|
||||
if (ret < 0)
|
||||
DPRINTF(E_LOG, L_PLAYER, "Database error while updating queue with new metadata\n");
|
||||
}
|
||||
|
||||
free_queue_item(queue_item, 0);
|
||||
input_metadata_free(metadata, 0);
|
||||
|
||||
// Now return to the player thread and run metadata_finalize
|
||||
commands_exec_async(cmdbase, metadata_finalize, NULL);
|
||||
}
|
||||
|
||||
|
||||
/* ----------- Audio source handling (interfaces with input module) --------- */
|
||||
|
||||
|
@ -500,6 +620,7 @@ source_create(struct db_queue_item *queue_item, uint32_t seek_ms)
|
|||
ps->data_kind = queue_item->data_kind;
|
||||
ps->media_kind = queue_item->media_kind;
|
||||
ps->len_ms = queue_item->song_length;
|
||||
ps->is_seekable = (queue_item->song_length > 0);
|
||||
ps->path = strdup(queue_item->path);
|
||||
ps->seek_ms = seek_ms;
|
||||
|
||||
|
@ -595,8 +716,9 @@ source_print(char *line, size_t linesize, struct player_source *ps, const char *
|
|||
pos += snprintf(line + pos, linesize - pos, "%s.play_start=%" PRIu64 "; ", name, ps->play_start);
|
||||
pos += snprintf(line + pos, linesize - pos, "%s.read_end=%" PRIu64 "; ", name, ps->read_end);
|
||||
pos += snprintf(line + pos, linesize - pos, "%s.play_end=%" PRIu64 "; ", name, ps->play_end);
|
||||
pos += snprintf(line + pos, linesize - pos, "%s.pos_ms=%d; ", name, ps->pos_ms);
|
||||
pos += snprintf(line + pos, linesize - pos, "%s.seek_ms=%d; ", name, ps->seek_ms);
|
||||
pos += snprintf(line + pos, linesize - pos, "%s.metadata_update=%" PRIu64 "; ", name, ps->metadata_update);
|
||||
pos += snprintf(line + pos, linesize - pos, "%s.pos_ms=%u; ", name, ps->pos_ms);
|
||||
pos += snprintf(line + pos, linesize - pos, "%s.seek_ms=%u; ", name, ps->seek_ms);
|
||||
}
|
||||
else
|
||||
pos += snprintf(line + pos, linesize - pos, "%s=(null); ", name);
|
||||
|
@ -712,6 +834,8 @@ session_update_read_start(uint32_t seek_ms)
|
|||
static inline void
|
||||
session_update_read(int nsamples)
|
||||
{
|
||||
uint32_t step_ms;
|
||||
|
||||
// Did we just complete our first read? Then set the start timestamp
|
||||
if (pb_session.start_ts.tv_sec == 0)
|
||||
{
|
||||
|
@ -722,9 +846,15 @@ session_update_read(int nsamples)
|
|||
// Advance position
|
||||
pb_session.pos += nsamples;
|
||||
|
||||
// Need to know sample rate to calculate pos_ms step
|
||||
if (!pb_session.playing_now->quality.sample_rate)
|
||||
return;
|
||||
|
||||
step_ms = (1000 * nsamples) / pb_session.playing_now->quality.sample_rate;
|
||||
|
||||
// After we have started playing we also must calculate new pos_ms
|
||||
if (pb_session.playing_now->quality.sample_rate && pb_session.pos > pb_session.playing_now->play_start)
|
||||
pb_session.playing_now->pos_ms = pb_session.playing_now->seek_ms + 1000UL * (pb_session.pos - pb_session.playing_now->play_start) / pb_session.playing_now->quality.sample_rate;
|
||||
if (pb_session.pos > pb_session.playing_now->play_start)
|
||||
pb_session.playing_now->pos_ms += step_ms;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -764,6 +894,25 @@ session_update_read_quality(struct media_quality *quality)
|
|||
free(quality);
|
||||
}
|
||||
|
||||
static void
|
||||
session_update_read_metadata(void)
|
||||
{
|
||||
if (!pb_session.reading_now)
|
||||
return;
|
||||
|
||||
// Sets when to trigger the next event_play_metadata()
|
||||
pb_session.reading_now->metadata_update = metadata_pending_next_pos();
|
||||
}
|
||||
|
||||
static void
|
||||
session_update_play_metadata(struct input_metadata *metadata)
|
||||
{
|
||||
if (metadata->pos_is_updated)
|
||||
pb_session.playing_now->pos_ms = metadata->pos_ms;
|
||||
if (metadata->len_ms)
|
||||
pb_session.playing_now->len_ms = metadata->len_ms;
|
||||
}
|
||||
|
||||
static void
|
||||
session_restart(void)
|
||||
{
|
||||
|
@ -850,16 +999,31 @@ event_read_start_next()
|
|||
static void
|
||||
event_read_metadata(struct input_metadata *metadata)
|
||||
{
|
||||
uint64_t delay;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_DBG, L_PLAYER, "event_read_metadata()\n");
|
||||
|
||||
// FIXME Right now, metadata->pos_ms is not honoured. If it is >0 it is sent
|
||||
// to the outputs, but the player's pos_ms is not adjusted. That means we
|
||||
// don't always show correct progress for http streams, pipes and files with
|
||||
// chapters.
|
||||
// Add the metadata to the register of pending events with a trigger position
|
||||
// that corresponds to OUTPUTS_BUFFER_DURATION into the future. If we have
|
||||
// received a negative position we assume the metadata needs to be delayed
|
||||
// until the position is 0.
|
||||
if (metadata->pos_is_updated && metadata->pos_ms < 0)
|
||||
{
|
||||
delay = pb_session.reading_now->output_buffer_samples + (-metadata->pos_ms) * (uint64_t)pb_session.reading_now->quality.sample_rate / 1000;
|
||||
metadata->pos_ms = 0;
|
||||
}
|
||||
else
|
||||
delay = pb_session.reading_now->output_buffer_samples;
|
||||
|
||||
outputs_metadata_send(pb_session.playing_now->item_id, false, metadata_finalize_cb);
|
||||
ret = metadata_pending_add(metadata, pb_session.pos + delay);
|
||||
if (ret < 0)
|
||||
{
|
||||
input_metadata_free(metadata, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
status_update(player_state);
|
||||
session_update_read_metadata();
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -912,6 +1076,38 @@ event_play_start()
|
|||
status_update(PLAY_PLAYING);
|
||||
}
|
||||
|
||||
static void
|
||||
event_play_metadata()
|
||||
{
|
||||
int i;
|
||||
|
||||
DPRINTF(E_DBG, L_PLAYER, "event_play_metadata()\n");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(metadata_pending); i++)
|
||||
{
|
||||
// Proces all events with position from metadata_update (included) to
|
||||
// current read position (excluded)
|
||||
if (!(metadata_pending[i].pos >= pb_session.playing_now->metadata_update && metadata_pending[i].pos < pb_session.pos))
|
||||
continue;
|
||||
|
||||
// Just in case
|
||||
if (!metadata_pending[i].metadata)
|
||||
continue;
|
||||
|
||||
session_update_play_metadata(metadata_pending[i].metadata);
|
||||
|
||||
// Triggers an async chain of metadata update, first worker will do an
|
||||
// update of the db, then the player will update outputs, where the worker
|
||||
// may be called by the output, and then player sends status_update
|
||||
worker_execute(metadata_update_queue_cb, &(metadata_pending[i].metadata), sizeof(metadata_pending[i].metadata), 0);
|
||||
|
||||
memset(&metadata_pending[i], 0, sizeof(struct metadata_pending_register));
|
||||
}
|
||||
|
||||
// Set trigger (playing_now->metadata_update) to next pending metadata
|
||||
session_update_read_metadata();
|
||||
}
|
||||
|
||||
// Checks if the new playback position requires change of play status, plus
|
||||
// calls session_update_read that updates playback position
|
||||
static inline void
|
||||
|
@ -935,8 +1131,16 @@ event_read(int nsamples)
|
|||
session_update_read(nsamples);
|
||||
|
||||
// Check if the playback position passed the play_start position
|
||||
if (pb_session.pos - nsamples < pb_session.playing_now->play_start && pb_session.pos >= pb_session.playing_now->play_start)
|
||||
if (pb_session.pos > pb_session.playing_now->play_start && pb_session.pos <= pb_session.playing_now->play_start + nsamples)
|
||||
event_play_start();
|
||||
|
||||
if (pb_session.playing_now->metadata_update == 0)
|
||||
return;
|
||||
|
||||
// Check if the playback position passed an input metadata update. The event
|
||||
// must process all metadata updates in the read interval.
|
||||
if (pb_session.pos > pb_session.playing_now->metadata_update && pb_session.pos <= pb_session.playing_now->metadata_update + nsamples)
|
||||
event_play_metadata();
|
||||
}
|
||||
|
||||
|
||||
|
@ -2178,7 +2382,7 @@ static enum command_state
|
|||
playback_seek(void *arg, int *retval)
|
||||
{
|
||||
// Only check if the current playing track is seekable, other checks will be done in playback_pause()
|
||||
if (pb_session.playing_now && pb_session.playing_now->len_ms <= 0)
|
||||
if (pb_session.playing_now && !pb_session.playing_now->is_seekable)
|
||||
{
|
||||
DPRINTF(E_WARN, L_PLAYER, "Failed to seek, track is not seekable\n");
|
||||
|
||||
|
|
Loading…
Reference in New Issue