diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst index 10bcb942ba..8fa325a870 100644 --- a/DOCS/client-api-changes.rst +++ b/DOCS/client-api-changes.rst @@ -25,6 +25,7 @@ API changes :: + 1.13 - add MPV_EVENT_QUEUE_OVERFLOW 1.12 - add class Handle to qthelper.hpp - improve opengl_cb.h API uninitialization behavior, and fix the qml example diff --git a/libmpv/client.h b/libmpv/client.h index a86717df08..7208912ee0 100644 --- a/libmpv/client.h +++ b/libmpv/client.h @@ -167,7 +167,7 @@ extern "C" { * relational operators (<, >, <=, >=). */ #define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL) -#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 12) +#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 13) /** * Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with. @@ -1117,7 +1117,17 @@ typedef enum mpv_event_id { * "chapter" property. The event is redundant, and might * be removed in the far future. */ - MPV_EVENT_CHAPTER_CHANGE = 23 + MPV_EVENT_CHAPTER_CHANGE = 23, + /** + * Happens if the internal per-mpv_handle ringbuffer overflows, and at + * least 1 event had to be dropped. This can happen if the client doesn't + * read the event queue quickly enough with mpv_wait_event(), or if the + * client makes a very large number of asynchronous calls at once. + * + * Event delivery will continue normally once this event was returned + * (this forces the client to empty the queue completely). + */ + MPV_EVENT_QUEUE_OVERFLOW = 24 // Internal note: adjust INTERNAL_EVENT_BASE when adding new events. } mpv_event_id; @@ -1353,8 +1363,8 @@ int mpv_request_log_messages(mpv_handle *ctx, const char *min_level); * The API won't complain if more than one thread calls this, but it will cause * race conditions in the client when accessing the shared mpv_event struct. * Note that most other API functions are not restricted by this, and no API - * function internally calls mpv_wait_event(). This does not apply to concurrent - * calls of this function on different mpv_handles: these are always safe. + * function internally calls mpv_wait_event(). Additionally, concurrent calls + * to different mpv_handles are always safe. * * @param timeout Timeout in seconds, after which the function returns even if * no event was received. A MPV_EVENT_NONE is returned on @@ -1364,7 +1374,8 @@ int mpv_request_log_messages(mpv_handle *ctx, const char *min_level); * fields in the struct) stay valid until the next mpv_wait_event() * call, or until the mpv_handle is destroyed. You must not write to * the struct, and all memory referenced by it will be automatically - * released by the API. The return value is never NULL. + * released by the API on the next mpv_wait_event() call, or when the + * context is destroyed. The return value is never NULL. */ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout); diff --git a/player/client.c b/player/client.c index 50daf45393..c4f02b84c8 100644 --- a/player/client.c +++ b/player/client.c @@ -107,7 +107,6 @@ struct mpv_handle { uint64_t event_mask; bool queued_wakeup; - bool choke_warning; int suspend_count; mpv_event *events; // ringbuffer of max_events entries @@ -115,6 +114,7 @@ struct mpv_handle { 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 + bool choked; // recovering from queue overflow struct observe_property **properties; int num_properties; @@ -541,7 +541,8 @@ static int reserve_reply(struct mpv_handle *ctx) { int res = MPV_ERROR_EVENT_QUEUE_FULL; pthread_mutex_lock(&ctx->lock); - if (ctx->reserved_events + ctx->num_events < ctx->max_events) { + if (ctx->reserved_events + ctx->num_events < ctx->max_events && !ctx->choked) + { ctx->reserved_events++; res = 0; } @@ -567,14 +568,17 @@ static int send_event(struct mpv_handle *ctx, struct mpv_event *event, bool copy uint64_t mask = 1ULL << event->event_id; if (ctx->property_event_masks & mask) notify_property_events(ctx, mask); + int r; if (!(ctx->event_mask & mask)) { - pthread_mutex_unlock(&ctx->lock); - return 0; - } - int r = append_event(ctx, *event, copy); - if (r < 0 && !ctx->choke_warning) { - mp_err(ctx->log, "Too many events queued.\n"); - ctx->choke_warning = true; + r = 0; + } else if (ctx->choked) { + r = -1; + } else { + r = append_event(ctx, *event, copy); + if (r < 0) { + MP_ERR(ctx, "Too many events queued.\n"); + ctx->choked = true; + } } pthread_mutex_unlock(&ctx->lock); return r; @@ -729,6 +733,12 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout) while (1) { if (ctx->queued_wakeup) deadline = 0; + // Recover from overflow. + if (ctx->choked && !ctx->num_events) { + ctx->choked = false; + 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"); @@ -741,10 +751,11 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout) talloc_steal(event, event->data); break; } + // If there's a changed property, generate change event (never queued). if (gen_property_change_event(ctx)) break; + // Pop item from message queue, and return as event. if (ctx->messages) { - // Poll the log message queue. Currently we can't/don't do better. struct mp_log_buffer_entry *msg = mp_msg_log_buffer_read(ctx->messages); if (msg) { @@ -1608,6 +1619,7 @@ static const char *const event_table[] = { [MPV_EVENT_PLAYBACK_RESTART] = "playback-restart", [MPV_EVENT_PROPERTY_CHANGE] = "property-change", [MPV_EVENT_CHAPTER_CHANGE] = "chapter-change", + [MPV_EVENT_QUEUE_OVERFLOW] = "event-queue-overflow", }; const char *mpv_event_name(mpv_event_id event) diff --git a/player/command.h b/player/command.h index d4097fee6f..d6891b66cd 100644 --- a/player/command.h +++ b/player/command.h @@ -42,7 +42,7 @@ uint64_t mp_get_property_event_mask(const char *name); enum { // Must start with the first unused positive value in enum mpv_event_id // MPV_EVENT_* and MP_EVENT_* must not overlap. - INTERNAL_EVENT_BASE = 24, + INTERNAL_EVENT_BASE = 25, MP_EVENT_CACHE_UPDATE, MP_EVENT_WIN_RESIZE, MP_EVENT_WIN_STATE,