summaryrefslogtreecommitdiffstats
path: root/player/playloop.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2013-12-17 00:53:22 +0100
committerwm4 <wm4@nowhere>2013-12-17 00:53:22 +0100
commite44911142914783c9ec717f329bd9b6a8bb9b70e (patch)
tree92bb653f7d56553ffd3bb6e5a22ffc0db91142e8 /player/playloop.c
parent7dc7b900c622235d595337c988a0c75280084b7c (diff)
downloadmpv-e44911142914783c9ec717f329bd9b6a8bb9b70e.tar.bz2
mpv-e44911142914783c9ec717f329bd9b6a8bb9b70e.tar.xz
Move mpvcore/player/ to player/
Diffstat (limited to 'player/playloop.c')
-rw-r--r--player/playloop.c1343
1 files changed, 1343 insertions, 0 deletions
diff --git a/player/playloop.c b/player/playloop.c
new file mode 100644
index 0000000000..dbc87a7ec6
--- /dev/null
+++ b/player/playloop.c
@@ -0,0 +1,1343 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <math.h>
+#include <assert.h>
+
+#include "config.h"
+#include "talloc.h"
+
+#include "mpvcore/mp_msg.h"
+#include "mpvcore/options.h"
+#include "mpvcore/mp_common.h"
+#include "mpvcore/encode.h"
+#include "mpvcore/m_property.h"
+#include "mpvcore/playlist.h"
+#include "mpvcore/input/input.h"
+
+#include "osdep/timer.h"
+
+#include "audio/mixer.h"
+#include "audio/decode/dec_audio.h"
+#include "audio/filter/af.h"
+#include "audio/out/ao.h"
+#include "demux/demux.h"
+#include "stream/stream.h"
+#include "sub/osd.h"
+#include "video/filter/vf.h"
+#include "video/decode/dec_video.h"
+#include "video/out/vo.h"
+
+#include "mp_core.h"
+#include "screenshot.h"
+#include "command.h"
+
+#define WAKEUP_PERIOD 0.5
+
+static const char av_desync_help_text[] = _(
+"\n\n"
+" *************************************************\n"
+" **** Audio/Video desynchronisation detected! ****\n"
+" *************************************************\n\n"
+"This means either the audio or the video is played too slowly.\n"
+"Possible reasons, problems, workarounds:\n"
+"- Your system is simply too slow for this file.\n"
+" Transcode it to a lower bitrate file with tools like HandBrake.\n"
+"- Broken/buggy _audio_ driver.\n"
+" Experiment with different values for --autosync, 30 is a good start.\n"
+" If you have PulseAudio, try --ao=alsa .\n"
+"- Slow video output.\n"
+" Try a different -vo driver (-vo help for a list) or try -framedrop!\n"
+"- Playing a video file with --vo=opengl with higher FPS than the monitor.\n"
+" This is due to vsync limiting the framerate.\n"
+"- Playing from a slow network source.\n"
+" Download the file instead.\n"
+"- Try to find out whether audio or video is causing this by experimenting\n"
+" with --no-video and --no-audio.\n"
+"- If you swiched audio or video tracks, try seeking to force synchronization.\n"
+"If none of this helps you, file a bug report.\n\n");
+
+
+void pause_player(struct MPContext *mpctx)
+{
+ mp_notify_property(mpctx, "pause");
+
+ mpctx->opts->pause = 1;
+
+ if (mpctx->video_out)
+ vo_control(mpctx->video_out, VOCTRL_RESTORE_SCREENSAVER, NULL);
+
+ if (mpctx->paused)
+ return;
+ mpctx->paused = true;
+ mpctx->step_frames = 0;
+ mpctx->time_frame -= get_relative_time(mpctx);
+ mpctx->osd_function = 0;
+ mpctx->paused_for_cache = false;
+
+ if (mpctx->video_out && mpctx->d_video && mpctx->video_out->config_ok)
+ vo_control(mpctx->video_out, VOCTRL_PAUSE, NULL);
+
+ if (mpctx->ao && mpctx->d_audio)
+ ao_pause(mpctx->ao); // pause audio, keep data if possible
+
+ // Only print status if there's actually a file being played.
+ if (mpctx->num_sources)
+ print_status(mpctx);
+
+ if (!mpctx->opts->quiet)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_PAUSED\n");
+}
+
+void unpause_player(struct MPContext *mpctx)
+{
+ mp_notify_property(mpctx, "pause");
+
+ mpctx->opts->pause = 0;
+
+ if (mpctx->video_out && mpctx->opts->stop_screensaver)
+ vo_control(mpctx->video_out, VOCTRL_KILL_SCREENSAVER, NULL);
+
+ if (!mpctx->paused)
+ return;
+ // Don't actually unpause while cache is loading.
+ if (mpctx->paused_for_cache)
+ return;
+ mpctx->paused = false;
+ mpctx->osd_function = 0;
+
+ if (mpctx->ao && mpctx->d_audio)
+ ao_resume(mpctx->ao);
+ if (mpctx->video_out && mpctx->d_video && mpctx->video_out->config_ok)
+ vo_control(mpctx->video_out, VOCTRL_RESUME, NULL); // resume video
+ (void)get_relative_time(mpctx); // ignore time that passed during pause
+}
+
+static void draw_osd(struct MPContext *mpctx)
+{
+ struct vo *vo = mpctx->video_out;
+
+ mpctx->osd->vo_pts = mpctx->video_pts;
+ vo_draw_osd(vo, mpctx->osd);
+}
+
+static bool redraw_osd(struct MPContext *mpctx)
+{
+ struct vo *vo = mpctx->video_out;
+ if (vo_redraw_frame(vo) < 0)
+ return false;
+
+ draw_osd(mpctx);
+
+ vo_flip_page(vo, 0, -1);
+ return true;
+}
+
+void add_step_frame(struct MPContext *mpctx, int dir)
+{
+ if (!mpctx->d_video)
+ return;
+ if (dir > 0) {
+ mpctx->step_frames += 1;
+ unpause_player(mpctx);
+ } else if (dir < 0) {
+ if (!mpctx->backstep_active && !mpctx->hrseek_active) {
+ mpctx->backstep_active = true;
+ mpctx->backstep_start_seek_ts = mpctx->vo_pts_history_seek_ts;
+ pause_player(mpctx);
+ }
+ }
+}
+
+static void seek_reset(struct MPContext *mpctx, bool reset_ao)
+{
+ if (mpctx->d_video) {
+ video_reset_decoding(mpctx->d_video);
+ vo_seek_reset(mpctx->video_out);
+ }
+
+ if (mpctx->d_audio) {
+ audio_reset_decoding(mpctx->d_audio);
+ if (reset_ao)
+ clear_audio_output_buffers(mpctx);
+ }
+
+ reset_subtitles(mpctx);
+
+ mpctx->video_pts = MP_NOPTS_VALUE;
+ mpctx->video_next_pts = MP_NOPTS_VALUE;
+ mpctx->playing_last_frame = false;
+ mpctx->last_frame_duration = 0;
+ mpctx->delay = 0;
+ mpctx->time_frame = 0;
+ mpctx->restart_playback = true;
+ mpctx->hrseek_active = false;
+ mpctx->hrseek_framedrop = false;
+ mpctx->total_avsync_change = 0;
+ mpctx->drop_frame_cnt = 0;
+ mpctx->dropped_frames = 0;
+ mpctx->playback_pts = MP_NOPTS_VALUE;
+
+#if HAVE_ENCODING
+ encode_lavc_discontinuity(mpctx->encode_lavc_ctx);
+#endif
+}
+
+// return -1 if seek failed (non-seekable stream?), 0 otherwise
+// timeline_fallthrough: true if used to explicitly switch timeline - in this
+// case, don't drop buffered AO audio data, so that
+// timeline segment transition is seemless
+static int mp_seek(MPContext *mpctx, struct seek_params seek,
+ bool timeline_fallthrough)
+{
+ struct MPOpts *opts = mpctx->opts;
+ uint64_t prev_seek_ts = mpctx->vo_pts_history_seek_ts;
+
+ if (!mpctx->demuxer)
+ return -1;
+
+ if (!mpctx->demuxer->seekable) {
+ MP_ERR(mpctx, "Can't seek in this file.\n");
+ return -1;
+ }
+
+ if (mpctx->stop_play == AT_END_OF_FILE)
+ mpctx->stop_play = KEEP_PLAYING;
+
+ double hr_seek_offset = opts->hr_seek_demuxer_offset;
+ // Always try to compensate for possibly bad demuxers in "special"
+ // situations where we need more robustness from the hr-seek code, even
+ // if the user doesn't use --hr-seek-demuxer-offset.
+ // The value is arbitrary, but should be "good enough" in most situations.
+ if (seek.exact > 1)
+ hr_seek_offset = MPMAX(hr_seek_offset, 0.5); // arbitrary
+
+ bool hr_seek = mpctx->demuxer->accurate_seek && opts->correct_pts;
+ hr_seek &= seek.exact >= 0 && seek.type != MPSEEK_FACTOR;
+ hr_seek &= (opts->hr_seek == 0 && seek.type == MPSEEK_ABSOLUTE) ||
+ opts->hr_seek > 0 || seek.exact > 0;
+ if (seek.type == MPSEEK_FACTOR || seek.amount < 0 ||
+ (seek.type == MPSEEK_ABSOLUTE && seek.amount < mpctx->last_chapter_pts))
+ mpctx->last_chapter_seek = -2;
+ if (seek.type == MPSEEK_FACTOR) {
+ double len = get_time_length(mpctx);
+ if (len > 0 && !mpctx->demuxer->ts_resets_possible) {
+ seek.amount = seek.amount * len + get_start_time(mpctx);
+ seek.type = MPSEEK_ABSOLUTE;
+ }
+ }
+ if ((mpctx->demuxer->accurate_seek || mpctx->timeline)
+ && seek.type == MPSEEK_RELATIVE) {
+ seek.type = MPSEEK_ABSOLUTE;
+ seek.direction = seek.amount > 0 ? 1 : -1;
+ seek.amount += get_current_time(mpctx);
+ }
+
+ bool need_reset = false;
+ double demuxer_amount = seek.amount;
+ if (mpctx->timeline) {
+ demuxer_amount = timeline_set_from_time(mpctx, seek.amount,
+ &need_reset);
+ if (demuxer_amount == -1) {
+ assert(!need_reset);
+ mpctx->stop_play = AT_END_OF_FILE;
+ if (mpctx->d_audio && !timeline_fallthrough) {
+ // Seek outside of the file -> clear audio from current position
+ clear_audio_decode_buffers(mpctx);
+ clear_audio_output_buffers(mpctx);
+ }
+ return -1;
+ }
+ }
+ if (need_reset) {
+ reinit_video_chain(mpctx);
+ reinit_audio_chain(mpctx);
+ reinit_subs(mpctx);
+ }
+
+ int demuxer_style = 0;
+ switch (seek.type) {
+ case MPSEEK_FACTOR:
+ demuxer_style |= SEEK_ABSOLUTE | SEEK_FACTOR;
+ break;
+ case MPSEEK_ABSOLUTE:
+ demuxer_style |= SEEK_ABSOLUTE;
+ break;
+ }
+ if (hr_seek || seek.direction < 0)
+ demuxer_style |= SEEK_BACKWARD;
+ else if (seek.direction > 0)
+ demuxer_style |= SEEK_FORWARD;
+ if (hr_seek || opts->mkv_subtitle_preroll)
+ demuxer_style |= SEEK_SUBPREROLL;
+
+ if (hr_seek)
+ demuxer_amount -= hr_seek_offset;
+ int seekresult = demux_seek(mpctx->demuxer, demuxer_amount, demuxer_style);
+ if (seekresult == 0) {
+ if (need_reset)
+ seek_reset(mpctx, !timeline_fallthrough);
+ return -1;
+ }
+
+ // If audio or demuxer subs come from different files, seek them too:
+ bool have_external_tracks = false;
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ struct track *track = mpctx->current_track[type];
+ have_external_tracks |= track && track->is_external && track->demuxer;
+ }
+ if (have_external_tracks) {
+ double main_new_pos;
+ if (seek.type == MPSEEK_ABSOLUTE) {
+ main_new_pos = seek.amount - mpctx->video_offset;
+ } else {
+ main_new_pos = get_main_demux_pts(mpctx);
+ }
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ struct track *track = mpctx->current_track[type];
+ if (track && track->is_external && track->demuxer)
+ demux_seek(track->demuxer, main_new_pos, SEEK_ABSOLUTE);
+ }
+ }
+
+ seek_reset(mpctx, !timeline_fallthrough);
+
+ if (timeline_fallthrough) {
+ // Important if video reinit happens.
+ mpctx->vo_pts_history_seek_ts = prev_seek_ts;
+ } else {
+ mpctx->vo_pts_history_seek_ts++;
+ mpctx->backstep_active = false;
+ }
+
+ /* Use the target time as "current position" for further relative
+ * seeks etc until a new video frame has been decoded */
+ if (seek.type == MPSEEK_ABSOLUTE) {
+ mpctx->video_pts = seek.amount;
+ mpctx->last_seek_pts = seek.amount;
+ } else
+ mpctx->last_seek_pts = MP_NOPTS_VALUE;
+
+ // The hr_seek==false case is for skipping frames with PTS before the
+ // current timeline chapter start. It's not really known where the demuxer
+ // level seek will end up, so the hrseek mechanism is abused to skip all
+ // frames before chapter start by setting hrseek_pts to the chapter start.
+ // It does nothing when the seek is inside of the current chapter, and
+ // seeking past the chapter is handled elsewhere.
+ if (hr_seek || mpctx->timeline) {
+ mpctx->hrseek_active = true;
+ mpctx->hrseek_framedrop = true;
+ mpctx->hrseek_pts = hr_seek ? seek.amount
+ : mpctx->timeline[mpctx->timeline_part].start;
+ }
+
+ mpctx->start_timestamp = mp_time_sec();
+
+ return 0;
+}
+
+void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
+ int exact)
+{
+ struct seek_params *seek = &mpctx->seek;
+ switch (type) {
+ case MPSEEK_RELATIVE:
+ if (seek->type == MPSEEK_FACTOR)
+ return; // Well... not common enough to bother doing better
+ seek->amount += amount;
+ seek->exact = MPMAX(seek->exact, exact);
+ if (seek->type == MPSEEK_NONE)
+ seek->exact = exact;
+ if (seek->type == MPSEEK_ABSOLUTE)
+ return;
+ if (seek->amount == 0) {
+ *seek = (struct seek_params){ 0 };
+ return;
+ }
+ seek->type = MPSEEK_RELATIVE;
+ return;
+ case MPSEEK_ABSOLUTE:
+ case MPSEEK_FACTOR:
+ *seek = (struct seek_params) {
+ .type = type,
+ .amount = amount,
+ .exact = exact,
+ };
+ return;
+ case MPSEEK_NONE:
+ *seek = (struct seek_params){ 0 };
+ return;
+ }
+ abort();
+}
+
+void execute_queued_seek(struct MPContext *mpctx)
+{
+ if (mpctx->seek.type) {
+ mp_seek(mpctx, mpctx->seek, false);
+ mpctx->seek = (struct seek_params){0};
+ }
+}
+
+double get_time_length(struct MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->demuxer;
+ if (!demuxer)
+ return 0;
+
+ if (mpctx->timeline)
+ return mpctx->timeline[mpctx->num_timeline_parts].start;
+
+ double len = demuxer_get_time_length(demuxer);
+ if (len >= 0)
+ return len;
+
+ // Unknown
+ return 0;
+}
+
+/* If there are timestamps from stream level then use those (for example
+ * DVDs can have consistent times there while the MPEG-level timestamps
+ * reset). */
+double get_current_time(struct MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->demuxer;
+ if (!demuxer)
+ return 0;
+ if (demuxer->stream_pts != MP_NOPTS_VALUE)
+ return demuxer->stream_pts;
+ if (mpctx->playback_pts != MP_NOPTS_VALUE)
+ return mpctx->playback_pts;
+ if (mpctx->last_seek_pts != MP_NOPTS_VALUE)
+ return mpctx->last_seek_pts;
+ return 0;
+}
+
+// Return playback position in 0.0-1.0 ratio, or -1 if unknown.
+double get_current_pos_ratio(struct MPContext *mpctx, bool use_range)
+{
+ struct demuxer *demuxer = mpctx->demuxer;
+ if (!demuxer)
+ return -1;
+ double ans = -1;
+ double start = get_start_time(mpctx);
+ double len = get_time_length(mpctx);
+ if (use_range) {
+ double startpos = rel_time_to_abs(mpctx, mpctx->opts->play_start,
+ MP_NOPTS_VALUE);
+ double endpos = get_play_end_pts(mpctx);
+ if (endpos == MP_NOPTS_VALUE || endpos > start + len)
+ endpos = start + len;
+ if (startpos == MP_NOPTS_VALUE || startpos < start)
+ startpos = start;
+ if (endpos < startpos)
+ endpos = startpos;
+ start = startpos;
+ len = endpos - startpos;
+ }
+ double pos = get_current_time(mpctx);
+ if (len > 0 && !demuxer->ts_resets_possible) {
+ ans = MPCLAMP((pos - start) / len, 0, 1);
+ } else {
+ struct stream *s = demuxer->stream;
+ int64_t size = s->end_pos - s->start_pos;
+ int64_t fpos = demuxer->filepos >= 0 ?
+ demuxer->filepos : stream_tell(demuxer->stream);
+ if (size > 0)
+ ans = MPCLAMP((double)(fpos - s->start_pos) / size, 0, 1);
+ }
+ if (use_range) {
+ if (mpctx->opts->play_frames > 0)
+ ans = MPMAX(ans, 1.0 -
+ mpctx->max_frames / (double) mpctx->opts->play_frames);
+ }
+ return ans;
+}
+
+int get_percent_pos(struct MPContext *mpctx)
+{
+ int pos = get_current_pos_ratio(mpctx, false) * 100;
+ return MPCLAMP(pos, 0, 100);
+}
+
+// -2 is no chapters, -1 is before first chapter
+int get_current_chapter(struct MPContext *mpctx)
+{
+ double current_pts = get_current_time(mpctx);
+ if (mpctx->chapters) {
+ int i;
+ for (i = 1; i < mpctx->num_chapters; i++)
+ if (current_pts < mpctx->chapters[i].start)
+ break;
+ return MPMAX(mpctx->last_chapter_seek, i - 1);
+ }
+ if (mpctx->master_demuxer)
+ return MPMAX(mpctx->last_chapter_seek,
+ demuxer_get_current_chapter(mpctx->master_demuxer, current_pts));
+ return -2;
+}
+
+char *chapter_display_name(struct MPContext *mpctx, int chapter)
+{
+ char *name = chapter_name(mpctx, chapter);
+ char *dname = name;
+ if (name) {
+ dname = talloc_asprintf(NULL, "(%d) %s", chapter + 1, name);
+ } else if (chapter < -1) {
+ dname = talloc_strdup(NULL, "(unavailable)");
+ } else {
+ int chapter_count = get_chapter_count(mpctx);
+ if (chapter_count <= 0)
+ dname = talloc_asprintf(NULL, "(%d)", chapter + 1);
+ else
+ dname = talloc_asprintf(NULL, "(%d) of %d", chapter + 1,
+ chapter_count);
+ }
+ if (dname != name)
+ talloc_free(name);
+ return dname;
+}
+
+// returns NULL if chapter name unavailable
+char *chapter_name(struct MPContext *mpctx, int chapter)
+{
+ if (mpctx->chapters) {
+ if (chapter < 0 || chapter >= mpctx->num_chapters)
+ return NULL;
+ return talloc_strdup(NULL, mpctx->chapters[chapter].name);
+ }
+ if (mpctx->master_demuxer)
+ return demuxer_chapter_name(mpctx->master_demuxer, chapter);
+ return NULL;
+}
+
+// returns the start of the chapter in seconds (-1 if unavailable)
+double chapter_start_time(struct MPContext *mpctx, int chapter)
+{
+ if (chapter == -1)
+ return get_start_time(mpctx);
+ if (mpctx->chapters)
+ return mpctx->chapters[chapter].start;
+ if (mpctx->master_demuxer)
+ return demuxer_chapter_time(mpctx->master_demuxer, chapter);
+ return -1;
+}
+
+int get_chapter_count(struct MPContext *mpctx)
+{
+ if (mpctx->chapters)
+ return mpctx->num_chapters;
+ if (mpctx->master_demuxer)
+ return demuxer_chapter_count(mpctx->master_demuxer);
+ return 0;
+}
+
+// Seek to a given chapter. Tries to queue the seek, but might seek immediately
+// in some cases. Returns success, no matter if seek is queued or immediate.
+bool mp_seek_chapter(struct MPContext *mpctx, int chapter)
+{
+ int num = get_chapter_count(mpctx);
+ if (num == 0)
+ return false;
+ if (chapter < -1 || chapter >= num)
+ return false;
+
+ mpctx->last_chapter_seek = -2;
+
+ double pts;
+ if (chapter == -1) {
+ pts = get_start_time(mpctx);
+ goto do_seek;
+ } else if (mpctx->chapters) {
+ pts = mpctx->chapters[chapter].start;
+ goto do_seek;
+ } else if (mpctx->master_demuxer) {
+ int res = demuxer_seek_chapter(mpctx->master_demuxer, chapter, &pts);
+ if (res >= 0) {
+ if (pts == -1) {
+ // for DVD/BD - seek happened via stream layer
+ seek_reset(mpctx, true);
+ mpctx->seek = (struct seek_params){0};
+ return true;
+ }
+ chapter = res;
+ goto do_seek;
+ }
+ }
+ return false;
+
+do_seek:
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, pts, 0);
+ mpctx->last_chapter_seek = chapter;
+ mpctx->last_chapter_pts = pts;
+ return true;
+}
+
+static void update_avsync(struct MPContext *mpctx)
+{
+ if (!mpctx->d_audio || !mpctx->d_video)
+ return;
+
+ double a_pos = playing_audio_pts(mpctx);
+
+ mpctx->last_av_difference = a_pos - mpctx->video_pts - mpctx->audio_delay;
+ if (mpctx->time_frame > 0)
+ mpctx->last_av_difference +=
+ mpctx->time_frame * mpctx->opts->playback_speed;
+ if (a_pos == MP_NOPTS_VALUE || mpctx->video_pts == MP_NOPTS_VALUE)
+ mpctx->last_av_difference = MP_NOPTS_VALUE;
+ if (mpctx->last_av_difference > 0.5 && mpctx->drop_frame_cnt > 50
+ && !mpctx->drop_message_shown) {
+ MP_WARN(mpctx, "%s", av_desync_help_text);
+ mpctx->drop_message_shown = true;
+ }
+}
+
+/* Modify video timing to match the audio timeline. There are two main
+ * reasons this is needed. First, video and audio can start from different
+ * positions at beginning of file or after a seek (MPlayer starts both
+ * immediately even if they have different pts). Second, the file can have
+ * audio timestamps that are inconsistent with the duration of the audio
+ * packets, for example two consecutive timestamp values differing by
+ * one second but only a packet with enough samples for half a second
+ * of playback between them.
+ */
+static void adjust_sync(struct MPContext *mpctx, double frame_time)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ if (!mpctx->d_audio || mpctx->syncing_audio)
+ return;
+
+ double a_pts = written_audio_pts(mpctx) - mpctx->delay;
+ double v_pts = mpctx->video_next_pts;
+ double av_delay = a_pts - v_pts;
+ // Try to sync vo_flip() so it will *finish* at given time
+ av_delay += mpctx->last_vo_flip_duration;
+ av_delay -= mpctx->audio_delay; // This much pts difference is desired
+
+ double change = av_delay * 0.1;
+ double max_change = opts->default_max_pts_correction >= 0 ?
+ opts->default_max_pts_correction : frame_time * 0.1;
+ if (change < -max_change)
+ change = -max_change;
+ else if (change > max_change)
+ change = max_change;
+ mpctx->delay += change;
+ mpctx->total_avsync_change += change;
+}
+
+static bool handle_osd_redraw(struct MPContext *mpctx)
+{
+ if (!mpctx->video_out || !mpctx->video_out->config_ok)
+ return false;
+ bool want_redraw = vo_get_want_redraw(mpctx->video_out);
+ if (mpctx->video_out->driver->draw_osd)
+ want_redraw |= mpctx->osd->want_redraw;
+ mpctx->osd->want_redraw = false;
+ if (want_redraw) {
+ if (redraw_osd(mpctx))
+ return true;
+ }
+ return false;
+}
+
+static void handle_metadata_update(struct MPContext *mpctx)
+{
+ if (mp_time_sec() > mpctx->last_metadata_update + 2) {
+ demux_info_update(mpctx->demuxer);
+ mpctx->last_metadata_update = mp_time_sec();
+ }
+}
+
+static void handle_pause_on_low_cache(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ int cache = mp_get_cache_percent(mpctx);
+ bool idle = mp_get_cache_idle(mpctx);
+ if (mpctx->paused && mpctx->paused_for_cache) {
+ if (cache < 0 || cache >= opts->stream_cache_min_percent || idle) {
+ mpctx->paused_for_cache = false;
+ if (!opts->pause)
+ unpause_player(mpctx);
+ }
+ } else {
+ if (cache >= 0 && cache <= opts->stream_cache_pause && !idle &&
+ opts->stream_cache_pause < opts->stream_cache_min_percent)
+ {
+ bool prev_paused_user = opts->pause;
+ pause_player(mpctx);
+ mpctx->paused_for_cache = true;
+ opts->pause = prev_paused_user;
+ }
+ }
+}
+
+static void handle_heartbeat_cmd(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (opts->heartbeat_cmd && !mpctx->paused) {
+ double now = mp_time_sec();
+ if (now - mpctx->last_heartbeat > opts->heartbeat_interval) {
+ mpctx->last_heartbeat = now;
+ system(opts->heartbeat_cmd);
+ }
+ }
+}
+
+static void handle_cursor_autohide(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct vo *vo = mpctx->video_out;
+
+ if (!vo)
+ return;
+
+ bool mouse_cursor_visible = mpctx->mouse_cursor_visible;
+
+ unsigned mouse_event_ts = mp_input_get_mouse_event_counter(mpctx->input);
+ if (mpctx->mouse_event_ts != mouse_event_ts) {
+ mpctx->mouse_event_ts = mouse_event_ts;
+ mpctx->mouse_timer =
+ mp_time_sec() + opts->cursor_autohide_delay / 1000.0;
+ mouse_cursor_visible = true;
+ }
+
+ if (mp_time_sec() >= mpctx->mouse_timer)
+ mouse_cursor_visible = false;
+
+ if (opts->cursor_autohide_delay == -1)
+ mouse_cursor_visible = true;
+
+ if (opts->cursor_autohide_delay == -2)
+ mouse_cursor_visible = false;
+
+ if (opts->cursor_autohide_fs && !opts->vo.fullscreen)
+ mouse_cursor_visible = true;
+
+ if (mouse_cursor_visible != mpctx->mouse_cursor_visible)
+ vo_control(vo, VOCTRL_SET_CURSOR_VISIBILITY, &mouse_cursor_visible);
+ mpctx->mouse_cursor_visible = mouse_cursor_visible;
+}
+
+static void handle_input_and_seek_coalesce(struct MPContext *mpctx)
+{
+ mp_flush_events(mpctx);
+
+ mp_cmd_t *cmd;
+ while ((cmd = mp_input_get_cmd(mpctx->input, 0, 1)) != NULL) {
+ /* Allow running consecutive seek commands to combine them,
+ * but execute the seek before running other commands.
+ * If the user seeks continuously (keeps arrow key down)
+ * try to finish showing a frame from one location before doing
+ * another seek (which could lead to unchanging display). */
+ if ((mpctx->seek.type && cmd->id != MP_CMD_SEEK) ||
+ (mpctx->restart_playback && cmd->id == MP_CMD_SEEK &&
+ mp_time_sec() - mpctx->start_timestamp < 0.3))
+ break;
+ cmd = mp_input_get_cmd(mpctx->input, 0, 0);
+ run_command(mpctx, cmd);
+ mp_cmd_free(cmd);
+ if (mpctx->stop_play)
+ break;
+ }
+}
+
+void add_frame_pts(struct MPContext *mpctx, double pts)
+{
+ if (pts == MP_NOPTS_VALUE || mpctx->hrseek_framedrop) {
+ mpctx->vo_pts_history_seek_ts++; // mark discontinuity
+ return;
+ }
+ for (int n = MAX_NUM_VO_PTS - 1; n >= 1; n--) {
+ mpctx->vo_pts_history_seek[n] = mpctx->vo_pts_history_seek[n - 1];
+ mpctx->vo_pts_history_pts[n] = mpctx->vo_pts_history_pts[n - 1];
+ }
+ mpctx->vo_pts_history_seek[0] = mpctx->vo_pts_history_seek_ts;
+ mpctx->vo_pts_history_pts[0] = pts;
+}
+
+static double find_previous_pts(struct MPContext *mpctx, double pts)
+{
+ for (int n = 0; n < MAX_NUM_VO_PTS - 1; n++) {
+ if (pts == mpctx->vo_pts_history_pts[n] &&
+ mpctx->vo_pts_history_seek[n] != 0 &&
+ mpctx->vo_pts_history_seek[n] == mpctx->vo_pts_history_seek[n + 1])
+ {
+ return mpctx->vo_pts_history_pts[n + 1];
+ }
+ }
+ return MP_NOPTS_VALUE;
+}
+
+static double get_last_frame_pts(struct MPContext *mpctx)
+{
+ if (mpctx->vo_pts_history_seek[0] == mpctx->vo_pts_history_seek_ts)
+ return mpctx->vo_pts_history_pts[0];
+ return MP_NOPTS_VALUE;
+}
+
+static void handle_backstep(struct MPContext *mpctx)
+{
+ if (!mpctx->backstep_active)
+ return;
+
+ double current_pts = mpctx->last_vo_pts;
+ mpctx->backstep_active = false;
+ bool demuxer_ok = mpctx->demuxer && mpctx->demuxer->accurate_seek;
+ if (demuxer_ok && mpctx->d_video && current_pts != MP_NOPTS_VALUE) {
+ double seek_pts = find_previous_pts(mpctx, current_pts);
+ if (seek_pts != MP_NOPTS_VALUE) {
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, seek_pts, 2);
+ } else {
+ double last = get_last_frame_pts(mpctx);
+ if (last != MP_NOPTS_VALUE && last >= current_pts &&
+ mpctx->backstep_start_seek_ts != mpctx->vo_pts_history_seek_ts)
+ {
+ MP_ERR(mpctx, "Backstep failed.\n");
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, current_pts, 2);
+ } else if (!mpctx->hrseek_active) {
+ MP_VERBOSE(mpctx, "Start backstep indexing.\n");
+ // Force it to index the video up until current_pts.
+ // The whole point is getting frames _before_ that PTS,
+ // so apply an arbitrary offset. (In theory the offset
+ // has to be large enough to reach the previous frame.)
+ mp_seek(mpctx, (struct seek_params){
+ .type = MPSEEK_ABSOLUTE,
+ .amount = current_pts - 1.0,
+ }, false);
+ // Don't leave hr-seek mode. If all goes right, hr-seek
+ // mode is cancelled as soon as the frame before
+ // current_pts is found during hr-seeking.
+ // Note that current_pts should be part of the index,
+ // otherwise we can't find the previous frame, so set the
+ // seek target an arbitrary amount of time after it.
+ if (mpctx->hrseek_active) {
+ mpctx->hrseek_pts = current_pts + 10.0;
+ mpctx->hrseek_framedrop = false;
+ mpctx->backstep_active = true;
+ }
+ } else {
+ mpctx->backstep_active = true;
+ }
+ }
+ }
+}
+
+static void handle_sstep(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (opts->step_sec > 0 && !mpctx->stop_play && !mpctx->paused &&
+ !mpctx->restart_playback)
+ {
+ set_osd_function(mpctx, OSD_FFW);
+ queue_seek(mpctx, MPSEEK_RELATIVE, opts->step_sec, 0);
+ }
+}
+
+static void handle_keep_open(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (opts->keep_open && mpctx->stop_play == AT_END_OF_FILE) {
+ mpctx->stop_play = KEEP_PLAYING;
+ mpctx->playback_pts = mpctx->last_vo_pts;
+ pause_player(mpctx);
+ }
+}
+
+// Execute a forceful refresh of the VO window, if it hasn't had a valid frame
+// for a while. The problem is that a VO with no valid frame (vo->hasframe==0)
+// doesn't redraw video and doesn't OSD interaction. So screw it, hard.
+void handle_force_window(struct MPContext *mpctx, bool reconfig)
+{
+ // Don't interfere with real video playback
+ if (mpctx->d_video)
+ return;
+
+ struct vo *vo = mpctx->video_out;
+ if (!vo)
+ return;
+
+ if (!vo->config_ok || reconfig) {
+ MP_INFO(mpctx, "Creating non-video VO window.\n");
+ // Pick whatever works
+ int config_format = 0;
+ for (int fmt = IMGFMT_START; fmt < IMGFMT_END; fmt++) {
+ if (vo->driver->query_format(vo, fmt)) {
+ config_format = fmt;
+ break;
+ }
+ }
+ int w = 960;
+ int h = 480;
+ struct mp_image_params p = {
+ .imgfmt = config_format,
+ .w = w, .h = h,
+ .d_w = w, .d_h = h,
+ };
+ vo_reconfig(vo, &p, 0);
+ redraw_osd(mpctx);
+ }
+}
+
+static double timing_sleep(struct MPContext *mpctx, double time_frame)
+{
+ // assume kernel HZ=100 for softsleep, works with larger HZ but with
+ // unnecessarily high CPU usage
+ struct MPOpts *opts = mpctx->opts;
+ double margin = opts->softsleep ? 0.011 : 0;
+ while (time_frame > margin) {
+ mp_sleep_us(1000000 * (time_frame - margin));
+ time_frame -= get_relative_time(mpctx);
+ }
+ if (opts->softsleep) {
+ if (time_frame < 0)
+ MP_WARN(mpctx, "Warning! Softsleep underflow!\n");
+ while (time_frame > 0)
+ time_frame -= get_relative_time(mpctx); // burn the CPU
+ }
+ return time_frame;
+}
+
+static double get_wakeup_period(struct MPContext *mpctx)
+{
+ /* Even if we can immediately wake up in response to most input events,
+ * there are some timers which are not registered to the event loop
+ * and need to be checked periodically (like automatic mouse cursor hiding).
+ * OSD content updates behave similarly. Also some uncommon input devices
+ * may not have proper FD event support.
+ */
+ double sleeptime = WAKEUP_PERIOD;
+
+#ifndef HAVE_POSIX_SELECT
+ // No proper file descriptor event handling; keep waking up to poll input
+ sleeptime = MPMIN(sleeptime, 0.02);
+#endif
+
+ if (mpctx->video_out)
+ if (mpctx->video_out->wakeup_period > 0)
+ sleeptime = MPMIN(sleeptime, mpctx->video_out->wakeup_period);
+
+ return sleeptime;
+}
+
+void run_playloop(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ bool full_audio_buffers = false;
+ bool audio_left = false, video_left = false;
+ double endpts = get_play_end_pts(mpctx);
+ bool end_is_chapter = false;
+ double sleeptime = get_wakeup_period(mpctx);
+ bool was_restart = mpctx->restart_playback;
+ bool new_frame_shown = false;
+
+#if HAVE_ENCODING
+ if (encode_lavc_didfail(mpctx->encode_lavc_ctx)) {
+ mpctx->stop_play = PT_QUIT;
+ return;
+ }
+#endif
+
+ // Add tracks that were added by the demuxer later (e.g. MPEG)
+ if (!mpctx->timeline && mpctx->demuxer)
+ add_demuxer_tracks(mpctx, mpctx->demuxer);
+
+ if (mpctx->timeline) {
+ double end = mpctx->timeline[mpctx->timeline_part + 1].start;
+ if (endpts == MP_NOPTS_VALUE || end < endpts) {
+ endpts = end;
+ end_is_chapter = true;
+ }
+ }
+
+ if (opts->chapterrange[1] > 0) {
+ int cur_chapter = get_current_chapter(mpctx);
+ if (cur_chapter != -1 && cur_chapter + 1 > opts->chapterrange[1])
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ }
+
+ if (mpctx->d_audio && !mpctx->restart_playback && !mpctx->ao->untimed) {
+ int status = fill_audio_out_buffers(mpctx, endpts);