diff --git a/src/PreCompiledHeaders.hh b/src/PreCompiledHeaders.hh
index b8aec3cf8594b5e49afe13880268536dabada109..e39b970f195aa0697806625c68e465f0efb5d653 100644
--- a/src/PreCompiledHeaders.hh
+++ b/src/PreCompiledHeaders.hh
@@ -30,6 +30,7 @@
 
 /* EXT_INC_PRIVATE */
 #include <mpv/client.h>
+#include <mpv/render_gl.h>
 
 /* QT_STRUCT_INC */
 #include <qglobal.h>
@@ -65,6 +66,7 @@
 #include <QJsonArray>
 #include <QJsonDocument>
 #include <QJsonValue>
+#include <QOpenGLWidget>
 
 /* QT_WIDGET_INC */
 #include <QCoreApplication>
diff --git a/src/UI/DocumentViews/MpvContainer.cc b/src/UI/DocumentViews/MpvContainer.cc
index f52c629eedc74d2813b8790ca38047e7b433cef9..104463b87d8c8216ea6a32147b775f1b94634226 100644
--- a/src/UI/DocumentViews/MpvContainer.cc
+++ b/src/UI/DocumentViews/MpvContainer.cc
@@ -1,18 +1,31 @@
 #include "PreCompiledHeaders.hh"
 #include "UI/DocumentViews/MpvContainer.hh"
+#include <QtGui/QOpenGLContext>
+#include <QtCore/QMetaObject>
+#include <mpv/client.h>
+#include <mpv/render_gl.h>
 
 using namespace Vivy;
 using namespace std::string_literals;
 
+static void *
+get_proc_address(void *ctx, const char *name)
+{
+    QOpenGLContext *glctx = QOpenGLContext::currentContext();
+    if (!glctx)
+        return nullptr;
+    return reinterpret_cast<void *>(glctx->getProcAddress(QByteArray(name)));
+}
+
 void
 MpvContainer::mpvEventWakeUpCB(void *user) noexcept
 {
-    MpvContainer *container = reinterpret_cast<MpvContainer *>(user);
-    emit container->mpvEvent();
+    QMetaObject::invokeMethod(reinterpret_cast<MpvContainer *>(user), "mpvEvent",
+                              Qt::QueuedConnection);
 }
 
 MpvContainer::MpvContainer(QWidget *parent)
-    : QWidget(parent)
+    : QOpenGLWidget(parent)
     , mpv(mpv_create())
 {
     if (mpv == nullptr)
@@ -21,7 +34,7 @@ MpvContainer::MpvContainer(QWidget *parent)
     setAttribute(Qt::WA_DontCreateNativeAncestors);
     setAttribute(Qt::WA_NativeWindow);
 
-    logWarning() << "Don't init MPV by default, you should use the reload button!";
+    initializeMpv();
 }
 
 void
@@ -31,8 +44,6 @@ MpvContainer::initializeMpv()
         throw std::logic_error("MPV is already initialized!");
     isMpvAlreadyInitialized = true;
 
-    quint64 wid = winId();
-    mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
     mpv_set_option_string(mpv, "idle", "yes");
     mpv_set_option_string(mpv, "loop-file", "inf");
     mpv_set_option_string(mpv, "no-config", "yes");
@@ -56,14 +67,42 @@ MpvContainer::initializeMpv()
 }
 
 void
-MpvContainer::reCreateMpvContext()
+MpvContainer::initializeGL()
 {
-    closeMpv();
-    mpv = mpv_create();
-    if (mpv == nullptr)
-        throw std::runtime_error("Failed to create the MPV context");
-    initializeMpv();
-    loadFile(previousLoadedFile);
+    mpv_opengl_init_params gl_init_params;
+    memset(&gl_init_params, 0, sizeof(mpv_opengl_init_params));
+    gl_init_params.get_proc_address = get_proc_address;
+    mpv_render_param params[]{ { MPV_RENDER_PARAM_API_TYPE,
+                                 const_cast<char *>(MPV_RENDER_API_TYPE_OPENGL) },
+                               { MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params },
+                               { MPV_RENDER_PARAM_INVALID, nullptr } };
+
+    if (mpv_render_context_create(&mpv_gl, mpv, params) < 0) {
+        throw std::runtime_error("failed to initialize mpv GL context");
+    } else {
+        mpv_render_context_set_update_callback(mpv_gl, MpvContainer::onUpdate,
+                                               reinterpret_cast<void *>(this));
+    }
+}
+
+void
+MpvContainer::paintGL() noexcept
+{
+    mpv_opengl_fbo mpfbo{ static_cast<int>(defaultFramebufferObject()), width(), height(), 0 };
+    int flip_y{ 1 };
+
+    mpv_render_param params[] = { { MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo },
+                                  { MPV_RENDER_PARAM_FLIP_Y, &flip_y },
+                                  { MPV_RENDER_PARAM_INVALID, nullptr } };
+    // See render_gl.h on what OpenGL environment mpv expects, and
+    // other API details.
+    mpv_render_context_render(mpv_gl, params);
+}
+
+void
+MpvContainer::onUpdate(void *ctx) noexcept
+{
+    QMetaObject::invokeMethod(reinterpret_cast<MpvContainer *>(ctx), "maybeUpdate");
 }
 
 void
@@ -82,24 +121,24 @@ MpvContainer::registerMpvDurationCallback(std::function<void(double)> callback)
     mpvDurationCallback = callback;
 }
 
