0
0
mirror of https://github.com/mpv-player/mpv.git synced 2024-09-20 03:52:22 +02:00

player: allow seeking in cached parts of unseekable streams

Before this change and before the seekable stream cache became a thing,
we could possibly seek using the stream cache. But we couldn't know
whether the seek would succeed. We knew the available byte range, but
could in general not tell whether a demuxer would stay within the range
when trying to seek to a specific time position. We preferred to have
safe defaults, so seeking in streams that were detected as unseekable
were not honored. We allowed overriding this via --force-seekable=yes,
in which case it depended on your luck whether the seek would work, or
the player crapped its pants.

With the demuxer packet cache, we can tell exactly whether a seek will
work (at least if there's only 1 seek range). We can just let seeks go
through. Everything to allow this is already in place, and this commit
just moves around some minor things.

Note that the demux_seek() return value was not used before, because low
level (i.e. network level) seeks are usually asynchronous, and if they
fail, the state is pretty much undefined. We simply repurpose the return
value to signal whether cache seeking worked. If it didn't, we can just
resume playback normally, because demuxing continues unaffected, and no
decoder are reset.

This should be particularly helpful to people who for some reason stream
data into stdin via streamlink and such.
This commit is contained in:
wm4 2017-12-23 22:28:08 +01:00 committed by Martin Herkt
parent bf111f9c3c
commit c12d897a3a
3 changed files with 53 additions and 29 deletions

View File

@ -2240,17 +2240,16 @@ static struct demux_packet *find_seek_target(struct demux_queue *queue,
}
// must be called locked
static bool try_seek_cache(struct demux_internal *in, double pts, int flags)
static struct demux_cached_range *find_cache_seek_target(struct demux_internal *in,
double pts, int flags)
{
if ((flags & SEEK_FACTOR) || !in->seekable_cache)
return false;
// Note about queued low level seeks: in->seeking can be true here, and it
// might come from a previous resume seek to the current range. If we end
// up seeking into the current range (i.e. just changing time offset), the
// seek needs to continue. Otherwise, we override the queued seek anyway.
if ((flags & SEEK_FACTOR) || !in->seekable_cache)
return NULL;
struct demux_cached_range *range = NULL;
for (int n = 0; n < in->num_ranges; n++) {
struct demux_cached_range *r = in->ranges[n];
if (r->seek_start != MP_NOPTS_VALUE) {
@ -2259,15 +2258,21 @@ static bool try_seek_cache(struct demux_internal *in, double pts, int flags)
if (pts >= r->seek_start && pts <= r->seek_end) {
MP_VERBOSE(in, "...using this range for in-cache seek.\n");
range = r;
break;
return r;
}
}
}
if (!range)
return false;
return NULL;
}
// must be called locked
// range must be non-NULL and from find_cache_seek_target() using the same pts
// and flags, before any other changes to the cached state
static void execute_cache_seek(struct demux_internal *in,
struct demux_cached_range *range,
double pts, int flags)
{
// Adjust the seek target to the found video key frames. Otherwise the
// video will undershoot the seek target, while audio will be closer to it.
// The player frontend will play the additional video without audio, so
@ -2341,8 +2346,6 @@ static bool try_seek_cache(struct demux_internal *in, double pts, int flags)
MP_VERBOSE(in, "resuming demuxer to end of cached range\n");
}
return true;
}
// Create a new blank ache range, and backup the old one. If the seekable
@ -2369,23 +2372,36 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags)
{
struct demux_internal *in = demuxer->in;
assert(demuxer == in->d_user);
if (!demuxer->seekable) {
MP_WARN(demuxer, "Cannot seek in this file.\n");
return 0;
}
if (seek_pts == MP_NOPTS_VALUE)
return 0;
int res = 0;
pthread_mutex_lock(&in->lock);
if (seek_pts == MP_NOPTS_VALUE)
goto done;
MP_VERBOSE(in, "queuing seek to %f%s\n", seek_pts,
in->seeking ? " (cascade)" : "");
if (!(flags & SEEK_FACTOR))
seek_pts = MP_ADD_PTS(seek_pts, -in->ts_offset);
bool require_cache = flags & SEEK_CACHED;
flags &= ~(unsigned)SEEK_CACHED;
struct demux_cached_range *cache_target =
find_cache_seek_target(in, seek_pts, flags);
if (!cache_target) {
if (require_cache) {
MP_VERBOSE(demuxer, "Cached seek not possible.\n");
goto done;
}
if (!demuxer->seekable) {
MP_WARN(demuxer, "Cannot seek in this file.\n");
goto done;
}
}
clear_reader_state(in);
in->eof = false;
@ -2393,7 +2409,9 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags)
in->idle = true;
in->reading = false;
if (!try_seek_cache(in, seek_pts, flags)) {
if (cache_target) {
execute_cache_seek(in, cache_target, seek_pts, flags);
} else {
switch_to_fresh_cache_range(in);
in->seeking = true;
@ -2404,10 +2422,12 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags)
if (!in->threading && in->seeking)
execute_seek(in);
res = 1;
done:
pthread_cond_signal(&in->wakeup);
pthread_mutex_unlock(&in->lock);
return 1;
return res;
}
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,

View File

@ -68,6 +68,7 @@ struct demux_ctrl_stream_ctrl {
#define SEEK_FACTOR (1 << 1) // argument is in range [0,1]
#define SEEK_FORWARD (1 << 2) // prefer later time if not exact
// (if unset, prefer earlier time)
#define SEEK_CACHED (1 << 3) // allow packet cache seeks only
#define SEEK_HR (1 << 5) // hr-seek (this is a weak hint only)
// Strictness of the demuxer open format check.

View File

@ -253,12 +253,6 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
if (!mpctx->demuxer || seek.type == MPSEEK_NONE || seek.amount == MP_NOPTS_VALUE)
return;
if (!mpctx->demuxer->seekable) {
MP_ERR(mpctx, "Cannot seek in this file.\n");
MP_ERR(mpctx, "You can forcibly enable it with '--force-seekable=yes'.\n");
return;
}
bool hr_seek_very_exact = seek.exact == MPSEEK_VERY_EXACT;
double current_time = get_current_time(mpctx);
if (current_time == MP_NOPTS_VALUE && seek.type == MPSEEK_RELATIVE)
@ -325,7 +319,16 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
demux_flags = (demux_flags | SEEK_HR) & ~SEEK_FORWARD;
}
demux_seek(mpctx->demuxer, demux_pts, demux_flags);
if (!mpctx->demuxer->seekable)
demux_flags |= SEEK_CACHED;
if (!demux_seek(mpctx->demuxer, demux_pts, demux_flags)) {
if (!mpctx->demuxer->seekable) {
MP_ERR(mpctx, "Cannot seek in this file.\n");
MP_ERR(mpctx, "You can force it with '--force-seekable=yes'.\n");
}
return;
}
// Seek external, extra files too:
for (int t = 0; t < mpctx->num_tracks; t++) {