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 | } |