From 3c5fe76f7c31deaaa7cc211dd29b3db5cb866326 Mon Sep 17 00:00:00 2001
From: peijiankang <peijiankang@kylinos.cn>
Date: Tue, 9 Apr 2024 14:37:35 +0800
Subject: [PATCH] add opengl api for mpv-0.35

---
 libmpv/client.h     |  53 ++++++
 libmpv/mpv.def      |   7 +
 libmpv/opengl_cb.h  | 339 ++++++++++++++++++++++++++++++++++++++
 libmpv/qthelper.hpp | 386 ++++++++++++++++++++++++++++++++++++++++++++
 player/client.c     | 125 ++++++++++++++
 player/command.c    |   3 +
 player/playloop.c   |   4 +
 wscript_build.py    |   2 +-
 8 files changed, 918 insertions(+), 1 deletion(-)
 create mode 100644 libmpv/opengl_cb.h
 create mode 100644 libmpv/qthelper.hpp

diff --git a/libmpv/client.h b/libmpv/client.h
index fb10e5e..afb7510 100644
--- a/libmpv/client.h
+++ b/libmpv/client.h
@@ -1276,6 +1276,35 @@ typedef enum mpv_event_id {
      *             start of the program), while the property behaves correctly.
      */
     MPV_EVENT_IDLE              = 11,
+    /**
+     * Playback was paused. This indicates the user pause state.
+     *
+     * The user pause state is the state the user requested (changed with the
+     * "pause" property). There is an internal pause state too, which is entered
+     * if e.g. the network is too slow (the "core-idle" property generally
+     * indicates whether the core is playing or waiting).
+     *
+     * This event is sent whenever any pause states change, not only the user
+     * state. You might get multiple events in a row while these states change
+     * independently. But the event ID sent always indicates the user pause
+     * state.
+     *
+     * If you don't want to deal with this, use mpv_observe_property() on the
+     * "pause" property and ignore MPV_EVENT_PAUSE/UNPAUSE. Likewise, the
+     * "core-idle" property tells you whether video is actually playing or not.
+     *
+     * @deprecated The event is redundant with mpv_observe_property() as
+     *             mentioned above, and might be removed in the far future.
+     */
+    MPV_EVENT_PAUSE             = 12,
+    /**
+     * Playback was unpaused. See MPV_EVENT_PAUSE for not so obvious details.
+     *
+     * @deprecated The event is redundant with mpv_observe_property() as
+     *             explained in the MPV_EVENT_PAUSE comments, and might be
+     *             removed in the far future.
+     */
+    MPV_EVENT_UNPAUSE           = 13,
     /**
      * Sent every time after a video frame is displayed. Note that currently,
      * this will be sent in lower frequency if there is no video, or playback
@@ -1877,6 +1906,30 @@ MPV_EXPORT int mpv_hook_continue(mpv_handle *ctx, uint64_t id);
  */
 MPV_EXPORT int mpv_get_wakeup_pipe(mpv_handle *ctx);
 
+/**
+ * @deprecated use render.h
+ */
+typedef enum mpv_sub_api {
+    /**
+     * For using mpv's OpenGL renderer on an external OpenGL context.
+     * mpv_get_sub_api(MPV_SUB_API_OPENGL_CB) returns mpv_opengl_cb_context*.
+     * This context can be used with mpv_opengl_cb_* functions.
+     * Will return NULL if unavailable (if OpenGL support was not compiled in).
+     * See opengl_cb.h for details.
+     *
+     * @deprecated use render.h
+     */
+    MPV_SUB_API_OPENGL_CB = 1
+} mpv_sub_api;
+
+/**
+ * This is used for additional APIs that are not strictly part of the core API.
+ * See the individual mpv_sub_api member values.
+ *
+ * @deprecated use render.h
+ */
+void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api);
+
 #endif
 
 #ifdef __cplusplus
diff --git a/libmpv/mpv.def b/libmpv/mpv.def
index 232490d..2b0808d 100644
--- a/libmpv/mpv.def
+++ b/libmpv/mpv.def
@@ -21,6 +21,7 @@ mpv_get_property
 mpv_get_property_async
 mpv_get_property_osd_string
 mpv_get_property_string
+mpv_get_sub_api
 mpv_get_time_us
 mpv_get_wakeup_pipe
 mpv_hook_add
@@ -28,6 +29,12 @@ mpv_hook_continue
 mpv_initialize
 mpv_load_config_file
 mpv_observe_property
+mpv_opengl_cb_draw
+mpv_opengl_cb_init_gl
+mpv_opengl_cb_report_flip
+mpv_opengl_cb_render
+mpv_opengl_cb_set_update_callback
+mpv_opengl_cb_uninit_gl
 mpv_render_context_create
 mpv_render_context_free
 mpv_render_context_get_info
