Coverage Report

Created: 2026-09-28 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/player/client.c
Line
Count
Source
1
/* Copyright (C) 2017 the mpv developers
2
 *
3
 * Permission to use, copy, modify, and/or distribute this software for any
4
 * purpose with or without fee is hereby granted, provided that the above
5
 * copyright notice and this permission notice appear in all copies.
6
 *
7
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
 */
15
16
#include <assert.h>
17
#include <errno.h>
18
#include <fcntl.h>
19
#include <math.h>
20
#include <stdatomic.h>
21
#include <stddef.h>
22
#include <stdint.h>
23
#include <stdlib.h>
24
25
#include "common/common.h"
26
#include "common/global.h"
27
#include "common/msg.h"
28
#include "common/msg_control.h"
29
#include "input/input.h"
30
#include "input/cmd.h"
31
#include "misc/ctype.h"
32
#include "misc/dispatch.h"
33
#include "misc/node.h"
34
#include "misc/rendezvous.h"
35
#include "misc/thread_tools.h"
36
#include "options/m_config.h"
37
#include "options/m_option.h"
38
#include "options/m_property.h"
39
#include "options/path.h"
40
#include "options/parse_configfile.h"
41
#include "osdep/threads.h"
42
#include "osdep/timer.h"
43
#include "osdep/io.h"
44
#include "stream/stream.h"
45
46
#include "command.h"
47
#include "core.h"
48
#include "client.h"
49
50
/*
51
 * Locking hierarchy:
52
 *
53
 *  MPContext > mp_client_api.lock > mpv_handle.lock > * > mpv_handle.wakeup_lock
54
 *
55
 * MPContext strictly speaking has no locks, and instead is implicitly managed
56
 * by MPContext.dispatch, which basically stops the playback thread at defined
57
 * points in order to let clients access it in a synchronized manner. Since
58
 * MPContext code accesses the client API, it's on top of the lock hierarchy.
59
 *
60
 */
61
62
struct mp_client_api {
63
    struct MPContext *mpctx;
64
65
    mp_mutex lock;
66
67
    // -- protected by lock
68
69
    struct mpv_handle **clients;
70
    int num_clients;
71
    bool shutting_down; // do not allow new clients
72
    bool have_terminator; // a client took over the role of destroying the core
73
    bool terminate_core_thread; // make libmpv core thread exit
74
    // This is incremented whenever the clients[] array above changes. This is
75
    // used to safely unlock mp_client_api.lock while iterating the list of
76
    // clients.
77
    uint64_t clients_list_change_ts;
78
    int64_t id_alloc;
79
80
    struct mp_custom_protocol *custom_protocols;
81
    int num_custom_protocols;
82
83
    struct mpv_render_context *render_context;
84
};
85
86
struct observe_property {
87
    // -- immutable
88
    struct mpv_handle *owner;
89
    char *name;
90
    int id;                 // ==mp_get_property_id(name)
91
    uint64_t event_mask;    // ==mp_get_property_event_mask(name)
92
    int64_t reply_id;
93
    mpv_format format;
94
    const struct m_option *type;
95
    // -- protected by owner->lock
96
    size_t refcount;
97
    uint64_t change_ts;     // logical timestamp incremented on each change
98
    uint64_t value_ts;      // logical timestamp for value contents
99
    bool value_valid;
100
    union m_option_value value;
101
    uint64_t value_ret_ts;  // logical timestamp of value returned to user
102
    union m_option_value value_ret;
103
    bool waiting_for_hook;  // flag for draining old property changes on a hook
104
};
105
106
struct mpv_handle {
107
    // -- immutable
108
    char name[MAX_CLIENT_NAME];
109
    struct mp_log *log;
110
    struct MPContext *mpctx;
111
    struct mp_client_api *clients;
112
    int64_t id;
113
114
    // -- not thread-safe
115
    struct mpv_event *cur_event;
116
    struct mpv_event_property cur_property_event;
117
    struct observe_property *cur_property;
118
119
    mp_mutex lock;
120
121
    mp_mutex wakeup_lock;
122
    mp_cond wakeup;
123
124
    // -- protected by wakeup_lock
125
    bool need_wakeup;
126
    void (*wakeup_cb)(void *d);
127
    void *wakeup_cb_ctx;
128
    int wakeup_pipe[2];
129
130
    // -- protected by lock
131
132
    uint64_t event_mask;
133
    bool queued_wakeup;
134
135
    mpv_event *events;      // ringbuffer of max_events entries
136
    int max_events;         // allocated number of entries in events
137
    int first_event;        // events[first_event] is the first readable event
138
    int num_events;         // number of readable events
139
    int reserved_events;    // number of entries reserved for replies
140
    size_t async_counter;   // pending other async events
141
    bool choked;            // recovering from queue overflow
142
    bool destroying;        // pending destruction; no API accesses allowed
143
    bool hook_pending;      // hook events are returned after draining properties
144
145
    struct observe_property **properties;
146
    int num_properties;
147
    bool has_pending_properties; // (maybe) new property events (producer side)
148
    bool new_property_events; // new property events (consumer side)
149
    int cur_property_index; // round-robin for property events (consumer side)
150
    uint64_t property_event_masks; // or-ed together event masks of all properties
151
    // This is incremented whenever the properties[] array above changes. This
152
    // is used to safely unlock mpv_handle.lock while reading a property. If
153
    // the counter didn't change between unlock and relock, then it will assume
154
    // the array did not change.
155
    uint64_t properties_change_ts;
156
157
    bool fuzzy_initialized; // see scripting.c wait_loaded()
158
    bool is_weak;           // can not keep core alive on its own
159
    struct mp_log_buffer *messages;
160
    int messages_level;
161
};
162
163
static bool gen_log_message_event(struct mpv_handle *ctx);
164
static bool gen_property_change_event(struct mpv_handle *ctx);
165
static void notify_property_events(struct mpv_handle *ctx, int event);
166
167
// Must be called with prop->owner->lock held.
168
static void prop_unref(struct observe_property *prop)
169
1.35M
{
170
1.35M
    if (!prop)
171
263k
        return;
172
173
1.09M
    mp_assert(prop->refcount > 0);
174
1.09M
    prop->refcount -= 1;
175
1.09M
    if (!prop->refcount)
176
244k
        talloc_free(prop);
177
1.09M
}
178
179
void mp_clients_init(struct MPContext *mpctx)
180
138k
{
181
138k
    mpctx->clients = talloc_ptrtype(NULL, mpctx->clients);
182
138k
    *mpctx->clients = (struct mp_client_api) {
183
138k
        .mpctx = mpctx,
184
138k
    };
185
138k
    mpctx->global->client_api = mpctx->clients;
186
138k
    mp_mutex_init(&mpctx->clients->lock);
187
138k
}
188
189
void mp_clients_destroy(struct MPContext *mpctx)
190
138k
{
191
138k
    if (!mpctx->clients)
192
0
        return;
193
138k
    mp_assert(mpctx->clients->num_clients == 0);
194
195
    // The API user is supposed to call mpv_render_context_free(). It's simply
196
    // not allowed not to do this.
197
138k
    if (mpctx->clients->render_context) {
198
0
        MP_FATAL(mpctx, "Broken API use: mpv_render_context_free() not called.\n");
199
0
        abort();
200
0
    }
201
202
138k
    mp_mutex_destroy(&mpctx->clients->lock);
203
138k
    talloc_free(mpctx->clients);
204
138k
    mpctx->clients = NULL;
205
138k
}
206
207
// Test for "fuzzy" initialization of all clients. That is, all clients have
208
// at least called mpv_wait_event() at least once since creation (or exited).
209
bool mp_clients_all_initialized(struct MPContext *mpctx)
210
165k
{
211
165k
    bool all_ok = true;
212
165k
    mp_mutex_lock(&mpctx->clients->lock);
213
451k
    for (int n = 0; n < mpctx->clients->num_clients; n++) {
214
286k
        struct mpv_handle *ctx = mpctx->clients->clients[n];
215
286k
        mp_mutex_lock(&ctx->lock);
216
286k
        all_ok &= ctx->fuzzy_initialized;
217
286k
        mp_mutex_unlock(&ctx->lock);
218
286k
    }
219
165k
    mp_mutex_unlock(&mpctx->clients->lock);
220
165k
    return all_ok;
221
165k
}
222
223
static struct mpv_handle *find_client_id(struct mp_client_api *clients, int64_t id)
224
0
{
225
0
    for (int n = 0; n < clients->num_clients; n++) {
226
0
        if (clients->clients[n]->id == id)
227
0
            return clients->clients[n];
228
0
    }
229
0
    return NULL;
230
0
}
231
232
static struct mpv_handle *find_client(struct mp_client_api *clients,
233
                                      const char *name)
