diff options
Diffstat (limited to 'player/main.c')
-rw-r--r-- | player/main.c | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/player/main.c b/player/main.c new file mode 100644 index 0000000..27cf9b4 --- /dev/null +++ b/player/main.c @@ -0,0 +1,467 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <math.h> +#include <assert.h> +#include <string.h> +#include <locale.h> + +#include "config.h" + +#include <libplacebo/config.h> + +#include "mpv_talloc.h" + +#include "misc/dispatch.h" +#include "misc/thread_pool.h" +#include "osdep/io.h" +#include "osdep/terminal.h" +#include "osdep/threads.h" +#include "osdep/timer.h" +#include "osdep/main-fn.h" + +#include "common/av_log.h" +#include "common/codecs.h" +#include "common/encode.h" +#include "options/m_config.h" +#include "options/m_option.h" +#include "options/m_property.h" +#include "common/common.h" +#include "common/msg.h" +#include "common/msg_control.h" +#include "common/stats.h" +#include "common/global.h" +#include "filters/f_decoder_wrapper.h" +#include "options/parse_configfile.h" +#include "options/parse_commandline.h" +#include "common/playlist.h" +#include "options/options.h" +#include "options/path.h" +#include "input/input.h" + +#include "audio/out/ao.h" +#include "misc/thread_tools.h" +#include "sub/osd.h" +#include "video/out/vo.h" + +#include "core.h" +#include "client.h" +#include "command.h" +#include "screenshot.h" + +static const char def_config[] = +#include "etc/builtin.conf.inc" +; + +#if HAVE_COCOA +#include "osdep/macosx_events.h" +#endif + +#ifndef FULLCONFIG +#define FULLCONFIG "(missing)\n" +#endif + +enum exit_reason { + EXIT_NONE, + EXIT_NORMAL, + EXIT_ERROR, +}; + +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=<file> specify subtitle file to use\n" +" --playlist=<file> specify playlist file\n" +"\n" +" --list-options list all mpv options\n" +" --h=<string> print options which contain the given string in their name\n" +"\n"; + +static mp_static_mutex terminal_owner_lock = MP_STATIC_MUTEX_INITIALIZER; +static struct MPContext *terminal_owner; + +static bool cas_terminal_owner(struct MPContext *old, struct MPContext *new) +{ + mp_mutex_lock(&terminal_owner_lock); + bool r = terminal_owner == old; + if (r) + terminal_owner = new; + mp_mutex_unlock(&terminal_owner_lock); + return r; +} + +void mp_update_logging(struct MPContext *mpctx, bool preinit) +{ + bool had_log_file = mp_msg_has_log_file(mpctx->global); + + mp_msg_update_msglevels(mpctx->global, mpctx->opts); + + bool enable = mpctx->opts->use_terminal; + bool enabled = cas_terminal_owner(mpctx, mpctx); + if (enable != enabled) { + if (enable && cas_terminal_owner(NULL, mpctx)) { + terminal_init(); + enabled = true; + } else if (!enable) { + terminal_uninit(); + cas_terminal_owner(mpctx, NULL); + } + } + + if (mp_msg_has_log_file(mpctx->global) && !had_log_file) { + // for log-file=... in config files. + // we did flush earlier messages, but they were in a cyclic buffer, so + // the version might have been overwritten. ensure we have it. + mp_print_version(mpctx->log, false); + } + + if (enabled && !preinit && mpctx->opts->consolecontrols) + terminal_setup_getch(mpctx->input); +} + +void mp_print_version(struct mp_log *log, int always) +{ + int v = always ? MSGL_INFO : MSGL_V; + mp_msg(log, v, "%s %s\n", mpv_version, mpv_copyright); + if (strcmp(mpv_builddate, "UNKNOWN")) + mp_msg(log, v, " built on %s\n", mpv_builddate); + mp_msg(log, v, "libplacebo version: %s\n", PL_VERSION); + check_library_versions(log, v); + mp_msg(log, v, "\n"); + // Only in verbose mode. + if (!always) { + mp_msg(log, MSGL_V, "Configuration: " CONFIGURATION "\n"); + mp_msg(log, MSGL_V, "List of enabled features: %s\n", FULLCONFIG); + #ifdef NDEBUG + mp_msg(log, MSGL_V, "Built with NDEBUG.\n"); + #endif + } +} + +void mp_destroy(struct MPContext *mpctx) +{ + mp_shutdown_clients(mpctx); + + mp_uninit_ipc(mpctx->ipc_ctx); + mpctx->ipc_ctx = NULL; + + uninit_audio_out(mpctx); + uninit_video_out(mpctx); + + // If it's still set here, it's an error. + encode_lavc_free(mpctx->encode_lavc_ctx); + mpctx->encode_lavc_ctx = NULL; + + command_uninit(mpctx); + + mp_clients_destroy(mpctx); + + osd_free(mpctx->osd); + +#if HAVE_COCOA + cocoa_set_input_context(NULL); +#endif + + if (cas_terminal_owner(mpctx, mpctx)) { + terminal_uninit(); + cas_terminal_owner(mpctx, NULL); + } + + mp_input_uninit(mpctx->input); + + uninit_libav(mpctx->global); + + mp_msg_uninit(mpctx->global); + assert(!mpctx->num_abort_list); + talloc_free(mpctx->abort_list); + mp_mutex_destroy(&mpctx->abort_lock); + talloc_free(mpctx->mconfig); // destroy before dispatch + talloc_free(mpctx); +} + +static bool handle_help_options(struct MPContext *mpctx) +{ + struct MPOpts *opts = mpctx->opts; + struct mp_log *log = mpctx->log; + if (opts->ao_opts->audio_device && + strcmp(opts->ao_opts->audio_device, "help") == 0) + { + ao_print_devices(mpctx->global, log, mpctx->ao); + return true; + } + if (opts->property_print_help) { + property_print_help(mpctx); + return true; + } + if (encode_lavc_showhelp(log, opts->encode_opts)) + return true; + return false; +} + +static int cfg_include(void *ctx, char *filename, int flags) +{ + struct MPContext *mpctx = ctx; + char *fname = mp_get_user_path(NULL, mpctx->global, filename); + int r = m_config_parse_config_file(mpctx->mconfig, mpctx->global, fname, NULL, flags); + talloc_free(fname); + return r; +} + +// We mostly care about LC_NUMERIC, and how "." vs. "," is treated, +// Other locale stuff might break too, but probably isn't too bad. +static bool check_locale(void) +{ + char *name = setlocale(LC_NUMERIC, NULL); + return !name || strcmp(name, "C") == 0 || strcmp(name, "C.UTF-8") == 0; +} + +struct MPContext *mp_create(void) +{ + if (!check_locale()) { + // Normally, we never print anything (except if the "terminal" option + // is enabled), so this is an exception. + fprintf(stderr, "Non-C locale detected. This is not supported.\n" + "Call 'setlocale(LC_NUMERIC, \"C\");' in your code.\n"); + return NULL; + } + + char *enable_talloc = getenv("MPV_LEAK_REPORT"); + if (!enable_talloc) + enable_talloc = HAVE_TA_LEAK_REPORT ? "1" : "0"; + if (strcmp(enable_talloc, "1") == 0) + talloc_enable_leak_report(); + + mp_time_init(); + + struct MPContext *mpctx = talloc(NULL, MPContext); + *mpctx = (struct MPContext){ + .last_chapter = -2, + .term_osd_contents = talloc_strdup(mpctx, ""), + .osd_progbar = { .type = -1 }, + .playlist = talloc_zero(mpctx, struct playlist), + .dispatch = mp_dispatch_create(mpctx), + .playback_abort = mp_cancel_new(mpctx), + .thread_pool = mp_thread_pool_create(mpctx, 0, 1, 30), + .stop_play = PT_NEXT_ENTRY, + .play_dir = 1, + }; + + mp_mutex_init(&mpctx->abort_lock); + + mpctx->global = talloc_zero(mpctx, struct mpv_global); + + stats_global_init(mpctx->global); + + // Nothing must call mp_msg*() and related before this + mp_msg_init(mpctx->global); + mpctx->log = mp_log_new(mpctx, mpctx->global->log, "!cplayer"); + mpctx->statusline = mp_log_new(mpctx, mpctx->log, "!statusline"); + + mpctx->stats = stats_ctx_create(mpctx, mpctx->global, "main"); + + // Create the config context and register the options + mpctx->mconfig = m_config_new(mpctx, mpctx->log, &mp_opt_root); + mpctx->opts = mpctx->mconfig->optstruct; + mpctx->global->config = mpctx->mconfig->shadow; + mpctx->mconfig->includefunc = cfg_include; + mpctx->mconfig->includefunc_ctx = mpctx; + mpctx->mconfig->use_profiles = true; + mpctx->mconfig->is_toplevel = true; + mpctx->mconfig->global = mpctx->global; + m_config_parse(mpctx->mconfig, "", bstr0(def_config), NULL, 0); + + mpctx->input = mp_input_init(mpctx->global, mp_wakeup_core_cb, mpctx); + screenshot_init(mpctx); + command_init(mpctx); + init_libav(mpctx->global); + mp_clients_init(mpctx); + mpctx->osd = osd_create(mpctx->global); + +#if HAVE_COCOA + cocoa_set_input_context(mpctx->input); +#endif + + char *verbose_env = getenv("MPV_VERBOSE"); + if (verbose_env) + mpctx->opts->verbose = atoi(verbose_env); + + mp_cancel_trigger(mpctx->playback_abort); + + return mpctx; +} + +// Finish mpctx initialization. This must be done after setting up all options. +// Some of the initializations depend on the options, and can't be changed or +// undone later. +// If options is not NULL, apply them as command line player arguments. +// Returns: 0 on success, -1 on error, 1 if exiting normally (e.g. help). +int mp_initialize(struct MPContext *mpctx, char **options) +{ + struct MPOpts *opts = mpctx->opts; + + assert(!mpctx->initialized); + + // Preparse the command line, so we can init the terminal early. + if (options) { + m_config_preparse_command_line(mpctx->mconfig, mpctx->global, + &opts->verbose, options); + } + + mp_init_paths(mpctx->global, opts); + mp_msg_set_early_logging(mpctx->global, true); + mp_update_logging(mpctx, true); + + if (options) { + MP_VERBOSE(mpctx, "Command line options:"); + for (int i = 0; options[i]; i++) + MP_VERBOSE(mpctx, " '%s'", options[i]); + MP_VERBOSE(mpctx, "\n"); + } + + mp_print_version(mpctx->log, false); + + mp_parse_cfgfiles(mpctx); + + if (options) { + int r = m_config_parse_mp_command_line(mpctx->mconfig, mpctx->playlist, + mpctx->global, options); + if (r < 0) + return r == M_OPT_EXIT ? 1 : -1; + } + + if (opts->operation_mode == 1) { + m_config_set_profile(mpctx->mconfig, "builtin-pseudo-gui", + M_SETOPT_NO_OVERWRITE); + m_config_set_profile(mpctx->mconfig, "pseudo-gui", 0); + } + + // Backup the default settings, which should not be stored in the resume + // config files. This explicitly includes values set by config files and + // the command line. + m_config_backup_watch_later_opts(mpctx->mconfig); + + mp_input_load_config(mpctx->input); + + // From this point on, all mpctx members are initialized. + mpctx->initialized = true; + mpctx->mconfig->option_change_callback = mp_option_change_callback; + mpctx->mconfig->option_change_callback_ctx = mpctx; + m_config_set_update_dispatch_queue(mpctx->mconfig, mpctx->dispatch); + // Run all update handlers. + mp_option_change_callback(mpctx, NULL, UPDATE_OPTS_MASK, false); + + if (handle_help_options(mpctx)) + return 1; // help + + check_library_versions(mp_null_log, 0); + + if (!mpctx->playlist->num_entries && !opts->player_idle_mode && + options) + { + // nothing to play + mp_print_version(mpctx->log, true); + MP_INFO(mpctx, "%s", mp_help_text); + return 1; + } + + MP_STATS(mpctx, "start init"); + +#if HAVE_COCOA + mpv_handle *ctx = mp_new_client(mpctx->clients, "osx"); + cocoa_set_mpv_handle(ctx); +#endif + + if (opts->encode_opts->file && opts->encode_opts->file[0]) { + mpctx->encode_lavc_ctx = encode_lavc_init(mpctx->global); + if(!mpctx->encode_lavc_ctx) { + MP_INFO(mpctx, "Encoding initialization failed.\n"); + return -1; + } + m_config_set_profile(mpctx->mconfig, "encoding", 0); + mp_input_enable_section(mpctx->input, "encode", MP_INPUT_EXCLUSIVE); + } + + mp_load_scripts(mpctx); + + if (opts->force_vo == 2 && handle_force_window(mpctx, false) < 0) + return -1; + + // Needed to properly enter _initial_ idle mode if playlist empty. + if (mpctx->opts->player_idle_mode && !mpctx->playlist->num_entries) + mpctx->stop_play = PT_STOP; + + MP_STATS(mpctx, "end init"); + + return 0; +} + +int mpv_main(int argc, char *argv[]) +{ + mp_thread_set_name("mpv"); + struct MPContext *mpctx = mp_create(); + if (!mpctx) + return 1; + + mpctx->is_cli = true; + + char **options = argv && argv[0] ? argv + 1 : NULL; // skips program name + int r = mp_initialize(mpctx, options); + if (r == 0) + mp_play_files(mpctx); + + int rc = 0; + const char *reason = NULL; + if (r < 0) { + reason = "Fatal error"; + rc = 1; + } else if (r > 0) { + // nothing + } else if (mpctx->stop_play == PT_QUIT) { + reason = "Quit"; + } else if (mpctx->files_played) { + if (mpctx->files_errored || mpctx->files_broken) { + reason = "Some errors happened"; + rc = 3; + } else { + reason = "End of file"; + } + } else if (mpctx->files_broken && !mpctx->files_errored) { + reason = "Errors when loading file"; + rc = 2; + } else if (mpctx->files_errored) { + reason = "Interrupted by error"; + rc = 2; + } else { + reason = "No files played"; + } + + if (reason) + MP_INFO(mpctx, "Exiting... (%s)\n", reason); + if (mpctx->has_quit_custom_rc) + rc = mpctx->quit_custom_rc; + + mp_destroy(mpctx); + return rc; +} |