[vlc-commits] [Git][videolan/vlc][master] 12 commits: qt: move private properties in d_ptr in NetworkDeviceModel

Steve Lhomme (@robUx4) gitlab at videolan.org
Mon Nov 25 14:02:12 UTC 2024



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
b8ef45da by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: move private properties in d_ptr in NetworkDeviceModel

half the private properties were in the private class, half in the public class

- - - - -
797fc490 by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: remove undefined methods in NetworkDeviceModel

- - - - -
1047c9eb by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: move private properties in d_ptr in NetworkMediaModel

- - - - -
b537d9e5 by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: keep reference to the source in NetworkTreeItem

When NetworkDevice model is destroyed, the source is unloaded which cause the
items to be removed, hence invalidating items from the tree. This caused
browsing devices sub-nodes to present empty views as their content were removed
when the device model was destroyed

- - - - -
d9cf08f2 by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: remove unused variable in MLBaseModel

- - - - -
a801125a by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: move MLThreadRunner outside of MediaLib

So the mechanism can be reused by other classes

- - - - -
640129ba by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: expose a MLThreadRunner in MainCtx

- - - - -
3c83e6a4 by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: only run the blocking part of DeviceSource in a thread runner

DeviceSource object lives back in Qt thread

- - - - -
f3d5e504 by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: move mediasouce C++ wrapper definitions to a common file

- - - - -
8efeec95 by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: make NetworkTreeItem only reference it's origin media source

The models are closer to their counterparts from VLC:

- DeviceSourceProvider will expose each vlc_media_sources
- MediaSourceModel will expose each media belonging to a given media source
- NetworkTreeItem only reference it's origin media source rather than all the
  sources from the agregated item from NetworkDeviceModel

- - - - -
c3d37951 by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: remove workerThreads from MainCtx

- - - - -
e0986ee7 by Pierre Lamot at 2024-11-25T13:48:55+00:00
qt: create a cache for media source

this avoids creating a new media tree listenner each time the NetworkDeviceModel
is created, especially since the the MediaSourceModel can be retained by NetworkItem

- - - - -


20 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainctx.cpp
- modules/gui/qt/maininterface/mainctx.hpp
- modules/gui/qt/medialibrary/medialib.cpp
- modules/gui/qt/medialibrary/medialib.hpp
- modules/gui/qt/medialibrary/mlbasemodel.cpp
- modules/gui/qt/medialibrary/mlthreadpool.cpp
- modules/gui/qt/medialibrary/mlthreadpool.hpp
- modules/gui/qt/meson.build
- modules/gui/qt/network/devicesourceprovider.cpp
- modules/gui/qt/network/devicesourceprovider.hpp
- modules/gui/qt/network/mediatreelistener.hpp
- modules/gui/qt/network/networkdevicemodel.cpp
- modules/gui/qt/network/networkdevicemodel.hpp
- modules/gui/qt/network/networkmediamodel.cpp
- modules/gui/qt/network/networkmediamodel.hpp
- modules/gui/qt/network/standardpathmodel.cpp
- + modules/gui/qt/network/vlcmediasourcewrapper.hpp
- modules/gui/qt/qt.cpp
- modules/gui/qt/util/shared_input_item.hpp


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -259,6 +259,7 @@ libqt_plugin_la_SOURCES = \
 	network/servicesdiscoverymodel.hpp \
 	network/standardpathmodel.cpp \
 	network/standardpathmodel.hpp \
+	network/vlcmediasourcewrapper.hpp \
 	style/qtthemeprovider.hpp \
 	style/colorcontext.cpp \
 	style/colorcontext.hpp \


=====================================
modules/gui/qt/maininterface/mainctx.cpp
=====================================
@@ -35,7 +35,6 @@
 #include "compositor.hpp"
 #include "util/renderer_manager.hpp"
 #include "util/csdbuttonmodel.hpp"
-#include "util/workerthreadset.hpp"
 
 #include "widgets/native/customwidgets.hpp"               // qtEventToVLCKey, QVLCStackedWidget
 #include "util/qt_dirs.hpp"                     // toNativeSeparators
@@ -292,12 +291,16 @@ MainCtx::MainCtx(qt_intf_t *_p_intf)
         }
     }
 #endif
+
+    m_threadRunner = new ThreadRunner();
 }
 
 MainCtx::~MainCtx()
 {
     /* Save states */
 
+    m_threadRunner->destroy();
+
     settings->beginGroup("MainWindow");
     settings->setValue( "pl-dock-status", b_playlistDocked );
     settings->setValue( "ShowRemainingTime", m_showRemainingTime );
@@ -597,14 +600,9 @@ inline void MainCtx::initSystray()
         m_systray = std::make_unique<VLCSystray>(this);
 }
 
-WorkerThreadSet* MainCtx::workersThreads() const
+ThreadRunner* MainCtx::threadRunner() const
 {
-    if (!m_workersThreads)
-    {
-        m_workersThreads.reset( new WorkerThreadSet );
-    }
-
-    return m_workersThreads.get();
+    return m_threadRunner;
 }
 
 QUrl MainCtx::folderMRL(const QString &fileMRL) const


=====================================
modules/gui/qt/maininterface/mainctx.hpp
=====================================
@@ -62,7 +62,6 @@ class VideoSurfaceProvider;
 class ControlbarProfileModel;
 class SearchCtx;
 class SortCtx;
-class WorkerThreadSet;
 class VLCSystray;
 class MediaLib;
 class ColorSchemeModel;
@@ -71,6 +70,7 @@ class VLCVarChoiceModel;
 class UpdateModel;
 #endif
 struct vlc_preparser_t;
