summaryrefslogtreecommitdiffstats
path: root/mpvcore/player/playloop.c
diff options
context:
space:
mode:
Diffstat (limited to 'mpvcore/player/playloop.c')
-rw-r--r--mpvcore/player/playloop.c1343
1 files changed, 0 insertions, 1343 deletions
diff --git a/mpvcore/player/playloop.c b/mpvcore/player/playloop.c
deleted file mode 100644
index dbc87a7ec6..0000000000
--- a/mpvcore/player/playloop.c
+++ /dev/null
@@ -1,1343 +0,0 @@
-/*
- * 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);
- full_audio_buffers = status >= 0;
- // Not at audio stream EOF yet
- audio_left = status > -2;
- }
-
- if (mpctx->video_out) {
- vo_check_events(mpctx->video_out);
- handle_cursor_autohide(mpctx);
- }
-
- double buffered_audio = -1;
- while (mpctx->d_video) { // never loops, for "break;" only
- struct vo *vo = mpctx->video_out;
- update_fps(mpctx);
-
- video_left = vo->hasframe || vo->frame_loaded || mpctx->playing_last_frame;
- if (!vo->frame_loaded && (!mpctx->paused || mpctx->restart_playback)) {
-
- double frame_time = update_video(mpctx, endpts);
-