diff --git a/modules/gui/qt/maininterface/mainctx.cpp b/modules/gui/qt/maininterface/mainctx.cpp
index e7fc42a38bfdb372837796e89dfbfa17ea687c23..c68ff2848fcf4739f08a114a6644121c002013f4 100644
--- a/modules/gui/qt/maininterface/mainctx.cpp
+++ b/modules/gui/qt/maininterface/mainctx.cpp
@@ -294,7 +294,7 @@ MainCtx::~MainCtx()
     var_DelCallback( libvlc, "intf-popupmenu", PopupMenuCB, p_intf );
 
     if (m_medialib)
-        m_medialib->destroy();
+        delete m_medialib;
 
     p_intf->p_mi = NULL;
 }
diff --git a/modules/gui/qt/medialibrary/medialib.cpp b/modules/gui/qt/medialibrary/medialib.cpp
index 5fb0b8fb21f1865331e8ad5fc4839d7b5c883545..df00cbb57bf36c28cdab8a0165a1463f16b9e6fa 100644
--- a/modules/gui/qt/medialibrary/medialib.cpp
+++ b/modules/gui/qt/medialibrary/medialib.cpp
@@ -34,42 +34,12 @@ MediaLib::MediaLib(qt_intf_t *_intf, vlc::playlist::PlaylistController* playlist
 {
     m_event_cb.reset( vlc_ml_event_register_callback( m_ml, MediaLib::onMediaLibraryEvent,
                                                       this ) );
-
-    /* https://xkcd.com/221/ */
-    m_mlThreadPool.setMaxThreadCount(4);
+    m_runner = new MLThreadRunner(m_ml);
 }
 
 MediaLib::~MediaLib()
 {
-    assert(m_objectTasks.empty());
-    assert(m_runningTasks.empty());
-}
-
-void MediaLib::destroy()
-{
-    m_shuttingDown = true;
-    //try to cancel as many tasks as possible
-    for (auto taskIt = m_objectTasks.begin(); taskIt != m_objectTasks.end(); /**/)
-    {
-        const QObject* object = taskIt.key();
-        quint64 key = taskIt.value();
-        auto task = m_runningTasks.value(key, nullptr);
-        if (m_mlThreadPool.tryTake(task))
-        {
-            delete task;
-            m_runningTasks.remove(key);
-            taskIt = m_objectTasks.erase(taskIt);
-            if (m_objectTasks.count(object) == 0)
-                disconnect(object, &QObject::destroyed, this, &MediaLib::runOnMLThreadTargetDestroyed);
-        }
-        else
-            ++taskIt;
-    }
-
-    if (m_runningTasks.empty())
-    {
-        deleteLater();
-    }
+    m_runner->destroy();
 }
 
 static void convertMLItemToPlaylistMedias(vlc_medialibrary_t* ml, const MLItemId & itemId, const QStringList &options, QVector<vlc::playlist::Media>& medias)
@@ -434,13 +404,54 @@ void MediaLib::onMediaLibraryEvent( void* data, const vlc_ml_event_t* event )
     }
 }
 
