diff options
author | Aman Gupta <aman@tmm1.net> | 2016-08-07 18:10:05 +0200 |
---|---|---|
committer | wm4 <wm4@nowhere> | 2016-08-07 19:33:20 +0200 |
commit | 7ca4a453e03d76621c7740b71ba17157c7756737 (patch) | |
tree | ad29dc38fdd7719dfd536efca12e924afdc650b1 | |
parent | 52a0cbe4568afd11ad2e24596f81712ff508112d (diff) | |
download | mpv-7ca4a453e03d76621c7740b71ba17157c7756737.tar.bz2 mpv-7ca4a453e03d76621c7740b71ba17157c7756737.tar.xz |
client API: add stream_cb API for user-defined stream implementations
Based on #2630. Some heavy changes by committer.
Signed-off-by: wm4 <wm4@nowhere>
-rw-r--r-- | libmpv/client.h | 4 | ||||
-rw-r--r-- | libmpv/mpv.def | 1 | ||||
-rw-r--r-- | libmpv/stream_cb.h | 230 | ||||
-rw-r--r-- | player/client.c | 61 | ||||
-rw-r--r-- | player/client.h | 4 | ||||
-rw-r--r-- | stream/stream.c | 19 | ||||
-rw-r--r-- | stream/stream.h | 1 | ||||
-rw-r--r-- | stream/stream_cb.c | 108 | ||||
-rw-r--r-- | wscript_build.py | 3 |
9 files changed, 428 insertions, 3 deletions
diff --git a/libmpv/client.h b/libmpv/client.h index ba072b1d7d..57eeaf0af8 100644 --- a/libmpv/client.h +++ b/libmpv/client.h @@ -299,7 +299,7 @@ typedef enum mpv_error { */ MPV_ERROR_COMMAND = -12, /** - * Generic error on loading (used with mpv_event_end_file.error). + * Generic error on loading (usually used with mpv_event_end_file.error). */ MPV_ERROR_LOADING_FAILED = -13, /** @@ -1610,7 +1610,7 @@ typedef enum mpv_sub_api { * Will return NULL if unavailable (if OpenGL support was not compiled in). * See opengl_cb.h for details. */ - MPV_SUB_API_OPENGL_CB = 1 + MPV_SUB_API_OPENGL_CB = 1, } mpv_sub_api; /** diff --git a/libmpv/mpv.def b/libmpv/mpv.def index 840ba1e5ed..0384c7db9d 100644 --- a/libmpv/mpv.def +++ b/libmpv/mpv.def @@ -37,6 +37,7 @@ mpv_set_property mpv_set_property_async mpv_set_property_string mpv_set_wakeup_callback +mpv_stream_cb_add_ro mpv_suspend mpv_terminate_destroy mpv_unobserve_property diff --git a/libmpv/stream_cb.h b/libmpv/stream_cb.h new file mode 100644 index 0000000000..1e6839cac1 --- /dev/null +++ b/libmpv/stream_cb.h @@ -0,0 +1,230 @@ +/* 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. + */ + +/* + * Note: the client API is licensed under ISC (see above) to ease + * interoperability with other licenses. But keep in mind that the + * mpv core is still mostly GPLv2+. It's up to lawyers to decide + * whether applications using this API are affected by the GPL. + * One argument against this is that proprietary applications + * using mplayer in slave mode is apparently tolerated, and this + * API is basically equivalent to slave mode. + */ + +#ifndef MPV_CLIENT_API_STREAM_CB_H_ +#define MPV_CLIENT_API_STREAM_CB_H_ + +#include "client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Warning: this API is not stable yet. + * + * Overview + * -------- + * + * This API can be used to make mpv read from a stream with a custom + * implementation. This interface is inspired by funopen on BSD and + * fopencookie on linux. The stream is backed by user-defined callbacks + * which can implement customized open, read, seek, size and close behaviors. + * + * Usage + * ----- + * + * Register your stream callbacks with the mpv_stream_cb_add_ro() function. You + * have to provide a mpv_stream_cb_open_ro_fn callback to it (open_fn argument). + * + * Once registered, you can `loadfile myprotocol://myfile`. Your open_fn will be + * invoked with the URI and you must fill out the provided mpv_stream_cb_info + * struct. This includes your stream callbacks (like read_fn), and an opaque + * cookie, which will be passed as the first argument to all the remaining + * stream callbacks. + * + * Note that your custom callbacks must not invoke libmpv APIs as that would + * cause a deadlock. + * + * Stream lifetime + * --------------- + * + * A stream remains valid until its close callback has been called. It's up to + * libmpv to call the close callback, and the libmpv user cannot close it + * directly with the stream_cb API. + * + * For example, if you consider your custom stream to become suddenly invalid + * (maybe because the underlying stream died), libmpv will continue using your + * stream. All you can do is returning errors from each callback, until libmpv + * gives up and closes it. + * + * Protocol registration and lifetime + * ---------------------------------- + * + * Protocols remain registered until the mpv instance is terminated. This means + * in particular that it can outlive the mpv_handle that was used to register + * it, but once mpv_terminate_destroy() is called, your registered callbacks + * will not be called again. + * + * Protocol unregistration is finished after the mpv core has been destroyed + * (e.g. after mpv_terminate_destroy() has returned). + * + * If you do not call mpv_terminate_destroy() yourself (e.g. plugin-style code), + * you will have to deal with the registration or even streams outliving your + * code. Here are some possible ways to do this: + * - call mpv_terminate_destroy(), which destroys the core, and will make sure + * all streams are closed once this function returns + * - you refcount all resources your stream "cookies" reference, so that it + * doesn't matter if streams live longer than expected + * - create "cancellation" semantics: after your protocol has been unregistered, + * notify all your streams that are still opened, and make them drop all + * referenced resources - then return errors from the stream callbacks as + * long as the stream is still opened + * + */ + +/** + * Read callback used to implement a custom stream. The semantics of the + * callback match read(2) in blocking mode. Short reads are allowed (you can + * return less bytes than requested, and libmpv will retry reading the rest + * with a nother call). If no data can be immediately read, the callback must + * block until there is new data. A return of 0 will be interpreted as final + * EOF, although libmpv might retry the read, or seek to a different position. + * + * @param cookie opaque cookie identifying the stream, + * returned from mpv_stream_cb_open_fn + * @param buf buffer to read data into + * @param size of the buffer + * @return number of bytes read into the buffer + * @return 0 on EOF + * @return -1 on error + */ +typedef int64_t (*mpv_stream_cb_read_fn)(void *cookie, char *buf, uint64_t nbytes); + +/** + * Seek callback used to implement a custom stream. + * + * Note that mpv will issue a seek to position 0 immediately after opening. This + * is used to test whether the stream is seekable (since seekability might + * depend on the URI contents, not just the protocol). Return + * MPV_ERROR_UNSUPPORTED if seeking is not implemented for this stream. This + * seek also servies to establish the fact that streams start at position 0. + * + * This callback can be NULL, in which it behaves as if always returning + * MPV_ERROR_UNSUPPORTED. + * + * @param cookie opaque cookie identifying the stream, + * returned from mpv_stream_cb_open_fn + * @param offset target absolut stream position + * @return the resulting offset of the stream + * MPV_ERROR_UNSUPPORTED or MPV_ERROR_GENERIC if the seek failed + */ +typedef int64_t (*mpv_stream_cb_seek_fn)(void *cookie, int64_t offset); + +/** + * Size callback used to implement a custom stream. + * + * Return MPV_ERROR_UNSUPPORTED if no size is known. + * + * This callback can be NULL, in which it behaves as if always returning + * MPV_ERROR_UNSUPPORTED. + * + * @param cookie opaque cookie identifying the stream, + * returned from mpv_stream_cb_open_fn + * @return the total size in bytes of the stream + */ +typedef int64_t (*mpv_stream_cb_size_fn)(void *cookie); + +/** + * Close callback used to implement a custom stream. + * + * @param cookie opaque cookie identifying the stream, + * returned from mpv_stream_cb_open_fn + */ +typedef void (*mpv_stream_cb_close_fn)(void *cookie); + +/** + * Values for the mpv_stream_cb_control_fn cmd argument. + */ +typedef struct mpv_stream_cb_info { + /** + * Opaque user-provided value, which will be passed to the other callbacks. + * The close callback will be called to release the cookie. It is not + * interpreted by mpv. It doesn't even need to be a valid pointer. + * + * The user sets this in the mpv_stream_cb_open_ro_fn callback. + */ + void *cookie; + + /** + * Callbacks set by the user in the mpv_stream_cb_open_ro_fn callback. Some + * of them are optional, and can be left unset. + * + * The following callbacks are mandatory: read_fn, close_fn + */ + mpv_stream_cb_read_fn read_fn; + mpv_stream_cb_seek_fn seek_fn; + mpv_stream_cb_size_fn size_fn; + mpv_stream_cb_close_fn close_fn; +} mpv_stream_cb_info; + +/** + * Open callback used to implement a custom read-only (ro) stream. The user + * must set the callback fields in the passed info struct. The cookie field + * also can be set to store state associated to the stream instance. + * + * Note that the info struct is valid only for the duration of this callback. + * You can't change the callbacks or the pointer to the cookie at a later point. + * + * Each stream instance created by the open callback can have different + * callbacks. + * + * The close_fn callback will terminate the stream instance. The pointers to + * your callbacks and cookie will be discarded, and the callbacks will not be + * called again. + * + * @param user_data opaque user data provided via mpv_stream_cb_add() + * @param uri name of the stream to be opened (with protocol prefix) + * @param info fields which the user should fill + * @return opaque cookie identifing the newly opened stream + * @return 0 on success, MPV_ERROR_LOADING_FAILED if the URI cannot be opened. + */ +typedef int (*mpv_stream_cb_open_ro_fn)(void *user_data, char *uri, + mpv_stream_cb_info *info); + +/** + * Add a custom stream protocol. This will register a protocol handler under + * the given protocol prefix, and invoke the given callbacks if an URI with the + * matching protocol prefix is opened. + * + * The "ro" is for read-only - only read-only streams can be registered with + * this function. + * + * The callback remains registered until the mpv core is registered. + * + * If a custom stream with the same name is already registered, then the + * MPV_ERROR_INVALID_PARAMETER error is returned. + * + * @param protocol protocol prefix, for example "foo" for "foo://" URIs + * @param user_data opaque pointer passed into the mpv_stream_cb_open_fn + * callback. + * @return error code + */ +int mpv_stream_cb_add_ro(mpv_handle *ctx, const char *protocol, void *user_data, + mpv_stream_cb_open_ro_fn open_fn); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/player/client.c b/player/client.c index abe8ebfe61..b34247d730 100644 --- a/player/client.c +++ b/player/client.c @@ -24,6 +24,7 @@ #include "common/global.h" #include "common/msg.h" #include "common/msg_control.h" +#include "common/global.h" #include "input/input.h" #include "input/cmd_list.h" #include "misc/ctype.h" @@ -62,9 +63,13 @@ struct mp_client_api { pthread_mutex_t lock; // -- protected by lock + struct mpv_handle **clients; int num_clients; uint64_t event_masks; // combined events of all clients, or 0 if unknown + + struct mp_custom_protocol *custom_protocols; + int num_custom_protocols; }; struct observe_property { @@ -1723,3 +1728,59 @@ void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api) unlock_core(ctx); return res; } + +struct mp_custom_protocol { + char *protocol; + void *user_data; + mpv_stream_cb_open_ro_fn open_fn; +}; + +int mpv_stream_cb_add_ro(mpv_handle *ctx, const char *protocol, void *user_data, + mpv_stream_cb_open_ro_fn open_fn) +{ + if (!open_fn) + return MPV_ERROR_INVALID_PARAMETER; + + struct mp_client_api *clients = ctx->clients; + int r = 0; + pthread_mutex_lock(&clients->lock); + for (int n = 0; n < clients->num_custom_protocols; n++) { + struct mp_custom_protocol *proto = &clients->custom_protocols[n]; + if (strcmp(proto->protocol, protocol) == 0) { + r = MPV_ERROR_INVALID_PARAMETER; + break; + } + } + if (stream_has_proto(protocol)) + r = MPV_ERROR_INVALID_PARAMETER; + if (r >= 0) { + struct mp_custom_protocol proto = { + .protocol = talloc_strdup(clients, protocol), + .user_data = user_data, + .open_fn = open_fn, + }; + MP_TARRAY_APPEND(clients, clients->custom_protocols, + clients->num_custom_protocols, proto); + } + pthread_mutex_unlock(&clients->lock); + return r; +} + +bool mp_streamcb_lookup(struct mpv_global *g, const char *protocol, + void **out_user_data, mpv_stream_cb_open_ro_fn *out_fn) +{ + struct mp_client_api *clients = g->client_api; + bool found = false; + pthread_mutex_lock(&clients->lock); + for (int n = 0; n < clients->num_custom_protocols; n++) { + struct mp_custom_protocol *proto = &clients->custom_protocols[n]; + if (strcmp(proto->protocol, protocol) == 0) { + *out_user_data = proto->user_data; + *out_fn = proto->open_fn; + found = true; + break; + } + } + pthread_mutex_unlock(&clients->lock); + return found; +} diff --git a/player/client.h b/player/client.h index a9d6cbde52..e8866225a7 100644 --- a/player/client.h +++ b/player/client.h @@ -5,6 +5,7 @@ #include <stdbool.h> #include "libmpv/client.h" +#include "libmpv/stream_cb.h" struct MPContext; struct mpv_handle; @@ -46,4 +47,7 @@ struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g, struct mp_client_api *client_api); void kill_video(struct mp_client_api *client_api); +bool mp_streamcb_lookup(struct mpv_global *g, const char *protocol, + void **out_user_data, mpv_stream_cb_open_ro_fn *out_fn); + #endif diff --git a/stream/stream.c b/stream/stream.c index 846765f326..4b55b1134a 100644 --- a/stream/stream.c +++ b/stream/stream.c @@ -75,6 +75,7 @@ extern const stream_info_t stream_info_bdnav; extern const stream_info_t stream_info_rar; extern const stream_info_t stream_info_edl; extern const stream_info_t stream_info_libarchive; +extern const stream_info_t stream_info_cb; static const stream_info_t *const stream_list[] = { #if HAVE_CDDA @@ -115,6 +116,7 @@ static const stream_info_t *const stream_list[] = { &stream_info_edl, &stream_info_rar, &stream_info_file, + &stream_info_cb, NULL }; @@ -243,6 +245,9 @@ static stream_t *new_stream(void) static const char *match_proto(const char *url, const char *proto) { + if (strcmp(proto, "*") == 0) + return url; + int l = strlen(proto); if (l > 0) { if (strncasecmp(url, proto, l) == 0 && strncmp("://", url + l, 3) == 0) @@ -1111,3 +1116,17 @@ void stream_print_proto_list(struct mp_log *log) talloc_free(list); mp_info(log, "\nTotal: %d protocols\n", count); } + +bool stream_has_proto(const char *proto) +{ + for (int i = 0; stream_list[i]; i++) { + const stream_info_t *stream_info = stream_list[i]; + + for (int j = 0; stream_info->protocols && stream_info->protocols[j]; j++) { + if (strcmp(stream_info->protocols[j], proto) == 0) + return true; + } + } + + return false; +} diff --git a/stream/stream.h b/stream/stream.h index 21e497e563..1112e09f50 100644 --- a/stream/stream.h +++ b/stream/stream.h @@ -299,5 +299,6 @@ void mp_setup_av_network_options(struct AVDictionary **dict, void stream_print_proto_list(struct mp_log *log); char **stream_get_proto_list(void); +bool stream_has_proto(const char *proto); #endif /* MPLAYER_STREAM_H */ diff --git a/stream/stream_cb.c b/stream/stream_cb.c new file mode 100644 index 0000000000..4496e63255 --- /dev/null +++ b/stream/stream_cb.c @@ -0,0 +1,108 @@ +#include "config.h" + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include "osdep/io.h" + +#include "common/common.h" +#include "common/msg.h" +#include "common/global.h" +#include "stream.h" +#include "options/m_option.h" +#include "options/path.h" +#include "player/client.h" +#include "libmpv/stream_cb.h" + +struct priv { + mpv_stream_cb_info info; +}; + +static int fill_buffer(stream_t *s, char *buffer, int max_len) +{ + struct priv *p = s->priv; + return (int)p->info.read_fn(p->info.cookie, buffer, (size_t)max_len); +} + +static int seek(stream_t *s, int64_t newpos) +{ + struct priv *p = s->priv; + return (int)p->info.seek_fn(p->info.cookie, newpos) >= 0; +} + +static int control(stream_t *s, int cmd, void *arg) +{ + struct priv *p = s->priv; + switch (cmd) { + case STREAM_CTRL_GET_SIZE: { + if (!p->info.size_fn) + break; + int64_t size = p->info.size_fn(p->info.cookie); + if (size >= 0) { + *(int64_t *)arg = size; + return 1; + } + break; + } + } + return STREAM_UNSUPPORTED; +} + +static void s_close(stream_t *s) +{ + struct priv *p = s->priv; + p->info.close_fn(p->info.cookie); +} + +static int open_cb(stream_t *stream) +{ + struct priv *p = talloc_ptrtype(stream, p); + stream->priv = p; + + bstr bproto = mp_split_proto(bstr0(stream->url), NULL); + char *proto = bstrto0(stream, bproto); + + void *user_data; + mpv_stream_cb_open_ro_fn open_fn; + + if (!mp_streamcb_lookup(stream->global, proto, &user_data, &open_fn)) + return STREAM_UNSUPPORTED; + + mpv_stream_cb_info info = {0}; + + int r = open_fn(user_data, stream->url, &info); + if (r < 0) { + if (r != MPV_ERROR_LOADING_FAILED) + MP_WARN(stream, "unknown error from user callback\n"); + return STREAM_ERROR; + } + + if (!info.read_fn || !info.close_fn) { + MP_FATAL(stream, "required read_fn or close_fn callbacks not set.\n"); + return STREAM_ERROR; + } + + p->info = info; + + if (p->info.seek_fn && p->info.seek_fn(p->info.cookie, 0) >= 0) { + stream->seek = seek; + stream->seekable = true; + } + stream->fast_skip = true; + stream->fill_buffer = fill_buffer; + stream->control = control; + stream->read_chunk = 64 * 1024; + stream->close = s_close; + + return STREAM_OK; +} + +const stream_info_t stream_info_cb = { + .name = "stream_callback", + .open = open_cb, + .protocols = (const char*const[]){ "*", NULL }, +}; diff --git a/wscript_build.py b/wscript_build.py index ebbc3e8b8c..a637f79eea 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -247,6 +247,7 @@ def build(ctx): ( "stream/stream_dvdnav.c", "dvdnav" ), ( "stream/stream_edl.c" ), ( "stream/stream_file.c" ), + ( "stream/stream_cb.c" ), ( "stream/stream_lavf.c" ), ( "stream/stream_libarchive.c", "libarchive" ), ( "stream/stream_memory.c" ), @@ -548,7 +549,7 @@ def build(ctx): PRIV_LIBS = get_deps(), ) - headers = ["client.h", "qthelper.hpp", "opengl_cb.h"] + headers = ["client.h", "qthelper.hpp", "opengl_cb.h", "stream_cb.h"] for f in headers: ctx.install_as(ctx.env.INCDIR + '/mpv/' + f, 'libmpv/' + f) |