summaryrefslogtreecommitdiffstats
path: root/mpvcore/player/mplayer.c
diff options
context:
space:
mode:
Diffstat (limited to 'mpvcore/player/mplayer.c')
-rw-r--r--mpvcore/player/mplayer.c5079
1 files changed, 5079 insertions, 0 deletions
diff --git a/mpvcore/player/mplayer.c b/mpvcore/player/mplayer.c
new file mode 100644
index 0000000000..b92b38e050
--- /dev/null
+++ b/mpvcore/player/mplayer.c
@@ -0,0 +1,5079 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <math.h>
+#include <assert.h>
+#include <ctype.h>
+
+#ifdef PTW32_STATIC_LIB
+#include <pthread.h>
+#endif
+
+#include <libavutil/intreadwrite.h>
+#include <libavutil/attributes.h>
+#include <libavutil/md5.h>
+#include <libavutil/common.h>
+
+#include <libavcodec/version.h>
+
+#include "config.h"
+#include "talloc.h"
+
+#include "osdep/io.h"
+
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+#include <windows.h>
+#endif
+#define WAKEUP_PERIOD 0.5
+#include <string.h>
+#include <unistd.h>
+
+// #include <sys/mman.h>
+#include <sys/types.h>
+#ifndef __MINGW32__
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#endif
+
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+#include <time.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <errno.h>
+
+#include "mpvcore/mpv_global.h"
+#include "mpvcore/mp_msg.h"
+#include "mpvcore/av_log.h"
+
+
+#include "mpvcore/m_option.h"
+#include "mpvcore/m_config.h"
+#include "mpvcore/resolve.h"
+#include "mpvcore/m_property.h"
+
+#include "sub/find_subfiles.h"
+#include "sub/dec_sub.h"
+#include "sub/sd.h"
+
+#include "mp_osd.h"
+#include "video/out/vo.h"
+#include "screenshot.h"
+
+#include "sub/sub.h"
+#include "mpvcore/cpudetect.h"
+
+#ifdef CONFIG_X11
+#include "video/out/x11_common.h"
+#endif
+
+#ifdef CONFIG_COCOA
+#include "osdep/macosx_application.h"
+#endif
+
+#include "audio/out/ao.h"
+
+#include "mpvcore/codecs.h"
+
+#include "osdep/getch2.h"
+#include "osdep/timer.h"
+
+#include "mpvcore/input/input.h"
+#include "mpvcore/encode.h"
+
+#include "osdep/priority.h"
+
+#include "stream/tv.h"
+#include "stream/stream_radio.h"
+#ifdef CONFIG_DVBIN
+#include "stream/dvbin.h"
+#endif
+
+//**************************************************************************//
+// Playtree
+//**************************************************************************//
+#include "mpvcore/playlist.h"
+#include "mpvcore/playlist_parser.h"
+
+//**************************************************************************//
+// Config
+//**************************************************************************//
+#include "mpvcore/parser-cfg.h"
+#include "mpvcore/parser-mpcmd.h"
+
+//**************************************************************************//
+// Config file
+//**************************************************************************//
+
+#include "mpvcore/path.h"
+
+//**************************************************************************//
+//**************************************************************************//
+// Input media streaming & demultiplexer:
+//**************************************************************************//
+
+#include "stream/stream.h"
+#include "demux/demux.h"
+#include "demux/stheader.h"
+
+#include "audio/filter/af.h"
+#include "audio/decode/dec_audio.h"
+#include "video/decode/dec_video.h"
+#include "video/mp_image.h"
+#include "video/filter/vf.h"
+#include "video/decode/vd.h"
+
+#include "audio/mixer.h"
+
+#include "mp_core.h"
+#include "mpvcore/options.h"
+
+#include "mp_lua.h"
+
+const char mp_help_text[] = _(
+"Usage: mpv [options] [url|path/]filename\n"
+"\n"
+"Basic options:\n"
+" --start=<time> seek to given (percent, seconds, or hh:mm:ss) position\n"
+" --no-audio do not play sound\n"
+" --no-video do not play video\n"
+" --fs fullscreen playback\n"
+" --sub=<file> specify subtitle file to use\n"
+" --playlist=<file> specify playlist file\n"
+"\n"
+" --list-options list all mpv options\n"
+"\n");
+
+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");
+
+
+//**************************************************************************//
+//**************************************************************************//
+
+#include "sub/ass_mp.h"
+
+
+// ---
+
+#include "mpvcore/mp_common.h"
+#include "command.h"
+
+static void reset_subtitles(struct MPContext *mpctx);
+static void reinit_subs(struct MPContext *mpctx);
+static void handle_force_window(struct MPContext *mpctx, bool reconfig);
+
+static double get_relative_time(struct MPContext *mpctx)
+{
+ int64_t new_time = mp_time_us();
+ int64_t delta = new_time - mpctx->last_time;
+ mpctx->last_time = new_time;
+ return delta * 0.000001;
+}
+
+static double rel_time_to_abs(struct MPContext *mpctx, struct m_rel_time t,
+ double fallback_time)
+{
+ double length = get_time_length(mpctx);
+ switch (t.type) {
+ case REL_TIME_ABSOLUTE:
+ return t.pos;
+ case REL_TIME_NEGATIVE:
+ if (length != 0)
+ return FFMAX(length - t.pos, 0.0);
+ break;
+ case REL_TIME_PERCENT:
+ if (length != 0)
+ return length * (t.pos / 100.0);
+ break;
+ case REL_TIME_CHAPTER:
+ if (chapter_start_time(mpctx, t.pos) >= 0)
+ return chapter_start_time(mpctx, t.pos);
+ break;
+ }
+ return fallback_time;
+}
+
+static double get_play_end_pts(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (opts->play_end.type) {
+ return rel_time_to_abs(mpctx, opts->play_end, MP_NOPTS_VALUE);
+ } else if (opts->play_length.type) {
+ double startpts = get_start_time(mpctx);
+ double start = rel_time_to_abs(mpctx, opts->play_start, startpts);
+ double length = rel_time_to_abs(mpctx, opts->play_length, -1);
+ if (start != -1 && length != -1)
+ return start + length;
+ }
+ return MP_NOPTS_VALUE;
+}
+
+static void print_stream(struct MPContext *mpctx, struct track *t)
+{
+ struct sh_stream *s = t->stream;
+ const char *tname = "?";
+ const char *selopt = "?";
+ const char *langopt = "?";
+ const char *iid = NULL;
+ switch (t->type) {
+ case STREAM_VIDEO:
+ tname = "Video"; selopt = "vid"; langopt = NULL; iid = "VID";
+ break;
+ case STREAM_AUDIO:
+ tname = "Audio"; selopt = "aid"; langopt = "alang"; iid = "AID";
+ break;
+ case STREAM_SUB:
+ tname = "Subs"; selopt = "sid"; langopt = "slang"; iid = "SID";
+ break;
+ }
+ MP_INFO(mpctx, "[stream] %-5s %3s",
+ tname, mpctx->current_track[t->type] == t ? "(+)" : "");
+ MP_INFO(mpctx, " --%s=%d", selopt, t->user_tid);
+ if (t->lang && langopt)
+ MP_INFO(mpctx, " --%s=%s", langopt, t->lang);
+ if (t->default_track)
+ MP_INFO(mpctx, " (*)");
+ if (t->attached_picture)
+ MP_INFO(mpctx, " [P]");
+ if (t->title)
+ MP_INFO(mpctx, " '%s'", t->title);
+ const char *codec = s ? s->codec : NULL;
+ MP_INFO(mpctx, " (%s)", codec ? codec : "<unknown>");
+ if (t->is_external)
+ MP_INFO(mpctx, " (external)");
+ MP_INFO(mpctx, "\n");
+ // legacy compatibility
+ if (!iid)
+ return;
+ int id = t->user_tid;
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_%s_ID=%d\n", iid, id);
+ if (t->title)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_%s_%d_NAME=%s\n", iid, id, t->title);
+ if (t->lang)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_%s_%d_LANG=%s\n", iid, id, t->lang);
+}
+
+static void print_file_properties(struct MPContext *mpctx, const char *filename)
+{
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_FILENAME=%s\n",
+ filename);
+ if (mpctx->sh_video) {
+ /* Assume FOURCC if all bytes >= 0x20 (' ') */
+ if (mpctx->sh_video->format >= 0x20202020)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_FORMAT=%.4s\n", (char *)&mpctx->sh_video->format);
+ else
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_FORMAT=0x%08X\n", mpctx->sh_video->format);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_BITRATE=%d\n", mpctx->sh_video->i_bps * 8);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_WIDTH=%d\n", mpctx->sh_video->disp_w);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_HEIGHT=%d\n", mpctx->sh_video->disp_h);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_FPS=%5.3f\n", mpctx->sh_video->fps);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_ASPECT=%1.4f\n", mpctx->sh_video->aspect);
+ }
+ if (mpctx->sh_audio) {
+ /* Assume FOURCC if all bytes >= 0x20 (' ') */
+ if (mpctx->sh_audio->format >= 0x20202020)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_FORMAT=%.4s\n", (char *)&mpctx->sh_audio->format);
+ else
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_FORMAT=%d\n", mpctx->sh_audio->format);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_BITRATE=%d\n", mpctx->sh_audio->i_bps * 8);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_RATE=%d\n", mpctx->sh_audio->samplerate);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_NCH=%d\n", mpctx->sh_audio->channels.num);
+ }
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_LENGTH=%.2f\n", get_time_length(mpctx));
+ int chapter_count = get_chapter_count(mpctx);
+ if (chapter_count >= 0) {
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTERS=%d\n", chapter_count);
+ for (int i = 0; i < chapter_count; i++) {
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_ID=%d\n", i);
+ // print in milliseconds
+ double time = chapter_start_time(mpctx, i) * 1000.0;
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_%d_START=%"PRId64"\n",
+ i, (int64_t)(time < 0 ? -1 : time));
+ char *name = chapter_name(mpctx, i);
+ if (name) {
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_%d_NAME=%s\n", i,
+ name);
+ talloc_free(name);
+ }
+ }
+ }
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (demuxer->num_editions > 1)
+ MP_INFO(mpctx, "Playing edition %d of %d (--edition=%d).\n",
+ demuxer->edition + 1, demuxer->num_editions, demuxer->edition);
+ for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
+ for (int n = 0; n < mpctx->num_tracks; n++)
+ if (mpctx->tracks[n]->type == t)
+ print_stream(mpctx, mpctx->tracks[n]);
+ }
+}
+
+// Time used to seek external tracks to.
+static double get_main_demux_pts(struct MPContext *mpctx)
+{
+ double main_new_pos = MP_NOPTS_VALUE;
+ if (mpctx->demuxer) {
+ for (int n = 0; n < mpctx->demuxer->num_streams; n++) {
+ if (main_new_pos == MP_NOPTS_VALUE)
+ main_new_pos = demux_get_next_pts(mpctx->demuxer->streams[n]);
+ }
+ }
+ return main_new_pos;
+}
+
+static void set_demux_field(struct MPContext *mpctx, enum stream_type type,
+ struct sh_stream *s)
+{
+ mpctx->sh[type] = s;
+ // redundant fields for convenience access
+ switch(type) {
+ case STREAM_VIDEO: mpctx->sh_video = s ? s->video : NULL; break;
+ case STREAM_AUDIO: mpctx->sh_audio = s ? s->audio : NULL; break;
+ case STREAM_SUB: mpctx->sh_sub = s ? s->sub : NULL; break;
+ }
+}
+
+static void init_demux_stream(struct MPContext *mpctx, enum stream_type type)
+{
+ struct track *track = mpctx->current_track[type];
+ set_demux_field(mpctx, type, track ? track->stream : NULL);
+ struct sh_stream *stream = mpctx->sh[type];
+ if (stream) {
+ demuxer_switch_track(stream->demuxer, type, stream);
+ if (track->is_external) {
+ double pts = get_main_demux_pts(mpctx);
+ demux_seek(stream->demuxer, pts, SEEK_ABSOLUTE);
+ }
+ }
+}
+
+static void cleanup_demux_stream(struct MPContext *mpctx, enum stream_type type)
+{
+ struct sh_stream *stream = mpctx->sh[type];
+ if (stream)
+ demuxer_switch_track(stream->demuxer, type, NULL);
+ set_demux_field(mpctx, type, NULL);
+}
+
+// Switch the demuxers to current track selection. This is possibly important
+// for intialization: if something reads packets from the demuxer (like at least
+// reinit_audio_chain does, or when seeking), packets from the other streams
+// should be queued instead of discarded. So all streams should be enabled
+// before the first initialization function is called.
+static void preselect_demux_streams(struct MPContext *mpctx)
+{
+ // Disable all streams, just to be sure no unwanted streams are selected.
+ for (int n = 0; n < mpctx->num_sources; n++) {
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ struct track *track = mpctx->current_track[type];
+ if (!(track && track->demuxer == mpctx->sources[n] &&
+ demuxer_stream_is_selected(track->demuxer, track->stream)))
+ demuxer_switch_track(mpctx->sources[n], type, NULL);
+ }
+ }
+
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ struct track *track = mpctx->current_track[type];
+ if (track && track->stream)
+ demuxer_switch_track(track->stream->demuxer, type, track->stream);
+ }
+}
+
+static void uninit_subs(struct demuxer *demuxer)
+{
+ for (int i = 0; i < demuxer->num_streams; i++) {
+ struct sh_stream *sh = demuxer->streams[i];
+ if (sh->sub) {
+ sub_destroy(sh->sub->dec_sub);
+ sh->sub->dec_sub = NULL;
+ }
+ }
+}
+
+void uninit_player(struct MPContext *mpctx, unsigned int mask)
+{
+ mask &= mpctx->initialized_flags;
+
+ MP_DBG(mpctx, "\n*** uninit(0x%X)\n", mask);
+
+ if (mask & INITIALIZED_ACODEC) {
+ mpctx->initialized_flags &= ~INITIALIZED_ACODEC;
+ mixer_uninit_audio(mpctx->mixer);
+ if (mpctx->sh_audio)
+ uninit_audio(mpctx->sh_audio);
+ cleanup_demux_stream(mpctx, STREAM_AUDIO);
+ }
+
+ if (mask & INITIALIZED_SUB) {
+ mpctx->initialized_flags &= ~INITIALIZED_SUB;
+ if (mpctx->sh_sub)
+ sub_reset(mpctx->sh_sub->dec_sub);
+ cleanup_demux_stream(mpctx, STREAM_SUB);
+ mpctx->osd->dec_sub = NULL;
+ reset_subtitles(mpctx);
+ }
+
+ if (mask & INITIALIZED_LIBASS) {
+ mpctx->initialized_flags &= ~INITIALIZED_LIBASS;
+#ifdef CONFIG_ASS
+ if (mpctx->osd->ass_renderer)
+ ass_renderer_done(mpctx->osd->ass_renderer);
+ mpctx->osd->ass_renderer = NULL;
+ ass_clear_fonts(mpctx->ass_library);
+#endif
+ }
+
+ if (mask & INITIALIZED_VCODEC) {
+ mpctx->initialized_flags &= ~INITIALIZED_VCODEC;
+ if (mpctx->sh_video)
+ uninit_video(mpctx->sh_video);
+ cleanup_demux_stream(mpctx, STREAM_VIDEO);
+ mpctx->sync_audio_to_video = false;
+ }
+
+ if (mask & INITIALIZED_DEMUXER) {
+ mpctx->initialized_flags &= ~INITIALIZED_DEMUXER;
+ for (int i = 0; i < mpctx->num_tracks; i++) {
+ talloc_free(mpctx->tracks[i]);
+ }
+ mpctx->num_tracks = 0;
+ for (int t = 0; t < STREAM_TYPE_COUNT; t++)
+ mpctx->current_track[t] = NULL;
+ assert(!mpctx->sh_video && !mpctx->sh_audio && !mpctx->sh_sub);
+ mpctx->master_demuxer = NULL;
+ for (int i = 0; i < mpctx->num_sources; i++) {
+ uninit_subs(mpctx->sources[i]);
+ struct demuxer *demuxer = mpctx->sources[i];
+ if (demuxer->stream != mpctx->stream)
+ free_stream(demuxer->stream);
+ free_demuxer(demuxer);
+ }
+ talloc_free(mpctx->sources);
+ mpctx->sources = NULL;
+ mpctx->demuxer = NULL;
+ mpctx->num_sources = 0;
+ talloc_free(mpctx->timeline);
+ mpctx->timeline = NULL;
+ mpctx->num_timeline_parts = 0;
+ talloc_free(mpctx->chapters);
+ mpctx->chapters = NULL;
+ mpctx->num_chapters = 0;
+ mpctx->video_offset = 0;
+ }
+
+ // kill the cache process:
+ if (mask & INITIALIZED_STREAM) {
+ mpctx->initialized_flags &= ~INITIALIZED_STREAM;
+ if (mpctx->stream)
+ free_stream(mpctx->stream);
+ mpctx->stream = NULL;
+ }
+
+ if (mask & INITIALIZED_VO) {
+ mpctx->initialized_flags &= ~INITIALIZED_VO;
+ vo_destroy(mpctx->video_out);
+ mpctx->video_out = NULL;
+ }
+
+ // Must be after libvo uninit, as few vo drivers (svgalib) have tty code.
+ if (mask & INITIALIZED_GETCH2) {
+ mpctx->initialized_flags &= ~INITIALIZED_GETCH2;
+ MP_DBG(mpctx, "\n[[[uninit getch2]]]\n");
+ // restore terminal:
+ getch2_disable();
+ }
+
+ if (mask & INITIALIZED_AO) {
+ mpctx->initialized_flags &= ~INITIALIZED_AO;
+ if (mpctx->ao)
+ ao_uninit(mpctx->ao, mpctx->stop_play != AT_END_OF_FILE);
+ mpctx->ao = NULL;
+ }
+
+ if (mask & INITIALIZED_PLAYBACK)
+ mpctx->initialized_flags &= ~INITIALIZED_PLAYBACK;
+}
+
+static MP_NORETURN void exit_player(struct MPContext *mpctx,
+ enum exit_reason how)
+{
+ int rc;
+ uninit_player(mpctx, INITIALIZED_ALL);
+
+#ifdef CONFIG_ENCODING
+ encode_lavc_finish(mpctx->encode_lavc_ctx);
+ encode_lavc_free(mpctx->encode_lavc_ctx);
+#endif
+
+ mpctx->encode_lavc_ctx = NULL;
+
+#ifdef CONFIG_LUA
+ mp_lua_uninit(mpctx);
+#endif
+
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+ timeEndPeriod(1);
+#endif
+
+#ifdef CONFIG_COCOA
+ cocoa_set_input_context(NULL);
+#endif
+
+ command_uninit(mpctx);
+
+ mp_input_uninit(mpctx->input);
+
+ osd_free(mpctx->osd);
+
+#ifdef CONFIG_ASS
+ ass_library_done(mpctx->ass_library);
+ mpctx->ass_library = NULL;
+#endif
+
+ if (how != EXIT_NONE) {
+ const char *reason;
+ switch (how) {
+ case EXIT_SOMENOTPLAYED:
+ case EXIT_PLAYED:
+ reason = "End of file";
+ break;
+ case EXIT_NOTPLAYED:
+ reason = "No files played";
+ break;
+ case EXIT_ERROR:
+ reason = "Fatal error";
+ break;
+ default:
+ reason = "Quit";
+ }
+ MP_INFO(mpctx, "\nExiting... (%s)\n", reason);
+ }
+
+ if (mpctx->has_quit_custom_rc) {
+ rc = mpctx->quit_custom_rc;
+ } else {
+ switch (how) {
+ case EXIT_ERROR:
+ rc = 1; break;
+ case EXIT_NOTPLAYED:
+ rc = 2; break;
+ case EXIT_SOMENOTPLAYED:
+ rc = 3; break;
+ default:
+ rc = 0;
+ }
+ }
+
+ // must be last since e.g. mp_msg uses option values
+ // that will be freed by this.
+
+ mp_msg_uninit(mpctx->global);
+ talloc_free(mpctx);
+
+#ifdef CONFIG_COCOA
+ terminate_cocoa_application();
+ // never reach here:
+ // terminate calls exit itself, just silence compiler warning
+ exit(0);
+#else
+ exit(rc);
+#endif
+}
+
+static void mk_config_dir(char *subdir)
+{
+ void *tmp = talloc_new(NULL);
+ char *confdir = talloc_steal(tmp, mp_find_user_config_file(""));
+ if (confdir) {
+ if (subdir)
+ confdir = mp_path_join(tmp, bstr0(confdir), bstr0(subdir));
+ mkdir(confdir, 0777);
+ }
+ talloc_free(tmp);
+}
+
+static int cfg_include(struct m_config *conf, char *filename, int flags)
+{
+ return m_config_parse_config_file(conf, filename, flags);
+}
+
+#define DEF_CONFIG "# Write your default config options here!\n\n\n"
+
+static bool parse_cfgfiles(struct MPContext *mpctx, m_config_t *conf)
+{
+ struct MPOpts *opts = mpctx->opts;
+ char *conffile;
+ int conffile_fd;
+ if (!opts->load_config)
+ return true;
+ if (!m_config_parse_config_file(conf, MPLAYER_CONFDIR "/mpv.conf", 0) < 0)
+ return false;
+ mk_config_dir(NULL);
+ if ((conffile = mp_find_user_config_file("config")) == NULL)
+ MP_ERR(mpctx, "mp_find_user_config_file(\"config\") problem\n");
+ else {
+ if ((conffile_fd = open(conffile, O_CREAT | O_EXCL | O_WRONLY,
+ 0666)) != -1) {
+ MP_INFO(mpctx, "Creating config file: %s\n", conffile);
+ write(conffile_fd, DEF_CONFIG, sizeof(DEF_CONFIG) - 1);
+ close(conffile_fd);
+ }
+ if (m_config_parse_config_file(conf, conffile, 0) < 0)
+ return false;
+ talloc_free(conffile);
+ }
+ return true;
+}
+
+// Set options file-local, and don't set them if the user set them via the
+// command line.
+#define FILE_LOCAL_FLAGS (M_SETOPT_BACKUP | M_SETOPT_PRESERVE_CMDLINE)
+
+#define PROFILE_CFG_PROTOCOL "protocol."
+
+static void load_per_protocol_config(m_config_t *conf, const char * const file)
+{
+ char *str;
+ char protocol[strlen(PROFILE_CFG_PROTOCOL) + strlen(file) + 1];
+ m_profile_t *p;
+
+ /* does filename actually uses a protocol ? */
+ if (!mp_is_url(bstr0(file)))
+ return;
+ str = strstr(file, "://");
+ if (!str)
+ return;
+
+ sprintf(protocol, "%s%s", PROFILE_CFG_PROTOCOL, file);
+ protocol[strlen(PROFILE_CFG_PROTOCOL) + strlen(file) - strlen(str)] = '\0';
+ p = m_config_get_profile0(conf, protocol);
+ if (p) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Loading protocol-related profile '%s'\n", protocol);
+ m_config_set_profile(conf, p, FILE_LOCAL_FLAGS);
+ }
+}
+
+#define PROFILE_CFG_EXTENSION "extension."
+
+static void load_per_extension_config(m_config_t *conf, const char * const file)
+{
+ char *str;
+ char extension[strlen(PROFILE_CFG_EXTENSION) + 8];
+ m_profile_t *p;
+
+ /* does filename actually have an extension ? */
+ str = strrchr(file, '.');
+ if (!str)
+ return;
+
+ sprintf(extension, PROFILE_CFG_EXTENSION);
+ strncat(extension, ++str, 7);
+ p = m_config_get_profile0(conf, extension);
+ if (p) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Loading extension-related profile '%s'\n", extension);
+ m_config_set_profile(conf, p, FILE_LOCAL_FLAGS);
+ }
+}
+
+#define PROFILE_CFG_VO "vo."
+#define PROFILE_CFG_AO "ao."
+
+static void load_per_output_config(m_config_t *conf, char *cfg, char *out)
+{
+ char profile[strlen(cfg) + strlen(out) + 1];
+ m_profile_t *p;
+
+ if (!out && !out[0])
+ return;
+
+ sprintf(profile, "%s%s", cfg, out);
+ p = m_config_get_profile0(conf, profile);
+ if (p) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Loading extension-related profile '%s'\n", profile);
+ m_config_set_profile(conf, p, FILE_LOCAL_FLAGS);
+ }
+}
+
+/**
+ * Tries to load a config file (in file local mode)
+ * @return 0 if file was not found, 1 otherwise
+ */
+static int try_load_config(m_config_t *conf, const char *file, int flags)
+{
+ if (!mp_path_exists(file))
+ return 0;
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "Loading config '%s'\n", file);
+ m_config_parse_config_file(conf, file, flags);
+ return 1;
+}
+
+static void load_per_file_config(m_config_t *conf, const char * const file,
+ bool search_file_dir)
+{
+ char *confpath;
+ char cfg[MP_PATH_MAX];
+ const char *name;
+
+ if (strlen(file) > MP_PATH_MAX - 14) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "Filename is too long, "
+ "can not load file or directory specific config files\n");
+ return;
+ }
+ sprintf(cfg, "%s.conf", file);
+
+ name = mp_basename(cfg);
+ if (search_file_dir) {
+ char dircfg[MP_PATH_MAX];
+ strcpy(dircfg, cfg);
+ strcpy(dircfg + (name - cfg), "mpv.conf");
+ try_load_config(conf, dircfg, FILE_LOCAL_FLAGS);
+
+ if (try_load_config(conf, cfg, FILE_LOCAL_FLAGS))
+ return;
+ }
+
+ if ((confpath = mp_find_user_config_file(name)) != NULL) {
+ try_load_config(conf, confpath, FILE_LOCAL_FLAGS);
+
+ talloc_free(confpath);
+ }
+}
+
+#define MP_WATCH_LATER_CONF "watch_later"
+
+static char *get_playback_resume_config_filename(const char *fname,
+ struct MPOpts *opts)
+{
+ char *res = NULL;
+ void *tmp = talloc_new(NULL);
+ const char *realpath = fname;
+ bstr bfname = bstr0(fname);
+ if (!mp_is_url(bfname)) {
+ char *cwd = mp_getcwd(tmp);
+ if (!cwd)
+ goto exit;
+ realpath = mp_path_join(tmp, bstr0(cwd), bstr0(fname));
+ }
+#ifdef CONFIG_DVDREAD
+ if (bstr_startswith0(bfname, "dvd://"))
+ realpath = talloc_asprintf(tmp, "%s - %s", realpath, dvd_device);
+#endif
+#ifdef CONFIG_LIBBLURAY
+ if (bstr_startswith0(bfname, "br://") || bstr_startswith0(bfname, "bd://") ||
+ bstr_startswith0(bfname, "bluray://"))
+ realpath = talloc_asprintf(tmp, "%s - %s", realpath, bluray_device);
+#endif
+ uint8_t md5[16];
+ av_md5_sum(md5, realpath, strlen(realpath));
+ char *conf = talloc_strdup(tmp, "");
+ for (int i = 0; i < 16; i++)
+ conf = talloc_asprintf_append(conf, "%02X", md5[i]);
+
+ conf = talloc_asprintf(tmp, "%s/%s", MP_WATCH_LATER_CONF, conf);
+
+ res = mp_find_user_config_file(conf);
+
+exit:
+ talloc_free(tmp);
+ return res;
+}
+
+static const char *backup_properties[] = {
+ "osd-level",
+ //"loop",
+ "speed",
+ "edition",
+ "pause",
+ "volume-restore-data",
+ "audio-delay",
+ //"balance",
+ "fullscreen",
+ "colormatrix",
+ "colormatrix-input-range",
+ "colormatrix-output-range",
+ "ontop",
+ "border",
+ "gamma",
+ "brightness",
+ "contrast",
+ "saturation",
+ "hue",
+ "deinterlace",
+ "vf",
+ "af",
+ "panscan",
+ "aid",
+ "vid",
+ "sid",
+ "sub-delay",
+ "sub-pos",
+ "sub-visibility",
+ "sub-scale",
+ "ass-use-margins",
+ "ass-vsfilter-aspect-compat",
+ "ass-style-override",
+ 0
+};
+
+// Should follow what parser-cfg.c does/needs
+static bool needs_config_quoting(const char *s)
+{
+ for (int i = 0; s && s[i]; i++) {
+ unsigned char c = s[i];
+ if (!isprint(c) || isspace(c) || c == '#' || c == '\'' || c == '"')
+ return true;
+ }
+ return false;
+}
+
+void mp_write_watch_later_conf(struct MPContext *mpctx)
+{
+ void *tmp = talloc_new(NULL);
+ char *filename = mpctx->filename;
+ if (!filename)
+ goto exit;
+
+ double pos = get_current_time(mpctx);
+ if (pos == MP_NOPTS_VALUE)
+ goto exit;
+
+ mk_config_dir(MP_WATCH_LATER_CONF);
+
+ char *conffile = get_playback_resume_config_filename(mpctx->filename,
+ mpctx->opts);
+ talloc_steal(tmp, conffile);
+ if (!conffile)
+ goto exit;
+
+ MP_INFO(mpctx, "Saving state.\n");
+
+ FILE *file = fopen(conffile, "wb");
+ if (!file)
+ goto exit;
+ fprintf(file, "start=%f\n", pos);
+ for (int i = 0; backup_properties[i]; i++) {
+ const char *pname = backup_properties[i];
+ char *val = NULL;
+ int r = mp_property_do(pname, M_PROPERTY_GET_STRING, &val, mpctx);
+ if (r == M_PROPERTY_OK) {
+ if (needs_config_quoting(val)) {
+ // e.g. '%6%STRING'
+ fprintf(file, "%s=%%%d%%%s\n", pname, (int)strlen(val), val);
+ } else {
+ fprintf(file, "%s=%s\n", pname, val);
+ }
+ }
+ talloc_free(val);
+ }
+ fclose(file);
+
+exit:
+ talloc_free(tmp);
+}
+
+static void load_playback_resume(m_config_t *conf, const char *file)
+{
+ char *fname = get_playback_resume_config_filename(file, conf->optstruct);
+ if (fname && mp_path_exists(fname)) {
+ // Never apply the saved start position to following files
+ m_config_backup_opt(conf, "start");
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "Resuming playback. This behavior can "
+ "be disabled with --no-resume-playback.\n");
+ try_load_config(conf, fname, M_SETOPT_PRESERVE_CMDLINE);
+ unlink(fname);
+ }
+ talloc_free(fname);
+}
+
+// Returns the first file that has a resume config.
+// Compared to hashing the playlist file or contents and managing separate
+// resume file for them, this is simpler, and also has the nice property
+// that appending to a playlist doesn't interfere with resuming (especially
+// if the playlist comes from the command line).
+struct playlist_entry *mp_resume_playlist(struct playlist *playlist,
+ struct MPOpts *opts)
+{
+ if (!opts->position_resume)
+ return NULL;
+ for (struct playlist_entry *e = playlist->first; e; e = e->next) {
+ char *conf = get_playback_resume_config_filename(e->filename, opts);
+ bool exists = conf && mp_path_exists(conf);
+ talloc_free(conf);
+ if (exists)
+ return e;
+ }
+ return NULL;
+}
+
+static void load_per_file_options(m_config_t *conf,
+ struct playlist_param *params,
+ int params_count)
+{
+ for (int n = 0; n < params_count; n++) {
+ m_config_set_option_ext(conf, params[n].name, params[n].value,
+ M_SETOPT_BACKUP);
+ }
+}
+
+/* When demux performs a blocking operation (network connection or
+ * cache filling) if the operation fails we use this function to check
+ * if it was interrupted by the user.
+ * The function returns whether it was interrupted. */
+static bool demux_was_interrupted(struct MPContext *mpctx)
+{
+ for (;;) {
+ if (mpctx->stop_play != KEEP_PLAYING
+ && mpctx->stop_play != AT_END_OF_FILE)
+ return true;
+ mp_cmd_t *cmd = mp_input_get_cmd(mpctx->input, 0, 0);
+ if (!cmd)
+ break;
+ if (mp_input_is_abort_cmd(cmd->id))
+ run_command(mpctx, cmd);
+ mp_cmd_free(cmd);
+ }
+ return false;
+}
+
+static int find_new_tid(struct MPContext *mpctx, enum stream_type t)
+{
+ int new_id = 0;
+ for (int i = 0; i < mpctx->num_tracks; i++) {
+ struct track *track = mpctx->tracks[i];
+ if (track->type == t)
+ new_id = FFMAX(new_id, track->user_tid);
+ }
+ return new_id + 1;
+}
+
+// Map stream number (as used by libdvdread) to MPEG IDs (as used by demuxer).
+static int map_id_from_demuxer(struct demuxer *d, enum stream_type type, int id)
+{
+ if (d->stream->uncached_type == STREAMTYPE_DVD && type == STREAM_SUB)
+ id = id & 0x1F;
+ return id;
+}
+
+static struct track *add_stream_track(struct MPContext *mpctx,
+ struct sh_stream *stream,
+ bool under_timeline)
+{
+ for (int i = 0; i < mpctx->num_tracks; i++) {
+ struct track *track = mpctx->tracks[i];
+ if (track->stream == stream)
+ return track;
+ // DVD subtitle track that was added later
+ if (stream->type == STREAM_SUB && track->type == STREAM_SUB &&
+ map_id_from_demuxer(stream->demuxer, stream->type,
+ stream->demuxer_id) == track->demuxer_id
+ && !track->stream)
+ {
+ track->stream = stream;
+ track->demuxer_id = stream->demuxer_id;
+ // Initialize lazily selected track
+ bool selected = track == mpctx->current_track[STREAM_SUB];
+ demuxer_select_track(track->demuxer, stream, selected);
+ if (selected)
+ reinit_subs(mpctx);
+ return track;
+ }
+ }
+
+ struct track *track = talloc_ptrtype(NULL, track);
+ *track = (struct track) {
+ .type = stream->type,
+ .user_tid = find_new_tid(mpctx, stream->type),
+ .demuxer_id = stream->demuxer_id,
+ .title = stream->title,
+ .default_track = stream->default_track,
+ .attached_picture = stream->attached_picture != NULL,
+ .lang = stream->lang,
+ .under_timeline = under_timeline,
+ .demuxer = stream->demuxer,