234
267k
{
235
267k
    if (name[0] == '@') {
236
0
        char *end;
237
0
        errno = 0;
238
0
        long long int id = strtoll(name + 1, &end, 10);
239
0
        if (errno || end[0])
240
0
            return NULL;
241
0
        return find_client_id(clients, id);
242
0
    }
243
244
436k
    for (int n = 0; n < clients->num_clients; n++) {
245
173k
        if (strcmp(clients->clients[n]->name, name) == 0)
246
4.19k
            return clients->clients[n];
247
173k
    }
248
249
263k
    return NULL;
250
267k
}
251
252
bool mp_client_id_exists(struct MPContext *mpctx, int64_t id)
253
0
{
254
0
    mp_mutex_lock(&mpctx->clients->lock);
255
0
    bool r = find_client_id(mpctx->clients, id);
256
0
    mp_mutex_unlock(&mpctx->clients->lock);
257
0
    return r;
258
0
}
259
260
struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name)
261
263k
{
262
263k
    mp_mutex_lock(&clients->lock);
263
264
263k
    char nname[MAX_CLIENT_NAME];
265
267k
    for (int n = 1; n < 1000; n++) {
266
267k
        if (!name)
267
0
            name = "client";
268
267k
        snprintf(nname, sizeof(nname) - 3, "%s", name); // - space for number
269
1.21M
        for (int i = 0; nname[i]; i++)
270
949k
            nname[i] = mp_isalnum(nname[i]) ? nname[i] : '_';
271
267k
        if (n > 1)
272
4.19k
            mp_snprintf_cat(nname, sizeof(nname), "%d", n);
273
267k
        if (!find_client(clients, nname))
274
263k
            break;
275
4.19k
        nname[0] = '\0';
276
4.19k
    }
277
278
263k
    if (!nname[0] || clients->shutting_down) {
279
68
        mp_mutex_unlock(&clients->lock);
280
68
        return NULL;
281
68
    }
282
283
263k
    int num_events = 1000;
284
285
263k
    struct mpv_handle *client = talloc_ptrtype(NULL, client);
286
263k
    *client = (struct mpv_handle){
287
263k
        .log = mp_log_new(client, clients->mpctx->log, nname),
288
263k
        .mpctx = clients->mpctx,
289
263k
        .clients = clients,
290
263k
        .id = ++(clients->id_alloc),
291
263k
        .cur_event = talloc_zero(client, struct mpv_event),
292
263k
        .events = talloc_array(client, mpv_event, num_events),
293
263k
        .max_events = num_events,
294
263k
        .event_mask = (1ULL << INTERNAL_EVENT_BASE) - 1, // exclude internal events
295
263k
        .wakeup_pipe = {-1, -1},
296
263k
    };
297
263k
    mp_mutex_init(&client->lock);
298
263k
    mp_mutex_init(&client->wakeup_lock);
299
263k
    mp_cond_init(&client->wakeup);
300
301
263k
    snprintf(client->name, sizeof(client->name), "%s", nname);
302
303
263k
    clients->clients_list_change_ts += 1;
304
263k
    MP_TARRAY_APPEND(clients, clients->clients, clients->num_clients, client);
305
306
263k
    if (clients->num_clients == 1 && !clients->mpctx->is_cli)
307
138k
        client->fuzzy_initialized = true;
308
309
263k
    mp_mutex_unlock(&clients->lock);
310
311
263k
    mpv_request_event(client, MPV_EVENT_TICK, 0);
312
313
263k
    return client;
314
263k
}
315
316
void mp_client_set_weak(struct mpv_handle *ctx)
317
2.93k
{
318
2.93k
    mp_mutex_lock(&ctx->lock);
319
2.93k
    ctx->is_weak = true;
320
2.93k
    mp_mutex_unlock(&ctx->lock);
321
2.93k
}
322
323
const char *mpv_client_name(mpv_handle *ctx)
324
4.70k
{
325
4.70k
    return ctx->name;
326
4.70k
}
327
328
int64_t mpv_client_id(mpv_handle *ctx)
329
2.93k
{
330
2.93k
    return ctx->id;
331
2.93k
}
332
333
struct mp_log *mp_client_get_log(struct mpv_handle *ctx)
334
4.70k
{
335
4.70k
    return ctx->log;
336
4.70k
}
337
338
struct mpv_global *mp_client_get_global(struct mpv_handle *ctx)
339
0
{
340
0
    return ctx->mpctx->global;
341
0
}
342
343
static void wakeup_client(struct mpv_handle *ctx)
344
3.15M
{
345
3.15M
    mp_mutex_lock(&ctx->wakeup_lock);
346
3.15M
    if (!ctx->need_wakeup) {
347
2.22M
        ctx->need_wakeup = true;
348
2.22M
        mp_cond_broadcast(&ctx->wakeup);
349
2.22M
        if (ctx->wakeup_cb)
350
0
            ctx->wakeup_cb(ctx->wakeup_cb_ctx);
351
2.22M
        if (ctx->wakeup_pipe[0] != -1)
352
11
            (void)write(ctx->wakeup_pipe[1], &(char){0}, 1);
353
2.22M
    }
354
3.15M
    mp_mutex_unlock(&ctx->wakeup_lock);
355
3.15M
}
356
357
// Note: the caller has to deal with sporadic wakeups.
358
static int wait_wakeup(struct mpv_handle *ctx, int64_t end)
359
2.21M
{
360
2.21M
    int r = 0;
361
2.21M
    mp_mutex_unlock(&ctx->lock);
362
2.21M
    mp_mutex_lock(&ctx->wakeup_lock);
363
2.21M
    if (!ctx->need_wakeup)
364
2.05M
        r = mp_cond_timedwait_until(&ctx->wakeup, &ctx->wakeup_lock, end);
365
2.21M
    if (r == 0)
366
2.21M
        ctx->need_wakeup = false;
367
2.21M
    mp_mutex_unlock(&ctx->wakeup_lock);
368
2.21M
    mp_mutex_lock(&ctx->lock);
369
2.21M
    return r;
370
2.21M
}
371
372
void mpv_set_wakeup_callback(mpv_handle *ctx, void (*cb)(void *d), void *d)
373
0
{
374
0
    mp_mutex_lock(&ctx->wakeup_lock);
375
0
    ctx->wakeup_cb = cb;
376
0
    ctx->wakeup_cb_ctx = d;
377
0
    if (ctx->wakeup_cb)
378
0
        ctx->wakeup_cb(ctx->wakeup_cb_ctx);
379
0
    mp_mutex_unlock(&ctx->wakeup_lock);
380
0
}
381
382
static void lock_core(mpv_handle *ctx)
383
1.26M
{
384
1.26M
    mp_dispatch_lock(ctx->mpctx->dispatch);
385
1.26M
}
386
387
static void unlock_core(mpv_handle *ctx)
388
1.26M
{
389
1.26M
    mp_dispatch_unlock(ctx->mpctx->dispatch);
390
1.26M
}
391
392
void mpv_wait_async_requests(mpv_handle *ctx)
393
263k
{
394
263k
    mp_mutex_lock(&ctx->lock);
395
269k
    while (ctx->reserved_events || ctx->async_counter)
396
5.74k
        wait_wakeup(ctx, INT64_MAX);
397
263k
    mp_mutex_unlock(&ctx->lock);
398
263k
}
399
400
// Send abort signal to all matching work items.
401
// If type==0, destroy all of the matching ctx.
402
// If ctx==0, destroy all.
403
static void abort_async(struct MPContext *mpctx, mpv_handle *ctx,
404
                        int type, uint64_t id)
405
263k
{
406
263k
    mp_mutex_lock(&mpctx->abort_lock);
407
408
    // Destroy all => ensure any newly appearing work is aborted immediately.
409
263k
    if (ctx == NULL)
410
0
        mpctx->abort_all = true;
411
412
263k
    for (int n = 0; n < mpctx->num_abort_list; n++) {
413
0
        struct mp_abort_entry *abort = mpctx->abort_list[n];
414
0
        if (!ctx || (abort->client == ctx && (!type ||
415
0
            (abort->client_work_type == type && abort->client_work_id == id))))
416
0
        {
417
0
            mp_abort_trigger_locked(mpctx, abort);
418
0
        }
419
0
    }
420
421
263k
    mp_mutex_unlock(&mpctx->abort_lock);
422
263k
}
423
424
static void mp_destroy_client(mpv_handle *ctx, bool terminate)
425
265k
{
426
265k
    if (!ctx)
427
1.76k
        return;
428
429
263k
    struct MPContext *mpctx = ctx->mpctx;
430
263k
    struct mp_client_api *clients = ctx->clients;
431
432
263k
    MP_DBG(ctx, "Destroying client handle...\n");
433
434
263k
    if (terminate)
435
138k
        mpv_command(ctx, (const char*[]){"quit", NULL});
436
437
263k
    mp_mutex_lock(&ctx->lock);
438
439
263k
    ctx->destroying = true;
440
441
508k
    for (int n = 0; n < ctx->num_properties; n++)
442
244k
        prop_unref(ctx->properties[n]);
443
263k
    ctx->num_properties = 0;
444
263k
    ctx->properties_change_ts += 1;
445
446
263k
    prop_unref(ctx->cur_property);
447
263k
    ctx->cur_property = NULL;
448
449
263k
    mp_mutex_unlock(&ctx->lock);
450
451
263k
    abort_async(mpctx, ctx, 0, 0);
452
453
    // reserved_events equals the number of asynchronous requests that weren't
454
    // yet replied. In order to avoid that trying to reply to a removed client
455
    // causes a crash, block until all asynchronous requests were served.
456
263k
    mpv_wait_async_requests(ctx);
457
458
263k
    osd_set_external_remove_owner(mpctx->osd, ctx);
459
263k
    mp_input_remove_sections_by_owner(mpctx->input, ctx->name);
460
461
263k
    mp_mutex_lock(&clients->lock);
462
463
273k
    for (int n = 0; n < clients->num_clients; n++) {
464
273k
        if (clients->clients[n] == ctx) {
465
263k
            clients->clients_list_change_ts += 1;
466
263k
            MP_TARRAY_REMOVE_AT(clients->clients, clients->num_clients, n);
467
265k
            while (ctx->num_events) {
468
1.81k
                talloc_free(ctx->events[ctx->first_event].data);
469
1.81k
                ctx->first_event = (ctx->first_event + 1) % ctx->max_events;
470
1.81k
                ctx->num_events--;
471
1.81k
            }
472
263k
            mp_msg_log_buffer_destroy(ctx->messages);
473
263k
            mp_cond_destroy(&ctx->wakeup);
474
263k
            mp_mutex_destroy(&ctx->wakeup_lock);
475
263k
            mp_mutex_destroy(&ctx->lock);
476
263k
            if (ctx->wakeup_pipe[0] != -1) {
477
1.76k
                close(ctx->wakeup_pipe[0]);
478
1.76k
                close(ctx->wakeup_pipe[1]);
479
1.76k
            }
480
263k
            talloc_free(ctx);
481
263k
            ctx = NULL;
482
263k
            break;
483
263k
        }
484
273k
    }
485
263k
    mp_assert(!ctx);
486
487
263k
    if (mpctx->is_cli) {
488
0
        terminate = false;
489
263k
    } else {
490
        // If the last strong mpv_handle got destroyed, destroy the core.
491
263k
        bool has_strong_ref = false;
492
404k
        for (int n = 0; n < clients->num_clients; n++)
493
141k
            has_strong_ref |= !clients->clients[n]->is_weak;
494
263k
        if (!has_strong_ref)
495
138k
            terminate = true;
496
497
        // Reserve the right to destroy mpctx for us.
498
263k
        if (clients->have_terminator)
499
122k
            terminate = false;
500
263k
        clients->have_terminator |= terminate;
501
263k
    }
502
503
    // mp_shutdown_clients() sleeps to avoid wasting CPU.
504
    // mp_hook_test_completion() also relies on this a bit.
505
263k
    mp_wakeup_core(mpctx);
506
507
263k
    mp_mutex_unlock(&clients->lock);
508
509
    // Note that even if num_clients==0, having set have_terminator keeps mpctx
510
    // and the core thread alive.
511
263k
    if (terminate) {
512
        // Make sure the core stops playing files etc. Being able to lock the
513
        // dispatch queue requires that the core thread is still active.
514
138k
        mp_dispatch_lock(mpctx->dispatch);
515
138k
        mpctx->stop_play = PT_QUIT;
516
138k
        mp_dispatch_unlock(mpctx->dispatch);
517
518
        // Ask the core thread to stop.
519
138k
        mp_mutex_lock(&clients->lock);
520
138k
        clients->terminate_core_thread = true;
521
138k
        mp_mutex_unlock(&clients->lock);
522
138k
        mp_wakeup_core(mpctx);
523
524
        // Blocking wait for all clients and core thread to terminate.
525
138k
        mp_thread_join(mpctx->core_thread);
526
527
138k
        mp_destroy(mpctx);
528
138k
    }
529
263k
}
530
531
void mpv_destroy(mpv_handle *ctx)
532
126k
{
533
126k
    mp_destroy_client(ctx, false);
534
126k
}
535
536
void mpv_terminate_destroy(mpv_handle *ctx)
537
138k
{
538
138k
    mp_destroy_client(ctx, true);
539
138k
}
540
541
// Can be called on the core thread only. Idempotent.
542
// Also happens to take care of shutting down any async work.
543
void mp_shutdown_clients(struct MPContext *mpctx)
544
276k
{
545
276k
    struct mp_client_api *clients = mpctx->clients;
546
547
    // Forcefully abort async work after 2 seconds of waiting.
548
276k
    double abort_time = mp_time_sec() + 2;
549
550
276k
    mp_mutex_lock(&clients->lock);
551
552
    // Prevent that new clients can appear.
553
276k
    clients->shutting_down = true;
554
555
    // Wait until we can terminate.
556
457k
    while (clients->num_clients || mpctx->outstanding_async ||
557
277k
           !(mpctx->is_cli || clients->terminate_core_thread))
558
181k
    {
559
181k
        mp_mutex_unlock(&clients->lock);
560
561
181k
        double left = abort_time - mp_time_sec();
562
181k
        if (left >= 0) {
563
181k
            mp_set_timeout(mpctx, left);
564
181k
        } else {
565
            // Forcefully abort any ongoing async work. This is quite rude and
566
            // probably not what everyone wants, so it happens only after a
567
            // timeout.
568
0
            abort_async(mpctx, NULL, 0, 0);
569
0
        }
570
571
181k
        mp_client_broadcast_event(mpctx, MPV_EVENT_SHUTDOWN, NULL);
572
181k
        mp_wait_events(mpctx);
573
574
181k
        mp_mutex_lock(&clients->lock);
575
181k
    }
576
577
276k
    mp_mutex_unlock(&clients->lock);
578
276k
}
579
580
bool mp_is_shutting_down(struct MPContext *mpctx)
581
0
{
582
0
    struct mp_client_api *clients = mpctx->clients;
583
0
    mp_mutex_lock(&clients->lock);
584
0
    bool res = clients->shutting_down;
585
0
    mp_mutex_unlock(&clients->lock);
586
0
    return res;
587
0
}
588
589
static MP_THREAD_VOID core_thread(void *p)
590
138k
{
591
138k
    struct MPContext *mpctx = p;
592
593
138k
    mp_thread_set_name("core");
594
595
296k
    while (!mpctx->initialized && mpctx->stop_play != PT_QUIT)
596
158k
        mp_idle(mpctx);
597
598
138k
    if (mpctx->initialized)
599
122k
        mp_play_files(mpctx);
600
601
    // This actually waits until all clients are gone before actually
602
    // destroying mpctx. Actual destruction is done by whatever destroys
603
    // the last mpv_handle.
604
138k
    mp_shutdown_clients(mpctx);
605
606
138k
    MP_THREAD_RETURN();
607
138k
}
608
609
mpv_handle *mpv_create(void)
610
138k
{
611
138k
    struct MPContext *mpctx = mp_create();
612
138k
    if (!mpctx)
613
0
        return NULL;
614
615
138k
    m_config_set_profile(mpctx->mconfig, "libmpv", 0);
616
617
138k
    mpv_handle *ctx = mp_new_client(mpctx->clients, "main");
618
138k
    if (!ctx) {
619
0
        mp_destroy(mpctx);
620
0
        return NULL;
621
0
    }
622
623
138k
    if (mp_thread_create(&mpctx->core_thread, core_thread, mpctx) != 0) {
624
0
        ctx->clients->have_terminator = true; // avoid blocking
625
0
        mpv_terminate_destroy(ctx);
626
0
        mp_destroy(mpctx);
627
0
        return NULL;
628
0
    }
629
630
138k
    return ctx;
631
138k
}
632
633
mpv_handle *mpv_create_client(mpv_handle *ctx, const char *name)
634
0
{
635
0
    if (!ctx)
636
0
        return mpv_create();
637
0
    mpv_handle *new = mp_new_client(ctx->mpctx->clients, name);
638
0
    if (new)
639
0
        mpv_wait_event(new, 0); // set fuzzy_initialized
640
0
    return new;
641
0
}
642
643
mpv_handle *mpv_create_weak_client(mpv_handle *ctx, const char *name)
644
0
{
645
0
    mpv_handle *new = mpv_create_client(ctx, name);
646
0
    if (new)
647
0
        mp_client_set_weak(new);
648
0
    return new;
649
0
}
650
651
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
652
int mpv_initialize_opts(mpv_handle *ctx, char **options);
653
#else
654
static
655
#endif
656
int mpv_initialize_opts(mpv_handle *ctx, char **options)
657
126k
{
658
126k
    lock_core(ctx);
659
126k
    int res = mp_initialize(ctx->mpctx, options) ? MPV_ERROR_INVALID_PARAMETER : 0;
660
126k
    mp_wakeup_core(ctx->mpctx);
661
126k
    unlock_core(ctx);
662
126k
    return res;
663
126k
}
664
665
int mpv_initialize(mpv_handle *ctx)
666
113k
{
667
113k
    return mpv_initialize_opts(ctx, NULL);
668
113k
}
669
670
// set ev->data to a new copy of the original data
671
// (done only for message types that are broadcast)
672
static void dup_event_data(struct mpv_event *ev)
673
3.03M
{
674
3.03M
    switch (ev->event_id) {
675
0
    case MPV_EVENT_CLIENT_MESSAGE: {
676
0
        struct mpv_event_client_message *src = ev->data;
677
0
        struct mpv_event_client_message *msg =
678
0
            talloc_zero(NULL, struct mpv_event_client_message);
679
0
        for (int n = 0; n < src->num_args; n++) {
680
0
            MP_TARRAY_APPEND(msg, msg->args, msg->num_args,
681
0
                             talloc_strdup(msg, src->args[n]));
682
0
        }
683
0
        ev->data = msg;
684
0
        break;
685
0
    }
686
857k
    case MPV_EVENT_START_FILE:
687
857k
        ev->data = talloc_memdup(NULL, ev->data, sizeof(mpv_event_start_file));
688
857k
        break;
689
856k
    case MPV_EVENT_END_FILE:
690
856k
        ev->data = talloc_memdup(NULL, ev->data, sizeof(mpv_event_end_file));
691
856k
        break;
692
1.31M
    default:
693
        // Doesn't use events with memory allocation.
694
1.31M
        if (ev->data)
695
0
            abort();
696
3.03M
    }
697
3.03M
}
698
699
// Reserve an entry in the ring buffer. This can be used to guarantee that the
700
// reply can be made, even if the buffer becomes congested _after_ sending
701
// the request.
702
// Returns an error code if the buffer is full.
703
static int reserve_reply(struct mpv_handle *ctx)
704
0
{
705
0
    int res = MPV_ERROR_EVENT_QUEUE_FULL;
706
0
    mp_mutex_lock(&ctx->lock);
707
0
    if (ctx->reserved_events + ctx->num_events < ctx->max_events && !ctx->choked)
708
0
    {
709
0
        ctx->reserved_events++;
710
0
        res = 0;
711
0
    }
712
0
    mp_mutex_unlock(&ctx->lock);
713
0
    return res;
714
0
}
715
716
static int append_event(struct mpv_handle *ctx, struct mpv_event event, bool copy)
717
3.03M
{
718
3.03M
    if (ctx->num_events + ctx->reserved_events >= ctx->max_events)
719
0
        return -1;
720
3.03M
    if (copy)
721
3.03M
        dup_event_data(&event);
722
3.03M
    ctx->events[(ctx->first_event + ctx->num_events) % ctx->max_events] = event;
723
3.03M
    ctx->num_events++;
724
3.03M
    wakeup_client(ctx);
725
3.03M
    if (event.event_id == MPV_EVENT_SHUTDOWN)
726
123k
        ctx->event_mask &= ctx->event_mask & ~(1ULL << MPV_EVENT_SHUTDOWN);
727
3.03M
    return 0;
728
3.03M
}
729
730
static int send_event(struct mpv_handle *ctx, struct mpv_event *event, bool copy)
731
6.51M
{
732
6.51M
    mp_mutex_lock(&ctx->lock);
733
6.51M
    uint64_t mask = 1ULL << event->event_id;
734
6.51M
    if (ctx->property_event_masks & mask)
735
1.02M
        notify_property_events(ctx, event->event_id);
736
6.51M
    int r;
737
6.51M
    if (!(ctx->event_mask & mask)) {
738
3.48M
        r = 0;
739
3.48M
    } else if (ctx->choked) {
740
0
        r = -1;
741
3.03M
    } else {
742
3.03M
        r = append_event(ctx, *event, copy);
743
3.03M
        if (r < 0) {
744
0
            MP_ERR(ctx, "Too many events queued.\n");
745
0
            ctx->choked = true;
746
0
        }
747
3.03M
    }
748
6.51M
    mp_mutex_unlock(&ctx->lock);
749
6.51M
    return r;
750
6.51M
}
751
752
// Send a reply; the reply must have been previously reserved with
753
// reserve_reply (otherwise, use send_event()).
754
static void send_reply(struct mpv_handle *ctx, uint64_t userdata,
755
                       struct mpv_event *event)
