diff options
Diffstat (limited to 'player/client.c')
-rw-r--r-- | player/client.c | 1037 |
1 files changed, 612 insertions, 425 deletions
diff --git a/player/client.c b/player/client.c index 6a45ec7fb6..5087f89885 100644 --- a/player/client.c +++ b/player/client.c @@ -13,14 +13,15 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <math.h> +#include <stdatomic.h> #include <stddef.h> #include <stdint.h> #include <stdlib.h> #include <unistd.h> -#include <fcntl.h> -#include <errno.h> -#include <math.h> -#include <assert.h> #include "common/common.h" #include "common/global.h" @@ -63,55 +64,64 @@ struct mp_client_api { struct MPContext *mpctx; - pthread_mutex_t lock; + mp_mutex lock; // -- protected by lock struct mpv_handle **clients; int num_clients; - uint64_t event_masks; // combined events of all clients, or 0 if unknown bool shutting_down; // do not allow new clients bool have_terminator; // a client took over the role of destroying the core bool terminate_core_thread; // make libmpv core thread exit + // This is incremented whenever the clients[] array above changes. This is + // used to safely unlock mp_client_api.lock while iterating the list of + // clients. + uint64_t clients_list_change_ts; + int64_t id_alloc; struct mp_custom_protocol *custom_protocols; int num_custom_protocols; struct mpv_render_context *render_context; - struct mpv_opengl_cb_context *gl_cb_ctx; }; struct observe_property { + // -- immutable + struct mpv_handle *owner; char *name; int id; // ==mp_get_property_id(name) uint64_t event_mask; // ==mp_get_property_event_mask(name) int64_t reply_id; mpv_format format; - bool changed; // property change should be signaled to user - bool need_new_value; // a new value should be retrieved - bool updating; // a new value is being retrieved - bool updated; // a new value was successfully retrieved - bool dead; // property unobserved while retrieving value - bool new_value_valid, user_value_valid; - union m_option_value new_value, user_value; - struct mpv_handle *client; + const struct m_option *type; + // -- protected by owner->lock + size_t refcount; + uint64_t change_ts; // logical timestamp incremented on each change + uint64_t value_ts; // logical timestamp for value contents + bool value_valid; + union m_option_value value; + uint64_t value_ret_ts; // logical timestamp of value returned to user + union m_option_value value_ret; + bool waiting_for_hook; // flag for draining old property changes on a hook }; struct mpv_handle { - // -- immmutable + // -- immutable char name[MAX_CLIENT_NAME]; struct mp_log *log; struct MPContext *mpctx; struct mp_client_api *clients; + int64_t id; // -- not thread-safe struct mpv_event *cur_event; struct mpv_event_property cur_property_event; + struct observe_property *cur_property; - pthread_mutex_t lock; + mp_mutex lock; - pthread_mutex_t wakeup_lock; - pthread_cond_t wakeup; + mp_mutex wakeup_lock; + mp_cond wakeup; // -- protected by wakeup_lock bool need_wakeup; @@ -123,29 +133,50 @@ struct mpv_handle { uint64_t event_mask; bool queued_wakeup; - int suspend_count; mpv_event *events; // ringbuffer of max_events entries int max_events; // allocated number of entries in events int first_event; // events[first_event] is the first readable event int num_events; // number of readable events int reserved_events; // number of entries reserved for replies + size_t async_counter; // pending other async events bool choked; // recovering from queue overflow + bool destroying; // pending destruction; no API accesses allowed + bool hook_pending; // hook events are returned after draining properties struct observe_property **properties; int num_properties; - int lowest_changed; // attempt at making change processing incremental - int properties_updating; + bool has_pending_properties; // (maybe) new property events (producer side) + bool new_property_events; // new property events (consumer side) + int cur_property_index; // round-robin for property events (consumer side) uint64_t property_event_masks; // or-ed together event masks of all properties + // This is incremented whenever the properties[] array above changes. This + // is used to safely unlock mpv_handle.lock while reading a property. If + // the counter didn't change between unlock and relock, then it will assume + // the array did not change. + uint64_t properties_change_ts; bool fuzzy_initialized; // see scripting.c wait_loaded() bool is_weak; // can not keep core alive on its own struct mp_log_buffer *messages; + int messages_level; }; static bool gen_log_message_event(struct mpv_handle *ctx); static bool gen_property_change_event(struct mpv_handle *ctx); -static void notify_property_events(struct mpv_handle *ctx, uint64_t event_mask); +static void notify_property_events(struct mpv_handle *ctx, int event); + +// Must be called with prop->owner->lock held. +static void prop_unref(struct observe_property *prop) +{ + if (!prop) + return; + + assert(prop->refcount > 0); + prop->refcount -= 1; + if (!prop->refcount) + talloc_free(prop); +} void mp_clients_init(struct MPContext *mpctx) { @@ -154,7 +185,7 @@ void mp_clients_init(struct MPContext *mpctx) .mpctx = mpctx, }; mpctx->global->client_api = mpctx->clients; - pthread_mutex_init(&mpctx->clients->lock, NULL); + mp_mutex_init(&mpctx->clients->lock); } void mp_clients_destroy(struct MPContext *mpctx) @@ -163,8 +194,6 @@ void mp_clients_destroy(struct MPContext *mpctx) return; assert(mpctx->clients->num_clients == 0); - TA_FREEP(&mpctx->clients->gl_cb_ctx); - // The API user is supposed to call mpv_render_context_free(). It's simply // not allowed not to do this. if (mpctx->clients->render_context) { @@ -172,7 +201,7 @@ void mp_clients_destroy(struct MPContext *mpctx) abort(); } - pthread_mutex_destroy(&mpctx->clients->lock); + mp_mutex_destroy(&mpctx->clients->lock); talloc_free(mpctx->clients); mpctx->clients = NULL; } @@ -182,45 +211,57 @@ void mp_clients_destroy(struct MPContext *mpctx) bool mp_clients_all_initialized(struct MPContext *mpctx) { bool all_ok = true; - pthread_mutex_lock(&mpctx->clients->lock); + mp_mutex_lock(&mpctx->clients->lock); for (int n = 0; n < mpctx->clients->num_clients; n++) { struct mpv_handle *ctx = mpctx->clients->clients[n]; - pthread_mutex_lock(&ctx->lock); + mp_mutex_lock(&ctx->lock); all_ok &= ctx->fuzzy_initialized; - pthread_mutex_unlock(&ctx->lock); + mp_mutex_unlock(&ctx->lock); } - pthread_mutex_unlock(&mpctx->clients->lock); + mp_mutex_unlock(&mpctx->clients->lock); return all_ok; } -static void invalidate_global_event_mask(struct mpv_handle *ctx) +static struct mpv_handle *find_client_id(struct mp_client_api *clients, int64_t id) { - pthread_mutex_lock(&ctx->clients->lock); - ctx->clients->event_masks = 0; - pthread_mutex_unlock(&ctx->clients->lock); + for (int n = 0; n < clients->num_clients; n++) { + if (clients->clients[n]->id == id) + return clients->clients[n]; + } + return NULL; } static struct mpv_handle *find_client(struct mp_client_api *clients, const char *name) { + if (name[0] == '@') { + char *end; + errno = 0; + long long int id = strtoll(name + 1, &end, 10); + if (errno || end[0]) + return NULL; + return find_client_id(clients, id); + } + for (int n = 0; n < clients->num_clients; n++) { if (strcmp(clients->clients[n]->name, name) == 0) return clients->clients[n]; } + return NULL; } -bool mp_client_exists(struct MPContext *mpctx, const char *client_name) +bool mp_client_id_exists(struct MPContext *mpctx, int64_t id) { - pthread_mutex_lock(&mpctx->clients->lock); - bool r = find_client(mpctx->clients, client_name); - pthread_mutex_unlock(&mpctx->clients->lock); + mp_mutex_lock(&mpctx->clients->lock); + bool r = find_client_id(mpctx->clients, id); + mp_mutex_unlock(&mpctx->clients->lock); return r; } struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name) { - pthread_mutex_lock(&clients->lock); + mp_mutex_lock(&clients->lock); char nname[MAX_CLIENT_NAME]; for (int n = 1; n < 1000; n++) { @@ -237,7 +278,7 @@ struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name } if (!nname[0] || clients->shutting_down) { - pthread_mutex_unlock(&clients->lock); + mp_mutex_unlock(&clients->lock); return NULL; } @@ -248,25 +289,26 @@ struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name .log = mp_log_new(client, clients->mpctx->log, nname), .mpctx = clients->mpctx, .clients = clients, + .id = ++(clients->id_alloc), .cur_event = talloc_zero(client, struct mpv_event), .events = talloc_array(client, mpv_event, num_events), .max_events = num_events, .event_mask = (1ULL << INTERNAL_EVENT_BASE) - 1, // exclude internal events .wakeup_pipe = {-1, -1}, }; - pthread_mutex_init(&client->lock, NULL); - pthread_mutex_init(&client->wakeup_lock, NULL); - pthread_cond_init(&client->wakeup, NULL); + mp_mutex_init(&client->lock); + mp_mutex_init(&client->wakeup_lock); + mp_cond_init(&client->wakeup); snprintf(client->name, sizeof(client->name), "%s", nname); + clients->clients_list_change_ts += 1; MP_TARRAY_APPEND(clients, clients->clients, clients->num_clients, client); if (clients->num_clients == 1 && !clients->mpctx->is_cli) client->fuzzy_initialized = true; - clients->event_masks = 0; - pthread_mutex_unlock(&clients->lock); + mp_mutex_unlock(&clients->lock); mpv_request_event(client, MPV_EVENT_TICK, 0); @@ -275,9 +317,9 @@ struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name void mp_client_set_weak(struct mpv_handle *ctx) { - pthread_mutex_lock(&ctx->lock); + mp_mutex_lock(&ctx->lock); ctx->is_weak = true; - pthread_mutex_unlock(&ctx->lock); + mp_mutex_unlock(&ctx->lock); } const char *mpv_client_name(mpv_handle *ctx) @@ -285,6 +327,11 @@ const char *mpv_client_name(mpv_handle *ctx) return ctx->name; } +int64_t mpv_client_id(mpv_handle *ctx) +{ + return ctx->id; +} + struct mp_log *mp_client_get_log(struct mpv_handle *ctx) { return ctx->log; @@ -295,64 +342,43 @@ struct mpv_global *mp_client_get_global(struct mpv_handle *ctx) return ctx->mpctx->global; } -struct MPContext *mp_client_get_core(struct mpv_handle *ctx) -{ - return ctx->mpctx; -} - -struct MPContext *mp_client_api_get_core(struct mp_client_api *api) -{ - return api->mpctx; -} - static void wakeup_client(struct mpv_handle *ctx) { - pthread_mutex_lock(&ctx->wakeup_lock); + mp_mutex_lock(&ctx->wakeup_lock); if (!ctx->need_wakeup) { ctx->need_wakeup = true; - pthread_cond_broadcast(&ctx->wakeup); + mp_cond_broadcast(&ctx->wakeup); if (ctx->wakeup_cb) ctx->wakeup_cb(ctx->wakeup_cb_ctx); if (ctx->wakeup_pipe[0] != -1) (void)write(ctx->wakeup_pipe[1], &(char){0}, 1); } - pthread_mutex_unlock(&ctx->wakeup_lock); + mp_mutex_unlock(&ctx->wakeup_lock); } // Note: the caller has to deal with sporadic wakeups. static int wait_wakeup(struct mpv_handle *ctx, int64_t end) { int r = 0; - pthread_mutex_unlock(&ctx->lock); - pthread_mutex_lock(&ctx->wakeup_lock); - if (!ctx->need_wakeup) { - struct timespec ts = mp_time_us_to_timespec(end); - r = pthread_cond_timedwait(&ctx->wakeup, &ctx->wakeup_lock, &ts); - } + mp_mutex_unlock(&ctx->lock); + mp_mutex_lock(&ctx->wakeup_lock); + if (!ctx->need_wakeup) + r = mp_cond_timedwait_until(&ctx->wakeup, &ctx->wakeup_lock, end); if (r == 0) ctx->need_wakeup = false; - pthread_mutex_unlock(&ctx->wakeup_lock); - pthread_mutex_lock(&ctx->lock); + mp_mutex_unlock(&ctx->wakeup_lock); + mp_mutex_lock(&ctx->lock); return r; } void mpv_set_wakeup_callback(mpv_handle *ctx, void (*cb)(void *d), void *d) { - pthread_mutex_lock(&ctx->wakeup_lock); + mp_mutex_lock(&ctx->wakeup_lock); ctx->wakeup_cb = cb; ctx->wakeup_cb_ctx = d; if (ctx->wakeup_cb) ctx->wakeup_cb(ctx->wakeup_cb_ctx); - pthread_mutex_unlock(&ctx->wakeup_lock); -} - -void mpv_suspend(mpv_handle *ctx) -{ - MP_ERR(ctx, "mpv_suspend() is deprecated and does nothing.\n"); -} - -void mpv_resume(mpv_handle *ctx) -{ + mp_mutex_unlock(&ctx->wakeup_lock); } static void lock_core(mpv_handle *ctx) @@ -367,10 +393,10 @@ static void unlock_core(mpv_handle *ctx) void mpv_wait_async_requests(mpv_handle *ctx) { - pthread_mutex_lock(&ctx->lock); - while (ctx->reserved_events || ctx->properties_updating) + mp_mutex_lock(&ctx->lock); + while (ctx->reserved_events || ctx->async_counter) wait_wakeup(ctx, INT64_MAX); - pthread_mutex_unlock(&ctx->lock); + mp_mutex_unlock(&ctx->lock); } // Send abort signal to all matching work items. @@ -379,7 +405,7 @@ void mpv_wait_async_requests(mpv_handle *ctx) static void abort_async(struct MPContext *mpctx, mpv_handle *ctx, int type, uint64_t id) { - pthread_mutex_lock(&mpctx->abort_lock); + mp_mutex_lock(&mpctx->abort_lock); // Destroy all => ensure any newly appearing work is aborted immediately. if (ctx == NULL) @@ -394,12 +420,12 @@ static void abort_async(struct MPContext *mpctx, mpv_handle *ctx, } } - pthread_mutex_unlock(&mpctx->abort_lock); + mp_mutex_unlock(&mpctx->abort_lock); } -static void get_thread(void *ptr) +static void get_thread_id(void *ptr) { - *(pthread_t *)ptr = pthread_self(); + *(mp_thread_id *)ptr = mp_thread_current_id(); } static void mp_destroy_client(mpv_handle *ctx, bool terminate) @@ -410,11 +436,25 @@ static void mp_destroy_client(mpv_handle *ctx, bool terminate) struct MPContext *mpctx = ctx->mpctx; struct mp_client_api *clients = ctx->clients; - MP_VERBOSE(ctx, "Exiting...\n"); + MP_DBG(ctx, "Exiting...\n"); if (terminate) mpv_command(ctx, (const char*[]){"quit", NULL}); + mp_mutex_lock(&ctx->lock); + + ctx->destroying = true; + + for (int n = 0; n < ctx->num_properties; n++) + prop_unref(ctx->properties[n]); + ctx->num_properties = 0; + ctx->properties_change_ts += 1; + + prop_unref(ctx->cur_property); + ctx->cur_property = NULL; + + mp_mutex_unlock(&ctx->lock); + abort_async(mpctx, ctx, 0, 0); // reserved_events equals the number of asynchronous requests that weren't @@ -422,13 +462,14 @@ static void mp_destroy_client(mpv_handle *ctx, bool terminate) // causes a crash, block until all asynchronous requests were served. mpv_wait_async_requests(ctx); - osd_set_external(mpctx->osd, ctx, 0, 0, NULL); + osd_set_external_remove_owner(mpctx->osd, ctx); mp_input_remove_sections_by_owner(mpctx->input, ctx->name); - pthread_mutex_lock(&clients->lock); + mp_mutex_lock(&clients->lock); for (int n = 0; n < clients->num_clients; n++) { if (clients->clients[n] == ctx) { + clients->clients_list_change_ts += 1; MP_TARRAY_REMOVE_AT(clients->clients, clients->num_clients, n); while (ctx->num_events) { talloc_free(ctx->events[ctx->first_event].data); @@ -436,9 +477,9 @@ static void mp_destroy_client(mpv_handle *ctx, bool terminate) ctx->num_events--; } mp_msg_log_buffer_destroy(ctx->messages); - pthread_cond_destroy(&ctx->wakeup); - pthread_mutex_destroy(&ctx->wakeup_lock); - pthread_mutex_destroy(&ctx->lock); + mp_cond_destroy(&ctx->wakeup); + mp_mutex_destroy(&ctx->wakeup_lock); + mp_mutex_destroy(&ctx->lock); if (ctx->wakeup_pipe[0] != -1) { close(ctx->wakeup_pipe[0]); close(ctx->wakeup_pipe[1]); @@ -470,7 +511,7 @@ static void mp_destroy_client(mpv_handle *ctx, bool terminate) // mp_hook_test_completion() also relies on this a bit. mp_wakeup_core(mpctx); - pthread_mutex_unlock(&clients->lock); + mp_mutex_unlock(&clients->lock); // Note that even if num_clients==0, having set have_terminator keeps mpctx // and the core thread alive. @@ -481,17 +522,17 @@ static void mp_destroy_client(mpv_handle *ctx, bool terminate) mpctx->stop_play = PT_QUIT; mp_dispatch_unlock(mpctx->dispatch); - pthread_t playthread; - mp_dispatch_run(mpctx->dispatch, get_thread, &playthread); + mp_thread_id playthread; + mp_dispatch_run(mpctx->dispatch, get_thread_id, &playthread); // Ask the core thread to stop. - pthread_mutex_lock(&clients->lock); + mp_mutex_lock(&clients->lock); clients->terminate_core_thread = true; - pthread_mutex_unlock(&clients->lock); + mp_mutex_unlock(&clients->lock); mp_wakeup_core(mpctx); // Blocking wait for all clients and core thread to terminate. - pthread_join(playthread, NULL); + mp_thread_join_id(playthread); mp_destroy(mpctx); } @@ -502,11 +543,6 @@ void mpv_destroy(mpv_handle *ctx) mp_destroy_client(ctx, false); } -void mpv_detach_destroy(mpv_handle *ctx) -{ - mpv_destroy(ctx); -} - void mpv_terminate_destroy(mpv_handle *ctx) { mp_destroy_client(ctx, true); @@ -521,7 +557,7 @@ void mp_shutdown_clients(struct MPContext *mpctx) // Forcefully abort async work after 2 seconds of waiting. double abort_time = mp_time_sec() + 2; - pthread_mutex_lock(&clients->lock); + mp_mutex_lock(&clients->lock); // Prevent that new clients can appear. clients->shutting_down = true; @@ -530,7 +566,7 @@ void mp_shutdown_clients(struct MPContext *mpctx) while (clients->num_clients || mpctx->outstanding_async || !(mpctx->is_cli || clients->terminate_core_thread)) { - pthread_mutex_unlock(&clients->lock); + mp_mutex_unlock(&clients->lock); double left = abort_time - mp_time_sec(); if (left >= 0) { @@ -545,26 +581,26 @@ void mp_shutdown_clients(struct MPContext *mpctx) mp_client_broadcast_event(mpctx, MPV_EVENT_SHUTDOWN, NULL); mp_wait_events(mpctx); - pthread_mutex_lock(&clients->lock); + mp_mutex_lock(&clients->lock); } - pthread_mutex_unlock(&clients->lock); + mp_mutex_unlock(&clients->lock); } bool mp_is_shutting_down(struct MPContext *mpctx) { struct mp_client_api *clients = mpctx->clients; - pthread_mutex_lock(&clients->lock); + mp_mutex_lock(&clients->lock); bool res = clients->shutting_down; - pthread_mutex_unlock(&clients->lock); + mp_mutex_unlock(&clients->lock); return res; } -static void *core_thread(void *p) +static MP_THREAD_VOID core_thread(void *p) { struct MPContext *mpctx = p; - mpthread_set_name("mpv core"); + mp_thread_set_name("core"); while (!mpctx->initialized && mpctx->stop_play != PT_QUIT) mp_idle(mpctx); @@ -577,7 +613,7 @@ static void *core_thread(void *p) // the last mpv_handle. mp_shutdown_clients(mpctx); - return NULL; + MP_THREAD_RETURN(); } mpv_handle *mpv_create(void) @@ -594,8 +630,8 @@ mpv_handle *mpv_create(void) return NULL; } - pthread_t thread; - if (pthread_create(&thread, NULL, core_thread, mpctx) != 0) { + mp_thread thread; + if (mp_thread_create(&thread, core_thread, mpctx) != 0) { ctx->clients->have_terminator = true; // avoid blocking mpv_terminate_destroy(ctx); mp_destroy(mpctx); @@ -648,6 +684,9 @@ static void dup_event_data(struct mpv_event *ev) ev->data = msg; break; } + case MPV_EVENT_START_FILE: + ev->data = talloc_memdup(NULL, ev->data, sizeof(mpv_event_start_file)); + break; case MPV_EVENT_END_FILE: ev->data = talloc_memdup(NULL, ev->data, sizeof(mpv_event_end_file)); break; @@ -665,13 +704,13 @@ static void dup_event_data(struct mpv_event *ev) static int reserve_reply(struct mpv_handle *ctx) { int res = MPV_ERROR_EVENT_QUEUE_FULL; - pthread_mutex_lock(&ctx->lock); + mp_mutex_lock(&ctx->lock); if (ctx->reserved_events + ctx->num_events < ctx->max_events && !ctx->choked) { ctx->reserved_events++; res = 0; } - pthread_mutex_unlock(&ctx->lock); + mp_mutex_unlock(&ctx->lock); return res; } @@ -691,10 +730,10 @@ static int append_event(struct mpv_handle *ctx, struct mpv_event event, bool cop static int send_event(struct mpv_handle *ctx, struct mpv_event *event, bool copy) { - pthread_mutex_lock(&ctx->lock); + mp_mutex_lock(&ctx->lock); uint64_t mask = 1ULL << event->event_id; if (ctx->property_event_masks & mask) - notify_property_events(ctx, mask); + notify_property_events(ctx, event->event_id); int r; if (!(ctx->event_mask & mask)) { r = 0; @@ -707,7 +746,7 @@ static int send_event(struct mpv_handle *ctx, struct mpv_event *event, bool copy ctx->choked = true; } } - pthread_mutex_unlock(&ctx->lock); + mp_mutex_unlock(&ctx->lock); return r; } @@ -717,43 +756,20 @@ static void send_reply(struct mpv_handle *ctx, uint64_t userdata, struct mpv_event *event) { event->reply_userdata = userdata; - pthread_mutex_lock(&ctx->lock); + mp_mutex_lock(&ctx->lock); // If this fails, reserve_reply() probably wasn't called. assert(ctx->reserved_events > 0); ctx->reserved_events--; if (append_event(ctx, *event, false) < 0) - abort(); // not reached - pthread_mutex_unlock(&ctx->lock); -} - -// Return whether there's any client listening to this event. -// If false is returned, the core doesn't need to send it. -bool mp_client_event_is_registered(struct MPContext *mpctx, int event) -{ - struct mp_client_api *clients = mpctx->clients; - - pthread_mutex_lock(&clients->lock); - - if (!clients->event_masks) { // lazy update - for (int n = 0; n < clients->num_clients; n++) { - struct mpv_handle *ctx = clients->clients[n]; - pthread_mutex_lock(&ctx->lock); - clients->event_masks |= ctx->event_mask | ctx->property_event_masks; - pthread_mutex_unlock(&ctx->lock); - } - } - bool r = clients->event_masks & (1ULL << event); - - pthread_mutex_unlock(&clients->lock); - - return r; + MP_ASSERT_UNREACHABLE(); + mp_mutex_unlock(&ctx->lock); } void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data) { struct mp_client_api *clients = mpctx->clients; - pthread_mutex_lock(&clients->lock); + mp_mutex_lock(&clients->lock); for (int n = 0; n < clients->num_clients; n++) { struct mpv_event event_data = { @@ -763,7 +779,18 @@ void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data) send_event(clients->clients[n], &event_data, true); } - pthread_mutex_unlock(&clients->lock); + mp_mutex_unlock(&clients->lock); +} + +// Like mp_client_broadcast_event(), but can be called from any thread. +// Avoid using this. +void mp_client_broadcast_event_external(struct mp_client_api *api, int event, + void *data) +{ + struct MPContext *mpctx = api->mpctx; + + mp_client_broadcast_event(mpctx, event, data); + mp_wakeup_core(mpctx); } // If client_name == NULL, then broadcast and free the event. @@ -785,7 +812,7 @@ int mp_client_send_event(struct MPContext *mpctx, const char *client_name, .reply_userdata = reply_userdata, }; - pthread_mutex_lock(&clients->lock); + mp_mutex_lock(&clients->lock); struct mpv_handle *ctx = find_client(clients, client_name); if (ctx) { @@ -795,7 +822,7 @@ int mp_client_send_event(struct MPContext *mpctx, const char *client_name, talloc_free(data); } - pthread_mutex_unlock(&clients->lock); + mp_mutex_unlock(&clients->lock); return r; } @@ -817,6 +844,11 @@ int mp_client_send_event_dup(struct MPContext *mpctx, const char *client_name, return mp_client_send_event(mpctx, client_name, 0, event, event_data.data); } +static const bool deprecated_events[] = { + [MPV_EVENT_IDLE] = true, + [MPV_EVENT_TICK] = true, +}; + int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable) { if (!mpv_event_name(event) || enable < 0 || enable > 1) @@ -824,19 +856,45 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable) if (event == MPV_EVENT_SHUTDOWN && !enable) return MPV_ERROR_INVALID_PARAMETER; assert(event < (int)INTERNAL_EVENT_BASE); // excluded above; they have no name - pthread_mutex_lock(&ctx->lock); + mp_mutex_lock(&ctx->lock); uint64_t bit = 1ULL << event; ctx->event_mask = enable ? ctx->event_mask | bit : ctx->event_mask & ~bit; - pthread_mutex_unlock(&ctx->lock); - invalidate_global_event_mask(ctx); + if (enable && event < MP_ARRAY_SIZE(deprecated_events) && + deprecated_events[event]) + { + MP_WARN(ctx, "The '%s' event is deprecated and will be removed.\n", + mpv_event_name(event)); + } + mp_mutex_unlock(&ctx->lock); return 0; } +// Set waiting_for_hook==true for all possibly pending properties. +static void set_wait_for_hook_flags(mpv_handle *ctx) +{ + for (int n = 0; n < ctx->num_properties; n++) { + struct observe_property *prop = ctx->properties[n]; + + if (prop->value_ret_ts != prop->change_ts) + prop->waiting_for_hook = true; + } +} + +// Return whether any property still has waiting_for_hook set. +static bool check_for_for_hook_flags(mpv_handle *ctx) +{ + for (int n = 0; n < ctx->num_properties; n++) { + if (ctx->properties[n]->waiting_for_hook) + return true; + } + return false; +} + mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout) { mpv_event *event = ctx->cur_event; - pthread_mutex_lock(&ctx->lock); + mp_mutex_lock(&ctx->lock); if (!ctx->fuzzy_initialized) mp_wakeup_core(ctx->clients->mpctx); @@ -845,7 +903,7 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout) if (timeout < 0) timeout = 1e20; - int64_t deadline = mp_add_timeout(mp_time_us(), timeout); + int64_t deadline = mp_time_ns_add(mp_time_ns(), timeout); *event = (mpv_event){0}; talloc_free_children(event); @@ -859,13 +917,24 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout) event->event_id = MPV_EVENT_QUEUE_OVERFLOW; break; } - // This will almost surely lead to a deadlock. (Polling is still ok.) - if (ctx->suspend_count && timeout > 0) { - MP_ERR(ctx, "attempting to wait while core is suspended"); - break; + struct mpv_event *ev = + ctx->num_events ? &ctx->events[ctx->first_event] : NULL; + if (ev && ev->event_id == MPV_EVENT_HOOK) { + // Give old property notifications priority over hooks. This is a + // guarantee given to clients to simplify their logic. New property + // changes after this are treated normally, so + if (!ctx->hook_pending) { + ctx->hook_pending = true; + set_wait_for_hook_flags(ctx); + } + if (check_for_for_hook_flags(ctx)) { + ev = NULL; // delay + } else { + ctx->hook_pending = false; + } } - if (ctx->num_events) { - *event = ctx->events[ctx->first_event]; + if (ev) { + *event = *ev; ctx->first_event = (ctx->first_event + 1) % ctx->max_events; ctx->num_events--; talloc_steal(event, event->data); @@ -883,17 +952,17 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout) } ctx->queued_wakeup = false; - pthread_mutex_unlock(&ctx->lock); + mp_mutex_unlock(&ctx->lock); return event; } void mpv_wakeup(mpv_handle *ctx) { - pthread_mutex_lock(&ctx->lock); + mp_mutex_lock(&ctx->lock); ctx->queued_wakeup = true; wakeup_client(ctx); - pthread_mutex_unlock(&ctx->lock); + mp_mutex_unlock(&ctx->lock); } // map client API types to internal types @@ -936,7 +1005,9 @@ static bool conv_node_to_format(void *dst, mpv_format dst_fmt, mpv_node *src) return true; } if (dst_fmt == MPV_FORMAT_INT64 && src->format == MPV_FORMAT_DOUBLE) { - if (src->u.double_ >= INT64_MIN && src->u.double_ <= INT64_MAX) { + if (src->u.double_ > (double)INT64_MIN && + src->u.double_ < (double)INT64_MAX) + { *(int64_t *)dst = src->u.double_; return true; } @@ -953,7 +1024,6 @@ void mpv_free_node_contents(mpv_node *node) int mpv_set_option(mpv_handle *ctx, const char *name, mpv_format format, void *data) { - int flags = ctx->mpctx->initialized ? M_SETOPT_RUNTIME : 0; const struct m_option *type = get_mp_type(format); if (!type) return MPV_ERROR_OPTION_FORMAT; @@ -964,8 +1034,7 @@ int mpv_set_option(mpv_handle *ctx, const char *name, mpv_format format, data = &tmp; } lock_core(ctx); - int err = m_config_set_option_node(ctx->mpctx->mconfig, bstr0(name), - data, flags); + int err = m_config_set_option_node(ctx->mpctx->mconfig, bstr0(name), data, 0); unlock_core(ctx); switch (err) { case M_OPT_MISSING_PARAM: @@ -1133,7 +1202,7 @@ static void async_cmd_fn(void *data) struct async_cmd_request *req = data; struct mp_cmd *cmd = req->cmd; - ta_xset_parent(cmd, NULL); + ta_set_parent(cmd, NULL); req->cmd = NULL; struct mp_abort_entry *abort = NULL; @@ -1266,6 +1335,12 @@ int mpv_set_property(mpv_handle *ctx, const char *name, mpv_format format, return req.status; } +int mpv_del_property(mpv_handle *ctx, const char *name) +{ + const char* args[] = { "del", name, NULL }; + return mpv_command(ctx, args); +} + int mpv_set_property_string(mpv_handle *ctx, const char *name, const char *data) { return mpv_set_property(ctx, name, MPV_FORMAT_STRING, &data); @@ -1325,7 +1400,7 @@ static void getproperty_fn(void *arg) struct getproperty_request *req = arg; const struct m_option *type = get_mp_type_get(req->format); - union m_option_value xdata = {0}; + union m_option_value xdata = m_option_value_default; void *data = req->data ? req->data : &xdata; int err = -1; @@ -1450,78 +1525,95 @@ int mpv_get_property_async(mpv_handle *ctx, uint64_t ud, const char *name, static void property_free(void *p) { struct observe_property *prop = p; - const struct m_option *type = get_mp_type_get(prop->format); - if (type) { - m_option_free(type, &prop->new_value); - m_option_free(type, &prop->user_value); + + assert(prop->refcount == 0); + + if (prop->type) { + m_option_free(prop->type, &prop->value); + m_option_free(prop->type, &prop->value_ret); } } int mpv_observe_property(mpv_handle *ctx, uint64_t userdata, const char *name, mpv_format format) { - if (format != MPV_FORMAT_NONE && !get_mp_type_get(format)) + const struct m_option *type = get_mp_type_get(format); + if (format != MPV_FORMAT_NONE && !type) return MPV_ERROR_PROPERTY_FORMAT; // Explicitly disallow this, because it would require a special code path. if (format == MPV_FORMAT_OSD_STRING) return MPV_ERROR_PROPERTY_FORMAT; - pthread_mutex_lock(&ctx->lock); + mp_mutex_lock(&ctx->lock); + assert(!ctx->destroying); struct observe_property *prop = talloc_ptrtype(ctx, prop); talloc_set_destructor(prop, property_free); *prop = (struct observe_property){ - .client = ctx, + .owner = ctx, .name = talloc_strdup(prop, name), .id = mp_get_property_id(ctx->mpctx, name), .event_mask = mp_get_property_event_mask(name), .reply_id = userdata, .format = format, - .changed = true, - .updating = false, - .updated = false, + .type = type, + .change_ts = 1, // force initial event + .refcount = 1, + .value = m_option_value_default, + .value_ret = m_option_value_default, }; + ctx->properties_change_ts += 1; MP_TARRAY_APPEND(ctx, ctx->properties, ctx->num_properties, prop); ctx->property_event_masks |= prop->event_mask; - ctx->lowest_changed = 0; - pthread_mutex_unlock(&ctx->lock); - invalidate_global_event_mask(ctx); + ctx->new_property_events = true; + ctx->cur_property_index = 0; + ctx->has_pending_properties = true; + mp_mutex_unlock(&ctx->lock); + mp_wakeup_core(ctx->mpctx); return 0; } int mpv_unobserve_property(mpv_handle *ctx, uint64_t userdata) { - pthread_mutex_lock(&ctx->lock); - ctx->property_event_masks = 0; + mp_mutex_lock(&ctx->lock); int count = 0; for (int n = ctx->num_properties - 1; n >= 0; n--) { struct observe_property *prop = ctx->properties[n]; + // Perform actual removal of the property lazily to avoid creating + // dangling pointers and such. if (prop->reply_id == userdata) { - if (prop->updating) { - prop->dead = true; - } else { - // In case mpv_unobserve_property() is called after mpv_wait_event |