Coverage Report

Created: 2026-06-25 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/player/main.c
Line
Count
Source
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
#include <stdio.h>
19
#include <stdlib.h>
20
#include <stdbool.h>
21
#include <math.h>
22
#include <assert.h>
23
#include <string.h>
24
#include <locale.h>
25
26
#include "config.h"
27
28
#include <libplacebo/config.h>
29
30
#include "mpv_talloc.h"
31
32
#include "misc/dispatch.h"
33
#include "misc/random.h"
34
#include "misc/thread_pool.h"
35
#include "osdep/io.h"
36
#include "osdep/terminal.h"
37
#include "osdep/threads.h"
38
#include "osdep/timer.h"
39
#include "osdep/main-fn.h"
40
41
#include "common/av_log.h"
42
#include "common/codecs.h"
43
#include "common/encode.h"
44
#include "options/m_config.h"
45
#include "options/m_option.h"
46
#include "options/m_property.h"
47
#include "common/common.h"
48
#include "common/encode_lavc.h"
49
#include "common/msg.h"
50
#include "common/msg_control.h"
51
#include "common/stats.h"
52
#include "common/global.h"
53
#include "filters/f_decoder_wrapper.h"
54
#include "options/parse_configfile.h"
55
#include "options/parse_commandline.h"
56
#include "common/playlist.h"
57
#include "options/options.h"
58
#include "options/path.h"
59
#include "input/dnd.h"
60
#include "input/input.h"
61
#include "demux/packet_pool.h"
62
63
#include "audio/out/ao.h"
64
#include "misc/thread_tools.h"
65
#include "sub/osd.h"
66
#include "video/out/vo.h"
67
68
#include "core.h"
69
#include "client.h"
70
#include "command.h"
71
#include "screenshot.h"
72
73
#include "stream/stream_curl.h"
74
75
static const char def_config[] =
76
#include "etc/builtin.conf.inc"
77
;
78
79
#if HAVE_WIN32_SMTC
80
#include "osdep/win32/smtc.h"
81
#endif
82
83
#if HAVE_WIN32_DESKTOP
84
#include "osdep/w32_register.h"
85
#endif
86
87
#if HAVE_COCOA
88
#include "osdep/mac/app_bridge.h"
89
#endif
90
91
#ifndef FULLCONFIG
92
#define FULLCONFIG "(missing)\n"
93
#endif
94
95
enum exit_reason {
96
    EXIT_NONE,
97
    EXIT_NORMAL,
98
    EXIT_ERROR,
99
};
100
101
const char mp_help_text[] =
102
"Usage:   mpv [options] [url|path/]filename\n"
103
"\n"
104
"Basic options:\n"
105
" --start=<time>    seek to given (percent, seconds, or hh:mm:ss) position\n"
106
" --no-audio        do not play sound\n"
107
" --no-video        do not play video\n"
108
" --fs              fullscreen playback\n"
109
" --sub-file=<file> specify subtitle file to use\n"
110
" --playlist=<file> specify playlist file\n"
111
"\n"
112
" --list-options    list all mpv options\n"
113
" --h=<string>      print options which contain the given string in their name\n";
114
115
static mp_static_mutex terminal_owner_lock = MP_STATIC_MUTEX_INITIALIZER;
116
static struct MPContext *terminal_owner;
117
118
static bool cas_terminal_owner(struct MPContext *old, struct MPContext *new)
119
337k
{
120
337k
    mp_mutex_lock(&terminal_owner_lock);
121
337k
    bool r = terminal_owner == old;
122
337k
    if (r)
123
1.27k
        terminal_owner = new;
124
337k
    mp_mutex_unlock(&terminal_owner_lock);
125
337k
    return r;
126
337k
}
127
128
void mp_update_logging(struct MPContext *mpctx, bool preinit)
129
215k
{
130
215k
    bool had_log_file = mp_msg_has_log_file(mpctx->global);
131
132
215k
    mp_msg_update_msglevels(mpctx->global, mpctx->opts);
133
134
215k
    bool enable = mpctx->opts->use_terminal;
135
215k
    bool enabled = cas_terminal_owner(mpctx, mpctx);
136
215k
    if (enable != enabled) {
137
357
        if (enable && cas_terminal_owner(NULL, mpctx)) {
138
357
            terminal_init();
139
357
            enabled = true;
140
357
        } else if (!enable) {
141
0
            terminal_uninit();
142
0
            cas_terminal_owner(mpctx, NULL);
143
0
        }
144
357
    }
145
146
215k
    if (mp_msg_has_log_file(mpctx->global) && !had_log_file) {
147
        // for log-file=... in config files.
148
        // we did flush earlier messages, but they were in a cyclic buffer, so
149
        // the version might have been overwritten. ensure we have it.
150
0
        mp_print_version(mpctx->log, false);
151
0
    }
152
153
215k
    if (enabled && !preinit && mpctx->opts->consolecontrols)
154
1
        terminal_setup_getch(mpctx->input);
155
156
215k
    if (enabled)
157
556
        encoder_update_log(mpctx->global);
158
215k
}
159
160
void mp_print_version(struct mp_log *log, int always)
161
107k
{
162
107k
    int v = always ? MSGL_INFO : MSGL_V;
163
107k
    mp_msg(log, v, "%s %s\n", mpv_version, mpv_copyright);
164
107k
    if (strcmp(mpv_builddate, "UNKNOWN"))
165
107k
        mp_msg(log, v, " built on %s\n", mpv_builddate);
166
107k
    mp_msg(log, v, "libplacebo version: %s\n", PL_VERSION);
167
107k
    check_library_versions(log, v);
168
    // Only in verbose mode.
169
107k
    if (!always) {
170
107k
        mp_msg(log, MSGL_V, "Configuration: " CONFIGURATION "\n");
171
107k
        mp_msg(log, MSGL_V, "List of enabled features: " FULLCONFIG "\n");
172
        #ifdef NDEBUG
173
            mp_msg(log, MSGL_V, "Built with NDEBUG.\n");
174
        #endif
175
107k
    }
176
107k
}
177
178
void mp_destroy(struct MPContext *mpctx)
179
120k
{
180
120k
    mp_shutdown_clients(mpctx);
181
182
120k
    mp_uninit_ipc(mpctx->ipc_ctx);
183
120k
    mpctx->ipc_ctx = NULL;
184
185
120k
    uninit_audio_out(mpctx);
186
120k
    uninit_video_out(mpctx);
187
188
    // If it's still set here, it's an error.
189
120k
    encode_lavc_free(mpctx->encode_lavc_ctx);
190
120k
    mpctx->encode_lavc_ctx = NULL;
191
192
120k
    command_uninit(mpctx);
193
194
120k
    mp_clients_destroy(mpctx);
195
196
120k
    osd_free(mpctx->osd);
197
198
#if HAVE_COCOA
199
    cocoa_set_input_context(NULL);
200
#endif
201
202
120k
    if (cas_terminal_owner(mpctx, mpctx)) {
203
357
        terminal_uninit();
204
357
        cas_terminal_owner(mpctx, NULL);
205
357
    }
206
207
120k
    mp_input_uninit(mpctx->input);
208
120k
    mp_clipboard_destroy(mpctx->clipboard);
209
210
120k
    uninit_libav(mpctx->global);
211
212
120k
    mp_msg_uninit(mpctx->global);
213
120k
    mp_assert(!mpctx->num_abort_list);
214
120k
    talloc_free(mpctx->abort_list);
215
120k
    mp_mutex_destroy(&mpctx->abort_lock);
216
120k
    talloc_free(mpctx->mconfig); // destroy before dispatch
217
120k
    talloc_free(mpctx);
218
120k
}
219
220
static bool handle_help_options(struct MPContext *mpctx)
221
107k
{
222
107k
    struct MPOpts *opts = mpctx->opts;
223
107k
    struct mp_log *log = mpctx->log;
224
107k
    if (opts->ao_opts->audio_device &&
225
107k
        strcmp(opts->ao_opts->audio_device, "help") == 0)
226
0
    {
227
0
        ao_print_devices(mpctx->global, log, mpctx->ao);
228
0
        return true;
229
0
    }
230
107k
    if (opts->property_print_help) {
231
0
        property_print_help(mpctx);
232
0
        return true;
233
0
    }
234
107k
    if (encode_lavc_showhelp(log, opts->encode_opts))
235
0
        return true;
236
107k
    return false;
237
107k
}
238
239
static int cfg_include(void *ctx, char *filename, int flags)
240
0
{
241
0
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
242
0
    return 1;
243
0
#endif
244
0
    struct MPContext *mpctx = ctx;
245
0
    char *fname = mp_get_user_path(NULL, mpctx->global, filename);
246
0
    int r = m_config_parse_config_file(mpctx->mconfig, mpctx->global, fname, NULL, flags);
247
0
    talloc_free(fname);
248
0
    return r;
249
0
}
250
251
// We mostly care about LC_NUMERIC, and how "." vs. "," is treated,
252
// Other locale stuff might break too, but probably isn't too bad.
253
static bool check_locale(void)
254
120k
{
255
120k
    char *name = setlocale(LC_NUMERIC, NULL);
256
120k
    return !name || strcmp(name, "C") == 0 || strcmp(name, "C.UTF-8") == 0;
257
120k
}
258
259
struct MPContext *mp_create(void)
260
120k
{
261
120k
    if (!check_locale()) {
262
        // Normally, we never print anything (except if the "terminal" option
263
        // is enabled), so this is an exception.
264
0
        fprintf(stderr, "Non-C locale detected. This is not supported.\n"
265
0
                        "Call 'setlocale(LC_NUMERIC, \"C\");' in your code.\n");
266
0
        return NULL;
267
0
    }
268
269
120k
    char *enable_talloc = getenv("MPV_LEAK_REPORT");
270
120k
    if (enable_talloc && strcmp(enable_talloc, "1") == 0)
271
0
        talloc_enable_leak_report();
272
273
120k
    mp_time_init();
274
275
120k
    struct MPContext *mpctx = talloc(NULL, MPContext);
276
120k
    *mpctx = (struct MPContext){
277
120k
        .last_chapter = -2,
278
120k
        .term_osd_contents = talloc_strdup(mpctx, ""),
279
120k
        .osd_progbar = { .type = -1 },
280
120k
        .playlist = talloc_zero(mpctx, struct playlist),
281
120k
        .dispatch = mp_dispatch_create(mpctx),
282
120k
        .playback_abort = mp_cancel_new(mpctx),
283
120k
        .thread_pool = mp_thread_pool_create(mpctx, 0, 1, 30),
284
120k
        .stop_play = PT_NEXT_ENTRY,
285
120k
        .play_dir = 1,
286
120k
    };
287
288
120k
    mp_mutex_init(&mpctx->abort_lock);
289
290
120k
    mpctx->global = talloc_zero(mpctx, struct mpv_global);
291
292
120k
    demux_packet_pool_init(mpctx->global);
293
120k
    stats_global_init(mpctx->global);
294
#if HAVE_LIBCURL
295
    mp_curl_global_init(mpctx->global);
296
#endif
297
298
    // Nothing must call mp_msg*() and related before this
299
120k
    mp_msg_init(mpctx->global);
300
120k
    mpctx->log = mp_log_new(mpctx, mpctx->global->log, "!cplayer");
301
120k
    mpctx->statusline = mp_log_new(mpctx, mpctx->log, "!statusline");
302
303
120k
    mpctx->stats = stats_ctx_create(mpctx, mpctx->global, "main");
304
305
    // Create the config context and register the options
306
120k
    mpctx->mconfig = m_config_new(mpctx, mpctx->log, &mp_opt_root);
307
120k
    mpctx->opts = mpctx->mconfig->optstruct;
308
120k
    mpctx->global->config = mpctx->mconfig->shadow;
309
120k
    mpctx->mconfig->includefunc = cfg_include;
310
120k
    mpctx->mconfig->includefunc_ctx = mpctx;
311
120k
    mpctx->mconfig->use_profiles = true;
312
120k
    mpctx->mconfig->is_toplevel = true;
313
120k
    mpctx->mconfig->global = mpctx->global;
314
120k
    m_config_parse(mpctx->mconfig, "", bstr0(def_config), NULL, 0);
315
316
120k
    mpctx->input = mp_input_init(mpctx->global, mp_wakeup_core_cb, mpctx);
317
120k
    clipboard_init(mpctx);
318
120k
    screenshot_init(mpctx);
319
120k
    command_init(mpctx);
320
120k
    init_libav(mpctx->global);
321
120k
    mp_clients_init(mpctx);
322
120k
    mpctx->osd = osd_create(mpctx->global);
323
324
#if HAVE_COCOA
325
    cocoa_set_input_context(mpctx->input);
326
#endif
327
328
120k
    char *verbose_env = getenv("MPV_VERBOSE");
329
120k
    if (verbose_env)
330
0
        mpctx->opts->verbose = strtol(verbose_env, NULL, 10);
331
332
120k
    mp_cancel_trigger(mpctx->playback_abort);
333
334
120k
    return mpctx;
335
120k
}
336
337
// Finish mpctx initialization. This must be done after setting up all options.
338
// Some of the initializations depend on the options, and can't be changed or
339
// undone later.
340
// If options is not NULL, apply them as command line player arguments.
341
// Returns: 0 on success, -1 on error, 1 if exiting normally (e.g. help).
342
int mp_initialize(struct MPContext *mpctx, char **options)
343
107k
{
344
107k
    struct MPOpts *opts = mpctx->opts;
345
346
107k
    mp_assert(!mpctx->initialized);
347
348
    // Preparse the command line, so we can init the terminal early.
349
107k
    if (options) {
350
419
        m_config_preparse_command_line(mpctx->mconfig, mpctx->global,
351
419
                                       &opts->verbose, options);
352
419
    }
353
354
107k
    mp_init_paths(mpctx->global, opts);
355
107k
    mp_msg_set_early_logging(mpctx->global, true);
356
107k
    mp_update_logging(mpctx, true);
357
358
107k
    if (options) {
359
419
        MP_VERBOSE(mpctx, "Command line options:");
360
39.1k
        for (int i = 0; options[i]; i++)
361
38.7k
            MP_VERBOSE(mpctx, " '%s'", options[i]);
362
419
        MP_VERBOSE(mpctx, "\n");
363
419
    }
364
365
107k
    mp_print_version(mpctx->log, false);
366
367
107k
    mp_parse_cfgfiles(mpctx);
368
369
107k
    if (options) {
370
419
        int r = m_config_parse_mp_command_line(mpctx->mconfig, mpctx->playlist,
371
419
                                               mpctx->global, options);
372
419
        if (r < 0)
373
60
            return r == M_OPT_EXIT ? 1 : -1;
374
419
    }
375
376
107k
    if (opts->operation_mode == 1) {
377
0
        m_config_set_profile(mpctx->mconfig, "builtin-pseudo-gui",
378
0
                             M_SETOPT_NO_OVERWRITE);
379
0
        m_config_set_profile(mpctx->mconfig, "pseudo-gui", 0);
380
0
    }
381
382
    // Backup the default settings, which should not be stored in the resume
383
    // config files. This explicitly includes values set by config files and
384
    // the command line.
385
107k
    m_config_backup_watch_later_opts(mpctx->mconfig);
386
387
107k
    mp_input_load_config(mpctx->input);
388
389
    // From this point on, all mpctx members are initialized.
390
107k
    mpctx->initialized = true;
391
107k
    mpctx->mconfig->option_change_callback = mp_option_change_callback;
392
107k
    mpctx->mconfig->option_change_callback_ctx = mpctx;
393
107k
    m_config_set_update_dispatch_queue(mpctx->mconfig, mpctx->dispatch);
394
    // Run all update handlers.
395
107k
    mp_option_change_callback(mpctx, NULL, UPDATE_OPTS_MASK, false);
396
107k
    handle_option_callbacks(mpctx);
397
398
107k
    if (handle_help_options(mpctx))
399
0
        return 1; // help
400
401
#if HAVE_WIN32_DESKTOP
402
    if (mp_w32_handle_register(mpctx))
403
        return 1; // register/unregister
404
#endif
405
406
107k
    check_library_versions(mp_null_log, 0);
407
408
107k
    if (!mpctx->playlist->num_entries && !opts->player_idle_mode &&
409
0
        options)
410
0
    {
411
        // nothing to play
412
0
        mp_print_version(mpctx->log, true);
413
0
        MP_INFO(mpctx, "%s", mp_help_text);
414
0
        return 1;
415
0
    }
416
417
107k
    MP_STATS(mpctx, "start init");
418
419
#if HAVE_COCOA
420
    mpv_handle *ctx = mp_new_client(mpctx->clients, "mac");
421
    cocoa_set_mpv_handle(ctx);
422
#endif
423
424
#if HAVE_WIN32_SMTC
425
    if (opts->media_controls)
426
        mp_smtc_init(mp_new_client(mpctx->clients, "SystemMediaTransportControls"));
427
#endif
428
429
107k
    mp_dnd_init(mp_new_client(mpctx->clients, "dnd"));
430
431
107k
    mpctx->ipc_ctx = mp_init_ipc(mpctx->clients, mpctx->global);
432
433
107k
    if (opts->encode_opts->file && opts->encode_opts->file[0]) {
434
13
        mpctx->encode_lavc_ctx = encode_lavc_init(mpctx->global);
435
13
        if(!mpctx->encode_lavc_ctx) {
436
11
            MP_INFO(mpctx, "Encoding initialization failed.\n");
437
11
            return -1;
438
11
        }
439
13
    }
440
441
107k
    mp_load_scripts(mpctx);
442
443
107k
    if (opts->force_vo == 2 && handle_force_window(mpctx, false) < 0)
444
0
        return -1;
445
446
    // Needed to properly enter _initial_ idle mode if playlist empty.
447
107k
    if (mpctx->opts->player_idle_mode && !mpctx->playlist->num_entries)
448
107k
        mpctx->stop_play = PT_STOP;
449
450
107k
    MP_STATS(mpctx, "end init");
451
452
107k
    return 0;
453
107k
}
454
455
int mpv_main(int argc, char *argv[])
456
0
{
457
0
    struct MPContext *mpctx = mp_create();
458
0
    if (!mpctx)
459
0
        return 1;
460
461
0
    mpctx->is_cli = true;
462
463
0
    char **options = argv && argv[0] ? argv + 1 : NULL; // skips program name
464
0
    int r = mp_initialize(mpctx, options);
465
0
    if (r == 0)
466
0
        mp_play_files(mpctx);
467
468
0
    int rc = 0;
469
0
    const char *reason = NULL;
470
0
    if (r < 0) {
471
0
        reason = "Fatal error";
472
0
        rc = 1;
473
0
    } else if (r > 0) {
474
        // nothing
475
0
    } else if (mpctx->stop_play == PT_QUIT) {
476
0
        reason = "Quit";
477
0
    } else if (mpctx->files_played) {
478
0
        if (mpctx->files_errored || mpctx->files_broken) {
479
0
            reason = "Some errors happened";
480
0
            rc = 3;
481
0
        } else {
482
0
            reason = "End of file";
483
0
        }
484
0
    } else if (mpctx->files_broken && !mpctx->files_errored) {
485
0
        reason = "Errors when loading file";
486
0
        rc = 2;
487
0
    } else if (mpctx->files_errored) {
488
0
        reason = "Interrupted by error";
489
0
        rc = 2;
490
0
    } else {
491
0
        reason = "No files played";
492
0
    }
493
494
0
    if (reason)
495
0
        MP_INFO(mpctx, "Exiting... (%s)\n", reason);
496
0
    if (mpctx->has_quit_custom_rc)
497
0
        rc = mpctx->quit_custom_rc;
498
499
0
    mp_destroy(mpctx);
500
0
    return rc;
501
0
}