756
0
{
757
0
    event->reply_userdata = userdata;
758
0
    mp_mutex_lock(&ctx->lock);
759
    // If this fails, reserve_reply() probably wasn't called.
760
0
    mp_assert(ctx->reserved_events > 0);
761
0
    ctx->reserved_events--;
762
0
    if (append_event(ctx, *event, false) < 0)
763
0
        MP_ASSERT_UNREACHABLE();
764
0
    mp_mutex_unlock(&ctx->lock);
765
0
}
766
767
void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data)
768
3.42M
{
769
3.42M
    struct mp_client_api *clients = mpctx->clients;
770
771
3.42M
    mp_mutex_lock(&clients->lock);
772
773
9.93M
    for (int n = 0; n < clients->num_clients; n++) {
774
6.51M
        struct mpv_event event_data = {
775
6.51M
            .event_id = event,
776
6.51M
            .data = data,
777
6.51M
        };
778
6.51M
        send_event(clients->clients[n], &event_data, true);
779
6.51M
    }
780
781
3.42M
    mp_mutex_unlock(&clients->lock);
782
3.42M
}
783
784
// Like mp_client_broadcast_event(), but can be called from any thread.
785
// Avoid using this.
786
void mp_client_broadcast_event_external(struct mp_client_api *api, int event,
787
                                        void *data)
788
66.1k
{
789
66.1k
    struct MPContext *mpctx = api->mpctx;
790
791
66.1k
    mp_client_broadcast_event(mpctx, event, data);
792
66.1k
    mp_wakeup_core(mpctx);
793
66.1k
}
794
795
// If client_name == NULL, then broadcast and free the event.
796
int mp_client_send_event(struct MPContext *mpctx, const char *client_name,
797
                         uint64_t reply_userdata, int event, void *data)
798
0
{
799
0
    if (!client_name) {
800
0
        mp_client_broadcast_event(mpctx, event, data);
801
0
        talloc_free(data);
802
0
        return 0;
803
0
    }
804
805
0
    struct mp_client_api *clients = mpctx->clients;
806
0
    int r = 0;
807
808
0
    struct mpv_event event_data = {
809
0
        .event_id = event,
810
0
        .data = data,
811
0
        .reply_userdata = reply_userdata,
812
0
    };
813
814
0
    mp_mutex_lock(&clients->lock);
815
816
0
    struct mpv_handle *ctx = find_client(clients, client_name);
817
0
    if (ctx) {
818
0
        r = send_event(ctx, &event_data, false);
819
0
    } else {
820
0
        r = -1;
821
0
    }
822
823
0
    mp_mutex_unlock(&clients->lock);
824
825
0
    if (r < 0)
826
0
        talloc_free(data);
827
828
0
    return r;
829
0
}
830
831
int mp_client_send_event_dup(struct MPContext *mpctx, const char *client_name,
832
                             int event, void *data)
