mirror of
https://github.com/owntone/owntone-server.git
synced 2025-11-09 13:39:47 -05:00
[player/input] Refactor - WIP
* Open input sources earlier * Gapless playback * Remove fixed 44100/16 from player * Complete restructure player internals
This commit is contained in:
@@ -30,96 +30,100 @@
|
||||
#include "input.h"
|
||||
|
||||
static int
|
||||
setup(struct player_source *ps)
|
||||
setup(struct input_source *source)
|
||||
{
|
||||
ps->input_ctx = transcode_setup(XCODE_PCM_NATIVE, ps->data_kind, ps->path, ps->len_ms, NULL);
|
||||
if (!ps->input_ctx)
|
||||
struct transcode_ctx *ctx;
|
||||
|
||||
ctx = transcode_setup(XCODE_PCM_NATIVE, source->data_kind, source->path, source->len_ms, NULL);
|
||||
if (!ctx)
|
||||
return -1;
|
||||
|
||||
ps->setup_done = 1;
|
||||
CHECK_NULL(L_PLAYER, source->evbuf = evbuffer_new());
|
||||
|
||||
source->quality.sample_rate = transcode_encode_query(ctx->encode_ctx, "sample_rate");
|
||||
source->quality.bits_per_sample = transcode_encode_query(ctx->encode_ctx, "bits_per_sample");
|
||||
source->quality.channels = transcode_encode_query(ctx->encode_ctx, "channels");
|
||||
|
||||
source->input_ctx = ctx;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
setup_http(struct player_source *ps)
|
||||
setup_http(struct input_source *source)
|
||||
{
|
||||
char *url;
|
||||
|
||||
if (http_stream_setup(&url, ps->path) < 0)
|
||||
if (http_stream_setup(&url, source->path) < 0)
|
||||
return -1;
|
||||
|
||||
free(ps->path);
|
||||
ps->path = url;
|
||||
free(source->path);
|
||||
source->path = url;
|
||||
|
||||
return setup(ps);
|
||||
return setup(source);
|
||||
}
|
||||
|
||||
static int
|
||||
start(struct player_source *ps)
|
||||
stop(struct input_source *source)
|
||||
{
|
||||
struct transcode_ctx *ctx = ps->input_ctx;
|
||||
struct media_quality quality = { 0 };
|
||||
struct evbuffer *evbuf;
|
||||
short flags;
|
||||
int ret;
|
||||
int icy_timer;
|
||||
|
||||
evbuf = evbuffer_new();
|
||||
|
||||
quality.sample_rate = transcode_encode_query(ctx->encode_ctx, "sample_rate");
|
||||
quality.bits_per_sample = transcode_encode_query(ctx->encode_ctx, "bits_per_sample");
|
||||
quality.channels = transcode_encode_query(ctx->encode_ctx, "channels");
|
||||
|
||||
ret = -1;
|
||||
flags = 0;
|
||||
while (!input_loop_break && !(flags & INPUT_FLAG_EOF))
|
||||
{
|
||||
// We set "wanted" to 1 because the read size doesn't matter to us
|
||||
// TODO optimize?
|
||||
ret = transcode(evbuf, &icy_timer, ctx, 1);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
flags = ((ret == 0) ? INPUT_FLAG_EOF : 0) |
|
||||
(icy_timer ? INPUT_FLAG_METADATA : 0);
|
||||
|
||||
ret = input_write(evbuf, &quality, flags);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
evbuffer_free(evbuf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
stop(struct player_source *ps)
|
||||
{
|
||||
struct transcode_ctx *ctx = ps->input_ctx;
|
||||
struct transcode_ctx *ctx = source->input_ctx;
|
||||
|
||||
transcode_cleanup(&ctx);
|
||||
evbuffer_free(source->evbuf);
|
||||
|
||||
ps->input_ctx = NULL;
|
||||
ps->setup_done = 0;
|
||||
source->input_ctx = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
seek(struct player_source *ps, int seek_ms)
|
||||
play(struct input_source *source)
|
||||
{
|
||||
return transcode_seek(ps->input_ctx, seek_ms);
|
||||
struct transcode_ctx *ctx = source->input_ctx;
|
||||
int icy_timer;
|
||||
int ret;
|
||||
short flags;
|
||||
|
||||
ret = input_wait();
|
||||
if (ret < 0)
|
||||
return 0; // Loop, input_buffer is not ready for writing
|
||||
|
||||
// We set "wanted" to 1 because the read size doesn't matter to us
|
||||
// TODO optimize?
|
||||
ret = transcode(source->evbuf, &icy_timer, ctx, 1);
|
||||
if (ret == 0)
|
||||
{
|
||||
input_write(source->evbuf, &source->quality, INPUT_FLAG_EOF);
|
||||
stop(source);
|
||||
return -1;
|
||||
}
|
||||
else if (ret < 0)
|
||||
{
|
||||
input_write(NULL, NULL, INPUT_FLAG_ERROR);
|
||||
stop(source);
|
||||
return -1;
|
||||
}
|
||||
|
||||
flags = (icy_timer ? INPUT_FLAG_METADATA : 0);
|
||||
|
||||
input_write(source->evbuf, &source->quality, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
metadata_get_http(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime)
|
||||
seek(struct input_source *source, int seek_ms)
|
||||
{
|
||||
return transcode_seek(source->input_ctx, seek_ms);
|
||||
}
|
||||
|
||||
static int
|
||||
metadata_get_http(struct input_metadata *metadata, struct input_source *source)
|
||||
{
|
||||
struct http_icy_metadata *m;
|
||||
int changed;
|
||||
|
||||
m = transcode_metadata(ps->input_ctx, &changed);
|
||||
m = transcode_metadata(source->input_ctx, &changed);
|
||||
if (!m)
|
||||
return -1;
|
||||
|
||||
@@ -147,7 +151,7 @@ struct input_definition input_file =
|
||||
.type = INPUT_TYPE_FILE,
|
||||
.disabled = 0,
|
||||
.setup = setup,
|
||||
.start = start,
|
||||
.play = play,
|
||||
.stop = stop,
|
||||
.seek = seek,
|
||||
};
|
||||
@@ -158,7 +162,7 @@ struct input_definition input_http =
|
||||
.type = INPUT_TYPE_HTTP,
|
||||
.disabled = 0,
|
||||
.setup = setup_http,
|
||||
.start = start,
|
||||
.play = play,
|
||||
.stop = stop,
|
||||
.metadata_get = metadata_get_http,
|
||||
};
|
||||
|
||||
@@ -125,7 +125,7 @@ static pthread_mutex_t pipe_metadata_lock;
|
||||
// True if there is new metadata to push to the player
|
||||
static bool pipe_metadata_is_new;
|
||||
|
||||
/* -------------------------------- HELPERS ------------------------------- */
|
||||
/* -------------------------------- HELPERS --------------------------------- */
|
||||
|
||||
static struct pipe *
|
||||
pipe_create(const char *path, int id, enum pipetype type, event_callback_fn cb)
|
||||
@@ -500,8 +500,8 @@ pipe_metadata_parse(struct input_metadata *m, struct evbuffer *evbuf)
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------- PIPE WATCHING ---------------------------- */
|
||||
/* Thread: pipe */
|
||||
/* ------------------------------ PIPE WATCHING ----------------------------- */
|
||||
/* Thread: pipe */
|
||||
|
||||
// Some data arrived on a pipe we watch - let's autostart playback
|
||||
static void
|
||||
@@ -617,8 +617,8 @@ pipe_thread_run(void *arg)
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------- METADATA PIPE HANDLING ---------------------- */
|
||||
/* Thread: worker */
|
||||
/* --------------------------- METADATA PIPE HANDLING ----------------------- */
|
||||
/* Thread: worker */
|
||||
|
||||
static void
|
||||
pipe_metadata_watch_del(void *arg)
|
||||
@@ -706,8 +706,8 @@ pipe_metadata_watch_add(void *arg)
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------- PIPE WATCH THREAD START/STOP -------------------- */
|
||||
/* Thread: filescanner */
|
||||
/* ----------------------- PIPE WATCH THREAD START/STOP --------------------- */
|
||||
/* Thread: filescanner */
|
||||
|
||||
static void
|
||||
pipe_thread_start(void)
|
||||
@@ -802,92 +802,46 @@ pipe_listener_cb(short event_mask)
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------- PIPE INPUT INTERFACE ------------------------ */
|
||||
/* Thread: player/input */
|
||||
/* --------------------------- PIPE INPUT INTERFACE ------------------------- */
|
||||
/* Thread: input */
|
||||
|
||||
static int
|
||||
setup(struct player_source *ps)
|
||||
setup(struct input_source *source)
|
||||
{
|
||||
struct pipe *pipe;
|
||||
int fd;
|
||||
|
||||
fd = pipe_open(ps->path, 0);
|
||||
fd = pipe_open(source->path, 0);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
CHECK_NULL(L_PLAYER, pipe = pipe_create(ps->path, ps->id, PIPE_PCM, NULL));
|
||||
CHECK_NULL(L_PLAYER, pipe = pipe_create(source->path, source->id, PIPE_PCM, NULL));
|
||||
CHECK_NULL(L_PLAYER, source->evbuf = evbuffer_new());
|
||||
|
||||
pipe->fd = fd;
|
||||
pipe->is_autostarted = (ps->id == pipe_autostart_id);
|
||||
pipe->is_autostarted = (source->id == pipe_autostart_id);
|
||||
|
||||
worker_execute(pipe_metadata_watch_add, ps->path, strlen(ps->path) + 1, 0);
|
||||
worker_execute(pipe_metadata_watch_add, source->path, strlen(source->path) + 1, 0);
|
||||
|
||||
ps->input_ctx = pipe;
|
||||
ps->setup_done = 1;
|
||||
source->input_ctx = pipe;
|
||||
|
||||
source->quality.sample_rate = pipe_sample_rate;
|
||||
source->quality.bits_per_sample = pipe_bits_per_sample;
|
||||
source->quality.channels = 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
start(struct player_source *ps)
|
||||
stop(struct input_source *source)
|
||||
{
|
||||
struct pipe *pipe = ps->input_ctx;
|
||||
struct media_quality quality = { 0 };
|
||||
struct evbuffer *evbuf;
|
||||
short flags;
|
||||
int ret;
|
||||
|
||||
evbuf = evbuffer_new();
|
||||
if (!evbuf)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Out of memory for pipe evbuf\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
quality.sample_rate = pipe_sample_rate;
|
||||
quality.bits_per_sample = pipe_bits_per_sample;
|
||||
quality.channels = 2;
|
||||
|
||||
ret = -1;
|
||||
while (!input_loop_break)
|
||||
{
|
||||
ret = evbuffer_read(evbuf, pipe->fd, PIPE_READ_MAX);
|
||||
if ((ret == 0) && (pipe->is_autostarted))
|
||||
{
|
||||
input_write(evbuf, NULL, INPUT_FLAG_EOF); // Autostop
|
||||
break;
|
||||
}
|
||||
else if ((ret == 0) || ((ret < 0) && (errno == EAGAIN)))
|
||||
{
|
||||
input_wait();
|
||||
continue;
|
||||
}
|
||||
else if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Could not read from pipe '%s': %s\n", ps->path, strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
flags = (pipe_metadata_is_new ? INPUT_FLAG_METADATA : 0);
|
||||
pipe_metadata_is_new = 0;
|
||||
|
||||
ret = input_write(evbuf, &quality, flags);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
evbuffer_free(evbuf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
stop(struct player_source *ps)
|
||||
{
|
||||
struct pipe *pipe = ps->input_ctx;
|
||||
struct pipe *pipe = source->input_ctx;
|
||||
union pipe_arg *cmdarg;
|
||||
|
||||
DPRINTF(E_DBG, L_PLAYER, "Stopping pipe\n");
|
||||
|
||||
evbuffer_free(source->evbuf);
|
||||
|
||||
pipe_close(pipe->fd);
|
||||
|
||||
// Reset the pipe and start watching it again for new data. Must be async or
|
||||
@@ -903,14 +857,51 @@ stop(struct player_source *ps)
|
||||
|
||||
pipe_free(pipe);
|
||||
|
||||
ps->input_ctx = NULL;
|
||||
ps->setup_done = 0;
|
||||
source->input_ctx = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
metadata_get(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime)
|
||||
play(struct input_source *source)
|
||||
{
|
||||
struct pipe *pipe = source->input_ctx;
|
||||
short flags;
|
||||
int ret;
|
||||
|
||||
ret = input_wait();
|
||||
if (ret < 0)
|
||||
return 0; // Loop, input_buffer is not ready for writing
|
||||
|
||||
ret = evbuffer_read(source->evbuf, pipe->fd, PIPE_READ_MAX);
|
||||
if ((ret == 0) && (pipe->is_autostarted))
|
||||
{
|
||||
input_write(source->evbuf, NULL, INPUT_FLAG_EOF); // Autostop
|
||||
stop(source);
|
||||
return -1;
|
||||
}
|
||||
else if ((ret == 0) || ((ret < 0) && (errno == EAGAIN)))
|
||||
{
|
||||
return 0; // Loop
|
||||
}
|
||||
else if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_PLAYER, "Could not read from pipe '%s': %s\n", source->path, strerror(errno));
|
||||
input_write(NULL, NULL, INPUT_FLAG_ERROR);
|
||||
stop(source);
|
||||
return -1;
|
||||
}
|
||||
|
||||
flags = (pipe_metadata_is_new ? INPUT_FLAG_METADATA : 0);
|
||||
pipe_metadata_is_new = 0;
|
||||
|
||||
input_write(source->evbuf, &source->quality, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
metadata_get(struct input_metadata *metadata, struct input_source *source)
|
||||
{
|
||||
pthread_mutex_lock(&pipe_metadata_lock);
|
||||
|
||||
@@ -927,8 +918,9 @@ metadata_get(struct input_metadata *metadata, struct player_source *ps, uint64_t
|
||||
|
||||
if (pipe_metadata_parsed.song_length)
|
||||
{
|
||||
if (rtptime > ps->stream_start)
|
||||
metadata->rtptime = rtptime - pipe_metadata_parsed.offset;
|
||||
// TODO this is probably broken
|
||||
if (metadata->rtptime > metadata->start)
|
||||
metadata->rtptime -= pipe_metadata_parsed.offset;
|
||||
metadata->offset = pipe_metadata_parsed.offset;
|
||||
metadata->song_length = pipe_metadata_parsed.song_length;
|
||||
}
|
||||
@@ -988,7 +980,7 @@ struct input_definition input_pipe =
|
||||
.type = INPUT_TYPE_PIPE,
|
||||
.disabled = 0,
|
||||
.setup = setup,
|
||||
.start = start,
|
||||
.play = play,
|
||||
.stop = stop,
|
||||
.metadata_get = metadata_get,
|
||||
.init = init,
|
||||
|
||||
@@ -31,12 +31,12 @@
|
||||
#define SPOTIFY_SETUP_RETRY_WAIT 500000
|
||||
|
||||
static int
|
||||
setup(struct player_source *ps)
|
||||
setup(struct input_source *source)
|
||||
{
|
||||
int i = 0;
|
||||
int ret;
|
||||
|
||||
while((ret = spotify_playback_setup(ps->path)) == SPOTIFY_SETUP_ERROR_IS_LOADING)
|
||||
while((ret = spotify_playback_setup(source->path)) == SPOTIFY_SETUP_ERROR_IS_LOADING)
|
||||
{
|
||||
if (i >= SPOTIFY_SETUP_RETRIES)
|
||||
break;
|
||||
@@ -49,32 +49,15 @@ setup(struct player_source *ps)
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
ps->setup_done = 1;
|
||||
ret = spotify_playback_play();
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
start(struct player_source *ps)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = spotify_playback_play();
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
while (!input_loop_break)
|
||||
{
|
||||
input_wait();
|
||||
}
|
||||
|
||||
ret = spotify_playback_pause();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
stop(struct player_source *ps)
|
||||
stop(struct input_source *source)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@@ -82,13 +65,11 @@ stop(struct player_source *ps)
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
ps->setup_done = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
seek(struct player_source *ps, int seek_ms)
|
||||
seek(struct input_source *source, int seek_ms)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user