diff --git a/player/core.h b/player/core.h index d3fc4195e2..1147d0e8f5 100644 --- a/player/core.h +++ b/player/core.h @@ -224,14 +224,16 @@ typedef struct MPContext { struct vo *video_out; // next_frame[0] is the next frame, next_frame[1] the one after that. struct mp_image *next_frame[2]; + struct mp_image *saved_frame; // for hrseek_lastframe enum playback_status video_status, audio_status; bool restart_complete; /* Set if audio should be timed to start with video frame after seeking, * not set when e.g. playing cover art */ bool sync_audio_to_video; - bool hrseek_active; - bool hrseek_framedrop; + bool hrseek_active; // skip all data until hrseek_pts + bool hrseek_framedrop; // allow decoder to drop frames before hrseek_pts + bool hrseek_lastframe; // drop everything until last frame reached double hrseek_pts; // AV sync: the next frame should be shown when the audio out has this // much (in seconds) buffered data left. Increased when more data is diff --git a/player/playloop.c b/player/playloop.c index e20e088e00..67e7f22001 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -151,6 +151,7 @@ void reset_playback_state(struct MPContext *mpctx) mpctx->hrseek_active = false; mpctx->hrseek_framedrop = false; + mpctx->hrseek_lastframe = false; mpctx->playback_pts = MP_NOPTS_VALUE; mpctx->last_seek_pts = MP_NOPTS_VALUE; mpctx->cache_wait_time = 0; @@ -780,6 +781,28 @@ static void handle_loop_file(struct MPContext *mpctx) } } +static void seek_to_last_frame(struct MPContext *mpctx) +{ + if (!mpctx->d_video) + return; + MP_VERBOSE(mpctx, "seeking to last frame...\n"); + // Approximately seek close to the end of the file. + // Usually, it will seek some seconds before end. + double end = get_play_end_pts(mpctx); + if (end == MP_NOPTS_VALUE) + end = get_time_length(mpctx); + mp_seek(mpctx, (struct seek_params){ + .type = MPSEEK_ABSOLUTE, + .amount = end, + .exact = 2, // "very exact", no framedrop + }, false); + // Make it exact: stop seek only if last frame was reached. + if (mpctx->hrseek_active) { + mpctx->hrseek_pts = 1e99; // "infinite" + mpctx->hrseek_lastframe = true; + } +} + static void handle_keep_open(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; @@ -787,8 +810,11 @@ static void handle_keep_open(struct MPContext *mpctx) !playlist_get_next(mpctx->playlist, 1) && opts->loop_times < 0) { mpctx->stop_play = KEEP_PLAYING; - if (mpctx->d_video) + if (mpctx->d_video) { + if (!vo_has_frame(mpctx->video_out)) // EOF not reached normally + seek_to_last_frame(mpctx); mpctx->playback_pts = mpctx->last_vo_pts; + } if (!mpctx->opts->pause) pause_player(mpctx); } diff --git a/player/video.c b/player/video.c index 506c7971a7..f0829a2c8d 100644 --- a/player/video.c +++ b/player/video.c @@ -214,6 +214,7 @@ void reset_video_state(struct MPContext *mpctx) mp_image_unrefp(&mpctx->next_frame[0]); mp_image_unrefp(&mpctx->next_frame[1]); + mp_image_unrefp(&mpctx->saved_frame); mpctx->delay = 0; mpctx->time_frame = 0; @@ -534,6 +535,8 @@ static bool have_new_frame(struct MPContext *mpctx) // returns VD_* code static int video_output_image(struct MPContext *mpctx, double endpts) { + bool hrseek = mpctx->hrseek_active && mpctx->video_status == STATUS_SYNCING; + if (mpctx->d_video->header->attached_picture) { if (vo_has_frame(mpctx->video_out)) return VD_EOF; @@ -591,16 +594,18 @@ static int video_output_image(struct MPContext *mpctx, double endpts) add_frame_pts(mpctx, img->pts); bool drop = false; - bool hrseek = mpctx->hrseek_active - && mpctx->video_status == STATUS_SYNCING; - if (hrseek && img->pts < mpctx->hrseek_pts - .005) - drop = true; if ((endpts != MP_NOPTS_VALUE && img->pts >= endpts) || mpctx->max_frames == 0) { drop = true; r = VD_EOF; } + if (!drop && hrseek && mpctx->hrseek_lastframe) { + mp_image_setrefp(&mpctx->saved_frame, img); + drop = true; + } + if (hrseek && img->pts < mpctx->hrseek_pts - .005) + drop = true; if (drop) { talloc_free(img); } else { @@ -613,6 +618,13 @@ static int video_output_image(struct MPContext *mpctx, double endpts) if (have_new_frame(mpctx) || (r <= 0 && mpctx->next_frame[0])) return VD_NEW_FRAME; + // Last-frame seek + if (r <= 0 && hrseek && mpctx->hrseek_lastframe && mpctx->saved_frame) { + mpctx->next_frame[1] = mpctx->saved_frame; + mpctx->saved_frame = NULL; + return VD_PROGRESS; + } + return r; }