833
0
{
834
0
    if (!client_name) {
835
0
        mp_client_broadcast_event(mpctx, event, data);
836
0
        return 0;
837
0
    }
838
839
0
    struct mpv_event event_data = {
840
0
        .event_id = event,
841
0
        .data = data,
842
0
    };
843
844
0
    dup_event_data(&event_data);
845
0
    return mp_client_send_event(mpctx, client_name, 0, event, event_data.data);
846
0
}
847
848
static const bool deprecated_events[] = {
849
    [MPV_EVENT_IDLE] = true,
850
    [MPV_EVENT_TICK] = true,
851
};
852
853
int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable)
854
263k
{
855
263k
    if (!mpv_event_name(event) || enable < 0 || enable > 1)
856
0
        return MPV_ERROR_INVALID_PARAMETER;
857
263k
    if (event == MPV_EVENT_SHUTDOWN && !enable)
858
0
        return MPV_ERROR_INVALID_PARAMETER;
859
263k
    mp_assert(event < (int)INTERNAL_EVENT_BASE); // excluded above; they have no name
860
263k
    mp_mutex_lock(&ctx->lock);
861
263k
    uint64_t bit = 1ULL << event;
862
263k
    ctx->event_mask = enable ? ctx->event_mask | bit : ctx->event_mask & ~bit;
863
263k
    if (enable && event < MP_ARRAY_SIZE(deprecated_events) &&
864
0
        deprecated_events[event])
865
0
    {
866
0
        MP_WARN(ctx, "The '%s' event is deprecated and will be removed.\n",
867
0
                mpv_event_name(event));
868
0
    }
869
263k
    mp_mutex_unlock(&ctx->lock);
870
263k
    return 0;
871
263k
}
872
873
// Set waiting_for_hook==true for all possibly pending properties.
874
static void set_wait_for_hook_flags(mpv_handle *ctx)
875
0
{
876
0
    for (int n = 0; n < ctx->num_properties; n++) {
877
0
        struct observe_property *prop = ctx->properties[n];
878
879
0
        if (prop->value_ret_ts != prop->change_ts)
880
0
            prop->waiting_for_hook = true;
881
0
    }
882
0
}
883
884
// Return whether any property still has waiting_for_hook set.
885
static bool check_for_for_hook_flags(mpv_handle *ctx)
886
0
{
887
0
    for (int n = 0; n < ctx->num_properties; n++) {
888
0
        if (ctx->properties[n]->waiting_for_hook)
889
0
            return true;
890
0
    }
891
0
    return false;
892
0
}
893
894
mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
895
3.26M
{
896
3.26M
    mpv_event *event = ctx->cur_event;
897
898
3.26M
    mp_mutex_lock(&ctx->lock);
899
900
3.26M
    if (!ctx->fuzzy_initialized)
901
124k
        mp_wakeup_core(ctx->clients->mpctx);
902
3.26M
    ctx->fuzzy_initialized = true;
903
904
3.26M
    if (timeout < 0)
905
3.07M
        timeout = 1e20;
906
907
3.26M
    int64_t deadline = mp_time_ns_add(mp_time_ns(), timeout);
908
909
3.26M
    *event = (mpv_event){0};
910
3.26M
    talloc_free_children(event);
911
912
5.47M
    while (1) {
913
5.47M
        if (ctx->queued_wakeup)
914
0
            deadline = 0;
915
        // Recover from overflow.
916
5.47M
        if (ctx->choked && !ctx->num_events) {
917
0
            ctx->choked = false;
918
0
            event->event_id = MPV_EVENT_QUEUE_OVERFLOW;
919
0
            break;
920
0
        }
921
5.47M
        struct mpv_event *ev =
922
5.47M
            ctx->num_events ? &ctx->events[ctx->first_event] : NULL;
923
5.47M
        if (ev && ev->event_id == MPV_EVENT_HOOK) {
924
            // Give old property notifications priority over hooks. This is a
925
            // guarantee given to clients to simplify their logic. New property
926
            // changes after this are treated normally, so
927
0
            if (!ctx->hook_pending) {
928
0
                ctx->hook_pending = true;
929
0
                set_wait_for_hook_flags(ctx);
930
0
            }
931
0
            if (check_for_for_hook_flags(ctx)) {
932
0
                ev = NULL; // delay
933
0
            } else {
934
0
                ctx->hook_pending = false;
935
0
            }
936
0
        }
937
5.47M
        if (ev) {
938
3.02M
            *event = *ev;
939
3.02M
            ctx->first_event = (ctx->first_event + 1) % ctx->max_events;
940
3.02M
            ctx->num_events--;
941
3.02M
            talloc_steal(event, event->data);
942
3.02M
            break;
943
3.02M
        }
944
        // If there's a changed property, generate change event (never queued).
945
2.44M
        if (gen_property_change_event(ctx))
946
233k
            break;
947
        // Pop item from message queue, and return as event.
948
2.21M
        if (gen_log_message_event(ctx))
949
0
            break;
950
2.21M
        int r = wait_wakeup(ctx, deadline);
951
2.21M
        if (r == ETIMEDOUT)
952
1.80k
            break;
953
2.21M
    }
954
3.26M
    ctx->queued_wakeup = false;
955
956
3.26M
    mp_mutex_unlock(&ctx->lock);
957
958
3.26M
    return event;
959
3.26M
}
960
961
void mpv_wakeup(mpv_handle *ctx)
962
0
{
963
0
    mp_mutex_lock(&ctx->lock);
964
0
    ctx->queued_wakeup = true;
965
0
    wakeup_client(ctx);
966
0
    mp_mutex_unlock(&ctx->lock);
967
0
}
968
969
// map client API types to internal types
970
static const struct m_option type_conv[] = {
971
    [MPV_FORMAT_STRING]     = { .type = CONF_TYPE_STRING },
972
    [MPV_FORMAT_FLAG]       = { .type = CONF_TYPE_FLAG },
973
    [MPV_FORMAT_INT64]      = { .type = CONF_TYPE_INT64 },
974
    [MPV_FORMAT_DOUBLE]     = { .type = CONF_TYPE_DOUBLE },
975
    [MPV_FORMAT_NODE]       = { .type = CONF_TYPE_NODE },
976
};
977
978
static const struct m_option *get_mp_type(mpv_format format)
979
2.46M
{
980
2.46M
    if ((unsigned)format >= MP_ARRAY_SIZE(type_conv))
981
0
        return NULL;
982
2.46M
    if (!type_conv[format].type)
983
0
        return NULL;
984
2.46M
    return &type_conv[format];
985
2.46M
}
986
987
// for read requests - MPV_FORMAT_OSD_STRING special handling
988
static const struct m_option *get_mp_type_get(mpv_format format)
989
1.10M
{
990
1.10M
    if (format == MPV_FORMAT_OSD_STRING)
991
0
        format = MPV_FORMAT_STRING; // it's string data, just other semantics
992
1.10M
    return get_mp_type(format);
993
1.10M
}
994
995
// move src->dst, and do implicit conversion if possible (conversions to or
996
// from strings are handled otherwise)
997
static bool conv_node_to_format(void *dst, mpv_format dst_fmt, mpv_node *src)
998
426k
{
999
426k
    if (dst_fmt == src->format) {
1000
426k
        const struct m_option *type = get_mp_type(dst_fmt);
1001
426k
        memcpy(dst, &src->u, type->type->size);
1002
426k
        return true;
1003
426k
    }
1004
0
    if (dst_fmt == MPV_FORMAT_DOUBLE && src->format == MPV_FORMAT_INT64) {
1005
0
        *(double *)dst = src->u.int64;
1006
0
        return true;
1007
0
    }
1008
0
    if (dst_fmt == MPV_FORMAT_INT64 && src->format == MPV_FORMAT_DOUBLE) {
1009
0
        if (src->u.double_ > (double)INT64_MIN &&
1010
0
            src->u.double_ < (double)INT64_MAX)
1011
0
        {
1012
0
            *(int64_t *)dst = src->u.double_;
1013
0
            return true;
1014
0
        }
1015
0
    }
1016
0
    return false;
1017
0
}
1018
1019
void mpv_free_node_contents(mpv_node *node)
1020
230k
{
1021
230k
    static const struct m_option type = { .type = CONF_TYPE_NODE };
1022
230k
    m_option_free(&type, node);
1023
230k
}
1024
1025
int mpv_set_option(mpv_handle *ctx, const char *name, mpv_format format,
1026
                   void *data)
1027
910k
{
1028
910k
    const struct m_option *type = get_mp_type(format);
1029
910k
    if (!type)
1030
0
        return MPV_ERROR_OPTION_FORMAT;
1031
910k
    struct mpv_node tmp;
1032
910k
    if (format != MPV_FORMAT_NODE) {
1033
870k
        tmp.format = format;
1034
870k
        memcpy(&tmp.u, data, type->type->size);
1035
870k
        data = &tmp;
1036
870k
    }
1037
910k
    lock_core(ctx);
1038
910k
    int err = m_config_set_option_node(ctx->mpctx->mconfig, bstr0(name), data, 0);
1039
910k
    unlock_core(ctx);
1040
910k
    switch (err) {
1041
0
    case M_OPT_MISSING_PARAM:
1042
4.50k
    case M_OPT_INVALID:
1043
4.50k
        return MPV_ERROR_OPTION_ERROR;
1044
523
    case M_OPT_OUT_OF_RANGE:
1045
523
        return MPV_ERROR_OPTION_FORMAT;
1046
991
    case M_OPT_UNKNOWN:
1047
991
        return MPV_ERROR_OPTION_NOT_FOUND;
1048
904k
    default:
1049
904k
        if (err >= 0)
1050
904k
            return 0;
1051
234
        return MPV_ERROR_OPTION_ERROR;
1052
910k
    }
1053
910k
}
1054
1055
int mpv_set_option_string(mpv_handle *ctx, const char *name, const char *data)
1056
858k
{
1057
858k
    return mpv_set_option(ctx, name, MPV_FORMAT_STRING, &data);
1058
858k
}
1059
1060
// Run a command in the playback thread.
1061
static void run_locked(mpv_handle *ctx, void (*fn)(void *fn_data), void *fn_data)
1062
137k
{
1063
137k
    mp_dispatch_lock(ctx->mpctx->dispatch);
1064
137k
    fn(fn_data);
1065
137k
    mp_dispatch_unlock(ctx->mpctx->dispatch);
1066
137k
}
1067
1068
// Run a command asynchronously. It's the responsibility of the caller to
1069
// actually send the reply. This helper merely saves a small part of the
1070
// required boilerplate to do so.
1071
//  fn: callback to execute the request
1072
//  fn_data: opaque caller-defined argument for fn. This will be automatically
1073
//           freed with talloc_free(fn_data).
1074
static int run_async(mpv_handle *ctx, void (*fn)(void *fn_data), void *fn_data)
1075
0
{
1076
0
    int err = reserve_reply(ctx);
1077
0
    if (err < 0) {
1078
0
        talloc_free(fn_data);
1079
0
        return err;
1080
0
    }
1081
0
    mp_dispatch_enqueue(ctx->mpctx->dispatch, fn, fn_data);
1082
0
    return 0;
1083
0
}
1084
1085
struct cmd_request {
1086
    struct MPContext *mpctx;
1087
    struct mp_cmd *cmd;
1088
    int status;
1089
    struct mpv_node *res;
1090
    struct mp_waiter completion;
1091
};
1092
1093
static void cmd_complete(struct mp_cmd_ctx *cmd)
1094
230k
{
1095
230k
    struct cmd_request *req = cmd->on_completion_priv;
1096
1097
230k
    req->status = cmd->success ? 0 : MPV_ERROR_COMMAND;
1098
230k
    if (req->res) {
1099
0
        *req->res = cmd->result;
1100
0
        cmd->result = (mpv_node){0};
1101
0
    }
1102
1103
    // Unblock the waiting thread (especially for async commands).
1104
230k
    mp_waiter_wakeup(&req->completion, 0);
1105
230k
}
1106
1107
static int run_client_command(mpv_handle *ctx, struct mp_cmd *cmd, mpv_node *res)
1108
247k
{
1109
247k
    if (!cmd)
1110
724
        return MPV_ERROR_INVALID_PARAMETER;
1111
246k
    if (!ctx->mpctx->initialized) {
1112
15.9k
        talloc_free(cmd);
1113
15.9k
        return MPV_ERROR_UNINITIALIZED;
1114
15.9k
    }
1115
1116
230k
    cmd->sender = ctx->name;
1117
1118
230k
    struct cmd_request req = {
1119
230k
        .mpctx = ctx->mpctx,
1120
230k
        .cmd = cmd,
1121
230k
        .res = res,
1122
230k
        .completion = MP_WAITER_INITIALIZER,
1123
230k
    };
1124
1125
230k
    bool async = cmd->flags & MP_ASYNC_CMD;
1126
1127
230k
    lock_core(ctx);
1128
230k
    if (async) {
1129
0
        run_command(ctx->mpctx, cmd, NULL, NULL, NULL);
1130
230k
    } else {
1131
230k
        struct mp_abort_entry *abort = NULL;
1132
230k
        if (cmd->def->can_abort) {
1133
0
            abort = talloc_zero(NULL, struct mp_abort_entry);
1134
0
            abort->client = ctx;
1135
0
        }
1136
230k
        run_command(ctx->mpctx, cmd, abort, cmd_complete, &req);
1137
230k
    }
1138
230k
    unlock_core(ctx);
1139
1140
230k
    if (!async)
1141
230k
        mp_waiter_wait(&req.completion);
1142
1143
230k
    return req.status;
1144
246k
}
1145
1146
int mpv_command(mpv_handle *ctx, const char **args)
1147
246k
{
1148
246k
    return run_client_command(ctx, mp_input_parse_cmd_strv(ctx->log, args), NULL);
1149
246k
}
1150
1151
int mpv_command_node(mpv_handle *ctx, mpv_node *args, mpv_node *result)
1152
0
{
1153
0
    struct mpv_node rn = {.format = MPV_FORMAT_NONE};
1154
0
    int r = run_client_command(ctx, mp_input_parse_cmd_node(ctx->log, args), result ? &rn : NULL);
1155
0
    if (result && r >= 0)
1156
0
        *result = rn;
1157
0
    return r;
1158
0
}
1159
1160
int mpv_command_ret(mpv_handle *ctx, const char **args, mpv_node *result)
1161
0
{
1162
0
    struct mpv_node rn = {.format = MPV_FORMAT_NONE};
1163
0
    int r = run_client_command(ctx, mp_input_parse_cmd_strv(ctx->log, args), result ? &rn : NULL);
1164
0
    if (result && r >= 0)
1165
0
        *result = rn;
1166
0
    return r;
1167
0
}
1168
1169
int mpv_command_string(mpv_handle *ctx, const char *args)
1170
724
{
1171
724
    return run_client_command(ctx,
1172
724
        mp_input_parse_cmd(ctx->mpctx->input, bstr0((char*)args), ctx->name), NULL);
1173
724
}
1174
1175
struct async_cmd_request {
1176
    struct MPContext *mpctx;
1177
    struct mp_cmd *cmd;
1178
    struct mpv_handle *reply_ctx;
1179
    uint64_t userdata;
1180
};
1181
1182
static void async_cmd_complete(struct mp_cmd_ctx *cmd)
1183
0
{
1184
0
    struct async_cmd_request *req = cmd->on_completion_priv;
1185
1186
0
    struct mpv_event_command *data = talloc_zero(NULL, struct mpv_event_command);
1187
0
    data->result = cmd->result;
1188
0
    cmd->result = (mpv_node){0};
1189
0
    talloc_steal(data, node_get_alloc(&data->result));
1190
1191
0
    struct mpv_event reply = {
1192
0
        .event_id = MPV_EVENT_COMMAND_REPLY,
1193
0
        .data = data,
1194
0
        .error = cmd->success ? 0 : MPV_ERROR_COMMAND,
1195
0
    };
1196
0
    send_reply(req->reply_ctx, req->userdata, &reply);
1197
1198
0
    talloc_free(req);
1199
0
}
1200
1201
static void async_cmd_fn(void *data)
1202
0
{
1203
0
    struct async_cmd_request *req = data;
1204
1205
0
    struct mp_cmd *cmd = req->cmd;
1206
0
    ta_set_parent(cmd, NULL);
1207
0
    req->cmd = NULL;
1208
1209
0
    struct mp_abort_entry *abort = NULL;
1210
0
    if (cmd->def->can_abort) {
1211
0
        abort = talloc_zero(NULL, struct mp_abort_entry);
1212
0
        abort->client = req->reply_ctx;
1213
0
        abort->client_work_type = MPV_EVENT_COMMAND_REPLY;
1214
0
        abort->client_work_id = req->userdata;
1215
0
    }
1216
1217
    // This will synchronously or asynchronously call cmd_complete (depending
1218
    // on the command).
1219
0
    run_command(req->mpctx, cmd, abort, async_cmd_complete, req);
1220
0
}
1221
1222
static int run_async_cmd(mpv_handle *ctx, uint64_t ud, struct mp_cmd *cmd)
1223
0
{
1224
0
    if (!cmd)
1225
0
        return MPV_ERROR_INVALID_PARAMETER;
1226
0
    if (!ctx->mpctx->initialized) {
1227
0
        talloc_free(cmd);
1228
0
        return MPV_ERROR_UNINITIALIZED;
1229
0
    }
1230
1231
0
    cmd->sender = ctx->name;
1232
1233
0
    struct async_cmd_request *req = talloc_ptrtype(NULL, req);
1234
0
    *req = (struct async_cmd_request){
1235
0
        .mpctx = ctx->mpctx,
1236
0
        .cmd = talloc_steal(req, cmd),
1237
0
        .reply_ctx = ctx,
1238
0
        .userdata = ud,
1239
0
    };
1240
0
    return run_async(ctx, async_cmd_fn, req);
1241
0
}
1242
1243
int mpv_command_async(mpv_handle *ctx, uint64_t ud, const char **args)
1244
0
{
1245
0
    return run_async_cmd(ctx, ud, mp_input_parse_cmd_strv(ctx->log, args));
1246
0
}
1247
1248
int mpv_command_node_async(mpv_handle *ctx, uint64_t ud, mpv_node *args)
1249
0
{
1250
0
    return run_async_cmd(ctx, ud, mp_input_parse_cmd_node(ctx->log, args));
1251
0
}
1252
1253
void mpv_abort_async_command(mpv_handle *ctx, uint64_t reply_userdata)
1254
0
{
1255
0
    abort_async(ctx->mpctx, ctx, MPV_EVENT_COMMAND_REPLY, reply_userdata);
1256
0
}
1257
1258
static int translate_property_error(int errc)
1259
749k
{
1260
749k
    switch (errc) {
1261
436k
    case M_PROPERTY_OK:                 return 0;
1262
159
    case M_PROPERTY_ERROR:              return MPV_ERROR_PROPERTY_ERROR;
1263
308k
    case M_PROPERTY_UNAVAILABLE:        return MPV_ERROR_PROPERTY_UNAVAILABLE;
1264
506
    case M_PROPERTY_NOT_IMPLEMENTED:    return MPV_ERROR_PROPERTY_ERROR;
1265
2.39k
    case M_PROPERTY_UNKNOWN:            return MPV_ERROR_PROPERTY_NOT_FOUND;
1266
1.34k
    case M_PROPERTY_INVALID_FORMAT:     return MPV_ERROR_PROPERTY_FORMAT;
1267
    // shouldn't happen
1268
0
    default:                            return MPV_ERROR_PROPERTY_ERROR;
1269
749k
    }
1270
749k
}
1271
1272
struct setproperty_request {
1273
    struct MPContext *mpctx;
1274
    const char *name;
1275
    int format;
1276
    void *data;
1277
    int status;
1278
    struct mpv_handle *reply_ctx;
1279
    uint64_t userdata;
1280
};
1281
1282
static void setproperty_fn(void *arg)
1283
14.7k
{
1284
14.7k
    struct setproperty_request *req = arg;
1285
14.7k
    const struct m_option *type = get_mp_type(req->format);
1286
1287
14.7k
    struct mpv_node *node;
1288
14.7k
    struct mpv_node tmp;
1289
14.7k
    if (req->format == MPV_FORMAT_NODE) {
1290
0
        node = req->data;
1291
14.7k
    } else {
1292
14.7k
        tmp.format = req->format;
1293
14.7k
        memcpy(&tmp.u, req->data, type->type->size);
1294
14.7k
        node = &tmp;
1295
14.7k
    }
1296
1297
14.7k
    int err = mp_property_do(req->name, M_PROPERTY_SET_NODE, node, req->mpctx);
1298
1299
14.7k
    req->status = translate_property_error(err);
1300
1301
14.7k
    if (req->reply_ctx) {
1302
0
        struct mpv_event reply = {
1303
0
            .event_id = MPV_EVENT_SET_PROPERTY_REPLY,
1304
0
            .error = req->status,
1305
0
        };
1306
0
        send_reply(req->reply_ctx, req->userdata, &reply);
1307
0
        talloc_free(req);
1308
0
    }
1309
14.7k
}
1310
1311
int mpv_set_property(mpv_handle *ctx, const char *name, mpv_format format,
1312
                     void *data)