+class ThreadRunner;
 
 namespace vlc {
 namespace playlist {
@@ -301,7 +301,7 @@ public:
 
     Q_INVOKABLE bool useXmasCone() const;
 
-    WorkerThreadSet *workersThreads() const;
+    ThreadRunner* threadRunner() const;
 
     Q_INVOKABLE QUrl folderMRL(const QString &fileMRL) const;
     Q_INVOKABLE QUrl folderMRL(const QUrl &fileMRL) const;
@@ -398,7 +398,7 @@ protected:
     mutable std::unique_ptr<UpdateModel> m_updateModel;
 #endif
 
-    mutable std::unique_ptr<WorkerThreadSet> m_workersThreads;
+    ThreadRunner* m_threadRunner = nullptr;
 
 public slots:
     void toggleToolbarMenu();


=====================================
modules/gui/qt/medialibrary/medialib.cpp
=====================================
@@ -34,7 +34,7 @@ MediaLib::MediaLib(qt_intf_t *_intf, vlc::playlist::PlaylistController* playlist
 {
     m_event_cb.reset( vlc_ml_event_register_callback( m_ml, MediaLib::onMediaLibraryEvent,
                                                       this ) );
-    m_runner = new MLThreadRunner(m_ml);
+    m_runner = new ThreadRunner();
 }
 
 MediaLib::~MediaLib()
@@ -404,55 +404,14 @@ 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 m_runner->runOnMLThread<NoCtx>(obj,
-    [mlCb](vlc_medialibrary_t* ml, NoCtx&){
+    return m_runner->runOnThread<NoCtx>(obj,
+    [mlCb, ml=m_ml](NoCtx&){
         mlCb(ml);
     },
     [uiCb](quint64, NoCtx&){
@@ -466,8 +425,8 @@ quint64 MediaLib::runOnMLThread(const QObject* obj,
                 std::function< void(quint64)> uiCb, const char* queue)
 {
     struct NoCtx{};
-    return m_runner->runOnMLThread<NoCtx>(obj,
-    [mlCb](vlc_medialibrary_t* ml, NoCtx&){
+    return m_runner->runOnThread<NoCtx>(obj,
+    [mlCb, ml=m_ml](NoCtx&){
         mlCb(ml);
     },
     [uiCb](quint64 requestId, NoCtx&){
@@ -481,8 +440,8 @@ quint64 MediaLib::runOnMLThread(const QObject* obj,
                 const char* queue)
 {
     struct NoCtx{};
-    return m_runner->runOnMLThread<NoCtx>(obj,
-    [mlCb](vlc_medialibrary_t* ml, NoCtx&){
+    return m_runner->runOnThread<NoCtx>(obj,
+    [mlCb, ml=m_ml](NoCtx&){
         mlCb(ml);
     },
     [](quint64, NoCtx&){
@@ -492,66 +451,6 @@ quint64 MediaLib::runOnMLThread(const QObject* obj,
 
 void MediaLib::cancelMLTask(const QObject* object, quint64 taskId)
 {
-    m_runner->cancelMLTask(object, taskId);
-}
-
-void MLThreadRunner::cancelMLTask(const QObject* object, quint64 taskId)
-{
-    assert(taskId != 0);
-
-    auto task = m_runningTasks.value(taskId, nullptr);
-    if (!task)
-        return;
-    task->cancel();
-    bool removed = m_mlThreadPool.tryTake(task);
-    if (removed)
-        delete task;
-    m_runningTasks.remove(taskId);
-    m_objectTasks.remove(object, taskId);
-    if (m_objectTasks.count(object) == 0)
-        disconnect(object, &QObject::destroyed, this, &MLThreadRunner::runOnMLThreadTargetDestroyed);
+    m_runner->cancelTask(object, taskId);
 }
 
-void MLThreadRunner::runOnMLThreadDone(RunOnMLThreadBaseRunner* runner, quint64 target, const QObject* object, int status)
-{
-    if (m_shuttingDown)
-    {
-        if (m_runningTasks.contains(target))
-        {
-            m_runningTasks.remove(target);
-            m_objectTasks.remove(object, target);
-            if (m_objectTasks.count(object) == 0)
-                disconnect(object, &QObject::destroyed, this, &MLThreadRunner::runOnMLThreadTargetDestroyed);
-        }
-        if (m_runningTasks.empty())
-            deleteLater();
-    }
-    else if (m_runningTasks.contains(target))
-    {
-        if (status == ML_TASK_STATUS_SUCCEED)
-            runner->runUICallback();
-        m_runningTasks.remove(target);
-        m_objectTasks.remove(object, target);
-        if (m_objectTasks.count(object) == 0)
-            disconnect(object, &QObject::destroyed, this, &MLThreadRunner::runOnMLThreadTargetDestroyed);
-    }
-    runner->deleteLater();
-}
-
-void MLThreadRunner::runOnMLThreadTargetDestroyed(QObject * object)
-{
-    if (m_objectTasks.contains(object))
-    {
-        for (auto taskId : m_objectTasks.values(object))
-        {
-            auto task = m_runningTasks.value(taskId, nullptr);
-            assert(task);
-            bool removed = m_mlThreadPool.tryTake(task);
-            if (removed)
-                delete task;
-            m_runningTasks.remove(taskId);
-        }
-        m_objectTasks.remove(object);
-        //no need to disconnect QObject::destroyed, as object is currently being destroyed
-    }
-}


=====================================
modules/gui/qt/medialibrary/medialib.hpp
=====================================
@@ -35,9 +35,6 @@ class Media;
 
 struct vlc_medialibrary_t;
 
-class MLThreadRunner;
-class RunOnMLThreadBaseRunner;
-
 class MediaLib : public QObject
 {
     Q_OBJECT
@@ -192,134 +189,16 @@ 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;
+    ThreadRunner* 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;
-
-    bool m_shuttingDown = false;
-    quint64 m_taskId = 1;
-    QMap<quint64, RunOnMLThreadBaseRunner*> m_runningTasks;
-    QMultiMap<const QObject*, quint64> m_objectTasks;
-
-
-private slots:
-    void runOnMLThreadDone(RunOnMLThreadBaseRunner* runner, quint64 target, const QObject* object, int status);
-    void runOnMLThreadTargetDestroyed(QObject * object);
-};
-
-class RunOnMLThreadBaseRunner : public QObject, public QRunnable
-{
-    Q_OBJECT
-public:
-    virtual ~RunOnMLThreadBaseRunner() = default;
-    virtual void runUICallback() = 0;
-    virtual void cancel() = 0;
-signals:
-    void done(RunOnMLThreadBaseRunner* runner, quint64 target, const QObject* object, int status);
-};
-
-template<typename Ctx>
-class RunOnMLThreadRunner : public RunOnMLThreadBaseRunner {
-public:
-    RunOnMLThreadRunner(
-        quint64 taskId,
-        const QObject* obj,
-        std::function<void (vlc_medialibrary_t*, Ctx&)> mlFun,
-        std::function<void (quint64, Ctx&)> uiFun,
-        vlc_medialibrary_t* ml
-    )
-        : RunOnMLThreadBaseRunner()
-        , m_taskId(taskId)
-        , m_obj(obj)
-        , m_mlFun(mlFun)
-        , m_uiFun(uiFun)
-        , m_ml(ml)
-    {
-        setAutoDelete(false);
-    }
-
-    void run() override
-    {
-        if (m_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, MLThreadRunner::ML_TASK_STATUS_SUCCEED);
-    }
-
-    //called from UI thread
-    void runUICallback() override
-    {
-        m_uiFun(m_taskId, m_ctx);
-    }
-
-    void cancel() override
-    {
-        m_canceled = true;
-    }
-private:
-    std::atomic_bool m_canceled {false};
-    quint64 m_taskId;
-    Ctx m_ctx; //default constructed
-    const QObject* m_obj = nullptr;
-    std::function<void (vlc_medialibrary_t*, Ctx&)> m_mlFun;
-    std::function<void (quint64, Ctx&)> m_uiFun;
-    vlc_medialibrary_t* m_ml = nullptr;
-};
-
-template<typename Ctx>
-quint64 MLThreadRunner::runOnMLThread(const QObject* obj,
-                            std::function<void (vlc_medialibrary_t*, Ctx&)> mlFun,
-                            std::function<void (quint64 taskId, Ctx&)> uiFun,
-                            const char* queue)
-{
-    if (m_shuttingDown)
-        return 0;
-
-    auto taskId = m_taskId++;
-    auto runnable = new RunOnMLThreadRunner<Ctx>(taskId, obj, mlFun, uiFun, m_ml);
-    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);
+    return m_runner->runOnThread<Ctx>(obj, [ml = m_ml, mlFun](Ctx& ctx){mlFun(ml,ctx);}, uiFun, queue);
 }


=====================================
modules/gui/qt/medialibrary/mlbasemodel.cpp
=====================================
@@ -380,7 +380,6 @@ MLItem *MLBaseModel::findInCache(const MLItemId& id, int *index) const
 
 void MLBaseModel::updateItemInCache(const MLItemId&)
 {
-    Q_D(MLBaseModel);
     // we can't safely update the item in the cache because our view may have a filter
     // and the update may cause the item filtered state to change requiring it to enter/leave
     // the cache


=====================================
modules/gui/qt/medialibrary/mlthreadpool.cpp
=====================================
@@ -119,3 +119,102 @@ QRunnable* MLThreadPool::getNextTaskFromQueue(const QString& queueName)
     queue.pop_front();
     return task;
 }
+
+ThreadRunner::ThreadRunner()
+{
+    m_threadPool.setMaxThreadCount(4);
+}
+
+ThreadRunner::~ThreadRunner()
+{
+    assert(m_objectTasks.empty());
+    assert(m_runningTasks.empty());
+}
+
+void ThreadRunner::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_threadPool.tryTake(task))
+        {
+            delete task;
+            m_runningTasks.remove(key);
+            taskIt = m_objectTasks.erase(taskIt);
+            if (m_objectTasks.count(object) == 0)
+                disconnect(object, &QObject::destroyed, this, &ThreadRunner::runOnThreadTargetDestroyed);
+        }
+        else
+            ++taskIt;
+    }
+
+    if (m_runningTasks.empty())
+    {
+        deleteLater();
+    }
+}
+
+void ThreadRunner::cancelTask(const QObject* object, quint64 taskId)
+{
+    assert(taskId != 0);
+
+    auto task = m_runningTasks.value(taskId, nullptr);
+    if (!task)
+        return;
+    task->cancel();
+    bool removed = m_threadPool.tryTake(task);
+    if (removed)
+        delete task;
+    m_runningTasks.remove(taskId);
+    m_objectTasks.remove(object, taskId);
+    if (m_objectTasks.count(object) == 0)
+        disconnect(object, &QObject::destroyed, this, &ThreadRunner::runOnThreadTargetDestroyed);
+}
+
+void ThreadRunner::runOnThreadDone(RunOnThreadBaseRunner* runner, quint64 target, const QObject* object, int status)
+{
+    if (m_shuttingDown)
+    {
+        if (m_runningTasks.contains(target))
+        {
+            m_runningTasks.remove(target);
+            m_objectTasks.remove(object, target);
+            if (m_objectTasks.count(object) == 0)
+                disconnect(object, &QObject::destroyed, this, &ThreadRunner::runOnThreadTargetDestroyed);
+        }
+        if (m_runningTasks.empty())
+            deleteLater();
+    }
+    else if (m_runningTasks.contains(target))
+    {
+        if (status == ML_TASK_STATUS_SUCCEED)
+            runner->runUICallback();
+        m_runningTasks.remove(target);
+        m_objectTasks.remove(object, target);
+        if (m_objectTasks.count(object) == 0)
+            disconnect(object, &QObject::destroyed, this, &ThreadRunner::runOnThreadTargetDestroyed);
+    }
+    runner->deleteLater();
+}
+
+void ThreadRunner::runOnThreadTargetDestroyed(QObject * object)
+{
+    if (m_objectTasks.contains(object))
+    {
+        for (auto taskId : m_objectTasks.values(object))
+        {
+            auto task = m_runningTasks.value(taskId, nullptr);
+            assert(task);
+            bool removed = m_threadPool.tryTake(task);
+            if (removed)
+                delete task;
+            m_runningTasks.remove(taskId);
+        }
+        m_objectTasks.remove(object);
+        //no need to disconnect QObject::destroyed, as object is currently being destroyed
+    }
+}


=====================================
modules/gui/qt/medialibrary/mlthreadpool.hpp
=====================================
@@ -80,4 +80,119 @@ private:
     QMap<QString, QQueue<QRunnable*>> m_serialTasks;
 };
 
+class RunOnThreadBaseRunner;
+
+class ThreadRunner : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum MLTaskStatus {
+        ML_TASK_STATUS_SUCCEED,
+        ML_TASK_STATUS_CANCELED
+    };
+
+    ThreadRunner();
+    ~ThreadRunner();
+
+    void destroy();
+    void cancelTask(const QObject* object, quint64 taskId);
+
+    template<typename Ctx>
+    quint64 runOnThread(const QObject* obj,
+                          std::function<void (Ctx&)> mlFun,
+                          std::function<void (quint64 taskId, Ctx&)> uiFun,
+                          const char* queue = nullptr);
+
+private slots:
+    void runOnThreadDone(RunOnThreadBaseRunner* runner, quint64 target, const QObject* object, int status);
+    void runOnThreadTargetDestroyed(QObject * object);
+
+private:
+    MLThreadPool m_threadPool;
+
+    bool m_shuttingDown = false;
+    quint64 m_taskId = 1;
+    QMap<quint64, RunOnThreadBaseRunner*> m_runningTasks;
+    QMultiMap<const QObject*, quint64> m_objectTasks;
+};
+
+class RunOnThreadBaseRunner : public QObject, public QRunnable
+{
+    Q_OBJECT
+public:
+    virtual ~RunOnThreadBaseRunner() = default;
+    virtual void runUICallback() = 0;
+    virtual void cancel() = 0;
+signals:
+    void done(RunOnThreadBaseRunner* runner, quint64 target, const QObject* object, int status);
+};
+
+template<typename Ctx>
+class RunOnThreadRunner : public RunOnThreadBaseRunner {
+public:
+    RunOnThreadRunner(
+        quint64 taskId,
+        const QObject* obj,
+        std::function<void (Ctx&)> mlFun,
+        std::function<void (quint64, Ctx&)> uiFun
+        )
+        : RunOnThreadBaseRunner()
+        , m_taskId(taskId)
+        , m_obj(obj)
+        , m_mlFun(mlFun)
+        , m_uiFun(uiFun)
+    {
+        setAutoDelete(false);
+    }
+
+    void run() override
+    {
+        if (m_canceled)
+        {
+            emit done(this, m_taskId, m_obj, ThreadRunner::ML_TASK_STATUS_CANCELED);
+            return;
+        }
+        m_mlFun(m_ctx);
+        emit done(this, m_taskId, m_obj, ThreadRunner::ML_TASK_STATUS_SUCCEED);
+    }
+
+    //called from UI thread
+    void runUICallback() override
+    {
+        m_uiFun(m_taskId, m_ctx);
+    }
+
+    void cancel() override
+    {
+        m_canceled = true;
+    }
+private:
+    std::atomic_bool m_canceled {false};
+    quint64 m_taskId;
+    Ctx m_ctx; //default constructed
+    const QObject* m_obj = nullptr;
+    std::function<void (Ctx&)> m_mlFun;
+    std::function<void (quint64, Ctx&)> m_uiFun;
+};
+
+template<typename Ctx>
+quint64 ThreadRunner::runOnThread(const QObject* obj,
+                                      std::function<void (Ctx&)> mlFun,
+                                      std::function<void (quint64 taskId, Ctx&)> uiFun,
+                                      const char* queue)
+{
+    if (m_shuttingDown)
+        return 0;
+
+    auto taskId = m_taskId++;
+    auto runnable = new RunOnThreadRunner<Ctx>(taskId, obj, mlFun, uiFun);
+    connect(runnable, &RunOnThreadBaseRunner::done, this, &ThreadRunner::runOnThreadDone);
+    connect(obj, &QObject::destroyed, this, &ThreadRunner::runOnThreadTargetDestroyed);
+    m_runningTasks.insert(taskId, runnable);
+    m_objectTasks.insert(obj, taskId);
+    m_threadPool.start(runnable, queue);
+    return taskId;
+}
+
 #endif // MLTHREADPOOL_HPP


=====================================
modules/gui/qt/meson.build
=====================================
@@ -385,6 +385,7 @@ some_sources = files(
     'network/servicesdiscoverymodel.hpp',
     'network/standardpathmodel.cpp',
     'network/standardpathmodel.hpp',
+    'network/vlcmediasourcewrapper.hpp',
     'style/qtthemeprovider.hpp',
     'style/colorcontext.cpp',
     'style/colorcontext.hpp',


=====================================
modules/gui/qt/network/devicesourceprovider.cpp
=====================================
@@ -18,13 +18,14 @@
 
 #include "devicesourceprovider.hpp"
 #include "networkmediamodel.hpp"
-
+#include "maininterface/mainctx.hpp"
+#include "medialibrary/mlthreadpool.hpp"
 
 //handle discovery events from the media source provider
-struct DeviceSourceProvider::ListenerCb : public MediaTreeListener::MediaTreeListenerCb {
-    ListenerCb(DeviceSourceProvider* provider, NetworkDeviceModel::MediaSourcePtr mediaSource)
-        : provider(provider)
-        , mediaSource(std::move(mediaSource))
+struct MediaSourceModel::ListenerCb : public MediaTreeListener::MediaTreeListenerCb {
+    ListenerCb(MediaSourceModel* model, MediaSourcePtr& mediaSource)
+        : m_model(model)
+        , m_mediaSource(mediaSource)
     {}
 
     inline void onItemPreparseEnded( MediaTreePtr, input_item_node_t *, int ) override final {}
@@ -56,11 +57,11 @@ struct DeviceSourceProvider::ListenerCb : public MediaTreeListener::MediaTreeLis
         for ( auto i = 0u; i < count; ++i )
             itemList.emplace_back( children[i]->p_item );
 
-        QMetaObject::invokeMethod(provider, [provider = this->provider,
+        QMetaObject::invokeMethod(m_model, [model = this->m_model,
                                   itemList = std::move(itemList),
-                                  mediaSource = this->mediaSource]()
+                                  mediaSource = this->m_mediaSource]()
         {
-            provider->removeItems(itemList, mediaSource);
+            model->removeItems(itemList, mediaSource);
         });
     }
 
@@ -73,156 +74,216 @@ struct DeviceSourceProvider::ListenerCb : public MediaTreeListener::MediaTreeLis
         for (size_t i = 0; i < count; i++)
             itemList.emplace_back(children[i]->p_item);
 
-        QMetaObject::invokeMethod(provider, [provider = this->provider,
+        QMetaObject::invokeMethod(m_model, [model = this->m_model,
                                   itemList = std::move(itemList),
-                                  mediaSource = this->mediaSource, clear]()
+                                  mediaSource = this->m_mediaSource, clear]()
         {
-            provider->addItems(itemList, mediaSource, clear);
+            model->addItems(itemList, mediaSource, clear);
         });
     }
 
-    DeviceSourceProvider *provider;
-    MediaSourcePtr mediaSource;
+    MediaSourceModel* m_model;
+    MediaSourcePtr m_mediaSource;
 };
 
-
-DeviceSourceProvider::DeviceSourceProvider(NetworkDeviceModel::SDCatType sdSource
-                                           , const QString &sourceName, QObject *parent)
-    : QObject(parent)
-    , m_sdSource {sdSource}
-    , m_sourceName {sourceName}
+MediaSourceModel::MediaSourceModel(MediaSourcePtr& mediaSource)
+    : m_mediaSource(mediaSource)
 {
 }
 
-void DeviceSourceProvider::init(qt_intf_t *intf)
+MediaSourceModel::~MediaSourceModel()
 {
-    using SourceMetaPtr = std::unique_ptr<vlc_media_source_meta_list_t,
-                                          decltype( &vlc_media_source_meta_list_Delete )>;
+    //reset the listenner before the source
+    m_listenner.reset();
 
-    auto libvlc = vlc_object_instance(intf);
+    for (const SharedInputItem & media : m_medias)
+        emit mediaRemoved(media);
+    m_medias.clear();
 
-    auto provider = vlc_media_source_provider_Get( libvlc );
-    SourceMetaPtr providerList( vlc_media_source_provider_List(
-                                    provider,
-                                    static_cast<services_discovery_category_e>(m_sdSource) ),
-                               &vlc_media_source_meta_list_Delete );
+    m_mediaSource.reset();
+}
 
-    if (!providerList)
-    {
-        emit failed();
+void MediaSourceModel::init()
+{
+    if (m_listenner)
         return;
-    }
 
-    size_t nbProviders = vlc_media_source_meta_list_Count( providerList.get() );
-    for ( auto i = 0u; i < nbProviders; ++i )
-    {
-        auto meta = vlc_media_source_meta_list_Get( providerList.get(), i );
-        const QString sourceName = qfu( meta->name );
-        if ( m_sourceName != '*' && m_sourceName != sourceName )
-            continue;
+    m_listenner = std::make_unique<MediaTreeListener>(
+        MediaTreePtr{ m_mediaSource->tree },
+        std::make_unique<MediaSourceModel::ListenerCb>(this, m_mediaSource)
+        );
+}
 
-        m_name += m_name.isEmpty() ? qfu( meta->longname ) : ", " + qfu( meta->longname );
+const std::vector<SharedInputItem>& MediaSourceModel::getMedias() const
+{
+    return m_medias;
+}
 
-        MediaSourcePtr mediaSource(
-                    vlc_media_source_provider_GetMediaSource(provider, meta->name)
-                    , false );
+QString MediaSourceModel::getDescription() const
+{
+    return qfu(m_mediaSource->description);
+}
 
-        if ( mediaSource == nullptr )
-            continue;
+MediaTreePtr MediaSourceModel::getTree() const
+{
+    return MediaTreePtr(m_mediaSource->tree);
+}
 
-        std::unique_ptr<MediaTreeListener> l{ new MediaTreeListener(
-            MediaTreePtr{ mediaSource->tree },
-            std::make_unique<DeviceSourceProvider::ListenerCb>(this, mediaSource) ) };
-        if ( l->listener == nullptr )
-            break;
+void MediaSourceModel::addItems(const std::vector<SharedInputItem> &inputList,
+                                    const MediaSourcePtr &mediaSource, const bool clear)
+{
+    if (mediaSource != m_mediaSource)
+    {
+        qWarning() << "unexpected media source";
+        return;
+    }
 
-        m_mediaSources.push_back( std::move( mediaSource ) );
-        m_listeners.push_back( std::move( l ) );
+    if (clear)
+    {
+        for (const SharedInputItem & media : m_medias)
+            emit mediaRemoved(media);
+        m_medias.clear();
     }
 
-    if ( !m_name.isEmpty() )
-        emit nameUpdated( m_name );
+    for (const SharedInputItem & inputItem : inputList)
+    {
+        auto it = std::find(
+            m_medias.cbegin(), m_medias.cend(),
+            inputItem
+        );
+        if (it != m_medias.end())
+            continue;
 
-    if ( !m_listeners.empty() )
-        emit itemsUpdated( m_items );
-    else
-        emit failed();
+        emit mediaAdded(inputItem);
+        m_medias.push_back(std::move(inputItem));
+    }
 }
 
-void DeviceSourceProvider::addItems(const std::vector<SharedInputItem> &inputList,
-                                    const MediaSourcePtr &mediaSource, const bool clear)
+void MediaSourceModel::removeItems(const std::vector<SharedInputItem> &inputList,
+                                       const MediaSourcePtr &mediaSource)
 {
-    bool dataChanged = false;
-
-    if (clear)
+    if (mediaSource != m_mediaSource)
     {
-        const qsizetype removed = m_items.removeIf([&mediaSource](const NetworkDeviceItemPtr &item)
-        {
-            return item->mediaSource == mediaSource;
-        });
-
-        if (removed > 0)
-            dataChanged = true;
+        qWarning() << "unexpected media source";
+        return;
     }
 
-    for (const SharedInputItem & inputItem : inputList)
+    for (const SharedInputItem& inputItem : inputList)
     {
-        auto newItem = std::make_shared<NetworkDeviceItem>(inputItem, mediaSource);
-        auto it = m_items.find(newItem);
-        if (it != m_items.end())
+        auto it = std::remove(m_medias.begin(), m_medias.end(), inputItem);
+        if (it != m_medias.end())
         {
-            (*it)->mrls.push_back(std::make_pair(newItem->mainMrl, mediaSource));
-        }
-        else
-        {
-            m_items.insert(std::move(newItem));
-            dataChanged = true;
+            m_medias.erase(it);
+            mediaRemoved(inputItem);
         }
     }
+}
+
+SharedMediaSourceModel MediaSourceCache::getMediaSourceModel(vlc_media_source_provider_t* provider, const char* name)
+{
+    //MediaSourceCache may be accessed by multiple threads
+    QMutexLocker lock{&m_mutex};
 
-    if (dataChanged)
+    QString key = qfu(name);
+    auto it = m_cache.find(key);
+    if (it != m_cache.end())
     {
-        emit itemsUpdated(m_items);
+        SharedMediaSourceModel ref = it->second.toStrongRef();
+        if (ref)
+            return ref;
     }
+    MediaSourcePtr mediaSource(
+        vlc_media_source_provider_GetMediaSource(provider, name),
+        false );
+
+    SharedMediaSourceModel item = SharedMediaSourceModel::create(mediaSource);
+    m_cache[key] = item;
+    return item;
 }
 
-void DeviceSourceProvider::removeItems(const std::vector<SharedInputItem> &inputList,
-                                       const MediaSourcePtr &mediaSource)
+DeviceSourceProvider::DeviceSourceProvider(
+    NetworkDeviceModel::SDCatType sdSource,
+    const QString &sourceName,
+    MainCtx* ctx,
+    QObject* parent)
+    : QObject(parent)
+    , m_ctx(ctx)
+    , m_sdSource {sdSource}
+    , m_sourceName {sourceName}
 {
-    bool dataChanged = false;
-    for (const SharedInputItem& p_item : inputList)
-    {
-        auto oldItem = std::make_shared<NetworkDeviceItem>(p_item, mediaSource);
-        NetworkDeviceItemSet::iterator it = m_items.find(oldItem);
-        if (it != m_items.end())
-        {
-            bool found = false;
+}
 
-            const NetworkDeviceItemPtr& item = *it;
-            if (item->mrls.size() > 1)
-            {
-                auto mrlIt = std::find_if(
-                    item->mrls.begin(), item->mrls.end(),
-                    [&oldItem]( const std::pair<QUrl, MediaSourcePtr>& mrl ) {
-                        return mrl.first.matches(oldItem->mainMrl, QUrl::StripTrailingSlash)
-                            && mrl.second == oldItem->mediaSource;
-                    });
-
-                if ( mrlIt != item->mrls.end() )
-                {
-                    found = true;
-                    item->mrls.erase( mrlIt );
-                }
-            }
+DeviceSourceProvider::~DeviceSourceProvider()
+{
+    if (m_taskId != 0)
+        m_ctx->threadRunner()->cancelTask(this, m_taskId);
+}
 
-            if (!found)
+void DeviceSourceProvider::init()
+{
+    using SourceMetaPtr = std::unique_ptr<vlc_media_source_meta_list_t,
+                                          decltype( &vlc_media_source_meta_list_Delete )>;
+
+    struct Ctx {
+        bool success = false;
+        QString name;
+        std::vector<SharedMediaSourceModel> sources;
+    };
+    QThread* thread = QThread::currentThread();
+    m_taskId = m_ctx->threadRunner()->runOnThread<Ctx>(
+        this,
+        //Worker thread
+        [intf = m_ctx->getIntf(), sdSource = m_sdSource, nameFilter = m_sourceName, thread](Ctx& ctx){
+            auto libvlc = vlc_object_instance(intf);
+
+            auto provider = vlc_media_source_provider_Get( libvlc );
+            SourceMetaPtr providerList( vlc_media_source_provider_List(
+                                           provider,
+                                           static_cast<services_discovery_category_e>(sdSource) ),
+                                       &vlc_media_source_meta_list_Delete );
+
+            if (!providerList)
+                return;
+
+            size_t nbProviders = vlc_media_source_meta_list_Count( providerList.get() );
+
+            for ( size_t i = 0u; i < nbProviders; ++i )
             {
-                m_items.erase(it);
-                dataChanged = true;
+                auto meta = vlc_media_source_meta_list_Get( providerList.get(), i );
+                const QString sourceName = qfu( meta->name );
+                if ( nameFilter != '*' && nameFilter != sourceName )
+                    continue;
+
+                ctx.name += ctx.name.isEmpty() ? qfu( meta->longname ) : ", " + qfu( meta->longname );
+
+                SharedMediaSourceModel mediaSource = MediaSourceCache::getInstance()->getMediaSourceModel(provider, meta->name);
+                //ensure this QObject don't live in the worker thread
+                mediaSource->moveToThread(thread);
+
+                if (!mediaSource)
+                    continue;
+
+                ctx.sources.push_back(mediaSource);
+            }
+        },
+        //UI thread
+        [this](quint64, Ctx& ctx){
+            m_name = ctx.name;
+            emit nameUpdated( m_name );
+
+            for (auto& mediaSource : ctx.sources) {
+                mediaSource->init();
+                m_mediaSources.push_back( mediaSource );
             }
-        }
-    }
 
-    if (dataChanged)
-        emit itemsUpdated(m_items);
+            if ( !m_mediaSources.empty() )
+                emit itemsUpdated();
+            else
+                emit failed();
+        });
+}
+
+const std::vector<SharedMediaSourceModel>& DeviceSourceProvider::getMediaSources() const
+{
+    return m_mediaSources;
 }


=====================================
modules/gui/qt/network/devicesourceprovider.hpp
=====================================
@@ -27,93 +27,102 @@
 #include <QString>
 #include <QSet>
 #include <vector>
+#include <QMutex>
 
 #include "networkdevicemodel.hpp"
 #include "mediatreelistener.hpp"
+#include "util/shared_input_item.hpp"
+#include "vlcmediasourcewrapper.hpp"
+#include "util/singleton.hpp"
 
-//represents an entry of the model
-struct NetworkDeviceItem : public NetworkBaseItem
+static inline std::size_t qHash(const SharedInputItem& item, size_t seed = 0) noexcept
 {
-    NetworkDeviceItem(const SharedInputItem& item, const NetworkDeviceModel::MediaSourcePtr& source)
-    {
-        name = qfu(item->psz_name);
-        mainMrl = QUrl::fromEncoded(item->psz_uri);
-        protocol = mainMrl.scheme();
-        type = static_cast<NetworkDeviceModel::ItemType>(item->i_type);
-        mediaSource = source;
-        inputItem = item;
-
-        id = qHash(name) ^ qHash(protocol);
-        mrls.push_back(std::make_pair(mainMrl, source));
-
-        char* artworkUrl = input_item_GetArtworkURL(inputItem.get());
-        if (artworkUrl)
-        {
-            artwork = QString::fromUtf8(artworkUrl);
-            free(artworkUrl);
-        }
-    }
-
-    uint id;
-    std::vector<std::pair<QUrl, NetworkDeviceModel::MediaSourcePtr>> mrls;
-    NetworkDeviceModel::MediaSourcePtr mediaSource;
-    SharedInputItem inputItem;
- };
-
-using NetworkDeviceItemPtr =std::shared_ptr<NetworkDeviceItem>;
-
-static inline bool operator == (const NetworkDeviceItemPtr& a, const NetworkDeviceItemPtr& b) noexcept
-{
-    return a->id == b->id
-        && QString::compare(a->name, b->name, Qt::CaseInsensitive) == 0
-        && QString::compare(a->protocol, b->protocol, Qt::CaseInsensitive) == 0;
+    QString name = qfu(item->psz_name);
+    QUrl mainMrl = QUrl::fromEncoded(item->psz_uri);
+    QString protocol = mainMrl.scheme();
+    return qHash(name, seed) ^ qHash(protocol);;
 }
 
+class MediaSourceModel : public QObject
+{
+    Q_OBJECT
+public:
+
+    MediaSourceModel(MediaSourcePtr& mediaSource);
+    ~MediaSourceModel();
+
+public:
+    void init();
+
+    const std::vector<SharedInputItem>& getMedias() const;
+
+    QString getDescription() const;
+    MediaTreePtr getTree() const;
+
+signals:
+    void mediaAdded(SharedInputItem media);
+    void mediaRemoved(SharedInputItem media);
 
-static inline std::size_t qHash(const NetworkDeviceItemPtr& s, size_t = 0) noexcept
+private:
+    void addItems(const std::vector<SharedInputItem>& inputList,
+                  const MediaSourcePtr& mediaSource,
+                  bool clear);
+
+    void removeItems(const std::vector<SharedInputItem>& inputList,
+                     const MediaSourcePtr& mediaSource);
+
+    struct ListenerCb;
+    std::unique_ptr<MediaTreeListener> m_listenner;
+    MediaSourcePtr m_mediaSource;
+    std::vector<SharedInputItem> m_medias;
+};
+typedef QSharedPointer<MediaSourceModel> SharedMediaSourceModel;
+
+
+class MediaSourceCache : public Singleton<MediaSourceCache>
 {
-    return s->id;
-}
+public:
+    SharedMediaSourceModel getMediaSourceModel(vlc_media_source_provider_t* provider, const char* name);
+
+private:
+    MediaSourceCache() = default;
+    ~MediaSourceCache() = default;
+    friend class Singleton<MediaSourceCache>;
 
-using NetworkDeviceItemSet = QSet<NetworkDeviceItemPtr>;
+private:
+    //we keep a weak pointer to the model sources, if no other party is
+    //referencing the source, then it will be freed,
+    std::map<QString, QWeakPointer<MediaSourceModel>> m_cache;
+    QMutex m_mutex;
+};
 
 class DeviceSourceProvider : public QObject
 {
     Q_OBJECT
 
 public:
-    using MediaSourcePtr = NetworkDeviceModel::MediaSourcePtr;
-
     DeviceSourceProvider(NetworkDeviceModel::SDCatType sdSource,
                          const QString &sourceName,
+                         MainCtx* ctx,
                          QObject *parent = nullptr);
+    virtual ~DeviceSourceProvider();
+
+    void init();
 
-    void init(qt_intf_t *intf);
+    const std::vector<SharedMediaSourceModel>& getMediaSources() const;
 
 signals:
     void failed();
     void nameUpdated( QString name );
-    void itemsUpdated( NetworkDeviceItemSet items );
+    void itemsUpdated();
 
 private:
-    struct ListenerCb;
-
-    void addItems(const std::vector<SharedInputItem>& inputList,
-                  const MediaSourcePtr& mediaSource,
-                  bool clear);
-
-    void removeItems(const std::vector<SharedInputItem>& inputList,
-                     const MediaSourcePtr& mediaSource);
+    MainCtx* m_ctx = nullptr;
+    quint64 m_taskId = 0;
 
     NetworkDeviceModel::SDCatType m_sdSource;
     QString m_sourceName; // '*' -> all sources
     QString m_name; // source long name
 
-    NetworkDeviceItemSet m_items;
-
-    // destruction of listeners may cause destruction of source 'MediaSource'
-    // maintain a seperate reference of MediaSources to fix cyclic free
-    std::vector<MediaSourcePtr> m_mediaSources;
-
-    std::vector<std::unique_ptr<MediaTreeListener>> m_listeners;
+    std::vector<SharedMediaSourceModel> m_mediaSources;
 };


=====================================
modules/gui/qt/network/mediatreelistener.hpp
=====================================
@@ -19,23 +19,14 @@
 #ifndef MLNETWORKSOURCELISTENER_HPP
 #define MLNETWORKSOURCELISTENER_HPP
 
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <vlc_media_source.h>
-#include <vlc_cxx_helpers.hpp>
-
 #include <memory>
 #include <functional>
 
+#include "vlcmediasourcewrapper.hpp"
+
 class MediaTreeListener
 {
 public:
-    using MediaTreePtr = vlc_shared_data_ptr_type(vlc_media_tree_t,
-                                                  vlc_media_tree_Hold,
-                                                  vlc_media_tree_Release);
-
     using ListenerPtr = std::unique_ptr<vlc_media_tree_listener_id,
                                         std::function<void(vlc_media_tree_listener_id*)>>;
 


=====================================
modules/gui/qt/network/networkdevicemodel.cpp
=====================================
@@ -20,7 +20,6 @@
 #include <QTimer>
 
 #include "maininterface/mainctx.hpp"
-#include "util/workerthreadset.hpp"
 
 #include "devicesourceprovider.hpp"
 #include "networkmediamodel.hpp"
@@ -29,11 +28,126 @@
 #include "playlist/playlist_controller.hpp"
 
 #include "util/shared_input_item.hpp"
+#include "vlcmediasourcewrapper.hpp"
+
 #include "util/locallistbasemodel.hpp"
 
+#include "vlcmediasourcewrapper.hpp"
+
 namespace
 {
 
+//represents an entry of the model
+struct NetworkDeviceItem : public NetworkBaseItem
+{
+    NetworkDeviceItem(
+        const SharedInputItem& item,
+        const SharedMediaSourceModel& source
+        )
+    {
+        name = qfu(item->psz_name);
+        mainMrl = QUrl::fromEncoded(item->psz_uri);
+        protocol = mainMrl.scheme();
+        type = static_cast<NetworkDeviceModel::ItemType>(item->i_type);
+
+        id = qHash(item);
+        mrls.push_back(Source{mainMrl, item, source});
+
+        char* artworkUrl = input_item_GetArtworkURL(item.get());
+        if (artworkUrl)
+        {
+            artwork = QString::fromUtf8(artworkUrl);
+            free(artworkUrl);
+        }
+    }
+
+    QString sourceDesciption() const {
+        if (mrls.empty())
+            return {};
+        return mrls.front().source->getDescription();
+    }
+
+    NetworkTreeItem makeTreeItem() const
+    {
+        if (unlikely(mrls.empty()))
+            return {};
+        const Source& src = mrls.front();
+        return NetworkTreeItem(src.source, src.item);
+    }
+
+    SharedInputItem getInputItem() const
+    {
+        if (unlikely(mrls.empty()))
+            return {};
+        const Source& src = mrls.front();
+        return src.item;
+    }
+
+    void addSource(const SharedInputItem& item,
+                   const SharedMediaSourceModel& source)
+    {
+        mrls.emplace_back(Source{
+            QUrl::fromEncoded(item->psz_uri),
+            item,
+            source
+        });
+    }
+
+    /**
+     * @brief addRemoveSource
+     * @param item
+     * @param source
+     * @return false when no sources left
+     */
+    bool removeSource(const SharedInputItem& item,
+                   const SharedMediaSourceModel&)
+    {
+        mrls.erase(std::remove_if(
+            mrls.begin(), mrls.end(),
+            [item](const Source& source){
+                return source.item == item;
+            })
+        );
+
+        if (mrls.empty())
+        {
+            mainMrl = QUrl{};
+            return false;
+        }
+        else
+        {
+            mainMrl = mrls.front().mrl;
+            return true;
+        }
+    }
+
+
+    struct Source {
+        QUrl mrl;
+        SharedInputItem item;
+        SharedMediaSourceModel source;
+    };
+
+    size_t id;
+    std::vector<Source> mrls;
+};
+
+using NetworkDeviceItemPtr =std::shared_ptr<NetworkDeviceItem>;
+
+
+static inline bool operator == (const NetworkDeviceItemPtr& a, const NetworkDeviceItemPtr& b) noexcept
+{
+    return a->id == b->id
+           && QString::compare(a->name, b->name, Qt::CaseInsensitive) == 0
+           && QString::compare(a->protocol, b->protocol, Qt::CaseInsensitive) == 0;
+}
+
+static inline std::size_t qHash(const NetworkDeviceItemPtr& s, size_t seed = 0) noexcept
+{
+    VLC_UNUSED(seed);
+    return s->id;
+}
+
 bool itemMatchPattern(const NetworkDeviceItemPtr& a, const QString& pattern)
 {
     return a->name.contains(pattern, Qt::CaseInsensitive);
@@ -117,32 +231,20 @@ public:
     {
         Q_Q(NetworkDeviceModel);
 
-        if (m_qmlInitializing || !q->m_ctx || q->m_sdSource == NetworkDeviceModel::CAT_UNDEFINED || q->m_sourceName.isEmpty())
+        if (m_qmlInitializing || !m_ctx || m_sdSource == NetworkDeviceModel::CAT_UNDEFINED || m_sourceName.isEmpty())
             return false;
 
         m_items.clear();
 
-        if (m_sources)
-        {
-            q->disconnect( m_sources );
-            m_sources->deleteLater();
-            m_sources = nullptr;
-        }
+        if (m_sourcesProvider)
+            m_sourcesProvider.reset();
 
-        q->m_name = QString {};
+        m_name = QString {};
         emit q->nameChanged();
 
-        m_sources = new DeviceSourceProvider( q->m_sdSource, q->m_sourceName );
-        q->m_ctx->workersThreads()->assignToWorkerThread( m_sources );
-
-        // make sure we're not releasing resources on main thread
-        // by clearing copies of model before source provider
-        QObject::connect(q, &QObject::destroyed, m_sources, [sources = m_sources]()
-        {
-            sources->deleteLater();
-        });
+        m_sourcesProvider = std::make_unique<DeviceSourceProvider>( m_sdSource, m_sourceName, m_ctx );
 
-        QObject::connect(m_sources, &DeviceSourceProvider::failed, q,
+        QObject::connect(m_sourcesProvider.get(), &DeviceSourceProvider::failed, q,
                 [this]()
         {
             m_items.clear();
@@ -151,27 +253,40 @@ public:
             invalidateCache();
         });
 
-        QObject::connect(m_sources, &DeviceSourceProvider::nameUpdated, q,
-                [q](QString name)
+        QObject::connect(m_sourcesProvider.get(), &DeviceSourceProvider::nameUpdated, q,
+                [this, q](QString name)
         {
-            q->m_name = name;
+            m_name = name;
             emit q->nameChanged();
         });
 
-        QObject::connect(m_sources, &DeviceSourceProvider::itemsUpdated, q,
-                [this](NetworkDeviceItemSet items)
+        //itemsUpdated is called only once after init.
+        QObject::connect(m_sourcesProvider.get(), &DeviceSourceProvider::itemsUpdated, q,
+                [this]()
         {
-            m_items = items;
+            for (const auto& source: m_sourcesProvider->getMediaSources())
+            {
+                //fill initial values
+                for (const SharedInputItem& media : source->getMedias())
+                    onMediaAdded(source, media);
+
+                QObject::connect(
+                    source.get(), &MediaSourceModel::mediaAdded,
+                    q_ptr, [this, source](SharedInputItem media) {
+                        onMediaAdded(source, media);
+                    }, Qt::UniqueConnection);
+                QObject::connect(
+                    source.get(), &MediaSourceModel::mediaRemoved,
+                    q_ptr, [this, source](SharedInputItem media) {
+                        onMediaAdded(source, media);
+                    }, Qt::UniqueConnection);
+            }
 
             m_revision += 1;
             invalidateCache();
         });
 
-        QMetaObject::invokeMethod(m_sources,
-                                  [sources = this->m_sources, intf = q->m_ctx->getIntf()]()
-        {
-            sources->init( intf );
-        });
+        m_sourcesProvider->init();
 
         //service discovery don't notify preparse end
         m_loading = false;
@@ -188,6 +303,47 @@ public:
         return nullptr;
     }
 
+    void onMediaAdded(const QSharedPointer<MediaSourceModel>& mediaSource, SharedInputItem media)
+    {
+        std::size_t hash = qHash(media);
+        auto it = std::find_if(
+            m_items.begin(), m_items.end(),
+            [hash](const NetworkDeviceItemPtr& item) {
+                return item->id == hash;
+            });
+        if (it != m_items.end())
+        {
+            (*it)->addSource(media, mediaSource);
+        }
+        else
+        {
+            m_items.push_back(std::make_unique<NetworkDeviceItem>(media, mediaSource));
+            m_revision += 1;
+            invalidateCache();
+        }
+    }
+
+    void onMediaRemoved(const QSharedPointer<MediaSourceModel>& mediaSource, SharedInputItem media)
+    {
+        std::size_t hash = qHash(media);
+        auto it = std::find_if(
+            m_items.begin(), m_items.end(),
+            [hash, &media](const NetworkDeviceItemPtr& item) {
+                return item->id == hash;
+            });
+        if (it == m_items.end())
+            return;
+
+        bool needDestruction = (*it)->removeSource(media, mediaSource);
+        if (needDestruction)
+        {
+            m_items.erase(it);
+        }
+
+        m_revision += 1;
+        invalidateCache();
+    }
+
 public: //LocalListCacheLoader::ModelSource
     std::vector<NetworkDeviceItemPtr> getModelData(const QString& pattern) const override
     {
@@ -211,8 +367,13 @@ public: //LocalListCacheLoader::ModelSource
     }
 
 public:
-    NetworkDeviceItemSet m_items;
-    QPointer<DeviceSourceProvider> m_sources {};
+    std::unique_ptr<DeviceSourceProvider> m_sourcesProvider;
+    std::vector<NetworkDeviceItemPtr> m_items;
+
+    MainCtx* m_ctx = nullptr;
+    NetworkDeviceModel::SDCatType m_sdSource = NetworkDeviceModel::CAT_UNDEFINED;
+    QString m_sourceName; // '*' -> all sources
+    QString m_name; // source long name
 };
 
 NetworkDeviceModel::NetworkDeviceModel( QObject* parent )
@@ -228,7 +389,7 @@ NetworkDeviceModel::NetworkDeviceModel( NetworkDeviceModelPrivate* priv, QObject
 QVariant NetworkDeviceModel::data( const QModelIndex& index, int role ) const
 {
     Q_D(const NetworkDeviceModel);
-    if (!m_ctx)
+    if (!d->m_ctx)
         return {};
 
     const NetworkDeviceItem* item = d->getItemForRow(index.row());
@@ -238,9 +399,9 @@ QVariant NetworkDeviceModel::data( const QModelIndex& index, int role ) const
     switch ( role )
     {
         case NETWORK_SOURCE:
-            return item->mediaSource->description;
+            return item->sourceDesciption();
         case NETWORK_TREE:
-            return QVariant::fromValue( NetworkTreeItem(MediaTreePtr{ item->mediaSource->tree }, item->inputItem.get()) );
+            return QVariant::fromValue(item->makeTreeItem());
         default:
             return NetworkBaseModel::basedata(*item, role);
     }
@@ -257,19 +418,39 @@ QHash<int, QByteArray> NetworkDeviceModel::roleNames() const
 void NetworkDeviceModel::setCtx(MainCtx* ctx)
 {
     Q_D(NetworkDeviceModel);
-    if (m_ctx == ctx)
+    if (d->m_ctx == ctx)
         return;
-    m_ctx = ctx;
+    d->m_ctx = ctx;
     d->initializeModel();
     emit ctxChanged();
 }
 
+MainCtx* NetworkDeviceModel::getCtx() const
+{
+    Q_D(const NetworkDeviceModel);
+    return d->m_ctx;
+}
+
+NetworkDeviceModel::SDCatType NetworkDeviceModel::getSdSource() const {
+    Q_D(const NetworkDeviceModel);
+    return d->m_sdSource;
+}
+QString NetworkDeviceModel::getName() const {
+    Q_D(const NetworkDeviceModel);
+    return d->m_name;
+
+}
+QString NetworkDeviceModel::getSourceName() const {
+    Q_D(const NetworkDeviceModel);
+    return d->m_sourceName;
+}
+
 void NetworkDeviceModel::setSdSource(SDCatType s)
 {
     Q_D(NetworkDeviceModel);
-    if (m_sdSource == s)
+    if (d->m_sdSource == s)
         return;
-    m_sdSource = s;
+    d->m_sdSource = s;
     d->initializeModel();
     emit sdSourceChanged();
 }
@@ -277,9 +458,9 @@ void NetworkDeviceModel::setSdSource(SDCatType s)
 void NetworkDeviceModel::setSourceName(const QString& sourceName)
 {
     Q_D(NetworkDeviceModel);
-    if (m_sourceName == sourceName)
+    if (d->m_sourceName == sourceName)
         return;
-    m_sourceName = sourceName;
+    d->m_sourceName = sourceName;
     d->initializeModel();
     emit sourceNameChanged();
 }
@@ -287,7 +468,7 @@ void NetworkDeviceModel::setSourceName(const QString& sourceName)
 bool NetworkDeviceModel::insertIntoPlaylist(const QModelIndexList &itemIdList, ssize_t playlistIndex)
 {
     Q_D(NetworkDeviceModel);
-    if (!(m_ctx && m_sdSource != CAT_MYCOMPUTER))
+    if (!(d->m_ctx && d->m_sdSource != CAT_MYCOMPUTER))
         return false;
     QVector<vlc::playlist::Media> medias;
     medias.reserve( itemIdList.size() );
@@ -296,26 +477,26 @@ bool NetworkDeviceModel::insertIntoPlaylist(const QModelIndexList &itemIdList, s
         const NetworkDeviceItem* item = d->getItemForRow(id.row());
         if (!item)
             continue;
-        medias.append( vlc::playlist::Media {item->inputItem.get()} );
+        medias.append( vlc::playlist::Media {item->getInputItem().get() } );
     }
     if (medias.isEmpty())
         return false;
-    m_ctx->getIntf()->p_mainPlaylistController->insert(playlistIndex, medias, false);
+    d->m_ctx->getIntf()->p_mainPlaylistController->insert(playlistIndex, medias, false);
     return true;
 }
 
 bool NetworkDeviceModel::addToPlaylist(int row)
 {
     Q_D(NetworkDeviceModel);
-    if (!(m_ctx && m_sdSource != CAT_MYCOMPUTER))
+    if (!(d->m_ctx && d->m_sdSource != CAT_MYCOMPUTER))
         return false;
 
     const NetworkDeviceItem* item = d->getItemForRow(row);
     if (!item)
         return false;
 
-    vlc::playlist::Media media{ item->inputItem.get() };
-    m_ctx->getIntf()->p_mainPlaylistController->append( QVector<vlc::playlist::Media>{ media }, false);
+    vlc::playlist::Media media{ item->getInputItem().get() };
+    d->m_ctx->getIntf()->p_mainPlaylistController->append( QVector<vlc::playlist::Media>{ media }, false);
     return true;
 }
 
@@ -348,15 +529,15 @@ bool NetworkDeviceModel::addToPlaylist(const QModelIndexList &itemIdList)
 bool NetworkDeviceModel::addAndPlay(int row)
 {
     Q_D(NetworkDeviceModel);
-    if (!(m_ctx && m_sdSource != CAT_MYCOMPUTER))
+    if (!(d->m_ctx && d->m_sdSource != CAT_MYCOMPUTER))
         return false;
 
     const NetworkDeviceItem* item = d->getItemForRow(row);
     if (!item)
         return false;
 
-    vlc::playlist::Media media{ item->inputItem.get() };
-    m_ctx->getIntf()->p_mainPlaylistController->append( QVector<vlc::playlist::Media>{ media }, true);
+    vlc::playlist::Media media{ item->getInputItem().get() };
+    d->m_ctx->getIntf()->p_mainPlaylistController->append( QVector<vlc::playlist::Media>{ media }, true);
     return true;
 }
 
@@ -404,9 +585,8 @@ QVariantList NetworkDeviceModel::getItemsForIndexes(const QModelIndexList & inde
         if (!item)
             continue;
 
-        items.append(QVariant::fromValue(SharedInputItem(item->inputItem.get(), true)));
+        items.append(QVariant::fromValue(SharedInputItem(item->getInputItem().get(), true)));
     }
 
     return items;
 }
-


=====================================
modules/gui/qt/network/networkdevicemodel.hpp
=====================================
@@ -19,17 +19,10 @@
 #ifndef MLNETWORKDEVICEMODEL_HPP
 #define MLNETWORKDEVICEMODEL_HPP
 
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
 #include <QAbstractListModel>
 #include <QUrl>
 
-#include <vlc_media_source.h>
-
 #include "networkbasemodel.hpp"
-#include "util/shared_input_item.hpp"
 
 #include <memory>
 
@@ -63,13 +56,6 @@ public: // Enums
     };
     Q_ENUM( SDCatType )
 
-public: // Declarations
-    using MediaSourcePtr = vlc_shared_data_ptr_type(vlc_media_source_t,
-                                    vlc_media_source_Hold, vlc_media_source_Release);
-
-    using MediaTreePtr = vlc_shared_data_ptr_type(vlc_media_tree_t,
-                                                  vlc_media_tree_Hold,
-                                                  vlc_media_tree_Release);
 public:
     NetworkDeviceModel( QObject* parent = nullptr );
     virtual ~NetworkDeviceModel() = default;
@@ -84,10 +70,10 @@ public:
     void setSdSource(SDCatType s);
     void setSourceName(const QString& sourceName);
 
-    inline MainCtx* getCtx() const { return m_ctx; }
-    inline SDCatType getSdSource() const { return m_sdSource; }
-    inline QString getName() const { return m_name; }
-    inline QString getSourceName() const { return m_sourceName; }
+    MainCtx* getCtx() const;
+    SDCatType getSdSource() const;
+    QString getName() const;
+    QString getSourceName() const;
 
     Q_INVOKABLE bool insertIntoPlaylist( const QModelIndexList& itemIdList, ssize_t playlistIndex );
     Q_INVOKABLE bool addToPlaylist(int row );
@@ -107,15 +93,6 @@ signals:
     void itemsUpdated();
 
 private:
-    void refreshDeviceList(MediaSourcePtr mediaSource, input_item_node_t* const children[], size_t count , bool clear);
-    bool initializeMediaSources();
-
-private:
-    MainCtx* m_ctx = nullptr;
-    SDCatType m_sdSource = CAT_UNDEFINED;
-    QString m_sourceName; // '*' -> all sources
-    QString m_name; // source long name
-
     Q_DECLARE_PRIVATE(NetworkDeviceModel)
 };
 


=====================================
modules/gui/qt/network/networkmediamodel.cpp
=====================================
@@ -53,7 +53,6 @@ struct NetworkMediaItem : public NetworkBaseItem
 };
 
 using NetworkMediaItemPtr = std::shared_ptr<NetworkMediaItem>;
-using NetworkMediaItemSet = std::unordered_set<NetworkMediaItemPtr>;
 
 inline bool isADir(const NetworkMediaItemPtr& x)
 {
@@ -144,8 +143,7 @@ public:
 
     void removeItem(const SharedInputItem& node, const std::vector<SharedInputItem>& itemsList)
     {
-        Q_Q(NetworkMediaModel);
-        if (node != q->m_treeItem.media)
+        if (node != m_treeItem.media)
             return;
 
         for (auto p_item : itemsList)
@@ -172,7 +170,7 @@ public:
     }
 
     void refreshMediaList(
-        MediaTreePtr tree,
+        NetworkTreeItem& treeItem,
         std::vector<SharedInputItem> children,
         bool clear )
     {
@@ -229,7 +227,7 @@ public:
                     item->fileModified = QDateTime::fromSecsSinceEpoch(time, QTimeZone::systemTimeZone());
             }
 
-            item->tree = NetworkTreeItem( tree, inputItem.get() );
+            item->tree = NetworkTreeItem(treeItem, inputItem );
 
             if ( m_mediaLib && item->canBeIndexed)
             {
@@ -352,38 +350,38 @@ public:
     {
         Q_Q(NetworkMediaModel);
 
-        if (!q->m_ctx || !m_hasTree || m_qmlInitializing)
+        if (!m_ctx || !m_hasTree || m_qmlInitializing)
             return false;
 
-        auto parser = q->m_ctx->getNetworkPreparser();
+        auto parser = m_ctx->getNetworkPreparser();
         if (unlikely(parser == NULL))
             return false;
 
         m_listener.reset();
         m_items.clear();
 
-        if (!q->m_treeItem)
+        if (!m_treeItem)
             return false;
 
-        auto tree = q->m_treeItem.tree.get();
-        auto l = std::make_unique<MediaTreeListener>( q->m_treeItem.tree,
-                                                     std::make_unique<NetworkMediaModel::ListenerCb>(q) );
+        auto& tree = m_treeItem.tree;
+        auto l = std::make_unique<MediaTreeListener>(tree,
+                                                     std::make_unique<NetworkMediaModelPrivate::ListenerCb>(q) );
         if ( l->listener == nullptr )
             return false;
 
-        if (q->m_treeItem.media)
+        if (m_treeItem.media)
         {
-            q->m_name = q->m_treeItem.media->psz_name;
+            m_name = m_treeItem.media->psz_name;
             emit q->nameChanged();
-            q->m_url = QUrl::fromEncoded( QByteArray{ q->m_treeItem.media->psz_uri }.append( '/' ) );
+            m_url = QUrl::fromEncoded( QByteArray{ m_treeItem.media->psz_uri }.append( '/' ) );
             emit q->urlChanged();
-            q->m_type = static_cast<NetworkMediaModel::ItemType>(q->m_treeItem.media->i_type);
+            m_type = static_cast<NetworkMediaModel::ItemType>(m_treeItem.media->i_type);
             emit q->typeChanged();
-            q->m_canBeIndexed = canBeIndexed( q->m_url, q->m_type );
+            m_canBeIndexed = canBeIndexed( m_url, m_type );
             emit q->canBeIndexedChanged();
             if (m_mediaLib)
             {
-                auto uri = QByteArray(q->m_treeItem.media->psz_uri).append('/');
+                auto uri = QByteArray(m_treeItem.media->psz_uri).append('/');
                 struct Ctx {
                     bool succeed;
                     bool isIndexed;
@@ -399,7 +397,7 @@ public:
                         Q_Q(NetworkMediaModel);
                         if (!ctx.succeed)
                             return;
-                        q->m_indexed = ctx.isIndexed;
+                        m_indexed = ctx.isIndexed;
                         emit q->isIndexedChanged();
                     });
             }
@@ -408,37 +406,39 @@ public:
         {
             input_item_node_t* mediaNode = nullptr;
             input_item_node_t* parent = nullptr;
-            vlc_media_tree_Lock(tree);
-            if (m_parserId != VLC_PREPARSER_REQ_ID_INVALID)
-                vlc_preparser_Cancel( parser, m_parserId );
             std::vector<SharedInputItem> itemList;
-            q->m_path = {QVariant::fromValue(PathNode(q->m_treeItem, q->m_name))};
-            if (vlc_media_tree_Find( tree, q->m_treeItem.media.get(), &mediaNode, &parent))
             {
-                itemList.reserve(mediaNode->i_children);
-                for (int i = 0; i < mediaNode->i_children; i++)
-                    itemList.emplace_back(mediaNode->pp_children[i]->p_item);
-
-                while (parent && parent->p_item) {
-                    q->m_path.push_front(QVariant::fromValue(PathNode(
-                        NetworkTreeItem(q->m_treeItem.tree, parent->p_item),
-                        parent->p_item->psz_name)));
-                    input_item_node_t *node = nullptr;
-                    input_item_node_t *grandParent = nullptr;
-                    if (!vlc_media_tree_Find( tree, parent->p_item, &node, &grandParent)) {
-                        break;
+                MediaTreeLocker lock{tree};
+                if (m_parserId != VLC_PREPARSER_REQ_ID_INVALID)
+                    vlc_preparser_Cancel( parser, m_parserId );
+                m_path = {QVariant::fromValue(PathNode(m_treeItem, m_name))};
+                if (vlc_media_tree_Find( tree.get(), m_treeItem.media.get(), &mediaNode, &parent))
+                {
+                    itemList.reserve(mediaNode->i_children);
+                    for (int i = 0; i < mediaNode->i_children; i++)
+                        itemList.emplace_back(mediaNode->pp_children[i]->p_item);
+
+
+                    while (parent && parent->p_item) {
+                        m_path.push_front(QVariant::fromValue(PathNode(
+                            NetworkTreeItem(m_treeItem, SharedInputItem{parent->p_item}),
+                            parent->p_item->psz_name)));
+                        input_item_node_t *node = nullptr;
+                        input_item_node_t *grandParent = nullptr;
+                        if (!vlc_media_tree_Find( tree.get(), parent->p_item, &node, &grandParent)) {
+                            break;
+                        }
+                        parent = grandParent;
                     }
-                    parent = grandParent;
                 }
             }
-            vlc_media_tree_Unlock(tree);
             if (!itemList.empty())
-                refreshMediaList( q->m_treeItem.tree, std::move( itemList ), true );
+                refreshMediaList( m_treeItem, std::move( itemList ), true );
             emit q->pathChanged();
         }
 
         m_preparseSem.acquire();
-        m_parserId = vlc_media_tree_Preparse( tree, parser, q->m_treeItem.media.get() );
+        m_parserId = vlc_media_tree_Preparse( tree.get(), parser, m_treeItem.media.get() );
 
         m_listener = std::move( l );
 
@@ -467,13 +467,26 @@ public:
     std::unique_ptr<MediaTreeListener> m_listener;
     QHash<QString, NetworkMediaItemPtr> m_items;
     std::unique_ptr<MLMediaStore> m_MLMedias;
+
+    //properties of the current node
+    QString m_name;
+    QUrl m_url;
+    NetworkMediaModel::ItemType m_type = NetworkMediaModel::ItemType::TYPE_UNKNOWN;
+    bool m_indexed = false;
+    bool m_canBeIndexed  = false;
+
+    MainCtx* m_ctx = nullptr;
+    NetworkTreeItem m_treeItem;
+    QVariantList m_path;
+
+    struct ListenerCb;
 private:
     vlc_preparser_req_id m_parserId;
 };
 
 // NetworkMediaModel::ListenerCb implementation
 
-struct NetworkMediaModel::ListenerCb : public MediaTreeListener::MediaTreeListenerCb {
+struct NetworkMediaModelPrivate::ListenerCb : public MediaTreeListener::MediaTreeListenerCb {
     ListenerCb(NetworkMediaModel *model) : model(model) {}
 
     void onItemCleared( MediaTreePtr tree, input_item_node_t* node ) override;
@@ -497,7 +510,7 @@ NetworkMediaModel::~NetworkMediaModel()
     //this can only be acquired from UI thread
     if (!d->m_preparseSem.tryAcquire())
     {
-        auto parser = m_ctx->getNetworkPreparser();
+        auto parser = d->m_ctx->getNetworkPreparser();
         if (likely(parser != NULL))
         {
             if (d->m_parserId != VLC_PREPARSER_REQ_ID_INVALID)
@@ -514,7 +527,7 @@ NetworkMediaModel::~NetworkMediaModel()
 QVariant NetworkMediaModel::data( const QModelIndex& index, int role ) const
 {
     Q_D(const NetworkMediaModel);
-    if (!m_ctx)
+    if (!d->m_ctx)
         return {};
     const NetworkMediaItem* item = d->getItemForRow(index.row());
     if (!item)
@@ -659,9 +672,9 @@ void NetworkMediaModel::setIndexed(bool indexed)
 {
     Q_D(NetworkMediaModel);
 
-    if (indexed == m_indexed || !m_canBeIndexed)
+    if (indexed == d->m_indexed || !d->m_canBeIndexed)
         return;
-    QString url = m_url.toString( QUrl::FullyEncoded );
+    QString url = d->m_url.toString( QUrl::FullyEncoded );
     struct Ctx {
         bool success;
     };
@@ -680,7 +693,7 @@ void NetworkMediaModel::setIndexed(bool indexed)
     [this, indexed](quint64, Ctx& ctx){
         if (ctx.success)
         {
-            m_indexed = indexed;
+            d_func()->m_indexed = indexed;
             emit isIndexedChanged();
         }
     },
@@ -690,11 +703,11 @@ void NetworkMediaModel::setIndexed(bool indexed)
 void NetworkMediaModel::setCtx(MainCtx* ctx)
 {
     Q_D(NetworkMediaModel);
-    if (m_ctx == ctx)
+    if (d->m_ctx == ctx)
         return;
 
     assert(ctx);
-    m_ctx = ctx;
+    d->m_ctx = ctx;
     d->m_mediaLib = ctx->getMediaLibrary();
 
     d->m_MLMedias.reset();
@@ -716,9 +729,9 @@ void NetworkMediaModel::setTree(QVariant parentTree)
 {
     Q_D(NetworkMediaModel);
     if (parentTree.canConvert<NetworkTreeItem>())
-        m_treeItem = parentTree.value<NetworkTreeItem>();
+        d->m_treeItem = parentTree.value<NetworkTreeItem>();
     else
-        m_treeItem = NetworkTreeItem();
+        d->m_treeItem = NetworkTreeItem();
     d->m_hasTree = true;
 
     d->initializeModel();
@@ -728,7 +741,7 @@ void NetworkMediaModel::setTree(QVariant parentTree)
 bool NetworkMediaModel::insertIntoPlaylist(const QModelIndexList &itemIdList, const ssize_t playlistIndex)
 {
     Q_D(NetworkMediaModel);
-    if (!(m_ctx && d->m_hasTree))
+    if (!(d->m_ctx && d->m_hasTree))
         return false;
     QVector<vlc::playlist::Media> medias;
     medias.reserve( itemIdList.size() );
@@ -745,21 +758,21 @@ bool NetworkMediaModel::insertIntoPlaylist(const QModelIndexList &itemIdList, co
     }
     if (medias.isEmpty())
         return false;
-    m_ctx->getIntf()->p_mainPlaylistController->insert(playlistIndex, medias, false);
+    d->m_ctx->getIntf()->p_mainPlaylistController->insert(playlistIndex, medias, false);
     return true;
 }
 
 bool NetworkMediaModel::addToPlaylist(const int index)
 {
     Q_D(NetworkMediaModel);
-    if (!(m_ctx && d->m_hasTree))
+    if (!(d->m_ctx && d->m_hasTree))
         return false;
     const NetworkMediaItem* item = d->getItemForRow(index);
     if (!item)
         return false;
 
     vlc::playlist::Media media{ item->tree.media.get() };
-    m_ctx->getIntf()->p_mainPlaylistController->append( QVector<vlc::playlist::Media>{ media }, false);
+    d->m_ctx->getIntf()->p_mainPlaylistController->append( QVector<vlc::playlist::Media>{ media }, false);
     return true;
 }
 
@@ -797,7 +810,7 @@ bool NetworkMediaModel::addToPlaylist(const QModelIndexList &itemIdList)
 bool NetworkMediaModel::addAndPlay(int index)
 {
     Q_D(NetworkMediaModel);
-    if (!(m_ctx && d->m_hasTree))
+    if (!(d->m_ctx && d->m_hasTree))
         return false;
 
     const NetworkMediaItem* item = d->getItemForRow(index);
@@ -805,7 +818,7 @@ bool NetworkMediaModel::addAndPlay(int index)
         return false;
 
     vlc::playlist::Media media{ item->tree.media.get() };
-    m_ctx->getIntf()->p_mainPlaylistController->append( QVector<vlc::playlist::Media>{ media }, true);
+    d->m_ctx->getIntf()->p_mainPlaylistController->append( QVector<vlc::playlist::Media>{ media }, true);
     return true;
 }
 
@@ -869,32 +882,70 @@ QVariantList NetworkMediaModel::getItemsForIndexes(const QModelIndexList & index
     return items;
 }
 
-void NetworkMediaModel::ListenerCb::onItemCleared( MediaTreePtr tree, input_item_node_t* node)
+MainCtx* NetworkMediaModel::getCtx() const {
+    Q_D(const NetworkMediaModel);
+    return d->m_ctx;
+}
+QVariant NetworkMediaModel::getTree() const {
+    Q_D(const NetworkMediaModel);
+    return QVariant::fromValue( d->m_treeItem);
+}
+QVariantList NetworkMediaModel::getPath() const {
+    Q_D(const NetworkMediaModel);
+    return d->m_path;
+}
+
+QString NetworkMediaModel::getName() const {
+    Q_D(const NetworkMediaModel);
+    return d->m_name;
+}
+QUrl NetworkMediaModel::getUrl() const {
+    Q_D(const NetworkMediaModel);
+    return d->m_url;
+}
+NetworkMediaModel::ItemType NetworkMediaModel::getType() const {
+    Q_D(const NetworkMediaModel);
+    return d->m_type;
+}
+bool NetworkMediaModel::isIndexed() const {
+    Q_D(const NetworkMediaModel);
+    return d->m_indexed;
+}
+bool NetworkMediaModel::canBeIndexed() const {
+    Q_D(const NetworkMediaModel);
+    return d->m_canBeIndexed;
+}
+
+
+void NetworkMediaModelPrivate::ListenerCb::onItemCleared( MediaTreePtr tree, input_item_node_t* node)
 {
     SharedInputItem p_node { node->p_item };
     QMetaObject::invokeMethod(model, [model=model, p_node = std::move(p_node), tree = std::move(tree)]() {
-        if (p_node != model->m_treeItem.media)
+        NetworkMediaModelPrivate* d = model->d_func();
+        if (p_node != d->m_treeItem.media)
             return;
         input_item_node_t *res;
         input_item_node_t *parent;
         // XXX is tree == m_treeItem.tree?
-        vlc_media_tree_Lock( model->m_treeItem.tree.get() );
-        bool found = vlc_media_tree_Find( model->m_treeItem.tree.get(), model->m_treeItem.media.get(),
-                                          &res, &parent );
-        vlc_media_tree_Unlock( model->m_treeItem.tree.get() );
-        if (!found)
-            return;
+
+        {
+            MediaTreeLocker lock{ d->m_treeItem.tree };
+            bool found = vlc_media_tree_Find( d->m_treeItem.tree.get(), d->m_treeItem.media.get(),
+                                             &res, &parent );
+            if (!found)
+                return;
+        }
 
         std::vector<SharedInputItem> itemList;
         itemList.reserve( static_cast<size_t>(res->i_children) );
         for (int i = 0; i < res->i_children; i++)
             itemList.emplace_back(res->pp_children[i]->p_item);
 
-        model->d_func()->refreshMediaList( std::move( tree ), std::move( itemList ), true );
+        model->d_func()->refreshMediaList(  d->m_treeItem, std::move( itemList ), true );
     }, Qt::QueuedConnection);
 }
 
-void NetworkMediaModel::ListenerCb::onItemAdded( MediaTreePtr tree, input_item_node_t* parent,
+void NetworkMediaModelPrivate::ListenerCb::onItemAdded( MediaTreePtr tree, input_item_node_t* parent,
                                                  input_item_node_t *const children[],
                                                  size_t count )
 {
@@ -905,12 +956,13 @@ void NetworkMediaModel::ListenerCb::onItemAdded( MediaTreePtr tree, input_item_n
         itemList.emplace_back(children[i]->p_item);
 
     QMetaObject::invokeMethod(model, [model=model, p_parent = std::move(p_parent), tree = std::move(tree), itemList=std::move(itemList)]() {
-        if ( p_parent == model->m_treeItem.media )
-            model->d_func()->refreshMediaList( std::move( tree ), std::move( itemList ), false );
+        NetworkMediaModelPrivate* d = model->d_func();
+        if ( p_parent == d->m_treeItem.media )
+            d->refreshMediaList(  d->m_treeItem , std::move( itemList ), false );
     }, Qt::QueuedConnection);
 }
 
-void NetworkMediaModel::ListenerCb::onItemRemoved( MediaTreePtr, input_item_node_t * node,
+void NetworkMediaModelPrivate::ListenerCb::onItemRemoved( MediaTreePtr, input_item_node_t * node,
                                                    input_item_node_t *const children[],
                                                    size_t count )
 {
@@ -927,12 +979,12 @@ void NetworkMediaModel::ListenerCb::onItemRemoved( MediaTreePtr, input_item_node
     }, Qt::QueuedConnection);
 }
 
-void NetworkMediaModel::ListenerCb::onItemPreparseEnded(MediaTreePtr, input_item_node_t* node, int )
+void NetworkMediaModelPrivate::ListenerCb::onItemPreparseEnded(MediaTreePtr, input_item_node_t* node, int )
 {
     model->d_func()->m_preparseSem.release();
     SharedInputItem p_node { node->p_item };
     QMetaObject::invokeMethod(model, [model=model, p_node=std::move(p_node)]() {
-        if (p_node != model->m_treeItem.media)
+        if (p_node != model->d_func()->m_treeItem.media)
             return;
 
         model->d_func()->m_loading = false;


=====================================
modules/gui/qt/network/networkmediamodel.hpp
=====================================
@@ -19,17 +19,13 @@
 #ifndef MLNETWORKMEDIAMODEL_HPP
 #define MLNETWORKMEDIAMODEL_HPP
 
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
 #include <QAbstractListModel>
 #include <QUrl>
 
-#include <vlc_media_source.h>
-#include <vlc_cxx_helpers.hpp>
-
+#include "vlcmediasourcewrapper.hpp"
 #include "util/shared_input_item.hpp"
+#include "devicesourceprovider.hpp"
+
 #include "networkbasemodel.hpp"
 
 #include <memory>
@@ -38,21 +34,31 @@ Q_MOC_INCLUDE( "maininterface/mainctx.hpp" )
 
 class MainCtx;
 
-using MediaSourcePtr = vlc_shared_data_ptr_type(vlc_media_source_t,
-                                vlc_media_source_Hold, vlc_media_source_Release);
-
-using MediaTreePtr = vlc_shared_data_ptr_type(vlc_media_tree_t,
-                                              vlc_media_tree_Hold,
-                                              vlc_media_tree_Release);
-
 class NetworkTreeItem
 {
     Q_GADGET
 public:
-    NetworkTreeItem() : tree(nullptr), media(nullptr) {}
-    NetworkTreeItem( MediaTreePtr tree, input_item_t* m )
-        : tree( std::move( tree ) )
-        , media( m )
+    NetworkTreeItem() : source(nullptr), tree(nullptr), media(nullptr) {}
+
+    NetworkTreeItem( SharedMediaSourceModel source, const SharedInputItem& item )
+        : source(source)
+        , tree(source->getTree())
+        , media(item)
+    {
+    }
+
+    NetworkTreeItem( MediaTreePtr tree, const SharedInputItem& item )
+        : source(nullptr)
+        , tree(tree)
+        , media(item)
+    {
+    }
+
+    //build a NetworkTreeItem with the same source/tree as parent
+    NetworkTreeItem(NetworkTreeItem& parent, const SharedInputItem& item)
+        : source(parent.source)
+        , tree(parent.tree)
+        , media(item)
     {
     }
 
@@ -67,13 +73,12 @@ public:
     }
 
     bool isValid() {
-        vlc_media_tree_Lock(tree.get());
+        MediaTreeLocker lock{ tree };
         input_item_node_t* node;
-        bool ret = vlc_media_tree_Find( tree.get(), media.get(), &node, nullptr);
-        vlc_media_tree_Unlock(tree.get());
-        return ret;
+        return vlc_media_tree_Find( tree.get(), media.get(), &node, nullptr);
     }
 
+    SharedMediaSourceModel source;
     MediaTreePtr tree;
     SharedInputItem media;
 };
@@ -141,15 +146,15 @@ public:
     void setCtx(MainCtx* ctx);
     void setTree(QVariant tree);
 
-    inline MainCtx* getCtx() const { return m_ctx; }
-    inline QVariant getTree() const { return QVariant::fromValue( m_treeItem); }
-    inline QVariantList getPath() const { return m_path; }
+    MainCtx* getCtx() const;
+    QVariant getTree() const;
+    QVariantList getPath() const;
 
-    inline QString getName() const { return m_name; }
-    inline QUrl getUrl() const { return m_url; }
-    inline ItemType getType() const { return m_type; }
-    inline bool isIndexed() const { return m_indexed; }
-    inline bool canBeIndexed() const { return m_canBeIndexed; }
+    QString getName() const;
+    QUrl getUrl() const;
+    ItemType getType() const;
+    bool isIndexed() const;
+    bool canBeIndexed() const;
 
     Q_INVOKABLE bool insertIntoPlaylist( const QModelIndexList& itemIdList, ssize_t playlistIndex );
     Q_INVOKABLE bool addToPlaylist( int index );
@@ -175,18 +180,6 @@ signals:
     void pathChanged();
 
 private:
-    //properties of the current node
-    QString m_name;
-    QUrl m_url;
-    ItemType m_type = ItemType::TYPE_UNKNOWN;
-    bool m_indexed = false;
-    bool m_canBeIndexed  = false;
-
-    MainCtx* m_ctx = nullptr;
-    NetworkTreeItem m_treeItem;
-    QVariantList m_path;
-
-    struct ListenerCb;
     Q_DECLARE_PRIVATE(NetworkMediaModel);
 };
 


=====================================
modules/gui/qt/network/standardpathmodel.cpp
=====================================
@@ -24,7 +24,6 @@
 #include "networkmediamodel.hpp"
 
 #include "util/locallistbasemodel.hpp"
-#include "util/shared_input_item.hpp"
 
 // VLC includes
 #include <vlc_media_source.h>
@@ -34,9 +33,7 @@
 #include <QStandardPaths>
 #include <QUrl>
 
-using MediaTreePtr = vlc_shared_data_ptr_type(vlc_media_tree_t,
-                                              vlc_media_tree_Hold,
-                                              vlc_media_tree_Release);
+#include "vlcmediasourcewrapper.hpp"
 
 struct StandardPathItem : public NetworkBaseItem
 {
@@ -98,7 +95,7 @@ public:
         : LocalListBaseModelPrivate<StandardPathItemPtr>(pub)
     {}
 
-    StandardPathLoader::ItemCompare getSortFunction() const
+    StandardPathLoader::ItemCompare getSortFunction() const override
     {
         if (m_sortCriteria == "mrl")
         {
@@ -157,12 +154,12 @@ public:
         input_item_t * inputItem = input_item_NewDirectory(qtu(url.toString()), qtu(name), ITEM_LOCAL);
         item->inputItem = SharedInputItem(inputItem, false);
 
-        vlc_media_tree_t * tree = vlc_media_tree_New();
-        vlc_media_tree_Lock(tree);
-        vlc_media_tree_Add(tree, &(tree->root), inputItem);
-        vlc_media_tree_Unlock(tree);
+        item->tree = MediaTreePtr(vlc_media_tree_New(), false);
+        {
+            MediaTreeLocker lock{item->tree};
+            vlc_media_tree_Add(item->tree.get(), &(item->tree->root), inputItem);
+        }
 
-        item->tree = MediaTreePtr(tree, false);
         item->artwork = artwork;
 
         m_items.emplace_back(std::move(item));
@@ -216,7 +213,7 @@ QVariant StandardPathModel::data(const QModelIndex & index, int role) const /* o
     switch (role)
     {
         case PATH_TREE:
-            return QVariant::fromValue(NetworkTreeItem(item->tree, item->inputItem.get()));
+            return QVariant::fromValue(NetworkTreeItem(item->tree, item->inputItem));
         default:
             return NetworkBaseModel::basedata(*item, role);
     }


=====================================
modules/gui/qt/network/vlcmediasourcewrapper.hpp
=====================================
@@ -0,0 +1,58 @@
+/*****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef VLCMEDIASOURCEWRAPPER_HPP
+#define VLCMEDIASOURCEWRAPPER_HPP
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <vlc_media_source.h>
+#include <vlc_cxx_helpers.hpp>
+
+using MediaSourcePtr = vlc_shared_data_ptr_type(vlc_media_source_t,
+                                                vlc_media_source_Hold,
+                                                vlc_media_source_Release);
+
+using MediaTreePtr = vlc_shared_data_ptr_type(vlc_media_tree_t,
+                                              vlc_media_tree_Hold,
+                                              vlc_media_tree_Release);
+
+class MediaTreeLocker
+{
+public:
+    MediaTreeLocker(MediaTreePtr& tree)
+        : m_tree(tree)
+    {
+        vlc_media_tree_Lock(m_tree.get());
+    }
+
+    ~MediaTreeLocker() {
+        vlc_media_tree_Unlock(m_tree.get());
+    }
+
+    MediaTreeLocker( const MediaTreeLocker& ) = delete;
+    MediaTreeLocker( MediaTreeLocker&& ) = delete;
+
+    MediaTreeLocker& operator=( const MediaTreeLocker& ) = delete;
+    MediaTreeLocker& operator=( MediaTreeLocker&& ) = delete;
+
+private:
+    MediaTreePtr& m_tree;
+};
+
+#endif // VLCMEDIASOURCEWRAPPER_HPP


=====================================
modules/gui/qt/qt.cpp
=====================================
@@ -86,6 +86,7 @@ extern "C" char **environ;
 #include "util/shared_input_item.hpp"
 #include "util/model_recovery_agent.hpp"
 #include "network/networkmediamodel.hpp"
+#include "network/devicesourceprovider.hpp"
 #include "playlist/playlist_common.hpp"
 #include "playlist/playlist_item.hpp"
 #include "dialogs/dialogs/dialogmodel.hpp"
@@ -1165,6 +1166,7 @@ static void *ThreadCleanup( qt_intf_t *p_intf, CleanupReason cleanupReason )
     DialogsProvider::killInstance();
     VLCDialogModel::killInstance();
     DialogErrorModel::killInstance();
+    MediaSourceCache::killInstance();
 
     //destroy MainCtx, Compositor shouldn't not use MainCtx after `unloadGUI`
     if (p_intf->p_mi) {


=====================================
modules/gui/qt/util/shared_input_item.hpp
=====================================
@@ -19,6 +19,10 @@
 #ifndef SHAREDINPUTITEM_HPP
 #define SHAREDINPUTITEM_HPP
 
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
 #include <QMetaType>
 
 #include <vlc_common.h>



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/445daaa587e71441b8eaa448639d3da85e327449...e0986ee7833a16aab4c60c916fabaf9fc0472604

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/445daaa587e71441b8eaa448639d3da85e327449...e0986ee7833a16aab4c60c916fabaf9fc0472604
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list