Enable resuming playback from saved position for certain media kinds (eg audiobooks)

This commit is contained in:
ejurgensen 2015-08-04 22:33:32 +02:00
parent 7881df67f6
commit 68912efa1f
11 changed files with 91 additions and 62 deletions

View File

@ -187,7 +187,7 @@ expression returns [ pANTLR3_STRING result ]
} }
else if (strcmp((char *)val, "url") == 0) else if (strcmp((char *)val, "url") == 0)
{ {
sprintf(str, "f.data_kind = \%d", DATA_KIND_URL); sprintf(str, "f.data_kind = \%d", DATA_KIND_HTTP);
} }
else if (strcmp((char *)val, "spotify") == 0) else if (strcmp((char *)val, "spotify") == 0)
{ {

View File

@ -2050,7 +2050,7 @@ db_files_update_songalbumid(void)
void void
db_file_inc_playcount(int id) db_file_inc_playcount(int id)
{ {
#define Q_TMPL "UPDATE files SET play_count = play_count + 1, time_played = %" PRIi64 " WHERE id = %d;" #define Q_TMPL "UPDATE files SET play_count = play_count + 1, time_played = %" PRIi64 ", seek = 0 WHERE id = %d;"
char *query; char *query;
query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id); query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id);
@ -2754,6 +2754,27 @@ db_file_update_icy(int id, char *artist, char *album)
#undef Q_TMPL #undef Q_TMPL
} }
void
db_file_save_seek(int id, uint32_t seek)
{
#define Q_TMPL "UPDATE files SET seek = %d WHERE id = %d;"
char *query;
if (id == 0)
return;
query = sqlite3_mprintf(Q_TMPL, seek, id);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return;
}
db_query_run(query, 1, 0);
#undef Q_TMPL
}
void void
db_file_delete_bypath(char *path) db_file_delete_bypath(char *path)
{ {

View File

@ -104,7 +104,7 @@ enum media_kind {
enum data_kind { enum data_kind {
DATA_KIND_FILE = 0, /* normal file */ DATA_KIND_FILE = 0, /* normal file */
DATA_KIND_URL = 1, /* url/stream */ DATA_KIND_HTTP = 1, /* network stream (radio) */
DATA_KIND_SPOTIFY = 2, /* iTunes has no spotify data kind, but we use 2 */ DATA_KIND_SPOTIFY = 2, /* iTunes has no spotify data kind, but we use 2 */
DATA_KIND_PIPE = 3, /* iTunes has no pipe data kind, but we use 3 */ DATA_KIND_PIPE = 3, /* iTunes has no pipe data kind, but we use 3 */
}; };
@ -473,6 +473,9 @@ db_file_update(struct media_file_info *mfi);
void void
db_file_update_icy(int id, char *artist, char *album); db_file_update_icy(int id, char *artist, char *album);
void
db_file_save_seek(int id, uint32_t seek);
void void
db_file_delete_bypath(char *path); db_file_delete_bypath(char *path);

View File

@ -706,7 +706,7 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
} }
else if (type & F_SCAN_TYPE_URL) else if (type & F_SCAN_TYPE_URL)
{ {
mfi->data_kind = DATA_KIND_URL; mfi->data_kind = DATA_KIND_HTTP;
ret = scan_metadata_ffmpeg(path, mfi); ret = scan_metadata_ffmpeg(path, mfi);
if (ret < 0) if (ret < 0)
{ {

View File

@ -335,14 +335,14 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3) #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3)
# ifndef HAVE_FFMPEG # ifndef HAVE_FFMPEG
// Without this, libav is slow to probe some internet streams // Without this, libav is slow to probe some internet streams
if (mfi->data_kind == DATA_KIND_URL) if (mfi->data_kind == DATA_KIND_HTTP)
{ {
ctx = avformat_alloc_context(); ctx = avformat_alloc_context();
ctx->probesize = 64000; ctx->probesize = 64000;
} }
# endif # endif
if (mfi->data_kind == DATA_KIND_URL) if (mfi->data_kind == DATA_KIND_HTTP)
{ {
free(path); free(path);
ret = http_stream_setup(&path, file); ret = http_stream_setup(&path, file);
@ -481,8 +481,8 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
DPRINTF(E_DBG, L_SCAN, "Duration %d ms, bitrate %d kbps\n", mfi->song_length, mfi->bitrate); DPRINTF(E_DBG, L_SCAN, "Duration %d ms, bitrate %d kbps\n", mfi->song_length, mfi->bitrate);
/* Try to extract ICY metadata if url/stream */ /* Try to extract ICY metadata if http stream */
if (mfi->data_kind == DATA_KIND_URL) if (mfi->data_kind == DATA_KIND_HTTP)
{ {
icy_metadata = http_icy_metadata_get(ctx, 0); icy_metadata = http_icy_metadata_get(ctx, 0);
if (icy_metadata && icy_metadata->name) if (icy_metadata && icy_metadata->name)

View File

@ -170,7 +170,7 @@ dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct med
* FIXME: Giving the client invalid ids on purpose is hardly ideal, but the * FIXME: Giving the client invalid ids on purpose is hardly ideal, but the
* clients don't seem to use these ids for anything other than rating. * clients don't seem to use these ids for anything other than rating.
*/ */
if (mfi->data_kind == DATA_KIND_URL) if (mfi->data_kind == DATA_KIND_HTTP)
{ {
id = djb_hash(mfi->album, strlen(mfi->album)); id = djb_hash(mfi->album, strlen(mfi->album));
songalbumid = (int64_t)id; songalbumid = (int64_t)id;

View File

@ -491,7 +491,7 @@ scrobble(int id)
goto noscrobble; goto noscrobble;
// Don't scrobble non-music and radio stations // Don't scrobble non-music and radio stations
if ((mfi->media_kind != MEDIA_KIND_MUSIC) || (mfi->data_kind == DATA_KIND_URL)) if ((mfi->media_kind != MEDIA_KIND_MUSIC) || (mfi->data_kind == DATA_KIND_HTTP))
goto noscrobble; goto noscrobble;
// Don't scrobble songs with unknown artist // Don't scrobble songs with unknown artist

View File

@ -81,7 +81,7 @@ pipe_setup(struct media_file_info *mfi)
return -1; return -1;
} }
return 1; return 0;
} }
void void

View File

@ -1173,21 +1173,21 @@ player_queue_make_mpd(char *path, int recursive)
static void static void
source_free(struct player_source *ps) source_free(struct player_source *ps)
{ {
switch (ps->type) switch (ps->data_kind)
{ {
case SOURCE_FILE: case DATA_KIND_FILE:
case SOURCE_HTTP: case DATA_KIND_HTTP:
if (ps->ctx) if (ps->ctx)
transcode_cleanup(ps->ctx); transcode_cleanup(ps->ctx);
break; break;
case SOURCE_SPOTIFY: case DATA_KIND_SPOTIFY:
#ifdef HAVE_SPOTIFY_H #ifdef HAVE_SPOTIFY_H
spotify_playback_stop(); spotify_playback_stop();
#endif #endif
break; break;
case SOURCE_PIPE: case DATA_KIND_PIPE:
pipe_cleanup(); pipe_cleanup();
break; break;
} }
@ -1202,10 +1202,10 @@ source_stop(struct player_source *ps)
while (ps) while (ps)
{ {
switch (ps->type) switch (ps->data_kind)
{ {
case SOURCE_FILE: case DATA_KIND_FILE:
case SOURCE_HTTP: case DATA_KIND_HTTP:
if (ps->ctx) if (ps->ctx)
{ {
transcode_cleanup(ps->ctx); transcode_cleanup(ps->ctx);
@ -1213,13 +1213,13 @@ source_stop(struct player_source *ps)
} }
break; break;
case SOURCE_SPOTIFY: case DATA_KIND_SPOTIFY:
#ifdef HAVE_SPOTIFY_H #ifdef HAVE_SPOTIFY_H
spotify_playback_stop(); spotify_playback_stop();
#endif #endif
break; break;
case SOURCE_PIPE: case DATA_KIND_PIPE:
pipe_cleanup(); pipe_cleanup();
break; break;
} }
@ -1360,7 +1360,7 @@ source_reshuffle(void)
/* Helper */ /* Helper */
static int static int
source_open(struct player_source *ps, int no_md) source_open(struct player_source *ps, int no_md, int seek)
{ {
struct media_file_info *mfi; struct media_file_info *mfi;
char *url; char *url;
@ -1390,12 +1390,13 @@ source_open(struct player_source *ps, int no_md)
DPRINTF(E_INFO, L_PLAYER, "Opening '%s' (%s)\n", mfi->title, mfi->path); DPRINTF(E_INFO, L_PLAYER, "Opening '%s' (%s)\n", mfi->title, mfi->path);
ps->data_kind = mfi->data_kind;
ps->media_kind = mfi->media_kind;
// Setup the source type responsible for getting the audio // Setup the source type responsible for getting the audio
switch (mfi->data_kind) switch (mfi->data_kind)
{ {
case DATA_KIND_URL: case DATA_KIND_HTTP:
ps->type = SOURCE_HTTP;
ret = http_stream_setup(&url, mfi->path); ret = http_stream_setup(&url, mfi->path);
if (ret < 0) if (ret < 0)
break; break;
@ -1407,22 +1408,29 @@ source_open(struct player_source *ps, int no_md)
break; break;
case DATA_KIND_SPOTIFY: case DATA_KIND_SPOTIFY:
ps->type = SOURCE_SPOTIFY;
#ifdef HAVE_SPOTIFY_H #ifdef HAVE_SPOTIFY_H
ret = spotify_playback_play(mfi); ret = spotify_playback_play(mfi);
if (seek && mfi->seek)
{
DPRINTF(E_DBG, L_PLAYER, "Source id %d started with seek %d\n", ps->id, mfi->seek);
ret = spotify_playback_seek(mfi->seek);
}
#else #else
ret = -1; ret = -1;
#endif #endif
break; break;
case DATA_KIND_PIPE: case DATA_KIND_PIPE:
ps->type = SOURCE_PIPE;
ret = pipe_setup(mfi); ret = pipe_setup(mfi);
break; break;
default: default:
ps->type = SOURCE_FILE;
ret = transcode_setup(&ps->ctx, mfi, NULL, 0); ret = transcode_setup(&ps->ctx, mfi, NULL, 0);
if (seek && mfi->seek)
{
DPRINTF(E_DBG, L_PLAYER, "Source id %d started with seek %d\n", ps->id, mfi->seek);
ret = transcode_seek(ps->ctx, mfi->seek);
}
} }
free_mfi(mfi, 0); free_mfi(mfi, 0);
@ -1439,7 +1447,7 @@ source_open(struct player_source *ps, int no_md)
ps->setup_done = 1; ps->setup_done = 1;
return 0; return ret;
} }
static int static int
@ -1479,7 +1487,7 @@ source_next(int force)
if (!cur_streaming) if (!cur_streaming)
break; break;
if ((cur_streaming->type == SOURCE_FILE) && cur_streaming->ctx) if ((cur_streaming->data_kind == DATA_KIND_FILE) && cur_streaming->ctx)
{ {
ret = transcode_seek(cur_streaming->ctx, 0); ret = transcode_seek(cur_streaming->ctx, 0);
@ -1491,7 +1499,7 @@ source_next(int force)
metadata_trigger(cur_streaming, 0); metadata_trigger(cur_streaming, 0);
} }
else else
ret = source_open(cur_streaming, force); ret = source_open(cur_streaming, force, 0);
if (ret < 0) if (ret < 0)
{ {
@ -1535,7 +1543,7 @@ source_next(int force)
do do
{ {
ret = source_open(ps, force); ret = source_open(ps, force, 0);
if (ret < 0) if (ret < 0)
{ {
if (shuffle) if (shuffle)
@ -1593,7 +1601,7 @@ source_prev(void)
do do
{ {
ret = source_open(ps, 1); ret = source_open(ps, 1, 0);
if (ret < 0) if (ret < 0)
{ {
if (shuffle) if (shuffle)
@ -1741,7 +1749,7 @@ source_check(void)
if (ps->setup_done) if (ps->setup_done)
{ {
if ((ps->type == SOURCE_FILE) && ps->ctx) if ((ps->data_kind == DATA_KIND_FILE) && ps->ctx)
{ {
transcode_cleanup(ps->ctx); transcode_cleanup(ps->ctx);
ps->ctx = NULL; ps->ctx = NULL;
@ -1796,7 +1804,7 @@ source_check(void)
if (ps->setup_done) if (ps->setup_done)
{ {
if ((ps->type == SOURCE_FILE) && ps->ctx) if ((ps->data_kind == DATA_KIND_FILE) && ps->ctx)
{ {
transcode_cleanup(ps->ctx); transcode_cleanup(ps->ctx);
ps->ctx = NULL; ps->ctx = NULL;
@ -1882,26 +1890,26 @@ source_read(uint8_t *buf, int len, uint64_t rtptime)
if (evbuffer_get_length(audio_buf) == 0) if (evbuffer_get_length(audio_buf) == 0)
{ {
switch (cur_streaming->type) switch (cur_streaming->data_kind)
{ {
case SOURCE_HTTP: case DATA_KIND_HTTP:
ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes, &icy_timer); ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes, &icy_timer);
if (icy_timer) if (icy_timer)
metadata_check_icy(); metadata_check_icy();
break; break;
case SOURCE_FILE: case DATA_KIND_FILE:
ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes, &icy_timer); ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes, &icy_timer);
break; break;
#ifdef HAVE_SPOTIFY_H #ifdef HAVE_SPOTIFY_H
case SOURCE_SPOTIFY: case DATA_KIND_SPOTIFY:
ret = spotify_audio_get(audio_buf, len - nbytes); ret = spotify_audio_get(audio_buf, len - nbytes);
break; break;
#endif #endif
case SOURCE_PIPE: case DATA_KIND_PIPE:
ret = pipe_audio_get(audio_buf, len - nbytes); ret = pipe_audio_get(audio_buf, len - nbytes);
break; break;
@ -2647,7 +2655,7 @@ artwork_url_get(struct player_command *cmd)
return -1; return -1;
/* Check that we are playing a viable stream, and that it has the requested id */ /* Check that we are playing a viable stream, and that it has the requested id */
if (!ps->ctx || ps->type != SOURCE_HTTP || ps->id != cmd->arg.icy.id) if (!ps->ctx || ps->data_kind != DATA_KIND_HTTP || ps->id != cmd->arg.icy.id)
return -1; return -1;
transcode_metadata_artwork_url(ps->ctx, &cmd->arg.icy.artwork_url); transcode_metadata_artwork_url(ps->ctx, &cmd->arg.icy.artwork_url);
@ -2874,7 +2882,7 @@ playback_start(struct player_command *cmd)
else else
cur_streaming = ps; cur_streaming = ps;
ret = source_open(cur_streaming, 0); ret = source_open(cur_streaming, 0, 1);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_PLAYER, "Couldn't jump to source %d in queue\n", cur_streaming->id); DPRINTF(E_LOG, L_PLAYER, "Couldn't jump to source %d in queue\n", cur_streaming->id);
@ -2886,7 +2894,7 @@ playback_start(struct player_command *cmd)
if (idx_id) if (idx_id)
*idx_id = cur_streaming->id; *idx_id = cur_streaming->id;
cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES; cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000;
cur_streaming->output_start = cur_streaming->stream_start; cur_streaming->output_start = cur_streaming->stream_start;
} }
else if (!cur_streaming) else if (!cur_streaming)
@ -3023,7 +3031,7 @@ playback_prev_bh(struct player_command *cmd)
} }
else else
{ {
ret = source_open(cur_streaming, 1); ret = source_open(cur_streaming, 1, 0);
if (ret < 0) if (ret < 0)
{ {
playback_abort(); playback_abort();
@ -3103,18 +3111,18 @@ playback_seek_bh(struct player_command *cmd)
ps->end = 0; ps->end = 0;
/* Seek to commanded position */ /* Seek to commanded position */
switch (ps->type) switch (ps->data_kind)
{ {
case SOURCE_FILE: case DATA_KIND_FILE:
ret = transcode_seek(ps->ctx, ms); ret = transcode_seek(ps->ctx, ms);
break; break;
#ifdef HAVE_SPOTIFY_H #ifdef HAVE_SPOTIFY_H
case SOURCE_SPOTIFY: case DATA_KIND_SPOTIFY:
ret = spotify_playback_seek(ms); ret = spotify_playback_seek(ms);
break; break;
#endif #endif
case SOURCE_PIPE: case DATA_KIND_PIPE:
case SOURCE_HTTP: case DATA_KIND_HTTP:
ret = 1; ret = 1;
break; break;
@ -3162,13 +3170,13 @@ playback_pause_bh(struct player_command *cmd)
pos -= ps->stream_start; pos -= ps->stream_start;
ms = (int)((pos * 1000) / 44100); ms = (int)((pos * 1000) / 44100);
switch (ps->type) switch (ps->data_kind)
{ {
case SOURCE_FILE: case DATA_KIND_FILE:
ret = transcode_seek(ps->ctx, ms); ret = transcode_seek(ps->ctx, ms);
break; break;
#ifdef HAVE_SPOTIFY_H #ifdef HAVE_SPOTIFY_H
case SOURCE_SPOTIFY: case DATA_KIND_SPOTIFY:
ret = spotify_playback_seek(ms); ret = spotify_playback_seek(ms);
break; break;
#endif #endif
@ -3192,6 +3200,9 @@ playback_pause_bh(struct player_command *cmd)
status_update(PLAY_PAUSED); status_update(PLAY_PAUSED);
if (ps->media_kind & (MEDIA_KIND_MOVIE | MEDIA_KIND_PODCAST | MEDIA_KIND_AUDIOBOOK | MEDIA_KIND_TVSHOW))
db_file_save_seek(ps->id, ret);
return 0; return 0;
} }

View File

@ -33,13 +33,6 @@ enum repeat_mode {
REPEAT_ALL = 2, REPEAT_ALL = 2,
}; };
enum source_type {
SOURCE_FILE = 0,
SOURCE_SPOTIFY,
SOURCE_PIPE,
SOURCE_HTTP,
};
struct spk_flags { struct spk_flags {
unsigned selected:1; unsigned selected:1;
unsigned has_password:1; unsigned has_password:1;
@ -84,7 +77,8 @@ struct player_source
uint32_t id; uint32_t id;
uint32_t len_ms; uint32_t len_ms;
enum source_type type; enum data_kind data_kind;
enum media_kind media_kind;
int setup_done; int setup_done;
uint64_t stream_start; uint64_t stream_start;

View File

@ -507,13 +507,13 @@ transcode_setup(struct transcode_ctx **nctx, struct media_file_info *mfi, off_t
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3) #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3)
# ifndef HAVE_FFMPEG # ifndef HAVE_FFMPEG
// Without this, libav is slow to probe some internet streams, which leads to RAOP timeouts // Without this, libav is slow to probe some internet streams, which leads to RAOP timeouts
if (mfi->data_kind == DATA_KIND_URL) if (mfi->data_kind == DATA_KIND_HTTP)
{ {
ctx->fmtctx = avformat_alloc_context(); ctx->fmtctx = avformat_alloc_context();
ctx->fmtctx->probesize = 64000; ctx->fmtctx->probesize = 64000;
} }
# endif # endif
if (mfi->data_kind == DATA_KIND_URL) if (mfi->data_kind == DATA_KIND_HTTP)
av_dict_set(&options, "icy", "1", 0); av_dict_set(&options, "icy", "1", 0);
ret = avformat_open_input(&ctx->fmtctx, mfi->path, NULL, &options); ret = avformat_open_input(&ctx->fmtctx, mfi->path, NULL, &options);