1313
26.3k
{
1314
26.3k
    if (!ctx->mpctx->initialized) {
1315
11.5k
        int r = mpv_set_option(ctx, name, format, data);
1316
11.5k
        if (r == MPV_ERROR_OPTION_NOT_FOUND &&
1317
991
            mp_get_property_id(ctx->mpctx, name) >= 0)
1318
200
            return MPV_ERROR_PROPERTY_UNAVAILABLE;
1319
11.3k
        switch (r) {
1320
5.31k
        case MPV_ERROR_SUCCESS:          return MPV_ERROR_SUCCESS;
1321
523
        case MPV_ERROR_OPTION_FORMAT:    return MPV_ERROR_PROPERTY_FORMAT;
1322
791
        case MPV_ERROR_OPTION_NOT_FOUND: return MPV_ERROR_PROPERTY_NOT_FOUND;
1323
4.74k
        default:                         return MPV_ERROR_PROPERTY_ERROR;
1324
11.3k
        }
1325
11.3k
    }
1326
14.7k
    if (!get_mp_type(format))
1327
0
        return MPV_ERROR_PROPERTY_FORMAT;
1328
1329
14.7k
    struct setproperty_request req = {
1330
14.7k
        .mpctx = ctx->mpctx,
1331
14.7k
        .name = name,
1332
14.7k
        .format = format,
1333
14.7k
        .data = data,
1334
14.7k
    };
1335
14.7k
    run_locked(ctx, setproperty_fn, &req);
1336
14.7k
    return req.status;
1337
14.7k
}
1338
1339
int mpv_del_property(mpv_handle *ctx, const char *name)
1340
0
{
1341
0
    const char* args[] = { "del", name, NULL };
1342
0
    return mpv_command(ctx, args);
1343
0
}
1344
1345
int mpv_set_property_string(mpv_handle *ctx, const char *name, const char *data)
1346
18.3k
{
1347
18.3k
    return mpv_set_property(ctx, name, MPV_FORMAT_STRING, &data);
1348
18.3k
}
1349
1350
static void free_prop_set_req(void *ptr)
1351
0
{
1352
0
    struct setproperty_request *req = ptr;
1353
0
    const struct m_option *type = get_mp_type(req->format);
1354
0
    m_option_free(type, req->data);
1355
0
}
1356
1357
int mpv_set_property_async(mpv_handle *ctx, uint64_t ud, const char *name,
1358
                           mpv_format format, void *data)
1359
0
{
1360
0
    const struct m_option *type = get_mp_type(format);
1361
0
    if (!ctx->mpctx->initialized)
1362
0
        return MPV_ERROR_UNINITIALIZED;
1363
0
    if (!type)
1364
0
        return MPV_ERROR_PROPERTY_FORMAT;
1365
1366
0
    struct setproperty_request *req = talloc_ptrtype(NULL, req);
1367
0
    *req = (struct setproperty_request){
1368
0
        .mpctx = ctx->mpctx,
1369
0
        .name = talloc_strdup(req, name),
1370
0
        .format = format,
1371
0
        .data = talloc_zero_size(req, type->type->size),
1372
0
        .reply_ctx = ctx,
1373
0
        .userdata = ud,
1374
0
    };
1375
1376
0
    m_option_copy(type, req->data, data);
1377
0
    talloc_set_destructor(req, free_prop_set_req);
1378
1379
0
    return run_async(ctx, setproperty_fn, req);
1380
0
}
1381
1382
struct getproperty_request {
1383
    struct MPContext *mpctx;
1384
    const char *name;
1385
    mpv_format format;
1386
    void *data;
1387
    int status;
1388
    struct mpv_handle *reply_ctx;
1389
    uint64_t userdata;
1390
};
1391
1392
static void free_prop_data(void *ptr)
1393
0
{
1394
0
    struct mpv_event_property *prop = ptr;
1395
0
    const struct m_option *type = get_mp_type_get(prop->format);
1396
0
    m_option_free(type, prop->data);
1397
0
}
1398
1399
static void getproperty_fn(void *arg)
1400
735k
{
1401
735k
    struct getproperty_request *req = arg;
1402
735k
    const struct m_option *type = get_mp_type_get(req->format);
1403
1404
735k
    union m_option_value xdata = m_option_value_default;
1405
735k
    void *data = req->data ? req->data : &xdata;
1406
1407
735k
    int err = -1;
1408
735k
    switch (req->format) {
1409
0
    case MPV_FORMAT_OSD_STRING:
1410
0
        err = mp_property_do(req->name, M_PROPERTY_PRINT, data, req->mpctx);
1411
0
        break;
1412
0
    case MPV_FORMAT_STRING: {
1413
0
        char *s = NULL;
1414
0
        err = mp_property_do(req->name, M_PROPERTY_GET_STRING, &s, req->mpctx);
1415
0
        if (err == M_PROPERTY_OK)
1416
0
            *(char **)data = s;
1417
0
        break;
1418
0
    }
1419
308k
    case MPV_FORMAT_NODE:
1420
735k
    case MPV_FORMAT_FLAG:
1421
735k
    case MPV_FORMAT_INT64:
1422
735k
    case MPV_FORMAT_DOUBLE: {
1423
735k
        struct mpv_node node = {{0}};
1424
735k
        err = mp_property_do(req->name, M_PROPERTY_GET_NODE, &node, req->mpctx);
1425
735k
        if (err == M_PROPERTY_NOT_IMPLEMENTED) {
1426
            // Go through explicit string conversion. Same reasoning as on the
1427
            // GET code path.
1428
0
            char *s = NULL;
1429
0
            err = mp_property_do(req->name, M_PROPERTY_GET_STRING, &s,
1430
0
                                 req->mpctx);
1431
0
            if (err != M_PROPERTY_OK)
1432
0
                break;
1433
0
            node.format = MPV_FORMAT_STRING;
1434
0
            node.u.string = s;
1435
735k
        } else if (err <= 0)
1436
308k
            break;
1437
426k
        if (req->format == MPV_FORMAT_NODE) {
1438
0
            *(struct mpv_node *)data = node;
1439
426k
        } else {
1440
426k
            if (!conv_node_to_format(data, req->format, &node)) {
1441
0
                err = M_PROPERTY_INVALID_FORMAT;
1442
0
                mpv_free_node_contents(&node);
1443
0
            }
1444
426k
        }
1445
426k
        break;
1446
735k
    }
1447
0
    default:
1448
0
        abort();
1449
735k
    }
1450
1451
735k
    req->status = translate_property_error(err);
1452
1453
735k
    if (req->reply_ctx) {
1454
0
        struct mpv_event_property *prop = talloc_ptrtype(NULL, prop);
1455
0
        *prop = (struct mpv_event_property){
1456
0
            .name = talloc_steal(prop, (char *)req->name),
1457
0
            .format = req->format,
1458
0
            .data = talloc_size(prop, type->type->size),
1459
0
        };
1460
        // move data
1461
0
        memcpy(prop->data, &xdata, type->type->size);
1462
0
        talloc_set_destructor(prop, free_prop_data);
1463
0
        struct mpv_event reply = {
1464
0
            .event_id = MPV_EVENT_GET_PROPERTY_REPLY,
1465
0
            .data = prop,
1466
0
            .error = req->status,
1467
0
        };
1468
0
        send_reply(req->reply_ctx, req->userdata, &reply);
1469
0
        talloc_free(req);
1470
0
    }
1471
735k
}
1472
1473
int mpv_get_property(mpv_handle *ctx, const char *name, mpv_format format,
1474
                     void *data)