-void
-MpvContainer::closeMpv() noexcept
+MpvContainer::~MpvContainer() noexcept
 {
+    makeCurrent();
+    logDebug() << "Closing the MPV context";
+    if (mpv_gl) {
+        mpv_render_context_free(mpv_gl);
+    }
     if (mpv) {
-        logDebug() << "Closing the MPV context";
         asyncCommand(AsyncCmdType::None, { "quit", nullptr });
+        mpv_wait_async_requests(mpv);
         registerMpvTimeCallback(nullptr);
         registerMpvDurationCallback(nullptr);
-        asyncCommand(AsyncCmdType::None, { "quit", nullptr });
-        mpv_wait_async_requests(mpv);
         mpv_destroy(mpv);
         mpv                     = nullptr; // Stop all other callbacks here
         isMpvAlreadyInitialized = false;   // De-init
     }
 }
 
-MpvContainer::~MpvContainer() noexcept { closeMpv(); }
-
 void
 MpvContainer::handleMpvEvent(const mpv_event *const event) noexcept
 {
@@ -117,7 +156,7 @@ MpvContainer::handleMpvEvent(const mpv_event *const event) noexcept
     };
 
     switch (event->event_id) {
-    case MPV_EVENT_SHUTDOWN: closeMpv(); break;
+    case MPV_EVENT_SHUTDOWN: logWarning() << "Ignore shutdown event for now..."; break;
 
     case MPV_EVENT_LOG_MESSAGE:
         msg     = reinterpret_cast<mpv_event_log_message *>(event->data);
@@ -233,6 +272,27 @@ MpvContainer::onMpvEvent() noexcept
     }
 }
 
+// Make Qt invoke mpv_render_context_render() to draw a new/updated video frame.
+void
+MpvContainer::maybeUpdate() noexcept
+{
+    // If the Qt window is not visible, Qt's update() will just skip rendering.
+    // This confuses mpv's render API, and may lead to small occasional
+    // freezes due to video rendering timing out.
+    // Handle this by manually redrawing.
+    // Note: Qt doesn't seem to provide a way to query whether update() will
+    //       be skipped, and the following code still fails when e.g. switching
+    //       to a different workspace with a reparenting window manager.
+    if (window()->isMinimized()) {
+        makeCurrent();
+        paintGL();
+        context()->swapBuffers(context()->surface());
+        doneCurrent();
+    } else {
+        update();
+    }
+}
+
 void
 MpvContainer::loadFile(const QString &filename) noexcept
 {
@@ -268,7 +328,7 @@ MpvContainer::printMpvError(int rc) const noexcept
     if (rc == MPV_ERROR_SUCCESS)
         return;
 
-    logError() << "MPV error:" << mpv_error_string(rc);
+    logError() << "MPV error: " << mpv_error_string(rc);
 }
 
 void
@@ -288,10 +348,6 @@ MpvContainer::mpvPause() noexcept
 void
 MpvContainer::mpvTogglePlayback() noexcept
 {
-    if (!isMpvAlreadyInitialized) {
-        reCreateMpvContext();
-    }
-
     logDebug() << "MPV: Toggling the playback";
     asyncCommand(AsyncCmdType::TogglePlayback, { "cycle", "pause", "up", nullptr });
 }
diff --git a/src/UI/DocumentViews/MpvContainer.hh b/src/UI/DocumentViews/MpvContainer.hh
index b83e6f9d65ccb9c6b4ecdd552da7982516e45a39..1649ede2c47e6abd91d4ae79b0a1208f9c04d450 100644
--- a/src/UI/DocumentViews/MpvContainer.hh
+++ b/src/UI/DocumentViews/MpvContainer.hh
@@ -9,14 +9,9 @@
 #include "Lib/Utils.hh"
 #include "Lib/Log.hh"
 
-extern "C" {
-struct mpv_handle;
-struct mpv_event;
-}
-
 namespace Vivy
 {
-class MpvContainer final : public QWidget {
+class MpvContainer final : public QOpenGLWidget {
     Q_OBJECT
     VIVY_UNMOVABLE_OBJECT(MpvContainer)
     VIVY_APP_LOGGABLE_OBJECT(MpvContainer, logger)
@@ -49,19 +44,22 @@ public:
 private:
     void initializeMpv();
     void handleMpvEvent(const mpv_event *const) noexcept;
-    void closeMpv() noexcept;
     void printMpvError(int) const noexcept;
     void asyncCommand(const AsyncCmdType, std::initializer_list<const char *>) noexcept;
     void unloadAssFile() noexcept;
     void handleMpvEventCommandReply(const AsyncCmdType) noexcept;
     int getAssSid() const noexcept;
 
-    // Must be static to be passed as a function ptr
+    void initializeGL() override;
+    void paintGL() noexcept override;
+
+    static void onUpdate(void *) noexcept;
     static void mpvEventWakeUpCB(void *) noexcept;
 
     bool isPlaybackPaused{ true };
     bool isMpvAlreadyInitialized{ false };
-    mpv_handle *volatile mpv{ nullptr };
+    mpv_handle *mpv{ nullptr };
+    mpv_render_context *mpv_gl{ nullptr };
     qint64 sid{ -1 };
     QString previousLoadedFile{};
     std::function<void(double)> mpvTimeCallback{ nullptr };
@@ -71,7 +69,7 @@ public slots:
     void mpvPlay() noexcept;
     void mpvPause() noexcept;
     void mpvTogglePlayback() noexcept;
-    void reCreateMpvContext();
+    void maybeUpdate() noexcept;
 
 private slots:
     void onMpvEvent() noexcept;