+
+
+MLThreadRunner::MLThreadRunner(vlc_medialibrary_t* ml)
+    : m_ml(ml)
+{
+    m_mlThreadPool.setMaxThreadCount(4);
+}
+
+MLThreadRunner::~MLThreadRunner()
+{
+    assert(m_objectTasks.empty());
+    assert(m_runningTasks.empty());
+}
+
+void MLThreadRunner::destroy()
+{
+    m_shuttingDown = true;
+    //try to cancel as many tasks as possible
+    for (auto taskIt = m_objectTasks.begin(); taskIt != m_objectTasks.end(); /**/)
+    {
+        const QObject* object = taskIt.key();
+        quint64 key = taskIt.value();
+        auto task = m_runningTasks.value(key, nullptr);
+        if (m_mlThreadPool.tryTake(task))
+        {
+            delete task;
+            m_runningTasks.remove(key);
+            taskIt = m_objectTasks.erase(taskIt);
+            if (m_objectTasks.count(object) == 0)
+                disconnect(object, &QObject::destroyed, this, &MLThreadRunner::runOnMLThreadTargetDestroyed);
+        }
+        else
+            ++taskIt;
+    }
+
+    if (m_runningTasks.empty())
+    {
+        deleteLater();
+    }
+}
+
 quint64 MediaLib::runOnMLThread(const QObject* obj,
                 std::function< void(vlc_medialibrary_t* ml)> mlCb,
                 std::function< void()> uiCb,
                 const char* queue)
 {
     struct NoCtx{};
-    return runOnMLThread<NoCtx>(obj,
+    return m_runner->runOnMLThread<NoCtx>(obj,
     [mlCb](vlc_medialibrary_t* ml, NoCtx&){
         mlCb(ml);
     },
@@ -455,7 +466,7 @@ quint64 MediaLib::runOnMLThread(const QObject* obj,
                 std::function< void(quint64)> uiCb, const char* queue)
 {
     struct NoCtx{};
-    return runOnMLThread<NoCtx>(obj,
+    return m_runner->runOnMLThread<NoCtx>(obj,
     [mlCb](vlc_medialibrary_t* ml, NoCtx&){
         mlCb(ml);
     },
@@ -470,7 +481,7 @@ quint64 MediaLib::runOnMLThread(const QObject* obj,
                 const char* queue)
 {
     struct NoCtx{};
-    return runOnMLThread<NoCtx>(obj,
+    return m_runner->runOnMLThread<NoCtx>(obj,
     [mlCb](vlc_medialibrary_t* ml, NoCtx&){
         mlCb(ml);
     },
@@ -479,8 +490,12 @@ quint64 MediaLib::runOnMLThread(const QObject* obj,
     queue);
 }
 
-
 void MediaLib::cancelMLTask(const QObject* object, quint64 taskId)
+{
+    m_runner->cancelMLTask(object, taskId);
+}
+
+void MLThreadRunner::cancelMLTask(const QObject* object, quint64 taskId)
 {
     assert(taskId != 0);
 
@@ -494,10 +509,10 @@ void MediaLib::cancelMLTask(const QObject* object, quint64 taskId)
     m_runningTasks.remove(taskId);
     m_objectTasks.remove(object, taskId);
     if (m_objectTasks.count(object) == 0)
-        disconnect(object, &QObject::destroyed, this, &MediaLib::runOnMLThreadTargetDestroyed);
+        disconnect(object, &QObject::destroyed, this, &MLThreadRunner::runOnMLThreadTargetDestroyed);
 }
 
-void MediaLib::runOnMLThreadDone(RunOnMLThreadBaseRunner* runner, quint64 target, const QObject* object, int status)
+void MLThreadRunner::runOnMLThreadDone(RunOnMLThreadBaseRunner* runner, quint64 target, const QObject* object, int status)
 {
     if (m_shuttingDown)
     {
@@ -506,7 +521,7 @@ void MediaLib::runOnMLThreadDone(RunOnMLThreadBaseRunner* runner, quint64 target
             m_runningTasks.remove(target);
             m_objectTasks.remove(object, target);
             if (m_objectTasks.count(object) == 0)
-                disconnect(object, &QObject::destroyed, this, &MediaLib::runOnMLThreadTargetDestroyed);
+                disconnect(object, &QObject::destroyed, this, &MLThreadRunner::runOnMLThreadTargetDestroyed);
         }
         if (m_runningTasks.empty())
             deleteLater();
@@ -518,12 +533,12 @@ void MediaLib::runOnMLThreadDone(RunOnMLThreadBaseRunner* runner, quint64 target
         m_runningTasks.remove(target);
         m_objectTasks.remove(object, target);
         if (m_objectTasks.count(object) == 0)
-            disconnect(object, &QObject::destroyed, this, &MediaLib::runOnMLThreadTargetDestroyed);
+            disconnect(object, &QObject::destroyed, this, &MLThreadRunner::runOnMLThreadTargetDestroyed);
     }
     runner->deleteLater();
 }
 
-void MediaLib::runOnMLThreadTargetDestroyed(QObject * object)
+void MLThreadRunner::runOnMLThreadTargetDestroyed(QObject * object)
 {
     if (m_objectTasks.contains(object))
     {
diff --git a/modules/gui/qt/medialibrary/medialib.hpp b/modules/gui/qt/medialibrary/medialib.hpp
index 68d0b763e124ea26a4e811434bf0d50d2e75607e..0bb30b02cd719997aae15267a10e247063028d85 100644
--- a/modules/gui/qt/medialibrary/medialib.hpp
+++ b/modules/gui/qt/medialibrary/medialib.hpp
@@ -35,6 +35,7 @@ class Media;
 
 struct vlc_medialibrary_t;
 
+class MLThreadRunner;
 class RunOnMLThreadBaseRunner;
 
 class MediaLib : public QObject
@@ -47,15 +48,9 @@ public:
     Q_PROPERTY(QString discoveryEntryPoint READ discoveryEntryPoint NOTIFY discoveryEntryPointChanged FINAL)
     Q_PROPERTY(bool idle READ idle NOTIFY idleChanged FINAL)
 
-    enum MLTaskStatus {
-        ML_TASK_STATUS_SUCCEED,
-        ML_TASK_STATUS_CANCELED
-    };
-
 public:
     MediaLib(qt_intf_t* _intf, vlc::playlist::PlaylistController* playlistController, QObject* _parent = nullptr );
-
-    void destroy();
+    ~MediaLib();
 
     Q_INVOKABLE void addToPlaylist(const MLItemId &itemId, const QStringList &options = {});
     Q_INVOKABLE void addToPlaylist(const QString& mrl, const QStringList &options = {});
@@ -183,14 +178,8 @@ signals:
     void idleChanged();
 
 private:
-    //use the destroy function
-    ~MediaLib();
     static void onMediaLibraryEvent( void* data, const vlc_ml_event_t* event );
 
-private slots:
-    void runOnMLThreadDone(RunOnMLThreadBaseRunner* runner, quint64 target, const QObject* object, int status);
-    void runOnMLThreadTargetDestroyed(QObject * object);
-
 private:
     qt_intf_t* m_intf;
     vlc::playlist::PlaylistController* m_playlistController = nullptr;
@@ -203,16 +192,46 @@ private:
     /* Medialibrary */
     vlc_medialibrary_t* m_ml;
     std::unique_ptr<vlc_ml_event_callback_t, std::function<void(vlc_ml_event_callback_t*)>> m_event_cb;
+    MLThreadRunner* m_runner = nullptr;
+
+    QMap<QVector<MLItemId>, QVector<QJSValue>> m_inputItemQuery;
+};
 
+class MLThreadRunner : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum MLTaskStatus {
+        ML_TASK_STATUS_SUCCEED,
+        ML_TASK_STATUS_CANCELED
+    };
+
+    MLThreadRunner(vlc_medialibrary_t* ml);
+    ~MLThreadRunner();
+
+    void destroy();
+    void cancelMLTask(const QObject* object, quint64 taskId);
+
+    template<typename Ctx>
+    quint64 runOnMLThread(const QObject* obj,
+                          std::function<void (vlc_medialibrary_t*, Ctx&)> mlFun,
+                          std::function<void (quint64 taskId, Ctx&)> uiFun,
+                          const char* queue);
+
+private:
+    vlc_medialibrary_t* m_ml = nullptr;
     MLThreadPool m_mlThreadPool;
 
-    /* run on ml thread properties */
     bool m_shuttingDown = false;
     quint64 m_taskId = 1;
     QMap<quint64, RunOnMLThreadBaseRunner*> m_runningTasks;
     QMultiMap<const QObject*, quint64> m_objectTasks;
 
-    QMap<QVector<MLItemId>, QVector<QJSValue>> m_inputItemQuery;
+
+private slots:
+    void runOnMLThreadDone(RunOnMLThreadBaseRunner* runner, quint64 target, const QObject* object, int status);
+    void runOnMLThreadTargetDestroyed(QObject * object);
 };
 
 class RunOnMLThreadBaseRunner : public QObject, public QRunnable
@@ -250,11 +269,11 @@ public:
     {
         if (m_canceled)
         {
-            emit done(this, m_taskId, m_obj, MediaLib::ML_TASK_STATUS_CANCELED);
+            emit done(this, m_taskId, m_obj, MLThreadRunner::ML_TASK_STATUS_CANCELED);
             return;
         }
         m_mlFun(m_ml, m_ctx);
-        emit done(this, m_taskId, m_obj, MediaLib::ML_TASK_STATUS_SUCCEED);
+        emit done(this, m_taskId, m_obj, MLThreadRunner::ML_TASK_STATUS_SUCCEED);
     }
 
     //called from UI thread
@@ -278,7 +297,7 @@ private:
 };
 
 template<typename Ctx>
-quint64 MediaLib::runOnMLThread(const QObject* obj,
+quint64 MLThreadRunner::runOnMLThread(const QObject* obj,
                             std::function<void (vlc_medialibrary_t*, Ctx&)> mlFun,
                             std::function<void (quint64 taskId, Ctx&)> uiFun,
                             const char* queue)
@@ -288,10 +307,19 @@ quint64 MediaLib::runOnMLThread(const QObject* obj,
 
     auto taskId = m_taskId++;
     auto runnable = new RunOnMLThreadRunner<Ctx>(taskId, obj, mlFun, uiFun, m_ml);
-    connect(runnable, &RunOnMLThreadBaseRunner::done, this, &MediaLib::runOnMLThreadDone);
-    connect(obj, &QObject::destroyed, this, &MediaLib::runOnMLThreadTargetDestroyed);
+    connect(runnable, &RunOnMLThreadBaseRunner::done, this, &MLThreadRunner::runOnMLThreadDone);
+    connect(obj, &QObject::destroyed, this, &MLThreadRunner::runOnMLThreadTargetDestroyed);
     m_runningTasks.insert(taskId, runnable);
     m_objectTasks.insert(obj, taskId);
     m_mlThreadPool.start(runnable, queue);
     return taskId;
 }
+
+template<typename Ctx>
+quint64 MediaLib::runOnMLThread(const QObject* obj,
+                                    std::function<void (vlc_medialibrary_t*, Ctx&)> mlFun,
+                                    std::function<void (quint64 taskId, Ctx&)> uiFun,
+                                    const char* queue)
+{
+    return m_runner->runOnMLThread<Ctx>(obj, mlFun, uiFun, queue);
+}