1475
122k
{
1476
122k
    if (!ctx->mpctx->initialized)
1477
0
        return MPV_ERROR_UNINITIALIZED;
1478
122k
    if (!data)
1479
0
        return MPV_ERROR_INVALID_PARAMETER;
1480
122k
    if (!get_mp_type_get(format))
1481
0
        return MPV_ERROR_PROPERTY_FORMAT;
1482
1483
122k
    struct getproperty_request req = {
1484
122k
        .mpctx = ctx->mpctx,
1485
122k
        .name = name,
1486
122k
        .format = format,
1487
122k
        .data = data,
1488
122k
    };
1489
122k
    run_locked(ctx, getproperty_fn, &req);
1490
122k
    return req.status;
1491
122k
}
1492
1493
char *mpv_get_property_string(mpv_handle *ctx, const char *name)
1494
0
{
1495
0
    char *str = NULL;
1496
0
    mpv_get_property(ctx, name, MPV_FORMAT_STRING, &str);
1497
0
    return str;
1498
0
}
1499
1500
char *mpv_get_property_osd_string(mpv_handle *ctx, const char *name)
1501
0
{
1502
0
    char *str = NULL;
1503
0
    mpv_get_property(ctx, name, MPV_FORMAT_OSD_STRING, &str);
1504
0
    return str;
1505
0
}
1506
1507
int mpv_get_property_async(mpv_handle *ctx, uint64_t ud, const char *name,
1508
                           mpv_format format)
1509
0
{
1510
0
    if (!ctx->mpctx->initialized)
1511
0
        return MPV_ERROR_UNINITIALIZED;
1512
0
    if (!get_mp_type_get(format))
1513
0
        return MPV_ERROR_PROPERTY_FORMAT;
1514
1515
0
    struct getproperty_request *req = talloc_ptrtype(NULL, req);
1516
0
    *req = (struct getproperty_request){
1517
0
        .mpctx = ctx->mpctx,
1518
0
        .name = talloc_strdup(req, name),
1519
0
        .format = format,
1520
0
        .reply_ctx = ctx,
1521
0
        .userdata = ud,
1522
0
    };
1523
0
    return run_async(ctx, getproperty_fn, req);
1524
0
}
1525
1526
static void property_free(void *p)
1527
244k
{
1528
244k
    struct observe_property *prop = p;
1529
1530
244k
    mp_assert(prop->refcount == 0);
1531
1532
244k
    if (prop->type) {
1533
244k
        m_option_free(prop->type, &prop->value);
1534
244k
        m_option_free(prop->type, &prop->value_ret);
1535
244k
    }
1536
244k
}
1537
1538
int mpv_observe_property(mpv_handle *ctx, uint64_t userdata,
1539
                         const char *name, mpv_format format)
1540
244k
{
1541
244k
    const struct m_option *type = get_mp_type_get(format);
1542
244k
    if (format != MPV_FORMAT_NONE && !type)
1543
0
        return MPV_ERROR_PROPERTY_FORMAT;
1544
    // Explicitly disallow this, because it would require a special code path.
1545
244k
    if (format == MPV_FORMAT_OSD_STRING)
1546
0
        return MPV_ERROR_PROPERTY_FORMAT;
1547
1548
244k
    mp_mutex_lock(&ctx->lock);
1549
244k
    mp_assert(!ctx->destroying);
1550
244k
    struct observe_property *prop = talloc_ptrtype(ctx, prop);
1551
244k
    talloc_set_destructor(prop, property_free);
1552
244k
    *prop = (struct observe_property){
1553
244k
        .owner = ctx,
1554
244k
        .name = talloc_strdup(prop, name),
1555
244k
        .id = mp_get_property_id(ctx->mpctx, name),
1556
244k
        .event_mask = mp_get_property_event_mask(name),
1557
244k
        .reply_id = userdata,
1558
244k
        .format = format,
1559
244k
        .type = type,
1560
244k
        .change_ts = 1, // force initial event
1561
244k
        .refcount = 1,
1562
244k
        .value = m_option_value_default,
1563
244k
        .value_ret = m_option_value_default,
1564
244k
    };
1565
244k
    ctx->properties_change_ts += 1;
1566
244k
    MP_TARRAY_APPEND(ctx, ctx->properties, ctx->num_properties, prop);
1567
244k
    ctx->property_event_masks |= prop->event_mask;
1568
244k
    ctx->new_property_events = true;
1569
244k
    ctx->cur_property_index = 0;
1570
244k
    ctx->has_pending_properties = true;
1571
244k
    mp_mutex_unlock(&ctx->lock);
1572
244k
    mp_wakeup_core(ctx->mpctx);
1573
244k
    return 0;
1574
244k
}
1575
1576
int mpv_unobserve_property(mpv_handle *ctx, uint64_t userdata)
1577
0
{
1578
0
    mp_mutex_lock(&ctx->lock);
1579
0
    int count = 0;
1580
0
    for (int n = ctx->num_properties - 1; n >= 0; n--) {
1581
0
        struct observe_property *prop = ctx->properties[n];
1582
        // Perform actual removal of the property lazily to avoid creating
1583
        // dangling pointers and such.
1584
0
        if (prop->reply_id == userdata) {
1585
0
            prop_unref(prop);
1586
0
            ctx->properties_change_ts += 1;
1587
0
            MP_TARRAY_REMOVE_AT(ctx->properties, ctx->num_properties, n);
1588
0
            ctx->cur_property_index = 0;
1589
0
            count++;
1590
0
        }
1591
0
    }
1592
0
    mp_mutex_unlock(&ctx->lock);
1593
0
    return count;
1594
0
}
1595
1596
static bool property_shared_prefix(const char *a0, const char *b0)
1597
0
{
1598
0
    bstr a = bstr0(a0);
1599
0
    bstr b = bstr0(b0);
1600
1601
    // Treat options and properties as equivalent.
1602
0
    bstr_eatstart0(&a, "options/");
1603
0
    bstr_eatstart0(&b, "options/");
1604
1605
    // Compare the potentially-common portion
1606
0
    if (memcmp(a.start, b.start, MPMIN(a.len, b.len)))
1607
0
        return false;
1608
1609
    // If lengths were equal, we're done
1610
0
    if (a.len == b.len)
1611
0
        return true;
1612
1613
    // Check for a slash in the first non-common byte of the longer string
1614
0
    if (a.len > b.len)
1615
0
        return a.start[b.len] == '/';
1616
0
    else
1617
0
        return b.start[a.len] == '/';
1618
0
}
1619
1620
// Broadcast that a property has changed.
1621
void mp_client_property_change(struct MPContext *mpctx, const char *name)
1622
307k
{
1623
307k
    struct mp_client_api *clients = mpctx->clients;
1624
307k
    int id = mp_get_property_id(mpctx, name);
1625
307k
    bool any_pending = false;
1626
1627
307k
    mp_mutex_lock(&clients->lock);
1628
1629
923k
    for (int n = 0; n < clients->num_clients; n++) {
1630
615k
        struct mpv_handle *client = clients->clients[n];
1631
615k
        mp_mutex_lock(&client->lock);
1632
1.23M
        for (int i = 0; i < client->num_properties; i++) {
1633
615k
            if (client->properties[i]->id == id &&
1634
0
                property_shared_prefix(name, client->properties[i]->name)) {
1635
0
                client->properties[i]->change_ts += 1;
1636
0
                client->has_pending_properties = true;
1637
0
                any_pending = true;
1638
0
            }
1639
615k
        }
1640
615k
        mp_mutex_unlock(&client->lock);
1641
615k
    }
1642
1643
307k
    mp_mutex_unlock(&clients->lock);
1644
1645
    // If we're inside mp_dispatch_queue_process(), this will cause the playloop
1646
    // to be re-run (to get mp_client_send_property_changes() called). If we're
1647
    // inside the normal playloop, this does nothing, but the latter function
1648
    // will be called at the end of the playloop anyway.
1649
307k
    if (any_pending)
1650
0
        mp_dispatch_adjust_timeout(mpctx->dispatch, 0);
1651
307k
}
1652
1653
// Mark properties as changed in reaction to specific events.
1654
// Called with ctx->lock held.
1655
static void notify_property_events(struct mpv_handle *ctx, int event)
1656
1.02M
{
1657
1.02M
    uint64_t mask = 1ULL << event;
1658
3.07M
    for (int i = 0; i < ctx->num_properties; i++) {
1659
2.05M
        if (ctx->properties[i]->event_mask & mask) {
1660
2.05M
            ctx->properties[i]->change_ts += 1;
1661
2.05M
            ctx->has_pending_properties = true;
1662
2.05M
        }
1663
2.05M
    }
1664
1665
    // Same as in mp_client_property_change().
1666
1.02M
    if (ctx->has_pending_properties)
1667
1.02M
        mp_dispatch_adjust_timeout(ctx->mpctx->dispatch, 0);
1668
1.02M
}
1669
1670
// Call with ctx->lock held (only). May temporarily drop the lock.
1671
static void send_client_property_changes(struct mpv_handle *ctx)
1672
308k
{
1673
308k
    uint64_t cur_ts = ctx->properties_change_ts;
1674
1675
308k
    ctx->has_pending_properties = false;
1676
1677
915k
    for (int n = 0; n < ctx->num_properties; n++) {
1678
612k
        struct observe_property *prop = ctx->properties[n];
1679
1680
612k
        if (prop->value_ts == prop->change_ts)
1681
26
            continue;
1682
1683
612k
        bool changed = false;
1684
612k
        if (prop->format) {
1685
612k
            const struct m_option *type = prop->type;
1686
612k
            union m_option_value val = m_option_value_default;
1687
612k
            struct getproperty_request req = {
1688
612k
                .mpctx = ctx->mpctx,
1689
612k
                .name = prop->name,
1690
612k
                .format = prop->format,
1691
612k
                .data = &val,
1692
612k
            };
1693
1694
            // Temporarily unlock and read the property. The very important
1695
            // thing is that property getters can do whatever they want, _and_
1696
            // that they may wait on the client API user thread (if vo_libmpv
1697
            // or similar things are involved).
1698
612k
            prop->refcount += 1; // keep prop alive (esp. prop->name)
1699
612k
            ctx->async_counter += 1; // keep ctx alive
1700
612k
            mp_mutex_unlock(&ctx->lock);
1701
612k
            getproperty_fn(&req);
1702
612k
            mp_mutex_lock(&ctx->lock);
1703
612k
            ctx->async_counter -= 1;
1704
612k
            prop_unref(prop);
1705
1706
            // Set if observed properties was changed or something similar
1707
            // => start over, retry next time.
1708
612k
            if (cur_ts != ctx->properties_change_ts || ctx->destroying) {
1709
6.09k
                m_option_free(type, &val);
1710
6.09k
                mp_wakeup_core(ctx->mpctx);
1711
6.09k
                ctx->has_pending_properties = true;
1712
6.09k
                break;
1713
6.09k
            }
1714
606k
            mp_assert(prop->refcount > 0);
1715
1716
606k
            bool val_valid = req.status >= 0;
1717
606k
            changed = prop->value_valid != val_valid;
1718
606k
            if (prop->value_valid && val_valid)
1719
185k
                changed = !equal_mpv_value(&prop->value, &val, prop->format);
1720
606k
            if (prop->value_ts == 0)
1721
235k
                changed = true; // initial event
1722
1723
606k
            prop->value_valid = val_valid;
1724
606k
            if (changed && val_valid) {
1725
                // move val to prop->value
1726
117k
                m_option_free(type, &prop->value);
1727
117k
                memcpy(&prop->value, &val, type->type->size);
1728
117k
                memset(&val, 0, type->type->size);
1729
117k
            }
1730
1731
606k
            m_option_free(prop->type, &val);
1732
606k
        } else {
1733
0
            changed = true;
1734
0
        }
1735
1736
606k
        if (prop->waiting_for_hook)
1737
0
            ctx->new_property_events = true; // make sure to wakeup
1738
1739
        // Avoid retriggering the change event if the property didn't change,
1740
        // and the previous value was actually returned to the client.
1741
606k
        if (!changed && prop->value_ret_ts == prop->value_ts) {
1742
370k
            prop->value_ret_ts = prop->change_ts; // no change => no event
1743
370k
            prop->waiting_for_hook = false;
1744
370k
        } else {
1745
236k
            ctx->new_property_events = true;
1746
236k
        }
1747
1748
606k
        prop->value_ts = prop->change_ts;
1749
606k
    }
1750
1751
308k
    if (ctx->destroying || ctx->new_property_events)
1752
123k
        wakeup_client(ctx);
1753
308k
}
1754
1755
void mp_client_send_property_changes(struct MPContext *mpctx)
1756
1.86M
{
1757
1.86M
    struct mp_client_api *clients = mpctx->clients;
1758
1759
1.86M
    mp_mutex_lock(&clients->lock);
1760
1.86M
    uint64_t cur_ts = clients->clients_list_change_ts;
1761
1762
5.25M
    for (int n = 0; n < clients->num_clients; n++) {
1763
3.38M
        struct mpv_handle *ctx = clients->clients[n];
1764
1765
3.38M
        mp_mutex_lock(&ctx->lock);
1766
3.38M
        if (!ctx->has_pending_properties || ctx->destroying) {
1767
3.07M
            mp_mutex_unlock(&ctx->lock);
1768
3.07M
            continue;
1769
3.07M
        }
1770
        // Keep ctx->lock locked (unlock order does not matter).
1771
308k
        mp_mutex_unlock(&clients->lock);
1772
308k
        send_client_property_changes(ctx);
1773
308k
        mp_mutex_unlock(&ctx->lock);
1774
308k
        mp_mutex_lock(&clients->lock);
1775
308k
        if (cur_ts != clients->clients_list_change_ts) {
1776
            // List changed; need to start over. Do it in the next iteration.
1777
116
            mp_wakeup_core(mpctx);
1778
116
            break;
1779
116
        }
1780
308k
    }
1781
1782
1.86M
    mp_mutex_unlock(&clients->lock);
1783
1.86M
}
1784
1785
// Set ctx->cur_event to a generated property change event, if there is any
1786
// outstanding property.
1787
static bool gen_property_change_event(struct mpv_handle *ctx)
1788
2.44M
{
1789
2.44M
    if (!ctx->mpctx->initialized)
1790
0
        return false;
1791
1792
2.89M
    while (1) {
1793
2.89M
        if (ctx->cur_property_index >= ctx->num_properties) {
1794
2.43M
            ctx->new_property_events &= ctx->num_properties > 0;
1795
2.43M
            if (!ctx->new_property_events)
1796
2.21M
                break;
1797
218k
            ctx->new_property_events = false;
1798
218k
            ctx->cur_property_index = 0;
1799
218k
        }
1800
1801
681k
        struct observe_property *prop = ctx->properties[ctx->cur_property_index++];
1802
1803
681k
        if (prop->value_ts == prop->change_ts &&    // not a stale value?
1804
276k
            prop->value_ret_ts != prop->value_ts)   // other value than last time?
1805
233k
        {
1806
233k
            prop->value_ret_ts = prop->value_ts;
1807
233k
            prop->waiting_for_hook = false;
1808
233k
            prop_unref(ctx->cur_property);
1809
233k
            ctx->cur_property = prop;
1810
233k
            prop->refcount += 1;
1811
1812
233k
            if (prop->value_valid)
1813
116k
                m_option_copy(prop->type, &prop->value_ret, &prop->value);
1814
1815
233k
            ctx->cur_property_event = (struct mpv_event_property){
1816
233k
                .name = prop->name,
1817
233k
                .format = prop->value_valid ? prop->format : 0,
1818
233k
                .data = prop->value_valid ? &prop->value_ret : NULL,
1819
233k
            };
1820
233k
            *ctx->cur_event = (struct mpv_event){
1821
233k
                .event_id = MPV_EVENT_PROPERTY_CHANGE,
1822
233k
                .reply_userdata = prop->reply_id,
1823
233k
                .data = &ctx->cur_property_event,
1824
233k
            };
1825
233k
            return true;
1826
233k
        }
1827
681k
    }
1828
1829
2.21M
    return false;
1830
2.44M
}
1831
1832
int mpv_hook_add(mpv_handle *ctx, uint64_t reply_userdata,
1833
                 const char *name, int priority)