diff --git a/libmpv/opengl_cb.h b/libmpv/opengl_cb.h
new file mode 100644
index 0000000..6820681
--- /dev/null
+++ b/libmpv/opengl_cb.h
@@ -0,0 +1,339 @@
+/* Copyright (C) 2017 the mpv developers
+ *
+ * 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.
+ */
+
+#ifndef MPV_CLIENT_API_OPENGL_CB_H_
+#define MPV_CLIENT_API_OPENGL_CB_H_
+
+#include "client.h"
+
+#if !MPV_ENABLE_DEPRECATED
+#error "This header and all API provided by it is deprecated. Use render.h instead."
+#else
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *
+ * Overview
+ * --------
+ *
+ * Warning: this API is deprecated. A very similar API is provided by render.h
+ * and render_gl.h. The deprecated API is emulated with the new API.
+ *
+ * This API can be used to make mpv render into a foreign OpenGL context. It
+ * can be used to handle video display.
+ *
+ * The renderer needs to be explicitly initialized with mpv_opengl_cb_init_gl(),
+ * and then video can be drawn with mpv_opengl_cb_draw(). The user thread can
+ * be notified by new frames with mpv_opengl_cb_set_update_callback().
+ *
+ * You can output and embed video without this API by setting the mpv "wid"
+ * option to a native window handle (see "Embedding the video window" section
+ * in the client.h header). In general, using the opengl-cb API is recommended,
+ * because window embedding can cause various issues, especially with GUI
+ * toolkits and certain platforms.
+ *
+ * OpenGL interop
+ * --------------
+ *
+ * This assumes the OpenGL context lives on a certain thread controlled by the
+ * API user. The following functions require access to the OpenGL context:
+ *      mpv_opengl_cb_init_gl
+ *      mpv_opengl_cb_draw
+ *      mpv_opengl_cb_uninit_gl
+ *
+ * The OpenGL context is indirectly accessed through the OpenGL function
+ * pointers returned by the get_proc_address callback in mpv_opengl_cb_init_gl.
+ * Generally, mpv will not load the system OpenGL library when using this API.
+ *
+ * Only "desktop" OpenGL version 2.1 and later and OpenGL ES version 2.0 and
+ * later are supported. With OpenGL 2.1, the GL_ARB_texture_rg is required. The
+ * renderer was written for the OpenGL 3.x core profile, with additional support
+ * for OpenGL 2.1 and OpenGL ES 2.0.
+ *
+ * Note that some hardware decoding interop API (as set with the "hwdec" option)
+ * may actually access some sort of host API, such as EGL.
+ *
+ * OpenGL state
+ * ------------
+ *
+ * OpenGL has a large amount of implicit state. All the mpv functions mentioned
+ * above expect that the OpenGL state is reasonably set to OpenGL standard
+ * defaults. Likewise, mpv will attempt to leave the OpenGL context with
+ * standard defaults. The following state is excluded from this:
+ *
+ *      - the glViewport state
+ *      - the glScissor state (but GL_SCISSOR_TEST is in its default value)
+ *      - glBlendFuncSeparate() state (but GL_BLEND is in its default value)
+ *      - glClearColor() state
+ *      - mpv may overwrite the callback set with glDebugMessageCallback()
+ *      - mpv always disables GL_DITHER at init
+ *
+ * Messing with the state could be avoided by creating shared OpenGL contexts,
+ * but this is avoided for the sake of compatibility and interoperability.
+ *
+ * On OpenGL 2.1, mpv will strictly call functions like glGenTextures() to
+ * create OpenGL objects. You will have to do the same. This ensures that
+ * objects created by mpv and the API users don't clash. Also, legacy state
+ * must be either in its defaults, or not interfere with core state.
+ *
+ * Threading
+ * ---------
+ *
+ * The mpv_opengl_cb_* functions can be called from any thread, under the
+ * following conditions:
+ *  - only one of the mpv_opengl_cb_* functions can be called at the same time
+ *    (unless they belong to different mpv cores created by mpv_create())
+ *  - for functions which need an OpenGL context (see above) the OpenGL context
+ *    must be "current" in the current thread, and it must be the same context
+ *    as used with mpv_opengl_cb_init_gl()
+ *  - never can be called from within the callbacks set with
+ *    mpv_set_wakeup_callback() or mpv_opengl_cb_set_update_callback()
+ *
+ * Context and handle lifecycle
+ * ----------------------------
+ *
+ * Video initialization will fail if the OpenGL context was not initialized yet
+ * (with mpv_opengl_cb_init_gl()). Likewise, mpv_opengl_cb_uninit_gl() will
+ * disable video.
+ *
+ * When the mpv core is destroyed (e.g. via mpv_terminate_destroy()), the OpenGL
+ * context must have been uninitialized. If this doesn't happen, undefined
+ * behavior will result.
+ *
+ * Hardware decoding
+ * -----------------
+ *
+ * Hardware decoding via opengl_cb is fully supported, but requires some
+ * additional setup. (At least if direct hardware decoding modes are wanted,
+ * instead of copying back surface data from GPU to CPU RAM.)
+ *
+ * While "normal" mpv loads the OpenGL hardware decoding interop on demand,
+ * this can't be done with opengl_cb for internal technical reasons. Instead,
+ * it loads them by default, even if hardware decoding is not going to be used.
+ * In older mpv releases, this had to be done by setting the
+ * "opengl-hwdec-interop" or "hwdec-preload" options before calling
+ * mpv_opengl_cb_init_gl(). You can still use the newer "gpu-hwdec-interop"
+ * option to prevent loading of interop, or to load only a specific interop.
+ *
+ * There may be certain requirements on the OpenGL implementation:
+ * - Windows: ANGLE is required (although in theory GL/DX interop could be used)
+ * - Intel/Linux: EGL is required, and also a glMPGetNativeDisplay() callback
+ *                must be provided (see sections below)
+ * - nVidia/Linux: Both GLX and EGL should work (GLX is required if vdpau is
+ *                 used, e.g. due to old drivers.)
+ * - OSX: CGL is required (CGLGetCurrentContext() returning non-NULL)
+ * - iOS: EAGL is required (EAGLContext.currentContext returning non-nil)
+ *
+ * Once these things are setup, hardware decoding can be enabled/disabled at
+ * any time by setting the "hwdec" property.
+ *
+ * Special windowing system interop considerations
+ * ------------------------------------------------
+ *
+ * In some cases, libmpv needs to have access to the windowing system's handles.
+ * This can be a pointer to a X11 "Display" for example. Usually this is needed
+ * only for hardware decoding.
+ *
+ * You can communicate these handles to libmpv by adding a pseudo-OpenGL
+ * extension "GL_MP_MPGetNativeDisplay" to the additional extension string when
+ * calling mpv_opengl_cb_init_gl(). The get_proc_address callback should resolve
+ * a function named "glMPGetNativeDisplay", which has the signature:
+ *
+ *    void* GLAPIENTRY glMPGetNativeDisplay(const char* name)
+ *
+ * See below what names are defined. Usually, libmpv will use the native handle
+ * up until mpv_opengl_cb_uninit_gl() is called. If the name is not anything
+ * you know/expected, return NULL from the function.
+ */
+
+// Legacy - not supported anymore.
+struct mpv_opengl_cb_window_pos {
+    int x;      // left coordinates of window (usually 0)
+    int y;      // top coordinates of window (usually 0)
+    int width;  // width of GL window
+    int height; // height of GL window
+};
+
+// Legacy - not supported anymore.
+struct mpv_opengl_cb_drm_params {
+    // DRM fd (int). set this to -1 if invalid.
+    int fd;
+
+    // currently used crtc id
+    int crtc_id;
+
+    // currently used connector id
+    int connector_id;
+
+    // pointer to the drmModeAtomicReq that is being used for the renderloop.
+    // This atomic request pointer should be usually created at every renderloop.
+    struct _drmModeAtomicReq *atomic_request;
+};
+
+/**
+ * nVidia/Linux via VDPAU requires GLX, which does not have this problem (the
+ * GLX API can return the current X11 Display).
+ *
+ * Windowing system interop on MS win32
+ * ------------------------------------
+ *
+ * You should use ANGLE, and make sure your application and libmpv are linked
+ * to the same ANGLE DLLs. libmpv will pick the device context (needed for
+ * hardware decoding) from the current ANGLE EGL context.
+ */
+
+/**
+ * Opaque context, returned by mpv_get_sub_api(MPV_SUB_API_OPENGL_CB).
+ *
+ * A context is bound to the mpv_handle it was retrieved from. The context
+ * will always be the same (for the same mpv_handle), and is valid until the
+ * mpv_handle it belongs to is released.
+ */
+typedef struct mpv_opengl_cb_context mpv_opengl_cb_context;
+
+typedef void (*mpv_opengl_cb_update_fn)(void *cb_ctx);
+typedef void *(*mpv_opengl_cb_get_proc_address_fn)(void *fn_ctx, const char *name);
+
+/**
+ * Set the callback that notifies you when a new video frame is available, or
+ * if the video display configuration somehow changed and requires a redraw.
+ * Similar to mpv_set_wakeup_callback(), you must not call any mpv API from
+ * the callback, and all the other listed restrictions apply (such as not
+ * exiting the callback by throwing exceptions).
+ *
+ * @param callback callback(callback_ctx) is called if the frame should be
+ *                 redrawn
+ * @param callback_ctx opaque argument to the callback
+ */
+void mpv_opengl_cb_set_update_callback(mpv_opengl_cb_context *ctx,
+                                       mpv_opengl_cb_update_fn callback,
+                                       void *callback_ctx);
+
+/**
+ * Initialize the mpv OpenGL state. This retrieves OpenGL function pointers via
+ * get_proc_address, and creates OpenGL objects needed by mpv internally. It
+ * will also call APIs needed for rendering hardware decoded video in OpenGL,
+ * according to the mpv "hwdec" option.
+ *
+ * You must free the associated state at some point by calling the
+ * mpv_opengl_cb_uninit_gl() function. Not doing so may result in memory leaks
+ * or worse.
+ *
+ * @param exts optional _additional_ extension string, can be NULL
+ * @param get_proc_address callback used to retrieve function pointers to OpenGL
+ *                         functions. This is used for both standard functions
+ *                         and extension functions. (The extension string is
+ *                         checked whether extensions are really available.)
+ *                         The callback will be called from this function only
+ *                         (it is not stored and never used later).
+ *                         Usually, GL context APIs do this for you (e.g. with
+ *                         glXGetProcAddressARB or wglGetProcAddress), but
+ *                         some APIs do not always return pointers for all
+ *                         standard functions (even if present); in this case
+ *                         you have to compensate by looking up these functions
+ *                         yourself.
+ * @param get_proc_address_ctx arbitrary opaque user context passed to the
+ *                             get_proc_address callback
+ * @return error code (same as normal mpv_* API), including but not limited to:
+ *      MPV_ERROR_UNSUPPORTED: the OpenGL version is not supported
+ *                             (or required extensions are missing)
+ *      MPV_ERROR_INVALID_PARAMETER: the OpenGL state was already initialized
+ */
+int mpv_opengl_cb_init_gl(mpv_opengl_cb_context *ctx, const char *exts,
+                          mpv_opengl_cb_get_proc_address_fn get_proc_address,
+                          void *get_proc_address_ctx);
+
+/**
+ * Render video. Requires that the OpenGL state is initialized.
+ *
+ * The video will use the full provided framebuffer. Options like "panscan" are
+ * applied to determine which part of the video should be visible and how the
+ * video should be scaled. You can change these options at runtime by using the
+ * mpv property API.
+ *
+ * The renderer will reconfigure itself every time the output rectangle/size
+ * is changed. (If you want to do animations, it might be better to do the
+ * animation on a FBO instead.)
+ *
+ * This function implicitly pulls a video frame from the internal queue and
+ * renders it. If no new frame is available, the previous frame is redrawn.
+ * The update callback set with mpv_opengl_cb_set_update_callback() notifies
+ * you when a new frame was added.
+ *
+ * @param fbo The framebuffer object to render on. Because the renderer might
+ *            manage multiple FBOs internally for the purpose of video
+ *            postprocessing, it will always bind and unbind FBOs itself. If
+ *            you want mpv to render on the main framebuffer, pass 0.
+ * @param w Width of the framebuffer. This is either the video size if the fbo
+ *          parameter is 0, or the allocated size of the texture backing the
+ *          fbo. The renderer will always use the full size of the fbo.
+ * @param h Height of the framebuffer. Same as with the w parameter, except
+ *          that this parameter can be negative. In this case, the video
+ *          frame will be rendered flipped.
+ * @return 0
+ */
+int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int w, int h);
+
+/**
+ * Deprecated. Use mpv_opengl_cb_draw(). This function is equivalent to:
+ *
+ * int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4])
+ *  { return mpv_opengl_cb_draw(ctx, fbo, vp[2], vp[3]); }
+ *
+ * vp[0] and vp[1] used to have a meaning, but are ignored in newer versions.
+ *
+ * This function will be removed in the future without version bump (this API
+ * was never marked as stable).
+ */
+int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4]);
+
+/**
+ * Tell the renderer that a frame was flipped at the given time. This is
+ * optional, but can help the player to achieve better timing.
+ *
+ * Note that calling this at least once informs libmpv that you will use this
+ * function. If you use it inconsistently, expect bad video playback.
+ *
+ * If this is called while no video or no OpenGL is initialized, it is ignored.
+ *
+ * @param time The mpv time (using mpv_get_time_us()) at which the flip call
+ *             returned. If 0 is passed, mpv_get_time_us() is used instead.
+ *             Currently, this parameter is ignored.
+ * @return error code
+ */
+int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time);
+
+/**
+ * Destroy the mpv OpenGL state.
+ *
+ * If video is still active (e.g. a file playing), video will be disabled
+ * forcefully.
+ *
+ * Calling this multiple times is ok.
+ *
+ * @return error code
+ */
+int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* else #if MPV_ENABLE_DEPRECATED */
+
+#endif
diff --git a/libmpv/qthelper.hpp b/libmpv/qthelper.hpp
new file mode 100644
index 0000000..3af86e3
--- /dev/null
+++ b/libmpv/qthelper.hpp
@@ -0,0 +1,386 @@
+/* Copyright (C) 2017 the mpv developers
+ *
+ * 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.
+ */
+
+#ifndef MPV_CLIENT_API_QTHELPER_H_
+#define MPV_CLIENT_API_QTHELPER_H_
+
+#include <mpv/client.h>
+
+#if !MPV_ENABLE_DEPRECATED
+#error "This helper is deprecated. Copy it into your project instead."
+#else
+
+/**
+ * Note: these helpers are provided for convenience for C++/Qt applications.
+ * This is based on the public API in client.h, and it does not encode any
+ * knowledge that is not known or guaranteed outside of the C client API. You
+ * can even copy and modify this code as you like, or implement similar things
+ * for other languages.
+ */
+
+#include <cstring>
+
+#include <QVariant>
+#include <QString>
+#include <QList>
+#include <QHash>
+#include <QSharedPointer>
+#include <QMetaType>
+
+namespace mpv {
+namespace qt {
+
+// Wrapper around mpv_handle. Does refcounting under the hood.
+class Handle
+{
+    struct container {
+        container(mpv_handle *h) : mpv(h) {}
+        ~container() { mpv_terminate_destroy(mpv); }
+        mpv_handle *mpv;
+    };
+    QSharedPointer<container> sptr;
+public:
+    // Construct a new Handle from a raw mpv_handle with refcount 1. If the
+    // last Handle goes out of scope, the mpv_handle will be destroyed with
+    // mpv_terminate_destroy().
+    // Never destroy the mpv_handle manually when using this wrapper. You
+    // will create dangling pointers. Just let the wrapper take care of
+    // destroying the mpv_handle.
+    // Never create multiple wrappers from the same raw mpv_handle; copy the
+    // wrapper instead (that's what it's for).
+    static Handle FromRawHandle(mpv_handle *handle) {
+        Handle h;
+        h.sptr = QSharedPointer<container>(new container(handle));
+        return h;
+    }
+
+    // Return the raw handle; for use with the libmpv C API.
+    operator mpv_handle*() const { return sptr ? (*sptr).mpv : 0; }
+};
+
+static inline QVariant node_to_variant(const mpv_node *node)
+{
+    switch (node->format) {
+    case MPV_FORMAT_STRING:
+        return QVariant(QString::fromUtf8(node->u.string));
+    case MPV_FORMAT_FLAG:
+        return QVariant(static_cast<bool>(node->u.flag));
+    case MPV_FORMAT_INT64:
+        return QVariant(static_cast<qlonglong>(node->u.int64));
+    case MPV_FORMAT_DOUBLE:
+        return QVariant(node->u.double_);
+    case MPV_FORMAT_NODE_ARRAY: {
+        mpv_node_list *list = node->u.list;
+        QVariantList qlist;
+        for (int n = 0; n < list->num; n++)
+            qlist.append(node_to_variant(&list->values[n]));
+        return QVariant(qlist);
+    }
+    case MPV_FORMAT_NODE_MAP: {
+        mpv_node_list *list = node->u.list;
+        QVariantMap qmap;
+        for (int n = 0; n < list->num; n++) {
+            qmap.insert(QString::fromUtf8(list->keys[n]),
+                        node_to_variant(&list->values[n]));
+        }
+        return QVariant(qmap);
+    }
+    default: // MPV_FORMAT_NONE, unknown values (e.g. future extensions)
+        return QVariant();
+    }
+}
+
+struct node_builder {
+    node_builder(const QVariant& v) {
+        set(&node_, v);
+    }
+    ~node_builder() {
+        free_node(&node_);
+    }
+    mpv_node *node() { return &node_; }
+private:
+    Q_DISABLE_COPY(node_builder)
+    mpv_node node_;
+    mpv_node_list *create_list(mpv_node *dst, bool is_map, int num) {
+        dst->format = is_map ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY;
+        mpv_node_list *list = new mpv_node_list();
+        dst->u.list = list;
+        if (!list)
+            goto err;
+        list->values = new mpv_node[num]();
+        if (!list->values)
+            goto err;
+        if (is_map) {
+            list->keys = new char*[num]();
+            if (!list->keys)
+                goto err;
+        }
+        return list;
+    err:
+        free_node(dst);
+        return NULL;
+    }
+    char *dup_qstring(const QString &s) {
+        QByteArray b = s.toUtf8();
+        char *r = new char[b.size() + 1];
+        if (r)
+            std::memcpy(r, b.data(), b.size() + 1);
+        return r;
+    }
+    bool test_type(const QVariant &v, QMetaType::Type t) {
+        // The Qt docs say: "Although this function is declared as returning
+        // "QVariant::Type(obsolete), the return value should be interpreted
+        // as QMetaType::Type."
+        // So a cast really seems to be needed to avoid warnings (urgh).
+        return static_cast<int>(v.type()) == static_cast<int>(t);
+    }
+    void set(mpv_node *dst, const QVariant &src) {
+        if (test_type(src, QMetaType::QString)) {
+            dst->format = MPV_FORMAT_STRING;
+            dst->u.string = dup_qstring(src.toString());
+            if (!dst->u.string)
+                goto fail;
+        } else if (test_type(src, QMetaType::Bool)) {
+            dst->format = MPV_FORMAT_FLAG;
+            dst->u.flag = src.toBool() ? 1 : 0;
+        } else if (test_type(src, QMetaType::Int) ||
+                   test_type(src, QMetaType::LongLong) ||
+                   test_type(src, QMetaType::UInt) ||
+                   test_type(src, QMetaType::ULongLong))
+        {
+            dst->format = MPV_FORMAT_INT64;
+            dst->u.int64 = src.toLongLong();
+        } else if (test_type(src, QMetaType::Double)) {
+            dst->format = MPV_FORMAT_DOUBLE;
+            dst->u.double_ = src.toDouble();
+        } else if (src.canConvert<QVariantList>()) {
+            QVariantList qlist = src.toList();
+            mpv_node_list *list = create_list(dst, false, qlist.size());
+            if (!list)
+                goto fail;
+            list->num = qlist.size();
+            for (int n = 0; n < qlist.size(); n++)
+                set(&list->values[n], qlist[n]);
+        } else if (src.canConvert<QVariantMap>()) {
+            QVariantMap qmap = src.toMap();
+            mpv_node_list *list = create_list(dst, true, qmap.size());
+            if (!list)
+                goto fail;
+            list->num = qmap.size();
+            for (int n = 0; n < qmap.size(); n++) {
+                list->keys[n] = dup_qstring(qmap.keys()[n]);
+                if (!list->keys[n]) {
+                    free_node(dst);
+                    goto fail;
+                }
+                set(&list->values[n], qmap.values()[n]);
+            }
+        } else {
+            goto fail;
+        }
+        return;
+    fail:
+        dst->format = MPV_FORMAT_NONE;
+    }
+    void free_node(mpv_node *dst) {
+        switch (dst->format) {
+        case MPV_FORMAT_STRING:
+            delete[] dst->u.string;
+            break;
+        case MPV_FORMAT_NODE_ARRAY:
+        case MPV_FORMAT_NODE_MAP: {
+            mpv_node_list *list = dst->u.list;
+            if (list) {
+                for (int n = 0; n < list->num; n++) {
+                    if (list->keys)
+                        delete[] list->keys[n];
+                    if (list->values)
+                        free_node(&list->values[n]);
+                }
+                delete[] list->keys;
+                delete[] list->values;
+            }
+            delete list;
+            break;
+        }
+        default: ;
+        }
+        dst->format = MPV_FORMAT_NONE;
+    }
+};
+
+/**
+ * RAII wrapper that calls mpv_free_node_contents() on the pointer.
+ */
+struct node_autofree {
+    mpv_node *ptr;
+    node_autofree(mpv_node *a_ptr) : ptr(a_ptr) {}
+    ~node_autofree() { mpv_free_node_contents(ptr); }
+};
+
+#if MPV_ENABLE_DEPRECATED
+
+/**
+ * Return the given property as mpv_node converted to QVariant, or QVariant()
+ * on error.
+ *
+ * @deprecated use get_property() instead
+ *
+ * @param name the property name
+ */
+static inline QVariant get_property_variant(mpv_handle *ctx, const QString &name)
+{
+    mpv_node node;
+    if (mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node) < 0)
+        return QVariant();
+    node_autofree f(&node);
+    return node_to_variant(&node);
+}
+
+/**
+ * Set the given property as mpv_node converted from the QVariant argument.
+
+ * @deprecated use set_property() instead
+ */
+static inline int set_property_variant(mpv_handle *ctx, const QString &name,
+                                       const QVariant &v)
+{
+    node_builder node(v);
+    return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
+}
+
+/**
+ * Set the given option as mpv_node converted from the QVariant argument.
+ *
+ * @deprecated use set_property() instead
+ */
+static inline int set_option_variant(mpv_handle *ctx, const QString &name,
+                                     const QVariant &v)
+{
+    node_builder node(v);
+    return mpv_set_option(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
+}
+
+/**
+ * mpv_command_node() equivalent. Returns QVariant() on error (and
+ * unfortunately, the same on success).
+ *
+ * @deprecated use command() instead
+ */
+static inline QVariant command_variant(mpv_handle *ctx, const QVariant &args)
+{
+    node_builder node(args);
+    mpv_node res;
+    if (mpv_command_node(ctx, node.node(), &res) < 0)
+        return QVariant();
+    node_autofree f(&res);
+    return node_to_variant(&res);
+}
+
+#endif
+
+/**
+ * This is used to return error codes wrapped in QVariant for functions which
+ * return QVariant.
+ *
+ * You can use get_error() or is_error() to extract the error status from a
+ * QVariant value.
+ */
+struct ErrorReturn
+{
+    /**
+     * enum mpv_error value (or a value outside of it if ABI was extended)
+     */
+    int error;
+
+    ErrorReturn() : error(0) {}
+    explicit ErrorReturn(int err) : error(err) {}
+};
+
+/**
+ * Return the mpv error code packed into a QVariant, or 0 (success) if it's not
+ * an error value.
+ *
+ * @return error code (<0) or success (>=0)
+ */
+static inline int get_error(const QVariant &v)
+{
+    if (!v.canConvert<ErrorReturn>())
+        return 0;
+    return v.value<ErrorReturn>().error;
+}
+
+/**
+ * Return whether the QVariant carries a mpv error code.
+ */
+static inline bool is_error(const QVariant &v)
+{
+    return get_error(v) < 0;
+}
+
+/**
+ * Return the given property as mpv_node converted to QVariant, or QVariant()
+ * on error.
+ *
+ * @param name the property name
+ * @return the property value, or an ErrorReturn with the error code
+ */
+static inline QVariant get_property(mpv_handle *ctx, const QString &name)
+{
+    mpv_node node;
+    int err = mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node);
+    if (err < 0)
+        return QVariant::fromValue(ErrorReturn(err));
+    node_autofree f(&node);
+    return node_to_variant(&node);
+}
+
+/**
+ * Set the given property as mpv_node converted from the QVariant argument.
+ *
+ * @return mpv error code (<0 on error, >= 0 on success)
+ */
+static inline int set_property(mpv_handle *ctx, const QString &name,
+                                       const QVariant &v)
+{
+    node_builder node(v);
+    return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
+}
+
+/**
+ * mpv_command_node() equivalent.
+ *
+ * @param args command arguments, with args[0] being the command name as string
+ * @return the property value, or an ErrorReturn with the error code
+ */
+static inline QVariant command(mpv_handle *ctx, const QVariant &args)
+{
+    node_builder node(args);
+    mpv_node res;
+    int err = mpv_command_node(ctx, node.node(), &res);
+    if (err < 0)
+        return QVariant::fromValue(ErrorReturn(err));
+    node_autofree f(&res);
+    return node_to_variant(&res);
+}
+
+}
+}
+
+Q_DECLARE_METATYPE(mpv::qt::ErrorReturn)
+
+#endif /* else #if MPV_ENABLE_DEPRECATED */
+
+#endif
diff --git a/player/client.c b/player/client.c
index dd81cdf..3f0306d 100644
--- a/player/client.c
+++ b/player/client.c
@@ -83,6 +83,7 @@ struct mp_client_api {
     int num_custom_protocols;
 
     struct mpv_render_context *render_context;
+    struct mpv_opengl_cb_context *gl_cb_ctx;
 };
 
 struct observe_property {
@@ -2075,6 +2076,8 @@ static const char *const event_table[] = {
     [MPV_EVENT_END_FILE] = "end-file",
     [MPV_EVENT_FILE_LOADED] = "file-loaded",
     [MPV_EVENT_IDLE] = "idle",
+    [MPV_EVENT_PAUSE] = "pause",
+    [MPV_EVENT_UNPAUSE] = "unpause",
     [MPV_EVENT_TICK] = "tick",
     [MPV_EVENT_CLIENT_MESSAGE] = "client-message",
     [MPV_EVENT_VIDEO_RECONFIG] = "video-reconfig",
@@ -2153,6 +2156,128 @@ mp_client_api_acquire_render_context(struct mp_client_api *ca)
     return res;
 }
 
+// Emulation of old opengl_cb API.
+
+#include "libmpv/opengl_cb.h"
+#include "libmpv/render_gl.h"
+
+struct mpv_opengl_cb_context {
+    struct mp_client_api *client_api;
+    mpv_opengl_cb_update_fn callback;
+    void *callback_ctx;
+};
+
+static mpv_opengl_cb_context *opengl_cb_get_context(mpv_handle *ctx)
+{
+    pthread_mutex_lock(&ctx->clients->lock);
+    mpv_opengl_cb_context *cb = ctx->clients->gl_cb_ctx;
+    if (!cb) {
+        cb = talloc_zero(NULL, struct mpv_opengl_cb_context);
+        cb->client_api = ctx->clients;
+        cb->client_api->gl_cb_ctx = cb;
+    }
+    pthread_mutex_unlock(&ctx->clients->lock);
+    return cb;
+}
+
+void mpv_opengl_cb_set_update_callback(mpv_opengl_cb_context *ctx,
+                                       mpv_opengl_cb_update_fn callback,
+                                       void *callback_ctx)
+{
+    // This was probably supposed to be thread-safe, but we don't care. It's
+    // compatibility code, and if you have problems, use the new API.
+    if (ctx->client_api->render_context) {
+        mpv_render_context_set_update_callback(ctx->client_api->render_context,
+                                               callback, callback_ctx);
+    }
+    // Nasty thing: could set this even while not initialized, so we need to
+    // preserve it.
+    ctx->callback = callback;
+    ctx->callback_ctx = callback_ctx;
+}
+
+int mpv_opengl_cb_init_gl(mpv_opengl_cb_context *ctx, const char *exts,
+                          mpv_opengl_cb_get_proc_address_fn get_proc_address,
+                          void *get_proc_address_ctx)
+{
+    if (ctx->client_api->render_context)
+        return MPV_ERROR_INVALID_PARAMETER;
+
+    // mpv_render_context_create() only calls mp_client_get_global() on it.
+    mpv_handle dummy = {.mpctx = ctx->client_api->mpctx};
+
+    mpv_render_param params[] = {
+        {MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_OPENGL},
+        {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &(mpv_opengl_init_params){
+            .get_proc_address = get_proc_address,
+            .get_proc_address_ctx = get_proc_address_ctx,
+        }},
+        // Hack for explicit legacy hwdec loading. We really want to make it
+        // impossible for proper render API users to trigger this.
+        {(mpv_render_param_type)-1, ctx->client_api->mpctx->global},
+        {0}
+    };
+    int err = mpv_render_context_create(&ctx->client_api->render_context,
+                                        &dummy, params);
+    if (err >= 0) {
+        mpv_render_context_set_update_callback(ctx->client_api->render_context,
+                                               ctx->callback, ctx->callback_ctx);
+    }
+    return err;
+}
+
+int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int w, int h)
+{
+    if (!ctx->client_api->render_context)
+        return MPV_ERROR_INVALID_PARAMETER;
+
+    mpv_render_param params[] = {
+        {MPV_RENDER_PARAM_OPENGL_FBO, &(mpv_opengl_fbo){
+            .fbo = fbo,
+            .w = w,
+            .h = abs(h),
+        }},
+        {MPV_RENDER_PARAM_FLIP_Y, &(int){h < 0}},
+        {0}
+    };
+    return mpv_render_context_render(ctx->client_api->render_context, params);
+}
+
+int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time)
+{
+    if (!ctx->client_api->render_context)
+        return MPV_ERROR_INVALID_PARAMETER;
+
+    mpv_render_context_report_swap(ctx->client_api->render_context);
+    return 0;
+}
+
+int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx)
+{
+    if (ctx->client_api->render_context)
+        mpv_render_context_free(ctx->client_api->render_context);
+    ctx->client_api->render_context = NULL;
+    return 0;
+}
+
+int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4])
+{
+    return mpv_opengl_cb_draw(ctx, fbo, vp[2], vp[3]);
+}
+
+void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api)
+{
+    if (!ctx->mpctx->initialized)
+        return NULL;
+    void *res = NULL;
+    switch (sub_api) {
+    case MPV_SUB_API_OPENGL_CB:
+        res = opengl_cb_get_context(ctx);
+        break;
+    default:;
+    }
+    return res;
+}
 // stream_cb
 
 struct mp_custom_protocol {
diff --git a/player/command.c b/player/command.c
index 2e6b987..cf71b9d 100644
--- a/player/command.c
+++ b/player/command.c
@@ -6606,6 +6606,9 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags,
     if (co)
         mp_notify_property(mpctx, co->name);
 
+    if (opt_ptr == &opts->pause)
+        mp_notify(mpctx, opts->pause ? MPV_EVENT_PAUSE : MPV_EVENT_UNPAUSE, 0);
+
     if (self_update)
         return;
 
diff --git a/player/playloop.c b/player/playloop.c
index 91badf0..27b5da6 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -180,6 +180,10 @@ void set_pause_state(struct MPContext *mpctx, bool user_pause)
         } else {
             (void)get_relative_time(mpctx); // ignore time that passed during pause
         }
+
+        // For some reason, these events are supposed to be sent even if only
+        // the internal pause state changed (and "pause" property didn't)... OK.
+        mp_notify(mpctx, opts->pause ? MPV_EVENT_PAUSE : MPV_EVENT_UNPAUSE, 0);
     }
 
     update_core_idle_state(mpctx);
diff --git a/wscript_build.py b/wscript_build.py
index 16c8cf0..3235afb 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -737,7 +737,7 @@ def build(ctx):
             PRIV_LIBS    = get_deps(),
         )
 
-        headers = ["client.h", "render.h",
+        headers = ["client.h", "qthelper.hpp", "opengl_cb.h", "render.h",
                    "render_gl.h", "stream_cb.h"]
         for f in headers:
             ctx.install_as(ctx.env.INCLUDEDIR + '/mpv/' + f, 'libmpv/' + f)
-- 
2.41.0

