summaryrefslogtreecommitdiffstats
path: root/mpvcore
diff options
context:
space:
mode:
Diffstat (limited to 'mpvcore')
-rw-r--r--mpvcore/encode.h3
-rw-r--r--mpvcore/encode_lavc.c4
-rw-r--r--mpvcore/path.c12
-rw-r--r--mpvcore/path.h2
-rw-r--r--mpvcore/player/audio.c414
-rw-r--r--mpvcore/player/configfiles.c355
-rw-r--r--mpvcore/player/loadfile.c1427
-rw-r--r--mpvcore/player/main.c440
-rw-r--r--mpvcore/player/misc.c174
-rw-r--r--mpvcore/player/mp_core.h39
-rw-r--r--mpvcore/player/mp_osd.h2
-rw-r--r--mpvcore/player/mplayer.c5079
-rw-r--r--mpvcore/player/osd.c527
-rw-r--r--mpvcore/player/playloop.c1297
-rw-r--r--mpvcore/player/sub.c225
-rw-r--r--mpvcore/player/video.c474
16 files changed, 5391 insertions, 5083 deletions
diff --git a/mpvcore/encode.h b/mpvcore/encode.h
index acdb75c5a3..fec14045ed 100644
--- a/mpvcore/encode.h
+++ b/mpvcore/encode.h
@@ -2,7 +2,6 @@
#define MPLAYER_ENCODE_H
#include <stdbool.h>
-#include <libavutil/avutil.h>
struct MPOpts;
struct encode_lavc_context;
@@ -15,7 +14,7 @@ void encode_lavc_free(struct encode_lavc_context *ctx);
void encode_lavc_discontinuity(struct encode_lavc_context *ctx);
bool encode_lavc_showhelp(struct MPOpts *opts);
int encode_lavc_getstatus(struct encode_lavc_context *ctx, char *buf, int bufsize, float relative_position);
-void encode_lavc_expect_stream(struct encode_lavc_context *ctx, enum AVMediaType mt);
+void encode_lavc_expect_stream(struct encode_lavc_context *ctx, int mt);
void encode_lavc_set_video_fps(struct encode_lavc_context *ctx, float fps);
bool encode_lavc_didfail(struct encode_lavc_context *ctx); // check if encoding failed
diff --git a/mpvcore/encode_lavc.c b/mpvcore/encode_lavc.c
index ce8d9a8e59..56744d53f0 100644
--- a/mpvcore/encode_lavc.c
+++ b/mpvcore/encode_lavc.c
@@ -20,6 +20,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <libavutil/avutil.h>
#include "encode_lavc.h"
#include "mpvcore/mp_msg.h"
@@ -1033,8 +1034,7 @@ int encode_lavc_getstatus(struct encode_lavc_context *ctx,
return 0;
}
-void encode_lavc_expect_stream(struct encode_lavc_context *ctx,
- enum AVMediaType mt)
+void encode_lavc_expect_stream(struct encode_lavc_context *ctx, int mt)
{
CHECK_FAIL(ctx, );
diff --git a/mpvcore/path.c b/mpvcore/path.c
index 647693fb05..93e2d09b04 100644
--- a/mpvcore/path.c
+++ b/mpvcore/path.c
@@ -202,3 +202,15 @@ bool mp_is_url(bstr path)
}
return true;
}
+
+void mp_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);
+}
diff --git a/mpvcore/path.h b/mpvcore/path.h
index 52656f8fc2..bae6956ec7 100644
--- a/mpvcore/path.h
+++ b/mpvcore/path.h
@@ -65,4 +65,6 @@ bool mp_path_isdir(const char *path);
bool mp_is_url(bstr path);
+void mp_mk_config_dir(char *subdir);
+
#endif /* MPLAYER_PATH_H */
diff --git a/mpvcore/player/audio.c b/mpvcore/player/audio.c
new file mode 100644
index 0000000000..443a1eed90
--- /dev/null
+++ b/mpvcore/player/audio.c
@@ -0,0 +1,414 @@
+/*
+ * 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 "audio/mixer.h"
+#include "audio/decode/dec_audio.h"
+#include "audio/filter/af.h"
+#include "audio/out/ao.h"
+#include "demux/demux.h"
+
+#include "mp_core.h"
+
+static int build_afilter_chain(struct MPContext *mpctx)
+{
+ struct sh_audio *sh_audio = mpctx->sh_audio;
+ struct ao *ao = mpctx->ao;
+ struct MPOpts *opts = mpctx->opts;
+ int new_srate;
+ if (af_control_any_rev(sh_audio->afilter,
+ AF_CONTROL_PLAYBACK_SPEED | AF_CONTROL_SET,
+ &opts->playback_speed))
+ new_srate = sh_audio->samplerate;
+ else {
+ new_srate = sh_audio->samplerate * opts->playback_speed;
+ if (new_srate != ao->samplerate) {
+ // limits are taken from libaf/af_resample.c
+ if (new_srate < 8000)
+ new_srate = 8000;
+ if (new_srate > 192000)
+ new_srate = 192000;
+ opts->playback_speed = (double)new_srate / sh_audio->samplerate;
+ }
+ }
+ return init_audio_filters(sh_audio, new_srate,
+ &ao->samplerate, &ao->channels, &ao->format);
+}
+
+static int recreate_audio_filters(struct MPContext *mpctx)
+{
+ assert(mpctx->sh_audio);
+
+ // init audio filters:
+ if (!build_afilter_chain(mpctx)) {
+ MP_ERR(mpctx, "Couldn't find matching filter/ao format!\n");
+ return -1;
+ }
+
+ mixer_reinit_audio(mpctx->mixer, mpctx->ao, mpctx->sh_audio->afilter);
+
+ return 0;
+}
+
+int reinit_audio_filters(struct MPContext *mpctx)
+{
+ struct sh_audio *sh_audio = mpctx->sh_audio;
+ if (!sh_audio)
+ return -2;
+
+ af_uninit(mpctx->sh_audio->afilter);
+ if (af_init(mpctx->sh_audio->afilter) < 0)
+ return -1;
+ if (recreate_audio_filters(mpctx) < 0)
+ return -1;
+
+ return 0;
+}
+
+void reinit_audio_chain(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ init_demux_stream(mpctx, STREAM_AUDIO);
+ if (!mpctx->sh_audio) {
+ uninit_player(mpctx, INITIALIZED_AO);
+ goto no_audio;
+ }
+
+ if (!(mpctx->initialized_flags & INITIALIZED_ACODEC)) {
+ if (!init_best_audio_codec(mpctx->sh_audio, opts->audio_decoders))
+ goto init_error;
+ mpctx->initialized_flags |= INITIALIZED_ACODEC;
+ }
+
+ int ao_srate = opts->force_srate;
+ int ao_format = opts->audio_output_format;
+ struct mp_chmap ao_channels = {0};
+ if (mpctx->initialized_flags & INITIALIZED_AO) {
+ ao_srate = mpctx->ao->samplerate;
+ ao_format = mpctx->ao->format;
+ ao_channels = mpctx->ao->channels;
+ } else {
+ // Automatic downmix
+ if (mp_chmap_is_stereo(&opts->audio_output_channels) &&
+ !mp_chmap_is_stereo(&mpctx->sh_audio->channels))
+ {
+ mp_chmap_from_channels(&ao_channels, 2);
+ }
+ }
+
+ // Determine what the filter chain outputs. build_afilter_chain() also
+ // needs this for testing whether playback speed is changed by resampling
+ // or using a special filter.
+ if (!init_audio_filters(mpctx->sh_audio, // preliminary init
+ // input:
+ mpctx->sh_audio->samplerate,
+ // output:
+ &ao_srate, &ao_channels, &ao_format)) {
+ MP_ERR(mpctx, "Error at audio filter chain pre-init!\n");
+ goto init_error;
+ }
+
+ if (!(mpctx->initialized_flags & INITIALIZED_AO)) {
+ mpctx->initialized_flags |= INITIALIZED_AO;
+ mp_chmap_remove_useless_channels(&ao_channels,
+ &opts->audio_output_channels);
+ mpctx->ao = ao_init_best(mpctx->global, mpctx->input,
+ mpctx->encode_lavc_ctx, ao_srate, ao_format,
+ ao_channels);
+ struct ao *ao = mpctx->ao;
+ if (!ao) {
+ MP_ERR(mpctx, "Could not open/initialize audio device -> no sound.\n");
+ goto init_error;
+ }
+ ao->buffer.start = talloc_new(ao);
+ char *s = mp_audio_fmt_to_str(ao->samplerate, &ao->channels, ao->format);
+ MP_INFO(mpctx, "AO: [%s] %s\n", ao->driver->name, s);
+ talloc_free(s);
+ MP_VERBOSE(mpctx, "AO: Description: %s\n", ao->driver->description);
+ }
+
+ if (recreate_audio_filters(mpctx) < 0)
+ goto init_error;
+
+ mpctx->syncing_audio = true;
+ return;
+
+init_error:
+ uninit_player(mpctx, INITIALIZED_ACODEC | INITIALIZED_AO);
+ cleanup_demux_stream(mpctx, STREAM_AUDIO);
+no_audio:
+ mpctx->current_track[STREAM_AUDIO] = NULL;
+ MP_INFO(mpctx, "Audio: no audio\n");
+}
+
+// Return pts value corresponding to the end point of audio written to the
+// ao so far.
+double written_audio_pts(struct MPContext *mpctx)
+{
+ sh_audio_t *sh_audio = mpctx->sh_audio;
+ if (!sh_audio)
+ return MP_NOPTS_VALUE;
+
+ double bps = sh_audio->channels.num * sh_audio->samplerate *
+ sh_audio->samplesize;
+
+ // first calculate the end pts of audio that has been output by decoder
+ double a_pts = sh_audio->pts;
+ if (a_pts == MP_NOPTS_VALUE)
+ return MP_NOPTS_VALUE;
+
+ // sh_audio->pts is the timestamp of the latest input packet with
+ // known pts that the decoder has decoded. sh_audio->pts_bytes is
+ // the amount of bytes the decoder has written after that timestamp.
+ a_pts += sh_audio->pts_bytes / bps;
+
+ // Now a_pts hopefully holds the pts for end of audio from decoder.
+ // Subtract data in buffers between decoder and audio out.
+
+ // Decoded but not filtered
+ a_pts -= sh_audio->a_buffer_len / bps;
+
+ // Data buffered in audio filters, measured in bytes of "missing" output
+ double buffered_output = af_calc_delay(sh_audio->afilter);
+
+ // Data that was ready for ao but was buffered because ao didn't fully
+ // accept everything to internal buffers yet
+ buffered_output += mpctx->ao->buffer.len;
+
+ // Filters divide audio length by playback_speed, so multiply by it
+ // to get the length in original units without speedup or slowdown
+ a_pts -= buffered_output * mpctx->opts->playback_speed / mpctx->ao->bps;
+
+ return a_pts + mpctx->video_offset;
+}
+
+// Return pts value corresponding to currently playing audio.
+double playing_audio_pts(struct MPContext *mpctx)
+{
+ double pts = written_audio_pts(mpctx);
+ if (pts == MP_NOPTS_VALUE)
+ return pts;
+ return pts - mpctx->opts->playback_speed * ao_get_delay(mpctx->ao);
+}
+
+static int write_to_ao(struct MPContext *mpctx, void *data, int len, int flags,
+ double pts)
+{
+ if (mpctx->paused)
+ return 0;
+ struct ao *ao = mpctx->ao;
+ double bps = ao->bps / mpctx->opts->playback_speed;
+ ao->pts = pts;
+ int played = ao_play(mpctx->ao, data, len, flags);
+ if (played > 0) {
+ mpctx->shown_aframes += played / (af_fmt2bits(ao->format) / 8);
+ mpctx->delay += played / bps;
+ // Keep correct pts for remaining data - could be used to flush
+ // remaining buffer when closing ao.
+ ao->pts += played / bps;
+ return played;
+ }
+ return 0;
+}
+
+#define ASYNC_PLAY_DONE -3
+static int audio_start_sync(struct MPContext *mpctx, int playsize)
+{
+ struct ao *ao = mpctx->ao;
+ struct MPOpts *opts = mpctx->opts;
+ sh_audio_t * const sh_audio = mpctx->sh_audio;
+ int res;
+
+ // Timing info may not be set without
+ res = decode_audio(sh_audio, &ao->buffer, 1);
+ if (res < 0)
+ return res;
+
+ int bytes;
+ bool did_retry = false;
+ double written_pts;
+ double bps = ao->bps / opts->playback_speed;
+ bool hrseek = mpctx->hrseek_active; // audio only hrseek
+ mpctx->hrseek_active = false;
+ while (1) {
+ written_pts = written_audio_pts(mpctx);
+ double ptsdiff;
+ if (hrseek)
+ ptsdiff = written_pts - mpctx->hrseek_pts;
+ else
+ ptsdiff = written_pts - mpctx->sh_video->pts - mpctx->delay
+ - mpctx->audio_delay;
+ bytes = ptsdiff * bps;
+ bytes -= bytes % (ao->channels.num * af_fmt2bits(ao->format) / 8);
+
+ // ogg demuxers give packets without timing
+ if (written_pts <= 1 && sh_audio->pts == MP_NOPTS_VALUE) {
+ if (!did_retry) {
+ // Try to read more data to see packets that have pts
+ res = decode_audio(sh_audio, &ao->buffer, ao->bps);
+ if (res < 0)
+ return res;
+ did_retry = true;
+ continue;
+ }
+ bytes = 0;
+ }
+
+ if (fabs(ptsdiff) > 300 || isnan(ptsdiff)) // pts reset or just broken?
+ bytes = 0;
+
+ if (bytes > 0)
+ break;
+
+ mpctx->syncing_audio = false;
+ int a = MPMIN(-bytes, MPMAX(playsize, 20000));
+ res = decode_audio(sh_audio, &ao->buffer, a);
+ bytes += ao->buffer.len;
+ if (bytes >= 0) {
+ memmove(ao->buffer.start,
+ ao->buffer.start + ao->buffer.len - bytes, bytes);
+ ao->buffer.len = bytes;
+ if (res < 0)
+ return res;
+ return decode_audio(sh_audio, &ao->buffer, playsize);
+ }
+ ao->buffer.len = 0;
+ if (res < 0)
+ return res;
+ }
+ if (hrseek)
+ // Don't add silence in audio-only case even if position is too late
+ return 0;
+ int fillbyte = 0;
+ if ((ao->format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_US)
+ fillbyte = 0x80;
+ if (bytes >= playsize) {
+ /* This case could fall back to the one below with
+ * bytes = playsize, but then silence would keep accumulating
+ * in a_out_buffer if the AO accepts less data than it asks for
+ * in playsize. */
+ char *p = malloc(playsize);
+ memset(p, fillbyte, playsize);
+ write_to_ao(mpctx, p, playsize, 0, written_pts - bytes / bps);
+ free(p);
+ return ASYNC_PLAY_DONE;
+ }
+ mpctx->syncing_audio = false;
+ decode_audio_prepend_bytes(&ao->buffer, bytes, fillbyte);
+ return decode_audio(sh_audio, &ao->buffer, playsize);
+}
+
+int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct ao *ao = mpctx->ao;
+ int playsize;
+ int playflags = 0;
+ bool audio_eof = false;
+ bool partial_fill = false;
+ sh_audio_t * const sh_audio = mpctx->sh_audio;
+ bool modifiable_audio_format = !(ao->format & AF_FORMAT_SPECIAL_MASK);
+ int unitsize = ao->channels.num * af_fmt2bits(ao->format) / 8;
+
+ if (mpctx->paused)
+ playsize = 1; // just initialize things (audio pts at least)
+ else
+ playsize = ao_get_space(ao);
+
+ // Coming here with hrseek_active still set means audio-only
+ if (!mpctx->sh_video || !mpctx->sync_audio_to_video)
+ mpctx->syncing_audio = false;
+ if (!opts->initial_audio_sync || !modifiable_audio_format) {
+ mpctx->syncing_audio = false;
+ mpctx->hrseek_active = false;
+ }
+
+ int res;
+ if (mpctx->syncing_audio || mpctx->hrseek_active)
+ res = audio_start_sync(mpctx, playsize);
+ else
+ res = decode_audio(sh_audio, &ao->buffer, playsize);
+
+ if (res < 0) { // EOF, error or format change
+ if (res == -2) {
+ /* The format change isn't handled too gracefully. A more precise
+ * implementation would require draining buffered old-format audio
+ * while displaying video, then doing the output format switch.
+ */
+ if (!mpctx->opts->gapless_audio)
+ uninit_player(mpctx, INITIALIZED_AO);
+ reinit_audio_chain(mpctx);
+ return -1;
+ } else if (res == ASYNC_PLAY_DONE)
+ return 0;
+ else if (demux_stream_eof(mpctx->sh_audio->gsh))
+ audio_eof = true;
+ }
+
+ if (endpts != MP_NOPTS_VALUE && modifiable_audio_format) {
+ double bytes = (endpts - written_audio_pts(mpctx) + mpctx->audio_delay)
+ * ao->bps / opts->playback_speed;
+ if (playsize > bytes) {
+ playsize = MPMAX(bytes, 0);
+ playflags |= AOPLAY_FINAL_CHUNK;
+ audio_eof = true;
+ partial_fill = true;
+ }
+ }
+
+ assert(ao->buffer.len % unitsize == 0);
+ if (playsize > ao->buffer.len) {
+ partial_fill = true;
+ playsize = ao->buffer.len;
+ if (audio_eof)
+ playflags |= AOPLAY_FINAL_CHUNK;
+ }
+ playsize -= playsize % unitsize;
+ if (!playsize)
+ return partial_fill && audio_eof ? -2 : -partial_fill;
+
+ // play audio:
+
+ int played = write_to_ao(mpctx, ao->buffer.start, playsize, playflags,
+ written_audio_pts(mpctx));
+ assert(played % unitsize == 0);
+ ao->buffer_playable_size = playsize - played;
+
+ if (played > 0) {
+ ao->buffer.len -= played;
+ memmove(ao->buffer.start, ao->buffer.start + played, ao->buffer.len);
+ } else if (!mpctx->paused && audio_eof && ao_get_delay(ao) < .04) {
+ // Sanity check to avoid hanging in case current ao doesn't output
+ // partial chunks and doesn't check for AOPLAY_FINAL_CHUNK
+ return -2;
+ }
+
+ return -partial_fill;
+}
diff --git a/mpvcore/player/configfiles.c b/mpvcore/player/configfiles.c
new file mode 100644
index 0000000000..1c75231365
--- /dev/null
+++ b/mpvcore/player/configfiles.c
@@ -0,0 +1,355 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include <libavutil/md5.h>
+
+#include "config.h"
+#include "talloc.h"
+
+#include "osdep/io.h"
+
+#include "mpvcore/mp_msg.h"
+#include "mpvcore/path.h"
+#include "mpvcore/m_config.h"
+#include "mpvcore/parser-cfg.h"
+#include "mpvcore/playlist.h"
+#include "mpvcore/options.h"
+#include "mpvcore/m_property.h"
+
+#include "stream/stream.h"
+
+#include "mp_core.h"
+#include "command.h"
+
+#define DEF_CONFIG "# Write your default config options here!\n\n\n"
+
+bool mp_parse_cfgfiles(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ m_config_t *conf = mpctx->mconfig;
+ 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;
+ mp_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."
+
+void mp_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."
+
+void mp_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);
+ }
+}
+
+void mp_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;
+}
+
+void mp_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"
+
+char *mp_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;
+
+ mp_mk_config_dir(MP_WATCH_LATER_CONF);
+
+ char *conffile = mp_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);
+}
+
+void mp_load_playback_resume(m_config_t *conf, const char *file)
+{
+ char *fname = mp_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 = mp_get_playback_resume_config_filename(e->filename, opts);
+ bool exists = conf && mp_path_exists(conf);
+ talloc_free(conf);
+ if (exists)
+ return e;
+ }
+ return NULL;
+}
+
diff --git a/mpvcore/player/loadfile.c b/mpvcore/player/loadfile.c
new file mode 100644
index 0000000000..876994ac93
--- /dev/null
+++ b/mpvcore/player/loadfile.c
@@ -0,0 +1,1427 @@
+/*
+ * 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 <assert.h>
+
+#include <libavutil/avutil.h>
+
+#include "config.h"
+#include "talloc.h"
+
+#include "osdep/getch2.h"
+#include "osdep/io.h"
+#include "osdep/timer.h"
+
+#include "mpvcore/mp_msg.h"
+#include "mpvcore/path.h"
+#include "mpvcore/m_config.h"
+#include "mpvcore/parser-cfg.h"
+#include "mpvcore/playlist.h"
+#include "mpvcore/options.h"
+#include "mpvcore/m_property.h"
+#include "mpvcore/mp_common.h"
+#include "mpvcore/resolve.h"
+#include "mpvcore/encode.h"
+#include "mpvcore/input/input.h"
+
+#include "audio/mixer.h"
+#include "audio/decode/dec_audio.h"
+#include "audio/out/ao.h"
+#include "demux/demux.h"
+#include "stream/stream.h"
+#include "sub/ass_mp.h"
+#include "sub/dec_sub.h"
+#include "sub/find_subfiles.h"
+#include "video/decode/dec_video.h"
+#include "video/out/vo.h"
+
+#include "mp_core.h"
+#include "command.h"
+
+#ifdef CONFIG_DVBIN
+#include "stream/dvbin.h"
+#endif
+
+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);
+ }
+