1834
0
{
1835
0
    lock_core(ctx);
1836
0
    mp_hook_add(ctx->mpctx, ctx->name, ctx->id, name, reply_userdata, priority);
1837
0
    unlock_core(ctx);
1838
0
    return 0;
1839
0
}
1840
1841
int mpv_hook_continue(mpv_handle *ctx, uint64_t id)
1842
0
{
1843
0
    lock_core(ctx);
1844
0
    int r = mp_hook_continue(ctx->mpctx, ctx->id, id);
1845
0
    unlock_core(ctx);
1846
0
    return r;
1847
0
}
1848
1849
int mpv_load_config_file(mpv_handle *ctx, const char *filename)
1850
0
{
1851
0
    lock_core(ctx);
1852
0
    int r = m_config_parse_config_file(ctx->mpctx->mconfig, ctx->mpctx->global, filename, NULL, 0);
1853
0
    unlock_core(ctx);
1854
0
    if (r == 0)
1855
0
        return MPV_ERROR_INVALID_PARAMETER;
1856
0
    if (r < 0)
1857
0
        return MPV_ERROR_OPTION_ERROR;
1858
0
    return 0;
1859
0
}
1860
1861
static void msg_wakeup(void *p)
1862
0
{
1863
0
    mpv_handle *ctx = p;
1864
0
    wakeup_client(ctx);
1865
0
}
1866
1867
// Undocumented: if min_level starts with "silent:", then log messages are not
1868
// returned to the API user, but are stored until logging is enabled normally
1869
// again by calling this without "silent:". (Using a different level will
1870
// flush it, though.)
1871
int mpv_request_log_messages(mpv_handle *ctx, const char *min_level)
1872
0
{
1873
0
    bstr blevel = bstr0(min_level);
1874
0
    bool silent = bstr_eatstart0(&blevel, "silent:");
1875
1876
0
    int level = -1;
1877
0
    for (int n = 0; n < MSGL_MAX + 1; n++) {
1878
0
        if (mp_log_levels[n] && bstr_equals0(blevel, mp_log_levels[n])) {
1879
0
            level = n;
1880
0
            break;
1881
0
        }
1882
0
    }
1883
0
    if (bstr_equals0(blevel, "terminal-default"))
1884
0
        level = MP_LOG_BUFFER_MSGL_TERM;
1885
1886
0
    if (level < 0 && strcmp(min_level, "no") != 0)
1887
0
        return MPV_ERROR_INVALID_PARAMETER;
1888
1889
0
    mp_mutex_lock(&ctx->lock);
1890
0
    if (level < 0 || level != ctx->messages_level) {
1891
0
        mp_msg_log_buffer_destroy(ctx->messages);
1892
0
        ctx->messages = NULL;
1893
0
    }
1894
0
    if (level >= 0) {
1895
0
        if (!ctx->messages) {
1896
0
            int size = level >= MSGL_V ? 10000 : 1000;
1897
0
            ctx->messages = mp_msg_log_buffer_new(ctx->mpctx->global, size,
1898
0
                                                  level, msg_wakeup, ctx);
1899
0
            ctx->messages_level = level;
1900
0
        }
1901
0
        mp_msg_log_buffer_set_silent(ctx->messages, silent);
1902
0
    }
1903
0
    wakeup_client(ctx);
1904
0
    mp_mutex_unlock(&ctx->lock);
1905
0
    return 0;
1906
0
}
1907
1908
// Set ctx->cur_event to a generated log message event, if any available.
1909
static bool gen_log_message_event(struct mpv_handle *ctx)
1910
2.21M
{
1911
2.21M
    if (ctx->messages) {
1912
0
        struct mp_log_buffer_entry *msg =
1913
0
            mp_msg_log_buffer_read(ctx->messages);
1914
0
        if (msg) {
1915
0
            struct mpv_event_log_message *cmsg =
1916
0
                talloc_ptrtype(ctx->cur_event, cmsg);
1917
0
            talloc_steal(cmsg, msg);
1918
0
            *cmsg = (struct mpv_event_log_message){
1919
0
                .prefix = msg->prefix,
1920
0
                .level = mp_log_levels[msg->level],
1921
0
                .log_level = mp_mpv_log_levels[msg->level],
1922
0
                .text = msg->text,
1923
0
            };
1924
0
            *ctx->cur_event = (struct mpv_event){
1925
0
                .event_id = MPV_EVENT_LOG_MESSAGE,
1926
0
                .data = cmsg,
1927
0
            };
1928
0
            return true;
1929
0
        }
1930
0
    }
1931
2.21M
    return false;
1932
2.21M
}
1933
1934
int mpv_get_wakeup_pipe(mpv_handle *ctx)
1935
1.76k
{
1936
1.76k
    mp_mutex_lock(&ctx->wakeup_lock);
1937
1.76k
    if (ctx->wakeup_pipe[0] == -1) {
1938
1.76k
        if (mp_make_wakeup_pipe(ctx->wakeup_pipe) >= 0)
1939
1.76k
            (void)write(ctx->wakeup_pipe[1], &(char){0}, 1);
1940
1.76k
    }
1941
1.76k
    int fd = ctx->wakeup_pipe[0];
1942
1.76k
    mp_mutex_unlock(&ctx->wakeup_lock);
1943
1.76k
    return fd;
1944
1.76k
}
1945
1946
unsigned long mpv_client_api_version(void)
1947
0
{
1948
0
    return MPV_CLIENT_API_VERSION;
1949
0
}
1950
1951
int mpv_event_to_node(mpv_node *dst, mpv_event *event)
1952
0
{
1953
0
    *dst = (mpv_node){0};
1954
1955
0
    node_init(dst, MPV_FORMAT_NODE_MAP, NULL);
1956
0
    node_map_add_string(dst, "event", mpv_event_name(event->event_id));
1957
1958
0
    if (event->error < 0)
1959
0
        node_map_add_string(dst, "error", mpv_error_string(event->error));
1960
1961
0
    if (event->reply_userdata)
1962
0
        node_map_add_int64(dst, "id", event->reply_userdata);
1963
1964
0
    switch (event->event_id) {
1965
1966
0
    case MPV_EVENT_START_FILE: {
1967
0
        mpv_event_start_file *esf = event->data;
1968
1969
0
        node_map_add_int64(dst, "playlist_entry_id", esf->playlist_entry_id);
1970
0
        break;
1971
0
    }
1972
1973
0
    case MPV_EVENT_END_FILE: {
1974
0
        mpv_event_end_file *eef = event->data;
1975
1976
0
        const char *reason;
1977
0
        switch (eef->reason) {
1978
0
        case MPV_END_FILE_REASON_EOF: reason = "eof"; break;
1979
0
        case MPV_END_FILE_REASON_STOP: reason = "stop"; break;
1980
0
        case MPV_END_FILE_REASON_QUIT: reason = "quit"; break;
1981
0
        case MPV_END_FILE_REASON_ERROR: reason = "error"; break;
1982
0
        case MPV_END_FILE_REASON_REDIRECT: reason = "redirect"; break;
1983
0
        default:
1984
0
            reason = "unknown";
1985
0
        }
1986
0
        node_map_add_string(dst, "reason", reason);
1987
1988
0
        node_map_add_int64(dst, "playlist_entry_id", eef->playlist_entry_id);
1989
1990
0
        if (eef->playlist_insert_id) {
1991
0
            node_map_add_int64(dst, "playlist_insert_id", eef->playlist_insert_id);
1992
0
            node_map_add_int64(dst, "playlist_insert_num_entries",
1993
0
                               eef->playlist_insert_num_entries);
1994
0
        }
1995
1996
0
        if (eef->reason == MPV_END_FILE_REASON_ERROR)
1997
0
            node_map_add_string(dst, "file_error", mpv_error_string(eef->error));
1998
0
        break;
1999
0
    }
2000
2001
0
    case MPV_EVENT_LOG_MESSAGE: {
2002
0
        mpv_event_log_message *msg = event->data;
2003
2004
0
        node_map_add_string(dst, "prefix", msg->prefix);
2005
0
        node_map_add_string(dst, "level",  msg->level);
2006
0
        node_map_add_string(dst, "text",   msg->text);
2007
0
        break;
2008
0
    }
2009
2010
0
    case MPV_EVENT_CLIENT_MESSAGE: {
2011
0
        mpv_event_client_message *msg = event->data;
2012
2013
0
        struct mpv_node *args = node_map_add(dst, "args", MPV_FORMAT_NODE_ARRAY);
2014
0
        for (int n = 0; n < msg->num_args; n++) {
2015
0
            struct mpv_node *sn = node_array_add(args, MPV_FORMAT_NONE);
2016
0
            sn->format = MPV_FORMAT_STRING;
2017
0
            sn->u.string = (char *)msg->args[n];
2018
0
        }
2019
0
        break;
2020
0
    }
2021
2022
0
    case MPV_EVENT_PROPERTY_CHANGE: {
2023
0
        mpv_event_property *prop = event->data;
2024
2025
0
        node_map_add_string(dst, "name", prop->name);
2026
2027
0
        switch (prop->format) {
2028
0
        case MPV_FORMAT_NODE:
2029
0
            *node_map_add(dst, "data", MPV_FORMAT_NONE) =
2030
0
                *(struct mpv_node *)prop->data;
2031
0
            break;
2032
0
        case MPV_FORMAT_DOUBLE:
2033
0
            node_map_add_double(dst, "data", *(double *)prop->data);
2034
0
            break;
2035
0
        case MPV_FORMAT_FLAG:
2036
0
            node_map_add_flag(dst, "data", *(int *)prop->data);
2037
0
            break;
2038
0
        case MPV_FORMAT_STRING:
2039
0
            node_map_add_string(dst, "data", *(char **)prop->data);
2040
0
            break;
2041
0
        default: ;
2042
0
        }
2043
0
        break;
2044
0
    }
2045
2046
0
    case MPV_EVENT_COMMAND_REPLY: {
2047
0
        mpv_event_command *cmd = event->data;
2048
2049
0
        *node_map_add(dst, "result", MPV_FORMAT_NONE) = cmd->result;
2050
0
        break;
2051
0
    }
2052
2053
0
    case MPV_EVENT_HOOK: {
2054
0
        mpv_event_hook *hook = event->data;
2055
2056
0
        node_map_add_int64(dst, "hook_id", hook->id);
2057
0
        break;
2058
0
    }
2059
2060
0
    }
2061
0
    return 0;
2062
0
}
2063
2064
static const char *const err_table[] = {
2065
    [-MPV_ERROR_SUCCESS] = "success",
2066
    [-MPV_ERROR_EVENT_QUEUE_FULL] = "event queue full",
2067
    [-MPV_ERROR_NOMEM] = "memory allocation failed",
2068
    [-MPV_ERROR_UNINITIALIZED] = "core not initialized",
2069
    [-MPV_ERROR_INVALID_PARAMETER] = "invalid parameter",
2070
    [-MPV_ERROR_OPTION_NOT_FOUND] = "option not found",
2071
    [-MPV_ERROR_OPTION_FORMAT] = "unsupported format for accessing option",
2072
    [-MPV_ERROR_OPTION_ERROR] = "error setting option",
2073
    [-MPV_ERROR_PROPERTY_NOT_FOUND] = "property not found",
2074
    [-MPV_ERROR_PROPERTY_FORMAT] = "unsupported format for accessing property",
2075
    [-MPV_ERROR_PROPERTY_UNAVAILABLE] = "property unavailable",
2076
    [-MPV_ERROR_PROPERTY_ERROR] = "error accessing property",
2077
    [-MPV_ERROR_COMMAND] = "error running command",
2078
    [-MPV_ERROR_LOADING_FAILED] = "loading failed",
2079
    [-MPV_ERROR_AO_INIT_FAILED] = "audio output initialization failed",
2080
    [-MPV_ERROR_VO_INIT_FAILED] = "video output initialization failed",
2081
    [-MPV_ERROR_NOTHING_TO_PLAY] = "no audio or video data played",
2082
    [-MPV_ERROR_UNKNOWN_FORMAT] = "unrecognized file format",
2083
    [-MPV_ERROR_UNSUPPORTED] = "not supported",
2084
    [-MPV_ERROR_NOT_IMPLEMENTED] = "operation not implemented",
2085
    [-MPV_ERROR_GENERIC] = "something happened",
2086
};
2087
2088
const char *mpv_error_string(int error)
2089
428k
{
2090
428k
    error = -error;
2091
428k
    if (error < 0)
2092
0
        error = 0;
2093
428k
    const char *name = NULL;
2094
428k
    if (error < MP_ARRAY_SIZE(err_table))
2095
428k
        name = err_table[error];
2096
428k
    return name ? name : "unknown error";
2097
428k
}
2098
2099
static const char *const event_table[] = {
2100
    [MPV_EVENT_NONE] = "none",
2101
    [MPV_EVENT_SHUTDOWN] = "shutdown",
2102
    [MPV_EVENT_LOG_MESSAGE] = "log-message",
2103
    [MPV_EVENT_GET_PROPERTY_REPLY] = "get-property-reply",
2104
    [MPV_EVENT_SET_PROPERTY_REPLY] = "set-property-reply",
2105
    [MPV_EVENT_COMMAND_REPLY] = "command-reply",
2106
    [MPV_EVENT_START_FILE] = "start-file",
2107
    [MPV_EVENT_END_FILE] = "end-file",
2108
    [MPV_EVENT_FILE_LOADED] = "file-loaded",
2109
    [MPV_EVENT_IDLE] = "idle",
2110
    [MPV_EVENT_TICK] = "tick",
2111
    [MPV_EVENT_CLIENT_MESSAGE] = "client-message",
2112
    [MPV_EVENT_VIDEO_RECONFIG] = "video-reconfig",
2113
    [MPV_EVENT_AUDIO_RECONFIG] = "audio-reconfig",
2114
    [MPV_EVENT_SEEK] = "seek",
2115
    [MPV_EVENT_PLAYBACK_RESTART] = "playback-restart",
2116
    [MPV_EVENT_PROPERTY_CHANGE] = "property-change",
2117
    [MPV_EVENT_QUEUE_OVERFLOW] = "event-queue-overflow",
2118
    [MPV_EVENT_HOOK] = "hook",
2119
};
2120
2121
const char *mpv_event_name(mpv_event_id event)
2122
263k
{
2123
263k
    if ((unsigned)event >= MP_ARRAY_SIZE(event_table))
2124
0
        return NULL;
2125
263k
    return event_table[event];
2126
263k
}
2127
2128
void mpv_free(void *data)
2129
0
{
2130
0
    talloc_free(data);
2131
0
}
2132
2133
int64_t mpv_get_time_ns(mpv_handle *ctx)
2134
0
{
2135
0
    return mp_time_ns();
2136
0
}
2137
2138
int64_t mpv_get_time_us(mpv_handle *ctx)
2139
0
{
2140
0
    return mp_time_ns() / 1000;
2141
0
}
2142
2143
#include "video/out/libmpv.h"
2144
2145
static void do_kill(void *ptr)
2146
0
{
2147
0
    struct MPContext *mpctx = ptr;
2148
2149
0
    struct track *track = mpctx->vo_chain ? mpctx->vo_chain->track : NULL;
2150
0
    uninit_video_out(mpctx);
2151
0
    if (track) {
2152
0
        mpctx->error_playing = MPV_ERROR_VO_INIT_FAILED;
2153
0
        error_on_track(mpctx, track);
2154
0
    }
2155
0
}
2156
2157
// Used by vo_libmpv to (a)synchronously uninitialize video.
2158
void kill_video_async(struct mp_client_api *client_api)
2159
0
{
2160
0
    struct MPContext *mpctx = client_api->mpctx;
2161
0
    mp_dispatch_enqueue(mpctx->dispatch, do_kill, mpctx);
2162
0
}
2163
2164
// Used by vo_libmpv to set the current render context.
2165
bool mp_set_main_render_context(struct mp_client_api *client_api,
2166
                                struct mpv_render_context *ctx, bool active)
