summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-04-05 23:54:21 +0200
committerwm4 <wm4@nowhere>2014-04-06 03:22:49 +0200
commit49d1b42f7088c0d41df346437b64fe20bbaac22f (patch)
tree9756ee23e00870608705b17371bc25c38eee38cd
parent14eb233da98bbac1c606e392e658f400deb5024b (diff)
downloadmpv-49d1b42f7088c0d41df346437b64fe20bbaac22f.tar.bz2
mpv-49d1b42f7088c0d41df346437b64fe20bbaac22f.tar.xz
client API: add a way to notify clients of property changes
This turned out ridiculously complex. I think it will have to be simplified some day. Main reason for the complexity are: - filtering properties by forcing clients to observe individual properties explicitly (to avoid spamming clients with changes they don't want) - optional retrieval of property value with the notification (the basic idea was that this is more user friendly) - allowing to the client to specify a format in which the value should be retrieved (because if a property changes its type, the client API couldn't convert it properly, and compatibility would break) I don't know yet which of these are important, and everything could change. In particular, the interface and semantics should be adjusted to reduce the implementation complexity. While I consider the API complete, there could (and probably will) be bugs left. Also while the implementation is complete, it's inefficient. The complexity of the property matching is O(a*b*c) with a clients, b observed properties, and c properties changing at once. I threw away an earlier implementation using bitmasks, because it was too unwieldy.
-rw-r--r--libmpv/client.h61
-rw-r--r--player/client.c214
-rw-r--r--player/client.h1
-rw-r--r--player/command.c31
-rw-r--r--player/command.h2
5 files changed, 306 insertions, 3 deletions
diff --git a/libmpv/client.h b/libmpv/client.h
index ecc9f09275..35c193f64a 100644
--- a/libmpv/client.h
+++ b/libmpv/client.h
@@ -725,6 +725,57 @@ char *mpv_get_property_osd_string(mpv_handle *ctx, const char *name);
int mpv_get_property_async(mpv_handle *ctx, uint64_t reply_userdata,
const char *name, mpv_format format);
+/**
+ * Get a notification whenever the given property changes. You will receive
+ * updates as MPV_EVENT_PROPERTY_CHANGE. Note that this is not very precise:
+ * it can send updates even if the property in fact did not change, or (in
+ * some cases) not send updates even if the property changed - it usually
+ * depends on the property. It's a valid feature request to ask for better
+ * update handling of a specific property.
+ *
+ * Property changes are coalesced: the change events are returned only once the
+ * event queue becomes empty (e.g. mpv_wait_event() would block or return
+ * MPV_EVENT_NONE), and then only one event per changed property is returned.
+ *
+ * Keep in mind that you will get change notifications even if you change a
+ * property yourself. Try to avoid endless feedback loops, which could happen
+ * if you react to change notifications which you caused yourself.
+ *
+ * If the format parameter is set to something other than MPV_FORMAT_NONE, the
+ * current property value will be returned as part of mpv_event_property.
+ *
+ * Warning: if a property is unavailable or retrieving it caused an error,
+ * MPV_FORMAT_NONE will be set in mpv_event_property, even if the
+ * format parameter was set to a different value. In this case, the
+ * mpv_event_property.data field is invalid.
+ *
+ * Observing a property that doesn't exist is allowed, although it may still
+ * cause some sporadic change events.
+ *
+ * @param reply_userdata This will be used for the mpv_event.reply_userdata
+ * field for the received MPV_EVENT_PROPERTY_CHANGE
+ * events. (Also see section about asynchronous calls,
+ * although this function is somewhat different from
+ * actual asynchronous calls.)
+ * Also see mpv_unobserve_property().
+ * @param name The property name.
+ * @param format see enum mpv_format. Can be MPV_FORMAT_NONE to omit values
+ * from the change events.
+ * @return error code (usually fails only on OOM)
+ */
+int mpv_observe_property(mpv_handle *mpv, uint64_t reply_userdata,
+ const char *name, mpv_format format);
+
+/**
+ * Undo mpv_observe_property(). This will remove all observed properties for
+ * which the given number was passed as reply_userdata to mpv_observe_property.
+ *
+ * @param registered_reply_userdata ID that was passed to mpv_observe_property
+ * @return negative value is an error code, number of removed properties on
+ * success (includes the case when 0 were removed)
+ */
+int mpv_unobserve_property(mpv_handle *mpv, uint64_t registered_reply_userdata);
+
typedef enum mpv_event_id {
/**
* Nothing happened. Happens on timeouts or sporadic wakeups.
@@ -843,7 +894,12 @@ typedef enum mpv_event_id {
* segment switches. The main purpose is allowing the client to detect
* when a seek request is finished.
*/
- MPV_EVENT_PLAYBACK_RESTART = 21
+ MPV_EVENT_PLAYBACK_RESTART = 21,
+ /**
+ * Event sent due to mpv_observe_property().
+ * See also mpv_event and mpv_event_property.
+ */
+ MPV_EVENT_PROPERTY_CHANGE = 22
} mpv_event_id;
/**
@@ -980,8 +1036,9 @@ typedef struct mpv_event {
*/
uint64_t reply_userdata;
/**
- * The meaning and contents of data member depend on the event_id:
+ * The meaning and contents of the data member depend on the event_id:
* MPV_EVENT_GET_PROPERTY_REPLY: mpv_event_property*
+ * MPV_EVENT_PROPERTY_CHANGE: mpv_event_property*
* MPV_EVENT_LOG_MESSAGE: mpv_event_log_message*
* MPV_EVENT_PAUSE: mpv_event_pause_reason*
* MPV_EVENT_UNPAUSE: mpv_event_pause_reason*
diff --git a/player/client.c b/player/client.c
index f33dcc2bf6..f35cb4a659 100644
--- a/player/client.c
+++ b/player/client.c
@@ -12,6 +12,7 @@
*/
#include <stddef.h>
+#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
@@ -30,6 +31,18 @@
#include "core.h"
#include "client.h"
+/*
+ * Locking hierarchy:
+ *
+ * MPContext > mp_client_api.lock > mpv_handle.lock
+ *
+ * MPContext strictly speaking has no locks, and instead implicitly managed
+ * by MPContext.dispatch, which basically stops the playback thread at defined
+ * points in order to let clients access it in a synchronized manner. Since
+ * MPContext code accesses the client API, it's on top of the lock hierarchy.
+ *
+ */
+
struct mp_client_api {
struct MPContext *mpctx;
@@ -40,6 +53,19 @@ struct mp_client_api {
int num_clients;
};
+struct observe_property {
+ char *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 dead; // property unobserved while retrieving value
+ bool value_valid;
+ union m_option_value value;
+ struct mpv_handle *client;
+};
+
struct mpv_handle {
// -- immmutable
char *name;
@@ -49,6 +75,7 @@ struct mpv_handle {
// -- not thread-safe
struct mpv_event *cur_event;
+ struct mpv_event_property cur_property_event;
pthread_mutex_t lock;
pthread_cond_t wakeup;
@@ -68,10 +95,17 @@ struct mpv_handle {
int num_events; // number of readable events
int reserved_events; // number of entries reserved for replies
+ struct observe_property **properties;
+ int num_properties;
+ int lowest_changed;
+ int properties_updating;
+
struct mp_log_buffer *messages;
int messages_level;
};
+static bool gen_property_change_event(struct mpv_handle *ctx);
+
void mp_clients_init(struct MPContext *mpctx)
{
mpctx->clients = talloc_ptrtype(NULL, mpctx->clients);
@@ -198,7 +232,7 @@ void mpv_destroy(mpv_handle *ctx)
// yet replied. In order to avoid that trying to reply to a removed client
// causes a crash, block until all asynchronous requests were served.
ctx->event_mask = 0;
- while (ctx->reserved_events)
+ while (ctx->reserved_events || ctx->properties_updating)
pthread_cond_wait(&ctx->wakeup, &ctx->lock);
pthread_mutex_unlock(&ctx->lock);
@@ -440,6 +474,8 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
talloc_steal(event, event->data);
break;
}
+ if (gen_property_change_event(ctx))
+ break;
if (ctx->shutdown) {
event->event_id = MPV_EVENT_SHUTDOWN;
break;
@@ -941,6 +977,181 @@ int mpv_get_property_async(mpv_handle *ctx, uint64_t ud, const char *name,
return run_async(ctx, getproperty_fn, req);
}
+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->value);
+}
+
+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))
+ 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);
+ struct observe_property *prop = talloc_ptrtype(ctx, prop);
+ talloc_set_destructor(prop, property_free);
+ *prop = (struct observe_property){
+ .client = ctx,
+ .name = talloc_strdup(prop, name),
+ .reply_id = userdata,
+ .format = format,
+ .changed = true,
+ .need_new_value = true,
+ };
+ MP_TARRAY_APPEND(ctx, ctx->properties, ctx->num_properties, prop);
+ ctx->lowest_changed = 0;
+ pthread_mutex_unlock(&ctx->lock);
+ return 0;
+}
+
+int mpv_unobserve_property(mpv_handle *ctx, uint64_t userdata)
+{
+ pthread_mutex_lock(&ctx->lock);
+ int count = 0;
+ for (int n = ctx->num_properties - 1; n >= 0; n--) {
+ struct observe_property *prop = ctx->properties[n];
+ if (prop->reply_id == userdata) {
+ if (prop->updating) {
+ prop->dead = true;
+ } else {
+ // In case mpv_unobserve_property() is called after mpv_wait_event()
+ // returned, and the mpv_event still references the name somehow,
+ // make sure it's not freed while in use. The same can happen
+ // with the value update mechanism.
+ talloc_steal(ctx->cur_event, prop);
+ }
+ MP_TARRAY_REMOVE_AT(ctx->properties, ctx->num_properties, n);
+ count++;
+ }
+ }
+ ctx->lowest_changed = 0;
+ pthread_mutex_unlock(&ctx->lock);
+ return count;
+}
+
+static int prefix_len(const char *p)
+{
+ const char *end = strchr(p, '/');
+ return end ? end - p : strlen(p);
+}
+
+static bool match_property(const char *a, const char *b)
+{
+ if (strcmp(b, "*") == 0)
+ return true;
+ int len_a = prefix_len(a);
+ int len_b = prefix_len(b);
+ return strncmp(a, b, MPMIN(len_a, len_b)) == 0;
+}
+
+// Broadcast that properties have changed.
+void mp_client_property_change(struct MPContext *mpctx, const char **list)
+{
+ struct mp_client_api *clients = mpctx->clients;
+
+ pthread_mutex_lock(&clients->lock);
+
+ for (int n = 0; n < clients->num_clients; n++) {
+ struct mpv_handle *client = clients->clients[n];
+ pthread_mutex_lock(&client->lock);
+
+ client->lowest_changed = client->num_properties;
+ for (int i = 0; i < client->num_properties; i++) {
+ struct observe_property *prop = client->properties[i];
+ if (!prop->changed && !prop->need_new_value) {
+ for (int x = 0; list && list[x]; x++) {
+ if (match_property(prop->name, list[x])) {
+ prop->changed = prop->need_new_value = true;
+ break;
+ }
+ }
+ }
+ if ((prop->changed || prop->updating) && i < client->lowest_changed)
+ client->lowest_changed = i;
+ }
+ if (client->lowest_changed < client->num_properties)
+ wakeup_client(client);
+ pthread_mutex_unlock(&client->lock);
+ }
+
+ pthread_mutex_unlock(&clients->lock);
+}
+
+static void update_prop(void *p)
+{
+ struct observe_property *prop = p;
+ struct mpv_handle *ctx = prop->client;
+
+ const struct m_option *type = get_mp_type_get(prop->format);
+ union m_option_value val = {0};
+
+ struct getproperty_request req = {
+ .mpctx = ctx->mpctx,
+ .name = prop->name,
+ .format = prop->format,
+ .data = &val,
+ };
+
+ getproperty_fn(&req);
+
+ pthread_mutex_lock(&ctx->lock);
+ ctx->properties_updating--;
+ prop->updating = false;
+ prop->changed = true;
+ prop->value_valid = req.status >= 0;
+ if (prop->value_valid) {
+ m_option_free(type, &prop->value);
+ memcpy(&prop->value, &val, type->type->size);
+ }
+ if (prop->dead)
+ talloc_steal(ctx->cur_event, prop);
+ wakeup_client(ctx);
+ pthread_mutex_unlock(&ctx->lock);
+}
+
+// Set ctx->cur_event to a generated property change event, if there is any
+// outstanding property.
+static bool gen_property_change_event(struct mpv_handle *ctx)
+{
+ int start = ctx->lowest_changed;
+ ctx->lowest_changed = ctx->num_properties;
+ for (int n = start; n < ctx->num_properties; n++) {
+ struct observe_property *prop = ctx->properties[n];
+ if ((prop->changed || prop->updating) && n < ctx->lowest_changed)
+ ctx->lowest_changed = n;
+ if (prop->changed) {
+ bool new_val = prop->need_new_value;
+ prop->changed = prop->need_new_value = false;
+ if (prop->format && new_val) {
+ ctx->properties_updating++;
+ prop->updating = true;
+ mp_dispatch_enqueue(ctx->mpctx->dispatch, update_prop, prop);
+ } else {
+ ctx->cur_property_event = (struct mpv_event_property){
+ .name = prop->name,
+ .format = prop->value_valid ? prop->format : 0,
+ };
+ if (prop->value_valid)
+ ctx->cur_property_event.data = &prop->value;
+ *ctx->cur_event = (struct mpv_event){
+ .event_id = MPV_EVENT_PROPERTY_CHANGE,
+ .reply_userdata = prop->reply_id,
+ .data = &ctx->cur_property_event,
+ };
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
int mpv_request_log_messages(mpv_handle *ctx, const char *min_level)
{
int level = -1;
@@ -1026,6 +1237,7 @@ static const char *event_table[] = {
[MPV_EVENT_METADATA_UPDATE] = "metadata-update",
[MPV_EVENT_SEEK] = "seek",
[MPV_EVENT_PLAYBACK_RESTART] = "playback-restart",
+ [MPV_EVENT_PROPERTY_CHANGE] = "property-change",
};
const char *mpv_event_name(mpv_event_id event)
diff --git a/player/client.h b/player/client.h
index 4c0c23c614..6e078e9d7b 100644
--- a/player/client.h
+++ b/player/client.h
@@ -17,6 +17,7 @@ int mp_clients_num(struct MPContext *mpctx);
void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data);
int mp_client_send_event(struct MPContext *mpctx, const char *client_name,
int event, void *data);
+void mp_client_property_change(struct MPContext *mpctx, const char **list);
struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name);
struct mp_log *mp_client_get_log(struct mpv_handle *ctx);
diff --git a/player/command.c b/player/command.c
index 14654af14b..127ef1e42d 100644
--- a/player/command.c
+++ b/player/command.c
@@ -23,6 +23,7 @@
#include <stdbool.h>
#include <assert.h>
#include <time.h>
+#include <pthread.h>
#include <sys/types.h>
#include <libavutil/avstring.h>
@@ -2272,6 +2273,29 @@ static const m_option_t mp_properties[] = {
{0},
};
+// Each entry describes which properties an event (possibly) changes.
+#define E(x, ...) [x] = (const char*[]){__VA_ARGS__, NULL}
+const char **mp_event_property_change[] = {
+ E(MPV_EVENT_START_FILE, "*"),
+ E(MPV_EVENT_END_FILE, "*"),
+ E(MPV_EVENT_FILE_LOADED, "*"),
+ E(MPV_EVENT_TRACKS_CHANGED, "track-list"),
+ E(MPV_EVENT_TRACK_SWITCHED, "vid", "video", "aid", "audio", "sid", "sub",
+ "secondary-sid"),
+ E(MPV_EVENT_IDLE, "*"),
+ E(MPV_EVENT_PAUSE, "pause"),
+ E(MPV_EVENT_UNPAUSE, "pause"),
+ E(MPV_EVENT_TICK, "time-pos", "stream-pos", "stream-time-pos", "avsync",
+ "percent-pos", "time-remaining", "playtime-remaining"),
+ E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
+ "video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
+ "width", "height", "fps", "aspect"),
+ E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate",
+ "samplerate", "channels", "audio"),
+ E(MPV_EVENT_METADATA_UPDATE, "metadata"),
+};
+#undef E
+
const struct m_option *mp_get_property_list(void)
{
return mp_properties;
@@ -3468,4 +3492,11 @@ void mp_notify(struct MPContext *mpctx, int event, void *arg)
ctx->last_seek_pts = MP_NOPTS_VALUE;
mp_client_broadcast_event(mpctx, event, arg);
+ if (event >= 0 && event < MP_ARRAY_SIZE(mp_event_property_change))
+ mp_client_property_change(mpctx, mp_event_property_change[event]);
+}
+
+void mp_notify_property(struct MPContext *mpctx, char *property)
+{
+ mp_client_property_change(mpctx, (const char*[]){property, NULL});
}
diff --git a/player/command.h b/player/command.h
index a04bfac343..795a759906 100644
--- a/player/command.h
+++ b/player/command.h
@@ -34,7 +34,9 @@ int mp_property_do(const char* name, int action, void* val,
struct MPContext *mpctx);
const struct m_option *mp_get_property_list(void);
+int mp_find_property_index(const char *property);
void mp_notify(struct MPContext *mpctx, int event, void *arg);
+void mp_notify_property(struct MPContext *mpctx, char *property);
#endif /* MPLAYER_COMMAND_H */