summaryrefslogtreecommitdiffstats
path: root/mpvcore/mplayer.c
diff options
context:
space:
mode:
authorStefano Pigozzi <stefano.pigozzi@gmail.com>2013-08-06 22:34:12 +0200
committerStefano Pigozzi <stefano.pigozzi@gmail.com>2013-08-06 22:48:47 +0200
commitbc27f946c27e33933a3a696cade78a04902c8dce (patch)
tree0e4166ba66072d407d0b972842b08cb7fea6f50b /mpvcore/mplayer.c
parentd40a91e804e19fb32430c5a80a984f5148324f52 (diff)
downloadmpv-bc27f946c27e33933a3a696cade78a04902c8dce.tar.bz2
mpv-bc27f946c27e33933a3a696cade78a04902c8dce.tar.xz
core: move contents to mpvcore (1/2)
core is used in many unix systems for core dumps. For that reason some tools work under the assumption that the file is indeed a core dump (for example autoconf does this). This commit just renames the files. The following one will change all the includes to fix compilation. This is done this way because git has a easier time tracing file changes if there is a pure rename commit.
Diffstat (limited to 'mpvcore/mplayer.c')
-rw-r--r--mpvcore/mplayer.c4747
1 files changed, 4747 insertions, 0 deletions
diff --git a/mpvcore/mplayer.c b/mpvcore/mplayer.c
new file mode 100644
index 0000000000..d3872442ee
--- /dev/null
+++ b/mpvcore/mplayer.c
@@ -0,0 +1,4747 @@
+/*
+ * 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 "core/mpv_global.h"
+#include "core/mp_msg.h"
+#include "av_log.h"
+
+
+#include "core/m_option.h"
+#include "core/m_config.h"
+#include "core/resolve.h"
+#include "core/m_property.h"
+
+#include "sub/find_subfiles.h"
+#include "sub/dec_sub.h"
+#include "sub/sd.h"
+
+#include "core/mp_osd.h"
+#include "video/out/vo.h"
+#include "core/screenshot.h"
+
+#include "sub/sub.h"
+#include "core/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 "core/codecs.h"
+
+#include "osdep/getch2.h"
+#include "osdep/timer.h"
+
+#include "core/input/input.h"
+#include "core/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 "core/playlist.h"
+#include "core/playlist_parser.h"
+
+//**************************************************************************//
+// Config
+//**************************************************************************//
+#include "core/parser-cfg.h"
+#include "core/parser-mpcmd.h"
+
+//**************************************************************************//
+// Config file
+//**************************************************************************//
+
+#include "core/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 "core/mp_core.h"
+#include "core/options.h"
+
+const char mp_help_text[] = _(
+"Usage: mpv [options] [url|path/]filename\n"
+"\n"
+"Basic options: (complete list in the man page)\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");
+
+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 none of this helps you, file a bug report.\n\n");
+
+
+//**************************************************************************//
+//**************************************************************************//
+
+#include "sub/ass_mp.h"
+
+
+// ---
+
+#include "core/mp_common.h"
+#include "core/command.h"
+
+static void reset_subtitles(struct MPContext *mpctx);
+static void reinit_subs(struct MPContext *mpctx);
+
+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 start = rel_time_to_abs(mpctx, opts->play_start, 0);
+ 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_msg(MSGT_CPLAYER, MSGL_INFO, "[stream] %-5s %3s",
+ tname, mpctx->current_track[t->type] == t ? "(+)" : "");
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " --%s=%d", selopt, t->user_tid);
+ if (t->lang && langopt)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " --%s=%s", langopt, t->lang);
+ if (t->default_track)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " (*)");
+ if (t->attached_picture)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " [P]");
+ if (t->title)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " '%s'", t->title);
+ const char *codec = s ? s->codec : NULL;
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " (%s)", codec ? codec : "<unknown>");
+ if (t->is_external)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " (external)");
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "\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_msg(MSGT_CPLAYER, MSGL_INFO,
+ "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, mpctx->audio_delay, 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)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ mask &= mpctx->initialized_flags;
+
+ mp_msg(MSGT_CPLAYER, MSGL_DBG2, "\n*** uninit(0x%X)\n", mask);
+
+ if (mask & INITIALIZED_ACODEC) {
+ mpctx->initialized_flags &= ~INITIALIZED_ACODEC;
+ if (mpctx->sh_audio)
+ uninit_audio(mpctx->sh_audio);
+ cleanup_demux_stream(mpctx, STREAM_AUDIO);
+ mpctx->mixer.afilter = NULL;
+ }
+
+ 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_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_msg(MSGT_CPLAYER, MSGL_DBG2, "\n[[[uninit getch2]]]\n");
+ // restore terminal:
+ getch2_disable();
+ }
+
+ if (mask & INITIALIZED_VOL) {
+ mpctx->initialized_flags &= ~INITIALIZED_VOL;
+ if (mpctx->mixer.ao) {
+ // Normally the mixer remembers volume, but do it even if the
+ // volume is set explicitly with --volume=... (so that the same
+ // volume is restored on reinit)
+ if (opts->mixer_init_volume >= 0 && mpctx->mixer.user_set_volume)
+ mixer_getbothvolume(&mpctx->mixer, &opts->mixer_init_volume);
+ if (opts->mixer_init_mute >= 0 && mpctx->mixer.user_set_mute)
+ opts->mixer_init_mute = mixer_getmute(&mpctx->mixer);
+ }
+ }
+
+ if (mask & INITIALIZED_AO) {
+ mpctx->initialized_flags &= ~INITIALIZED_AO;
+ if (mpctx->mixer.ao)
+ mixer_uninit(&mpctx->mixer);
+ mpctx->mixer.ao = NULL;
+ if (mpctx->ao)
+ ao_uninit(mpctx->ao, mpctx->stop_play != AT_END_OF_FILE);
+ mpctx->ao = NULL;
+ }
+}
+
+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;
+
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+ timeEndPeriod(1);
+#endif
+
+ 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_tmsg(MSGT_CPLAYER, MSGL_INFO, "\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 (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_tmsg(MSGT_CPLAYER, MSGL_ERR,
+ "mp_find_user_config_file(\"config\") problem\n");
+ else {
+ if ((conffile_fd = open(conffile, O_CREAT | O_EXCL | O_WRONLY,
+ 0666)) != -1) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "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;
+}
+
+#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 ? */
+ 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, M_SETOPT_BACKUP);
+ }
+}
+
+#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, M_SETOPT_BACKUP);
+ }
+}
+
+#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, M_SETOPT_BACKUP);
+ }
+}
+
+/**
+ * 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)
+{
+ 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, M_SETOPT_BACKUP);
+ 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);
+
+ if (try_load_config(conf, cfg))
+ return;
+ }
+
+ if ((confpath = mp_find_user_config_file(name)) != NULL) {
+ try_load_config(conf, confpath);
+
+ talloc_free(confpath);
+ }
+}
+
+static bool might_be_an_url(bstr f)
+{
+ return bstr_find0(f, "://") >= 0;
+}
+
+#define MP_WATCH_LATER_CONF "watch_later"
+
+static char *get_playback_resume_config_filename(const char *fname)
+{
+ char *res = NULL;
+ void *tmp = talloc_new(NULL);
+ const char *realpath = fname;
+ if (!might_be_an_url(bstr0(fname))) {
+ char *cwd = mp_getcwd(tmp);
+ if (!cwd)
+ goto exit;
+ realpath = mp_path_join(tmp, bstr0(cwd), bstr0(fname));
+ }
+ 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",
+ //"mute",
+ "audio-delay",
+ //"balance",
+ "fullscreen",
+ "colormatrix",
+ "colormatrix-input-range",
+ "colormatrix-output-range",
+ "ontop",
+ "border",
+ "gamma",
+ "brightness",
+ "contrast",
+ "saturation",
+ "hue",
+ "panscan",
+ "aid",
+ "vid",
+ "sid",
+ "sub-delay",
+ "sub-pos",
+ "sub-visibility",
+ "sub-scale",
+ "ass-use-margins",
+ "ass-vsfilter-aspect-compat",
+ "ass-style-override",
+ 0
+};
+
+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);
+ int percent = get_percent_pos(mpctx);
+ if (percent < 1 || percent > 99 || pos == MP_NOPTS_VALUE)
+ goto exit;
+
+ mk_config_dir(MP_WATCH_LATER_CONF);
+
+ char *conffile = get_playback_resume_config_filename(mpctx->filename);
+ talloc_steal(tmp, conffile);
+ if (!conffile)
+ goto exit;
+
+ 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)
+ 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);
+ if (fname) {
+ try_load_config(conf, fname);
+ unlink(fname);
+ }
+ talloc_free(fname);
+}
+
+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 = -1;
+ 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,
+ .stream = stream,
+ };
+ MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
+
+ if (stream->type == STREAM_SUB)
+ track->preloaded = !!stream->sub->track;
+
+ // Needed for DVD and Blu-ray.
+ if (!track->lang) {
+ struct stream_lang_req req = {
+ .type = track->type,
+ .id = map_id_from_demuxer(track->demuxer, track->type,
+ track->demuxer_id)
+ };
+ stream_control(track->demuxer->stream, STREAM_CTRL_GET_LANG, &req);
+ if (req.name[0])
+ track->lang = talloc_strdup(track, req.name);
+ }
+
+ demuxer_select_track(track->demuxer, stream, false);
+
+ return track;
+}
+
+static void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer)
+{
+ for (int n = 0; n < demuxer->num_streams; n++)
+ add_stream_track(mpctx, demuxer->streams[n], !!mpctx->timeline);
+}
+
+static void add_dvd_tracks(struct MPContext *mpctx)
+{
+#ifdef CONFIG_DVDREAD
+ struct demuxer *demuxer = mpctx->demuxer;