2167
0
{
2168
0
    mp_assert(ctx);
2169
2170
0
    mp_mutex_lock(&client_api->lock);
2171
0
    bool is_set = !!client_api->render_context;
2172
0
    bool is_same = client_api->render_context == ctx;
2173
    // Can set if it doesn't remove another existing ctx.
2174
0
    bool res = is_same || !is_set;
2175
0
    if (res)
2176
0
        client_api->render_context = active ? ctx : NULL;
2177
0
    mp_mutex_unlock(&client_api->lock);
2178
0
    return res;
2179
0
}
2180
2181
// Used by vo_libmpv. Relies on guarantees by mp_render_context_acquire().
2182
struct mpv_render_context *
2183
mp_client_api_acquire_render_context(struct mp_client_api *ca)
2184
1
{
2185
1
    struct mpv_render_context *res = NULL;
2186
1
    mp_mutex_lock(&ca->lock);
2187
1
    if (ca->render_context && mp_render_context_acquire(ca->render_context))
2188
0
        res = ca->render_context;
2189
1
    mp_mutex_unlock(&ca->lock);
2190
1
    return res;
2191
1
}
2192
2193
// stream_cb
2194
2195
struct mp_custom_protocol {
2196
    char *protocol;
2197
    void *user_data;
2198
    mpv_stream_cb_open_ro_fn open_fn;
2199
};
2200
2201
int mpv_stream_cb_add_ro(mpv_handle *ctx, const char *protocol, void *user_data,
2202
                         mpv_stream_cb_open_ro_fn open_fn)
2203
0
{
2204
0
    if (!open_fn)
2205
0
        return MPV_ERROR_INVALID_PARAMETER;
2206
2207
0
    struct mp_client_api *clients = ctx->clients;
2208
0
    int r = 0;
2209
0
    mp_mutex_lock(&clients->lock);
2210
0
    for (int n = 0; n < clients->num_custom_protocols; n++) {
2211
0
        struct mp_custom_protocol *proto = &clients->custom_protocols[n];
2212
0
        if (strcmp(proto->protocol, protocol) == 0) {
2213
0
            r = MPV_ERROR_INVALID_PARAMETER;
2214
0
            break;
2215
0
        }
2216
0
    }
2217
0
    if (stream_has_proto(protocol))
2218
0
        r = MPV_ERROR_INVALID_PARAMETER;
2219
0
    if (r >= 0) {
2220
0
        struct mp_custom_protocol proto = {
2221
0
            .protocol = talloc_strdup(clients, protocol),
2222
0
            .user_data = user_data,
2223
0
            .open_fn = open_fn,
2224
0
        };
2225
0
        MP_TARRAY_APPEND(clients, clients->custom_protocols,
2226
0
                         clients->num_custom_protocols, proto);
2227
0
    }
2228
0
    mp_mutex_unlock(&clients->lock);
2229
0
    return r;
2230
0
}
2231
2232
bool mp_streamcb_lookup(struct mpv_global *g, const char *protocol,
2233
                        void **out_user_data, mpv_stream_cb_open_ro_fn *out_fn)
2234
17.3k
{
2235
17.3k
    struct mp_client_api *clients = g->client_api;
2236
17.3k
    bool found = false;
2237
17.3k
    mp_mutex_lock(&clients->lock);
2238
17.3k
    for (int n = 0; n < clients->num_custom_protocols; n++) {
2239
0
        struct mp_custom_protocol *proto = &clients->custom_protocols[n];
2240
0
        if (strcmp(proto->protocol, protocol) == 0) {
2241
0
            *out_user_data = proto->user_data;
2242
0
            *out_fn = proto->open_fn;
2243
0
            found = true;
2244
0
            break;
2245
0
        }
2246
0
    }
2247
17.3k
    mp_mutex_unlock(&clients->lock);
2248
17.3k
    return found;
2249
17.3k
}