/* Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "common/common.h" #include "common/msg.h" #include "common/msg_control.h" #include "input/input.h" #include "misc/ring.h" #include "options/m_config.h" #include "options/m_option.h" #include "options/m_property.h" #include "osdep/threads.h" #include "osdep/timer.h" #include "command.h" #include "core.h" #include "client.h" struct mp_client_api { struct MPContext *mpctx; pthread_mutex_t lock; // -- protected by lock struct mpv_handle **clients; int num_clients; }; struct mpv_handle { // -- immmutable char *name; struct mp_log *log; struct MPContext *mpctx; struct mp_client_api *clients; // -- not thread-safe struct mpv_event *cur_event; pthread_mutex_t lock; pthread_cond_t wakeup; // -- protected by lock uint64_t event_mask; bool queued_wakeup; bool shutdown; bool choke_warning; void (*wakeup_cb)(void *d); void *wakeup_cb_ctx; struct mp_ring *events; // stores mpv_event int max_events; // allocated number of entries in events int reserved_events; // number of entries reserved for replies struct mp_log_buffer *messages; int messages_level; }; void mp_clients_init(struct MPContext *mpctx) { mpctx->clients = talloc_ptrtype(NULL, mpctx->clients); *mpctx->clients = (struct mp_client_api) { .mpctx = mpctx, }; pthread_mutex_init(&mpctx->clients->lock, NULL); } void mp_clients_destroy(struct MPContext *mpctx) { if (!mpctx->clients) return; assert(mpctx->clients->num_clients == 0); pthread_mutex_destroy(&mpctx->clients->lock); talloc_free(mpctx->clients); mpctx->clients = NULL; } int mp_clients_num(struct MPContext *mpctx) { pthread_mutex_lock(&mpctx->clients->lock); int num_clients = mpctx->clients->num_clients; pthread_mutex_unlock(&mpctx->clients->lock); return num_clients; } static struct mpv_handle *find_client(struct mp_client_api *clients, const char *name) { for (int n = 0; n < clients->num_clients; n++) { if (strcmp(clients->clients[n]->name, name) == 0) return clients->clients[n]; } return NULL; } struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name) { pthread_mutex_lock(&clients->lock); char *unique_name = NULL; if (find_client(clients, name)) { for (int n = 2; n < 1000; n++) { unique_name = talloc_asprintf(NULL, "%s%d", name, n); if (!find_client(clients, unique_name)) break; talloc_free(unique_name); unique_name = NULL; } if (!unique_name) { pthread_mutex_unlock(&clients->lock); return NULL; } } if (!unique_name) unique_name = talloc_strdup(NULL, name); int num_events = 1000; struct mpv_handle *client = talloc_ptrtype(NULL, client); *client = (struct mpv_handle){ .name = talloc_steal(client, unique_name), .log = mp_log_new(client, clients->mpctx->log, unique_name), .mpctx = clients->mpctx, .clients = clients, .cur_event = talloc_zero(client, struct mpv_event), .events = mp_ring_new(client, num_events * sizeof(struct mpv_event)), .max_events = num_events, .event_mask = ((uint64_t)-1) & ~(1ULL << MPV_EVENT_TICK), }; pthread_mutex_init(&client->lock, NULL); pthread_cond_init(&client->wakeup, NULL); MP_TARRAY_APPEND(clients, clients->clients, clients->num_clients, client); pthread_mutex_unlock(&clients->lock); return client; } const char *mpv_client_name(mpv_handle *ctx) { return ctx->name; } struct mp_log *mp_client_get_log(struct mpv_handle *ctx) { return ctx->log; } static void wakeup_client(struct mpv_handle *ctx) { pthread_cond_signal(&ctx->wakeup); if (ctx->wakeup_cb) ctx->wakeup_cb(ctx->wakeup_cb_ctx); } void mpv_set_wakeup_callback(mpv_handle *ctx, void (*cb)(void *d), void *d) { pthread_mutex_lock(&ctx->lock); ctx->wakeup_cb = cb; ctx->wakeup_cb_ctx = d; pthread_mutex_unlock(&ctx->lock); } void mpv_suspend(mpv_handle *ctx) { mp_dispatch_suspend(ctx->mpctx->dispatch); } void mpv_resume(mpv_handle *ctx) { mp_dispatch_resume(ctx->mpctx->dispatch); } void mpv_destroy(mpv_handle *ctx) { if (!ctx) return; pthread_mutex_lock(&ctx->lock); // reserved_events equals the number of asynchronous requests that weren't // yet replied. In order to avoid that trying to reply to a removed event // causes a crash, block until all asynchronous requests were served. ctx->event_mask = 0; while (ctx->reserved_events) pthread_cond_wait(&ctx->wakeup, &ctx->lock); pthread_mutex_unlock(&ctx->lock); struct mp_client_api *clients = ctx->clients; pthread_mutex_lock(&clients->lock); for (int n = 0; n < clients->num_clients; n++) { if (clients->clients[n] == ctx) { MP_TARRAY_REMOVE_AT(clients->clients, clients->num_clients, n); while (mp_ring_buffered(ctx->events)) { struct mpv_event event; int r = mp_ring_read(ctx->events, (unsigned char *)&event, sizeof(event)); assert(r == sizeof(event)); talloc_free(event.data); } mp_msg_log_buffer_destroy(ctx->messages); pthread_cond_destroy(&ctx->wakeup); pthread_mutex_destroy(&ctx->lock); talloc_free(ctx); ctx = NULL; // shutdown_clients() sleeps to avoid wasting CPU if (clients->mpctx->input) mp_input_wakeup(clients->mpctx->input); // TODO: make core quit if there are no clients break; } } pthread_mutex_unlock(&clients->lock); assert(!ctx); } mpv_handle *mpv_create(void) { struct MPContext *mpctx = mp_create(); mpv_handle *ctx = mp_new_client(mpctx->clients, "main"); if (ctx) { // Set some defaults. mpv_set_option_string(ctx, "idle", "yes"); mpv_set_option_string(ctx, "terminal", "no"); mpv_set_option_string(ctx, "osc", "no"); mpv_set_option_string(ctx, "input-default-bindings", "no"); } else { mp_destroy(mpctx); } return ctx; } static void *playback_thread(void *p) { struct MPContext *mpctx = p; pthread_detach(pthread_self()); mp_play_files(mpctx); // This actually waits until all clients are gone before actually // destroying mpctx. mp_destroy(mpctx); return NULL; } int mpv_initialize(mpv_handle *ctx) { if (mp_initialize(ctx->mpctx) < 0) return MPV_ERROR_INVALID_PARAMETER; pthread_t thread; if (pthread_create(&thread, NULL, playback_thread, ctx->mpctx) != 0) return MPV_ERROR_NOMEM; return 0; } // Reserve an entry in the ring buffer. This can be used to guarantee that the // reply can be made, even if the buffer becomes congested _after_ sending // the request. // Returns an error code if the buffer is full. 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->max_events) { ctx->reserved_events++; res = 0; } pthread_mutex_unlock(&ctx->lock); return res; } static int send_event(struct mpv_handle *ctx, struct mpv_event *event) { pthread_mutex_lock(&ctx->lock); if (!(ctx->event_mask & (1ULL << event->event_id))) { pthread_mutex_unlock(&ctx->lock); return 0; } int num_events = mp_ring_available(ctx->events) / sizeof(*event); int r = 0; if (num_events > ctx->reserved_events) { r = mp_ring_write(ctx->events, (unsigned char *)event, sizeof(*event)); if (r != sizeof(*event)) abort(); wakeup_client(ctx); } if (!r && !ctx->choke_warning) { mp_err(ctx->log, "Too many events queued.\n"); ctx->choke_warning = true; } pthread_mutex_unlock(&ctx->lock); return r ? 0 : -1; } // Send a reply; the reply must have been previously reserved with // reserve_reply (otherwise, use send_event()). static void send_reply(struct mpv_handle *ctx, uint64_t userdata, struct mpv_event *event) { event->reply_userdata = userdata; pthread_mutex_lock(&ctx->lock); assert(ctx->reserved_events > 0); ctx->reserved_events--; int r = mp_ring_write(ctx->events, (unsigned char *)event, sizeof(*event)); if (r != sizeof(*event)) abort(); wakeup_client(ctx); pthread_mutex_unlock(&ctx->lock); } static void status_reply(struct mpv_handle *ctx, int event, uint64_t userdata, int status) { struct mpv_event reply = { .event_id = event, .error = status, }; send_reply(ctx, userdata, &reply); } // set ev->data to a new copy of the original data static void dup_event_data(struct mpv_event *ev) { switch (ev->event_id) { case MPV_EVENT_PAUSE: case MPV_EVENT_UNPAUSE: ev->data = talloc_memdup(NULL, ev->data, sizeof(mpv_event_pause_reason)); break; default: // Doesn't use events with memory allocation. if (ev->data) abort(); } } void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data) { struct mp_client_api *clients = mpctx->clients; pthread_mutex_lock(&clients->lock); for (int n = 0; n < clients->num_clients; n++) { struct mpv_event event_data = { .event_id = event, .data = data, }; dup_event_data(&event_data); send_event(clients->clients[n], &event_data); } pthread_mutex_unlock(&clients->lock); } int mp_client_send_event(struct MPContext *mpctx, const char *client_name, int event, void *data) { struct mp_client_api *clients = mpctx->clients; int r = 0; struct mpv_event event_data = { .event_id = event, .data = data, }; pthread_mutex_lock(&clients->lock); struct mpv_handle *ctx = find_client(clients, client_name); if (ctx) { r = send_event(ctx, &event_data); } else { r = -1; talloc_free(data); } pthread_mutex_unlock(&clients->lock); return r; } int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable) { if (!mpv_event_name(event) || enable < 0 || enable > 1) return MPV_ERROR_INVALID_PARAMETER; pthread_mutex_lock(&ctx->lock); uint64_t bit = 1LLU << event; ctx->event_mask = enable ? ctx->event_mask | bit : ctx->event_mask & ~bit; pthread_mutex_unlock(&ctx->lock); return 0; } mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout) { mpv_event *event = ctx->cur_event; struct timespec deadline = mpthread_get_deadline(timeout); pthread_mutex_lock(&ctx->lock); *event = (mpv_event){0}; talloc_free_children(event); while (1) { if (mp_ring_buffered(ctx->events)) { int r = mp_ring_read(ctx->events, (unsigned char*)event, sizeof(*event)); if (r != sizeof(*event)) abort(); talloc_steal(event, event->data); break; } if (ctx->shutdown) { event->event_id = MPV_EVENT_SHUTDOWN; break; } 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) { event->event_id = MPV_EVENT_LOG_MESSAGE; struct mpv_event_log_message *cmsg = talloc_ptrtype(event, cmsg); *cmsg = (struct mpv_event_log_message){ .prefix = talloc_steal(event, msg->prefix), .level = mp_log_levels[msg->level], .text = talloc_steal(event, msg->text), }; event->data = cmsg; talloc_free(msg); break; } } if (ctx->queued_wakeup) break; if (timeout <= 0) break; pthread_cond_timedwait(&ctx->wakeup, &ctx->lock, &deadline); } ctx->queued_wakeup = false; pthread_mutex_unlock(&ctx->lock); return event; } void mpv_wakeup(mpv_handle *ctx) { pthread_mutex_lock(&ctx->lock); ctx->queued_wakeup = true; wakeup_client(ctx); pthread_mutex_unlock(&ctx->lock); } // map client API types to internal types static const struct m_option type_conv[] = { [MPV_FORMAT_STRING] = { .type = CONF_TYPE_STRING }, [MPV_FORMAT_FLAG] = { .type = CONF_TYPE_FLAG }, [MPV_FORMAT_INT64] = { .type = CONF_TYPE_INT64 }, [MPV_FORMAT_DOUBLE] = { .type = CONF_TYPE_DOUBLE }, [MPV_FORMAT_NODE] = { .type = CONF_TYPE_NODE }, }; static const struct m_option *get_mp_type(mpv_format format) { if (format < 0 || format >= MP_ARRAY_SIZE(type_conv)) return NULL; if (!type_conv[format].type) return NULL; return &type_conv[format]; } // for read requests - MPV_FORMAT_OSD_STRING special handling static const struct m_option *get_mp_type_get(mpv_format format) { if (format == MPV_FORMAT_OSD_STRING) format = MPV_FORMAT_STRING; // it's string data, just other semantics return get_mp_type(format); } // move src->dst, and do implicit conversion if possible (conversions to or // from strings are handled otherwise) static bool conv_node_to_format(void *dst, mpv_format dst_fmt, mpv_node *src) { if (dst_fmt == src->format) { const struct m_option *type = get_mp_type(dst_fmt); memcpy(dst, &src->u, type->type->size); return true; } if (dst_fmt == MPV_FORMAT_DOUBLE && src->format == MPV_FORMAT_INT64) { *(double *)dst = src->u.int64; return true; } return false; } void mpv_free_node_contents(mpv_node *node) { static const struct m_option type = { .type = CONF_TYPE_NODE }; m_option_free(&type, node); } int mpv_set_option(mpv_handle *ctx, const char *name, mpv_format format, void *data) { if (ctx->mpctx->initialized) { char prop[100]; snprintf(prop, sizeof(prop), "options/%s", name); int err = mpv_set_property(ctx, prop, format, data); switch (err) { case MPV_ERROR_PROPERTY_UNAVAILABLE: case MPV_ERROR_PROPERTY_ERROR: return MPV_ERROR_OPTION_ERROR; case MPV_ERROR_PROPERTY_FORMAT: return MPV_ERROR_OPTION_FORMAT; case MPV_ERROR_PROPERTY_NOT_FOUND: return MPV_ERROR_OPTION_NOT_FOUND; default: return err; } } else { const struct m_option *type = get_mp_type(format); if (!type) return MPV_ERROR_OPTION_FORMAT; struct mpv_node tmp; if (format != MPV_FORMAT_NODE) { tmp.format = format; memcpy(&tmp.u, data, type->type->size); format = MPV_FORMAT_NODE; data = &tmp; } int err = m_config_set_option_node(ctx->mpctx->mconfig, bstr0(name), data, 0); switch (err) { case M_OPT_MISSING_PARAM: case M_OPT_INVALID: return MPV_ERROR_OPTION_ERROR; case M_OPT_OUT_OF_RANGE: return MPV_ERROR_OPTION_FORMAT; case M_OPT_UNKNOWN: return MPV_ERROR_OPTION_NOT_FOUND; default: if (err >= 0) return 0; return MPV_ERROR_OPTION_ERROR; } } } int mpv_set_option_string(mpv_handle *ctx, const char *name, const char *data) { return mpv_set_option(ctx, name, MPV_FORMAT_STRING, &data); } // Run a command in the playback thread. // Note: once some things are fixed (like vo_opengl not being safe to be // called from any thread other than the playback thread), this can // be replaced by a simpler method. static void run_locked(mpv_handle *ctx, void (*fn)(void *fn_data), void *fn_data) { mp_dispatch_run(ctx->mpctx->dispatch, fn, fn_data); } // Run a command asynchronously. It's the responsibility of the caller to // actually send the reply. This helper merely saves a small part of the // required boilerplate to do so. // fn: callback to execute the request // fn_data: opaque caller-defined argument for fn. This will be automatically // freed with talloc_free(fn_data). static int run_async(mpv_handle *ctx, void (*fn)(void *fn_data), void *fn_data) { int err = reserve_reply(ctx); if (err < 0) { talloc_free(fn_data); return err; } mp_dispatch_enqueue_autofree(ctx->mpctx->dispatch, fn, fn_data); return 0; } struct cmd_request { struct MPContext *mpctx; struct mp_cmd *cmd; int status; struct mpv_handle *reply_ctx; uint64_t userdata; }; static void cmd_fn(void *data) { struct cmd_request *req = data; run_command(req->mpctx, req->cmd); req->status = 0; talloc_free(req->cmd); if (req->reply_ctx) { status_reply(req->reply_ctx, MPV_EVENT_COMMAND_REPLY, req->userdata, req->status); } } static int run_client_command(mpv_handle *ctx, struct mp_cmd *cmd) { if (!ctx->mpctx->initialized) return MPV_ERROR_UNINITIALIZED; if (!cmd) return MPV_ERROR_INVALID_PARAMETER; struct cmd_request req = { .mpctx = ctx->mpctx, .cmd = cmd, }; run_locked(ctx, cmd_fn, &req); return req.status; } int mpv_command(mpv_handle *ctx, const char **args) { return run_client_command(ctx, mp_input_parse_cmd_strv(ctx->log, 0, args, ctx->name)); } int mpv_command_string(mpv_handle *ctx, const char *args) { return run_client_command(ctx, mp_input_parse_cmd(ctx->mpctx->input, bstr0((char*)args), ctx->name)); } int mpv_command_async(mpv_handle *ctx, uint64_t ud, const char **args) { if (!ctx->mpctx->initialized) return MPV_ERROR_UNINITIALIZED; struct mp_cmd *cmd = mp_input_parse_cmd_strv(ctx->log, 0, args, ""); if (!cmd) return MPV_ERROR_INVALID_PARAMETER; struct cmd_request *req = talloc_ptrtype(NULL, req); *req = (struct cmd_request){ .mpctx = ctx->mpctx, .cmd = cmd, .reply_ctx = ctx, .userdata = ud, }; return run_async(ctx, cmd_fn, req); } static int translate_property_error(int errc) { switch (errc) { case M_PROPERTY_OK: return 0; case M_PROPERTY_ERROR: return MPV_ERROR_PROPERTY_ERROR; case M_PROPERTY_UNAVAILABLE: return MPV_ERROR_PROPERTY_UNAVAILABLE; case M_PROPERTY_NOT_IMPLEMENTED: return MPV_ERROR_PROPERTY_ERROR; case M_PROPERTY_UNKNOWN: return MPV_ERROR_PROPERTY_NOT_FOUND; case M_PROPERTY_INVALID_FORMAT: return MPV_ERROR_PROPERTY_FORMAT; // shouldn't happen default: return MPV_ERROR_PROPERTY_ERROR; } } struct setproperty_request { struct MPContext *mpctx; const char *name; int format; void *data; int status; struct mpv_handle *reply_ctx; uint64_t userdata; }; static void setproperty_fn(void *arg) { struct setproperty_request *req = arg; const struct m_option *type = get_mp_type(req->format); int err; switch (req->format) { case MPV_FORMAT_STRING: { // Go through explicit string conversion. M_PROPERTY_SET_NODE doesn't // do this, because it tries to be somewhat type-strict. But the client // needs a way to set everything by string. char *s = *(char **)req->data; err = mp_property_do(req->name, M_PROPERTY_SET_STRING, s, req->mpctx); break; } case MPV_FORMAT_NODE: case MPV_FORMAT_FLAG: case MPV_FORMAT_INT64: case MPV_FORMAT_DOUBLE: { struct mpv_node node; if (req->format == MPV_FORMAT_NODE) { node = *(struct mpv_node *)req->data; } else { // These are basically emulated via mpv_node. node.format = req->format; memcpy(&node.u, req->data, type->type->size); } err = mp_property_do(req->name, M_PROPERTY_SET_NODE, &node, req->mpctx); break; } default: abort(); } req->status = translate_property_error(err); if (req->reply_ctx) { status_reply(req->reply_ctx, MPV_EVENT_SET_PROPERTY_REPLY, req->userdata, req->status); } } int mpv_set_property(mpv_handle *ctx, const char *name, mpv_format format, void *data) { if (!ctx->mpctx->initialized) return MPV_ERROR_UNINITIALIZED; if (!get_mp_type(format)) return MPV_ERROR_PROPERTY_FORMAT; struct setproperty_request req = { .mpctx = ctx->mpctx, .name = name, .format = format, .data = data, }; run_locked(ctx, setproperty_fn, &req); return req.status; } int mpv_set_property_string(mpv_handle *ctx, const char *name, const char *data) { return mpv_set_property(ctx, name, MPV_FORMAT_STRING, &data); } static void free_prop_set_req(void *ptr) { struct setproperty_request *req = ptr; const struct m_option *type = get_mp_type(req->format); m_option_free(type, req->data); } int mpv_set_property_async(mpv_handle *ctx, uint64_t ud, const char *name, mpv_format format, void *data) { const struct m_option *type = get_mp_type(format); if (!ctx->mpctx->initialized) return MPV_ERROR_UNINITIALIZED; if (!type) return MPV_ERROR_PROPERTY_FORMAT; struct setproperty_request *req = talloc_ptrtype(NULL, req); *req = (struct setproperty_request){ .mpctx = ctx->mpctx, .name = talloc_strdup(req, name), .format = format, .data = talloc_zero_size(req, type->type->size), .reply_ctx = ctx, .userdata = ud, }; m_option_copy(type, req->data, data); talloc_set_destructor(req, free_prop_set_req); return run_async(ctx, setproperty_fn, req); } struct getproperty_request { struct MPContext *mpctx; const char *name; mpv_format format; void *data; int status; struct mpv_handle *reply_ctx; uint64_t userdata; }; static void free_prop_data(void *ptr) { struct mpv_event_property *prop = ptr; const struct m_option *type = get_mp_type_get(prop->format); m_option_free(type, prop->data); } 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}; void *data = req->data ? req->data : &xdata; int err = -1; switch (req->format) { case MPV_FORMAT_OSD_STRING: err = mp_property_do(req->name, M_PROPERTY_PRINT, data, req->mpctx); break; case MPV_FORMAT_STRING: { char *s = NULL; err = mp_property_do(req->name, M_PROPERTY_GET_STRING, &s, req->mpctx); if (err == M_PROPERTY_OK) *(char **)req->data = s; break; } case MPV_FORMAT_NODE: case MPV_FORMAT_FLAG: case MPV_FORMAT_INT64: case MPV_FORMAT_DOUBLE: { struct mpv_node node = {{0}}; err = mp_property_do(req->name, M_PROPERTY_GET_NODE, &node, req->mpctx); if (err == M_PROPERTY_NOT_IMPLEMENTED) { // Go through explicit string conversion. Same reasoning as on the // GET code path. char *s = NULL; err = mp_property_do(req->name, M_PROPERTY_GET_STRING, &s, req->mpctx); if (err != M_PROPERTY_OK) break; node.format = MPV_FORMAT_STRING; node.u.string = s; } else if (err <= 0) break; if (req->format == MPV_FORMAT_NODE) { *(struct mpv_node *)data = node; } else { if (!conv_node_to_format(data, req->format, &node)) { err = M_PROPERTY_INVALID_FORMAT; mpv_free_node_contents(&node); } } break; } default: abort(); } req->status = translate_property_error(err); if (req->reply_ctx) { struct mpv_event_property *prop = talloc_ptrtype(NULL, prop); *prop = (struct mpv_event_property){ .name = talloc_steal(prop, (char *)req->name), .format = req->format, .data = talloc_size(prop, type->type->size), }; // move data memcpy(prop->data, &xdata, type->type->size); talloc_set_destructor(prop, free_prop_data); struct mpv_event reply = { .event_id = MPV_EVENT_GET_PROPERTY_REPLY, .data = prop, .error = req->status, .reply_userdata = req->userdata, }; send_reply(req->reply_ctx, req->userdata, &reply); } } int mpv_get_property(mpv_handle *ctx, const char *name, mpv_format format, void *data) { if (!ctx->mpctx->initialized) return MPV_ERROR_UNINITIALIZED; if (!data) return MPV_ERROR_INVALID_PARAMETER; if (!get_mp_type_get(format)) return MPV_ERROR_PROPERTY_FORMAT; struct getproperty_request req = { .mpctx = ctx->mpctx, .name = name, .format = format, .data = data, }; run_locked(ctx, getproperty_fn, &req); return req.status; } char *mpv_get_property_string(mpv_handle *ctx, const char *name) { char *str = NULL; mpv_get_property(ctx, name, MPV_FORMAT_STRING, &str); return str; } char *mpv_get_property_osd_string(mpv_handle *ctx, const char *name) { char *str = NULL; mpv_get_property(ctx, name, MPV_FORMAT_OSD_STRING, &str); return str; } int mpv_get_property_async(mpv_handle *ctx, uint64_t ud, const char *name, mpv_format format) { if (!ctx->mpctx->initialized) return MPV_ERROR_UNINITIALIZED; if (!get_mp_type_get(format)) return MPV_ERROR_PROPERTY_FORMAT; struct getproperty_request *req = talloc_ptrtype(NULL, req); *req = (struct getproperty_request){ .mpctx = ctx->mpctx, .name = talloc_strdup(req, name), .format = format, .reply_ctx = ctx, .userdata = ud, }; return run_async(ctx, getproperty_fn, req); } int mpv_request_log_messages(mpv_handle *ctx, const char *min_level) { int level = -1; for (int n = 0; n < MSGL_MAX + 1; n++) { if (mp_log_levels[n] && strcmp(min_level, mp_log_levels[n]) == 0) { level = n; break; } } if (level < 0 && strcmp(min_level, "no") != 0) return MPV_ERROR_INVALID_PARAMETER; pthread_mutex_lock(&ctx->lock); if (!ctx->messages) ctx->messages_level = -1; if (ctx->messages_level != level) { mp_msg_log_buffer_destroy(ctx->messages); ctx->messages = NULL; if (level >= 0) { ctx->messages = mp_msg_log_buffer_new(ctx->mpctx->global, 1000, level); } ctx->messages_level = level; } pthread_mutex_unlock(&ctx->lock); return 0; } unsigned long mpv_client_api_version(void) { return MPV_CLIENT_API_VERSION; } static const char *err_table[] = { [-MPV_ERROR_SUCCESS] = "success", [-MPV_ERROR_EVENT_QUEUE_FULL] = "event queue full", [-MPV_ERROR_NOMEM] = "memory allocation failed", [-MPV_ERROR_UNINITIALIZED] = "core not uninitialized", [-MPV_ERROR_INVALID_PARAMETER] = "invalid parameter", [-MPV_ERROR_OPTION_NOT_FOUND] = "option not found", [-MPV_ERROR_OPTION_FORMAT] = "unsupported format for accessing option", [-MPV_ERROR_OPTION_ERROR] = "error setting option", [-MPV_ERROR_PROPERTY_NOT_FOUND] = "property not found", [-MPV_ERROR_PROPERTY_FORMAT] = "unsupported format for accessing property", [-MPV_ERROR_PROPERTY_UNAVAILABLE] = "property unavailable", [-MPV_ERROR_PROPERTY_ERROR] = "error accessing property", }; const char *mpv_error_string(int error) { error = -error; if (error < 0) error = 0; const char *name = NULL; if (error < MP_ARRAY_SIZE(err_table)) name = err_table[error]; return name ? name : "unknown error"; } static const char *event_table[] = { [MPV_EVENT_NONE] = "none", [MPV_EVENT_SHUTDOWN] = "shutdown", [MPV_EVENT_LOG_MESSAGE] = "log-message", [MPV_EVENT_GET_PROPERTY_REPLY] = "get-property-reply", [MPV_EVENT_SET_PROPERTY_REPLY] = "set-property-reply", [MPV_EVENT_COMMAND_REPLY] = "command-reply", [MPV_EVENT_START_FILE] = "start-file", [MPV_EVENT_END_FILE] = "end-file", [MPV_EVENT_FILE_LOADED] = "file-loaded", [MPV_EVENT_TRACKS_CHANGED] = "tracks-changed", [MPV_EVENT_TRACK_SWITCHED] = "track-switched", [MPV_EVENT_IDLE] = "idle", [MPV_EVENT_PAUSE] = "pause", [MPV_EVENT_UNPAUSE] = "unpause", [MPV_EVENT_TICK] = "tick", [MPV_EVENT_SCRIPT_INPUT_DISPATCH] = "script-input-dispatch", [MPV_EVENT_CLIENT_MESSAGE] = "client-message", [MPV_EVENT_VIDEO_RECONFIG] = "video-reconfig", [MPV_EVENT_AUDIO_RECONFIG] = "audio-reconfig", [MPV_EVENT_METADATA_UPDATE] = "metadata-update", [MPV_EVENT_SEEK] = "seek", [MPV_EVENT_PLAYBACK_RESTART] = "playback-restart", }; const char *mpv_event_name(mpv_event_id event) { if (event < 0 || event >= MP_ARRAY_SIZE(event_table)) return NULL; return event_table[event]; } void mpv_free(void *data) { talloc_free(data); } int64_t mpv_get_time_us(mpv_handle *ctx) { return mp_time_us(); }