POC_LIBVLC_PROTOCOL.md 4.8 KB

Idea synopsys

Expose LibVLC API and plugins through a dynamically generated API, much alike how Wayland protocol is exposed with libwayland-client.

Motivation

The libvlccore library is very flexible and has a lot of dynamic entrypoints that can be filled with features from different plugins.

However, the libvlc API currently cannot be "modularised" into different plugins. Thus, every features brought by the plugin need their API to be exposed in static form as symbols.

It means that:

  • When libVLC exposes an API to use with the plugin, this plugin becomes mandatory in libVLC builds, so it removes one of the main interest of plugins from the user point of view.

  • An API exposed by libVLC is meant to be stable, so as soon as the API exists, VLC Core and libVLC layers are tied to it.

  • It exposes a lot of symbols in libVLC, which are usually redundant with what libvlccore can expose.

Instead, the motivation here is to see libVLC through the concept of:

« Plugin as an API »

The libvlc interface can be stretched to the minimal to setup libvlc and expose global interfaces. Then most of the code can just be inline structure and inline code to setup and use those structures from the plugins.

In particular, it also allow different level of support without ever breaking the API/ABI at the link level.

Many features at Videolabs for clients, or prototypes of API that need to be tested and iterated before going to the stable API support level are currently made available directly through libVLC symbols, which means forks, unfinished function that need to be stabilized and sometimes big crude hacks to make things work.

By exposing this API at the plugin level, it's possible to expose the work-in-progress features and customers features without making transgressions on the public «supported» libVLC API, and most of the time expose those features as plugins in official release instead of libVLC builds.

Example client code

libvlc_instance *instance = libvlc_instance_New(argc, argv);

libvlc_plugin_interactive_zoom *zoom = NULL;

static void object_added(
    libvlc_discoverer *discoverer,
    libvlc_object *object
) {
    /* The libvlc_discoverer_attach_object must do some dark magic for
     * the binding of protocol. In particular, it must return an object
     * of type `libvlc_plugin_interactive_zoom *` (automatically casted
     * from `void *` in C) that must be created by the probe proxy.
    if (object->name == "vlc_filter_interactive_zoom")
        zoom = libvlc_plugin_interactive_zoom_attach_object(
            discoverer, object, INTERACTIVE_ZOOM_VERSION, NULL /*opaque*/
        );

        /* The code above is the type safe variant of
            zoom = libvlc_discoverer_attach_object(
                discoverer, object,
                libvlc_plugin_interactive_zoom_interface,
                INTERACTIVE_ZOOM_VERSION
        */
}

libvlc_discovery_callbacks callbacks = {
    .added = object_added,
    .removed = object_removed,
};

libvlc_discoverer *discoverer = libvlc_discoverer_new(instance);

/* Enable some global features */
libvlc_discoverer_add_option(/* ... */);
libvlc_discoverer_add_option(/* ... */);
libvlc_discoverer_add_option(/* ... */);

/* Setup global objects */
libvlc_discover(instance, &callbacks);

/* global objects are attached to the libvlc discoverer */

/* Interface callback for zoom */
static int onZoomPerformed(zoom_params params) {
    /* Check that we can use the interactive zoom API */
    if (zoom == NULL)
        return ENOTSUPPORTED;

    libvlc_plugin_interactive_zoom_zoom(zoom, params.x, params.y);
}

Exemple server code

/* create a proxy interface object which must expose the interface */
static void *create_proxy(
    vlc_api_t *api,
    const char *name,
    unsigned version,
    const void *listener;
) {
    VLC_UNUSED(name);

    /* Generated:
     *  - libvlc_listener_plugin_interactive_zoom (events handling)
     *  - libvlc_plugin_interactive_zoom (requests pointer table)
     **/
    struct libvlc_wrapper_plugin_interactive_zoom {
        libvlc_listener_plugin_interactive_zoom *listener;
        libvlc_plugin_interactive_zoom interface;
    };
    libvlc_wrapper_plugin_interactive_zoom *zoom = malloc(sizeof *zoom);
    if (!zoom) return NULL;

    zoom->listener = listener;
    zoom->interface = (struct libvlc_plugin_interactive_zoom) {
        .zoom = DoZoom
    };

    return &zoom->interface;
}

/* probe proxy exposing the API entrypoint and the creation
 * function */
static int OpenAPI(vlc_api_t *api)
{
    vlc_api_DefineGlobalObject(api,
        "vlc_filter_interactive_zoom",
        API_VERSION, &create_proxy)
}

vlc_module_begin()
    add_capability("api interface", API_VERSION)
    set_callback(OpenAPI)

    add_submodule()
        add_capability("video filter", 0)
        set_callbacks(Open, Close)
vlc_module_end()