[vlc-commits] [Git][videolan/vlc][master] 4 commits: qt: introduce DeviceSourcesProvider
Jean-Baptiste Kempf (@jbk)
gitlab at videolan.org
Sun Jun 9 16:03:53 UTC 2024
Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC
Commits:
c5dc278f by Prince Gupta at 2024-06-09T15:51:11+00:00
qt: introduce DeviceSourcesProvider
helper class to get list of media sources
- - - - -
f46dbd59 by Prince Gupta at 2024-06-09T15:51:11+00:00
qt: introduce WorkerThreadSet
- - - - -
e497bd8f by Prince Gupta at 2024-06-09T15:51:11+00:00
qt: provide WorkerThreadSet from MainCtx
- - - - -
4156b110 by Prince Gupta at 2024-06-09T15:51:11+00:00
qt: move data query from main thread in NetworkDeviceModel
fixes #28648 #27087 #27378
- - - - -
10 changed files:
- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainctx.cpp
- modules/gui/qt/maininterface/mainctx.hpp
- modules/gui/qt/meson.build
- + modules/gui/qt/network/devicesourceprovider.cpp
- + modules/gui/qt/network/devicesourceprovider.hpp
- modules/gui/qt/network/networkdevicemodel.cpp
- modules/gui/qt/network/networkdevicemodel.hpp
- + modules/gui/qt/util/workerthreadset.cpp
- + modules/gui/qt/util/workerthreadset.hpp
Changes:
=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -237,6 +237,8 @@ libqt_plugin_la_SOURCES = \
menus/menus.cpp menus/menus.hpp \
network/mediatreelistener.cpp \
network/mediatreelistener.hpp \
+ network/devicesourceprovider.cpp \
+ network/devicesourceprovider.hpp \
network/networkdevicemodel.cpp \
network/networkdevicemodel.hpp \
network/networksourcesmodel.cpp \
@@ -275,6 +277,7 @@ libqt_plugin_la_SOURCES = \
util/asynctask.hpp \
util/audio_device_model.cpp \
util/audio_device_model.hpp \
+ util/workerthreadset.hpp util/workerthreadset.cpp \
util/base_model.hpp util/base_model_p.hpp util/base_model.cpp \
util/color_scheme_model.cpp util/color_scheme_model.hpp \
util/color_svg_image_provider.cpp util/color_svg_image_provider.hpp \
@@ -422,6 +425,7 @@ nodist_libqt_plugin_la_SOURCES = \
menus/custom_menus.moc.cpp \
menus/qml_menu_wrapper.moc.cpp \
menus/menus.moc.cpp \
+ network/devicesourceprovider.moc.cpp \
network/networkdevicemodel.moc.cpp \
network/networksourcesmodel.moc.cpp \
network/networkmediamodel.moc.cpp \
@@ -440,6 +444,7 @@ nodist_libqt_plugin_la_SOURCES = \
playlist/playlist_model.moc.cpp \
util/asynctask.moc.cpp \
util/audio_device_model.moc.cpp \
+ util/workerthreadset.moc.cpp \
util/base_model.moc.cpp \
util/color_scheme_model.moc.cpp \
util/color_svg_image_provider.moc.cpp \
=====================================
modules/gui/qt/maininterface/mainctx.cpp
=====================================
@@ -34,6 +34,7 @@
#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
@@ -522,6 +523,16 @@ inline void MainCtx::initSystray()
createSystray();
}
+WorkerThreadSet* MainCtx::workersThreads() const
+{
+ if (!m_workersThreads)
+ {
+ m_workersThreads.reset( new WorkerThreadSet );
+ }
+
+ return m_workersThreads.get();
+}
+
void MainCtx::setMediaLibraryVisible( bool visible )
{
if (m_mediaLibraryVisible == visible)
=====================================
modules/gui/qt/maininterface/mainctx.hpp
=====================================
@@ -67,6 +67,7 @@ class VideoSurfaceProvider;
class ControlbarProfileModel;
class SearchCtx;
class SortCtx;
+class WorkerThreadSet;
namespace vlc {
namespace playlist {
@@ -277,6 +278,8 @@ public:
Q_INVOKABLE bool useXmasCone() const;
+ WorkerThreadSet *workersThreads() const;
+
protected:
/* Systray */
void createSystray();
@@ -354,6 +357,8 @@ protected:
SearchCtx* m_search = nullptr;
SortCtx* m_sort = nullptr;
+ mutable std::unique_ptr<WorkerThreadSet> m_workersThreads;
+
public slots:
void toggleUpdateSystrayMenu();
void showUpdateSystrayMenu();
=====================================
modules/gui/qt/meson.build
=====================================
@@ -102,6 +102,7 @@ moc_headers = files(
'menus/custom_menus.hpp',
'menus/qml_menu_wrapper.hpp',
'menus/menus.hpp',
+ 'network/devicesourceprovider.hpp',
'network/networkdevicemodel.hpp',
'network/networksourcesmodel.hpp',
'network/networkmediamodel.hpp',
@@ -120,6 +121,7 @@ moc_headers = files(
'playlist/playlist_model.hpp',
'util/asynctask.hpp',
'util/audio_device_model.hpp',
+ 'util/workerthreadset.hpp',
'util/base_model.hpp',
'util/color_scheme_model.hpp',
'util/color_svg_image_provider.hpp',
@@ -363,6 +365,8 @@ some_sources = files(
'menus/menus.hpp',
'network/mediatreelistener.cpp',
'network/mediatreelistener.hpp',
+ 'network/devicesourceprovider.cpp',
+ 'network/devicesourceprovider.hpp',
'network/networkdevicemodel.cpp',
'network/networkdevicemodel.hpp',
'network/networksourcesmodel.cpp',
@@ -405,6 +409,8 @@ some_sources = files(
'util/asynctask.hpp',
'util/audio_device_model.cpp',
'util/audio_device_model.hpp',
+ 'util/workerthreadset.cpp',
+ 'util/workerthreadset.hpp',
'util/base_model.cpp',
'util/base_model.hpp',
'util/base_model_p.hpp',
=====================================
modules/gui/qt/network/devicesourceprovider.cpp
=====================================
@@ -0,0 +1,228 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "devicesourceprovider.hpp"
+#include "networkmediamodel.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))
+ {}
+
+ inline void onItemPreparseEnded( MediaTreePtr, input_item_node_t *, enum input_item_preparse_status ) override final {}
+
+ void onItemCleared( MediaTreePtr tree, input_item_node_t* node ) override
+ {
+ if (node != &tree->root)
+ return;
+
+ refresh( node->pp_children, node->i_children, true);
+ }
+
+ void onItemAdded( MediaTreePtr tree, input_item_node_t* parent, input_item_node_t *const children[], size_t count ) override
+ {
+ if (parent != &tree->root)
+ return;
+
+ refresh( children, count, false );
+ }
+
+ void onItemRemoved( MediaTreePtr tree, input_item_node_t * node, input_item_node_t *const children[], size_t count ) override
+ {
+ if (node != &tree->root)
+ return;
+
+ std::vector<SharedInputItem> itemList;
+
+ itemList.reserve( count );
+ for ( auto i = 0u; i < count; ++i )
+ itemList.emplace_back( children[i]->p_item );
+
+ QMetaObject::invokeMethod(provider, [provider = this->provider,
+ itemList = std::move(itemList),
+ mediaSource = this->mediaSource]()
+ {
+ provider->removeItems(itemList, mediaSource);
+ });
+ }
+
+ void refresh(input_item_node_t * const children[], size_t count,
+ bool clear)
+ {
+ std::vector<SharedInputItem> itemList;
+
+ itemList.reserve(count);
+ for (size_t i = 0; i < count; i++)
+ itemList.emplace_back(children[i]->p_item);
+
+ QMetaObject::invokeMethod(provider, [provider = this->provider,
+ itemList = std::move(itemList),
+ mediaSource = this->mediaSource, clear]()
+ {
+ provider->addItems(itemList, mediaSource, clear);
+ });
+ }
+
+ DeviceSourceProvider *provider;
+ MediaSourcePtr mediaSource;
+};
+
+
+DeviceSourceProvider::DeviceSourceProvider(NetworkDeviceModel::SDCatType sdSource
+ , const QString &sourceName, QObject *parent)
+ : QObject(parent)
+ , m_sdSource {sdSource}
+ , m_sourceName {sourceName}
+{
+}
+
+void DeviceSourceProvider::init(qt_intf_t *intf)
+{
+ using SourceMetaPtr = std::unique_ptr<vlc_media_source_meta_list_t,
+ decltype( &vlc_media_source_meta_list_Delete )>;
+
+ 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>(m_sdSource) ),
+ &vlc_media_source_meta_list_Delete );
+
+ if (!providerList)
+ {
+ emit failed();
+ 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_name += m_name.isEmpty() ? qfu( meta->longname ) : ", " + qfu( meta->longname );
+
+ MediaSourcePtr mediaSource(
+ vlc_media_source_provider_GetMediaSource(provider, meta->name)
+ , false );
+
+ if ( mediaSource == nullptr )
+ continue;
+
+ std::unique_ptr<MediaTreeListener> l{ new MediaTreeListener(
+ MediaTreePtr{ mediaSource->tree },
+ std::make_unique<DeviceSourceProvider::ListenerCb>(this, mediaSource) ) };
+ if ( l->listener == nullptr )
+ break;
+
+ m_mediaSources.push_back( std::move( mediaSource ) );
+ m_listeners.push_back( std::move( l ) );
+ }
+
+ if ( !m_name.isEmpty() )
+ emit nameUpdated( m_name );
+
+ if ( !m_listeners.empty() )
+ emit itemsUpdated( m_items );
+ else
+ emit failed();
+}
+
+void DeviceSourceProvider::addItems(const std::vector<SharedInputItem> &inputList,
+ const MediaSourcePtr &mediaSource, const bool clear)
+{
+ bool dataChanged = false;
+
+ if (clear)
+ {
+ const qsizetype removed = m_items.removeIf([&mediaSource](const NetworkDeviceItemPtr &item)
+ {
+ return item->mediaSource == mediaSource;
+ });
+
+ if (removed > 0)
+ dataChanged = true;
+ }
+
+ for (const SharedInputItem & inputItem : inputList)
+ {
+ auto newItem = std::make_shared<NetworkDeviceItem>(inputItem, mediaSource);
+ auto it = m_items.find(newItem);
+ if (it != m_items.end())
+ {
+ (*it)->mrls.push_back(std::make_pair(newItem->mainMrl, mediaSource));
+ }
+ else
+ {
+ m_items.insert(std::move(newItem));
+ dataChanged = true;
+ }
+ }
+
+ if (dataChanged)
+ {
+ emit itemsUpdated(m_items);
+ }
+}
+
+void DeviceSourceProvider::removeItems(const std::vector<SharedInputItem> &inputList,
+ const MediaSourcePtr &mediaSource)
+{
+ 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 );
+ }
+ }
+
+ if (!found)
+ {
+ m_items.erase(it);
+ dataChanged = true;
+ }
+ }
+ }
+
+ if (dataChanged)
+ emit itemsUpdated(m_items);
+}
=====================================
modules/gui/qt/network/devicesourceprovider.hpp
=====================================
@@ -0,0 +1,123 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#pragma once
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "qt.hpp"
+
+#include <QString>
+#include <QSet>
+#include <vector>
+
+#include "networkdevicemodel.hpp"
+#include "mediatreelistener.hpp"
+
+//represents an entry of the model
+struct NetworkDeviceItem
+{
+ NetworkDeviceItem(const SharedInputItem& item, const NetworkDeviceModel::MediaSourcePtr& mediaSource)
+ : name(qfu(item->psz_name))
+ , mainMrl(QUrl::fromEncoded(item->psz_uri))
+ , protocol(mainMrl.scheme())
+ , type( static_cast<NetworkDeviceModel::ItemType>(item->i_type))
+ , mediaSource(mediaSource)
+ , inputItem(item)
+ {
+ id = qHash(name) ^ qHash(protocol);
+ mrls.push_back(std::make_pair(mainMrl, mediaSource));
+
+ char* artworkUrl = input_item_GetArtworkURL(inputItem.get());
+ if (artworkUrl)
+ {
+ artwork = QString::fromUtf8(artworkUrl);
+ free(artworkUrl);
+ }
+ }
+
+ uint id;
+ QString name;
+ QUrl mainMrl;
+ std::vector<std::pair<QUrl, NetworkDeviceModel::MediaSourcePtr>> mrls;
+ QString protocol;
+ NetworkDeviceModel::ItemType type;
+ NetworkDeviceModel::MediaSourcePtr mediaSource;
+ SharedInputItem inputItem;
+ QString artwork;
+};
+
+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 = 0) noexcept
+{
+ return s->id;
+}
+
+using NetworkDeviceItemSet = QSet<NetworkDeviceItemPtr>;
+
+class DeviceSourceProvider : public QObject
+{
+ Q_OBJECT
+
+public:
+ using MediaSourcePtr = NetworkDeviceModel::MediaSourcePtr;
+
+ DeviceSourceProvider(NetworkDeviceModel::SDCatType sdSource,
+ const QString &sourceName,
+ QObject *parent = nullptr);
+
+ void init(qt_intf_t *intf);
+
+signals:
+ void failed();
+ void nameUpdated( QString name );
+ void itemsUpdated( NetworkDeviceItemSet items );
+
+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);
+
+ 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;
+};
=====================================
modules/gui/qt/network/networkdevicemodel.cpp
=====================================
@@ -17,12 +17,13 @@
*****************************************************************************/
#include <unordered_set>
+#include <QTimer>
#include "maininterface/mainctx.hpp"
+#include "util/workerthreadset.hpp"
-#include "networkdevicemodel.hpp"
+#include "devicesourceprovider.hpp"
#include "networkmediamodel.hpp"
-#include "mediatreelistener.hpp"
#include "playlist/media.hpp"
#include "playlist/playlist_controller.hpp"
@@ -33,63 +34,6 @@
namespace
{
-//represents an entry of the model
-struct NetworkDeviceItem
-{
- NetworkDeviceItem(const SharedInputItem& item, const NetworkDeviceModel::MediaSourcePtr& mediaSource)
- : name(qfu(item->psz_name))
- , mainMrl(QUrl::fromEncoded(item->psz_uri))
- , protocol(mainMrl.scheme())
- , type( static_cast<NetworkDeviceModel::ItemType>(item->i_type))
- , mediaSource(mediaSource)
- , inputItem(item)
- {
- id = qHash(name) ^ qHash(protocol);
- mrls.push_back(std::make_pair(mainMrl, mediaSource));
-
- char* artworkUrl = input_item_GetArtworkURL(inputItem.get());
- if (artworkUrl)
- {
- artwork = QString::fromUtf8(artworkUrl);
- free(artworkUrl);
- }
- }
-
- uint id;
- QString name;
- QUrl mainMrl;
- std::vector<std::pair<QUrl, NetworkDeviceModel::MediaSourcePtr>> mrls;
- QString protocol;
- NetworkDeviceModel::ItemType type;
- NetworkDeviceModel::MediaSourcePtr mediaSource;
- SharedInputItem inputItem;
- QString artwork;
-};
-
-using NetworkDeviceItemPtr = std::shared_ptr<NetworkDeviceItem>;
-using NetworkDeviceModelLoader = LocalListCacheLoader<NetworkDeviceItemPtr>;
-
-//hash and compare function for std::unordered_set
-struct NetworkDeviceItemHash
-{
- std::size_t operator()(const NetworkDeviceItemPtr& s) const noexcept
- {
- return s->id;
- }
-};
-
-struct NetworkDeviceItemEqual
-{
- bool operator()(const NetworkDeviceItemPtr& a, const NetworkDeviceItemPtr& b) const noexcept
- {
- return a->id == b->id
- && QString::compare(a->name, b->name, Qt::CaseInsensitive) == 0
- && QString::compare(a->protocol, b->protocol, Qt::CaseInsensitive) == 0;
- }
-};
-
-using NetworkDeviceItemSet = std::unordered_set<NetworkDeviceItemPtr, NetworkDeviceItemHash, NetworkDeviceItemEqual>;
-
bool itemMatchPattern(const NetworkDeviceItemPtr& a, const QString& pattern)
{
return a->name.contains(pattern, Qt::CaseInsensitive);
@@ -129,22 +73,6 @@ bool descendingName(const NetworkDeviceItemPtr& a, const NetworkDeviceItemPtr& b
}
-//handle discovery events from the media source provider
-struct NetworkDeviceModel::ListenerCb : public MediaTreeListener::MediaTreeListenerCb {
- ListenerCb(NetworkDeviceModel* model, MediaSourcePtr mediaSource)
- : model(model)
- , mediaSource(std::move(mediaSource))
- {}
-
- void onItemCleared( MediaTreePtr tree, input_item_node_t* node ) override;
- void onItemAdded( MediaTreePtr tree, input_item_node_t* parent, input_item_node_t *const children[], size_t count ) override;
- void onItemRemoved( MediaTreePtr tree, input_item_node_t * node, input_item_node_t *const children[], size_t count ) override;
- inline void onItemPreparseEnded( MediaTreePtr, input_item_node_t *, enum input_item_preparse_status ) override {}
-
- NetworkDeviceModel *model;
- MediaSourcePtr mediaSource;
-};
-
// ListCache specialisation
template<>
@@ -156,6 +84,8 @@ bool ListCache<NetworkDeviceItemPtr>::compareItems(const NetworkDeviceItemPtr& a
// NetworkDeviceModelPrivate
+using NetworkDeviceModelLoader = LocalListCacheLoader<NetworkDeviceItemPtr>;
+
class NetworkDeviceModelPrivate
: public LocalListBaseModelPrivate<NetworkDeviceItemPtr>
{
@@ -163,7 +93,6 @@ class NetworkDeviceModelPrivate
public:
NetworkDeviceModelPrivate(NetworkDeviceModel * pub)
: LocalListBaseModelPrivate<NetworkDeviceItemPtr>(pub)
- , m_items(0, NetworkDeviceItemHash{}, NetworkDeviceItemEqual{})
{}
NetworkDeviceModelLoader::ItemCompare getSortFunction() const override
@@ -191,54 +120,64 @@ public:
if (m_qmlInitializing || !q->m_ctx || q->m_sdSource == NetworkDeviceModel::CAT_UNDEFINED || q->m_sourceName.isEmpty())
return false;
- auto libvlc = vlc_object_instance(q->m_ctx->getIntf());
-
- m_listeners.clear();
m_items.clear();
+ if (m_sources)
+ {
+ q->disconnect( m_sources );
+ m_sources->deleteLater();
+ m_sources = nullptr;
+ }
+
q->m_name = QString {};
+ emit q->nameChanged();
- auto provider = vlc_media_source_provider_Get( libvlc );
+ m_sources = new DeviceSourceProvider( q->m_sdSource, q->m_sourceName );
+ q->m_ctx->workersThreads()->assignToWorkerThread( m_sources );
- using SourceMetaPtr = std::unique_ptr<vlc_media_source_meta_list_t,
- decltype( &vlc_media_source_meta_list_Delete )>;
+ // 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();
+ });
- SourceMetaPtr providerList( vlc_media_source_provider_List( provider, static_cast<services_discovery_category_e>(q->m_sdSource) ),
- &vlc_media_source_meta_list_Delete );
- if ( providerList == nullptr )
- return false;
+ QObject::connect(m_sources, &DeviceSourceProvider::failed, q,
+ [this]()
+ {
+ m_items.clear();
- auto nbProviders = vlc_media_source_meta_list_Count( providerList.get() );
+ m_revision += 1;
+ invalidateCache();
+ });
- for ( auto i = 0u; i < nbProviders; ++i )
+ QObject::connect(m_sources, &DeviceSourceProvider::nameUpdated, q,
+ [q](QString name)
{
- auto meta = vlc_media_source_meta_list_Get( providerList.get(), i );
- const QString sourceName = qfu( meta->name );
- if ( q->m_sourceName != '*' && q->m_sourceName != sourceName )
- continue;
-
- q->m_name += q->m_name.isEmpty() ? qfu( meta->longname ) : ", " + qfu( meta->longname );
+ q->m_name = name;
emit q->nameChanged();
+ });
- MediaSourcePtr mediaSource(
- vlc_media_source_provider_GetMediaSource(provider, meta->name)
- , false );
-
- if ( mediaSource == nullptr )
- continue;
- std::unique_ptr<MediaTreeListener> l{ new MediaTreeListener(
- MediaTreePtr{ mediaSource->tree },
- std::make_unique<NetworkDeviceModel::ListenerCb>(q, MediaSourcePtr{ mediaSource }) ) };
- if ( l->listener == nullptr )
- return false;
- m_listeners.push_back( std::move( l ) );
- }
+ QObject::connect(m_sources, &DeviceSourceProvider::itemsUpdated, q,
+ [this](NetworkDeviceItemSet items)
+ {
+ m_items = items;
+
+ m_revision += 1;
+ invalidateCache();
+ });
+
+ QMetaObject::invokeMethod(m_sources,
+ [sources = this->m_sources, intf = q->m_ctx->getIntf()]()
+ {
+ sources->init( intf );
+ });
//service discovery don't notify preparse end
m_loading = false;
emit q->loadingChanged();
- return m_listeners.empty() == false;
+ return true;
}
const NetworkDeviceItem* getItemForRow(int row) const
@@ -249,96 +188,6 @@ public:
return nullptr;
}
- void addItems(
- const std::vector<SharedInputItem>& inputList,
- const MediaSourcePtr& mediaSource,
- bool clear)
- {
- bool dataChanged = false;
-
- if (clear)
- {
- //std::remove_if doesn't work with unordered_set
- //due to iterators being const
- for (auto it = m_items.begin(); it != m_items.end(); )
- {
- it = std::find_if(
- it, m_items.end(),
- [&mediaSource](const NetworkDeviceItemPtr& item) {
- return item->mediaSource == mediaSource;
- });
-
- if (it != m_items.end())
- it = m_items.erase(it);
- }
- dataChanged = true;
- }
-
- for (const SharedInputItem & inputItem : inputList)
- {
- auto newItem = std::make_shared<NetworkDeviceItem>(inputItem, mediaSource);
- auto it = m_items.find(newItem);
- if (it != m_items.end())
- {
- (*it)->mrls.push_back(std::make_pair(newItem->mainMrl, mediaSource));
- }
- else
- {
- m_items.emplace(std::move(newItem));
- dataChanged = true;
- }
- }
-
- if (dataChanged)
- {
- m_revision += 1;
- invalidateCache();
- }
- }
-
- void removeItems(const std::vector<SharedInputItem>& inputList, const MediaSourcePtr& mediaSource)
- {
- 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 );
- }
- }
-
- if (!found)
- {
- m_items.erase(it);
- dataChanged = true;
- }
- }
-
- }
- if (dataChanged)
- {
- m_revision += 1;
- invalidateCache();
- }
- }
-
public: //LocalListCacheLoader::ModelSource
std::vector<NetworkDeviceItemPtr> getModelData(const QString& pattern) const override
{
@@ -363,7 +212,7 @@ public: //LocalListCacheLoader::ModelSource
public:
NetworkDeviceItemSet m_items;
- std::vector<std::unique_ptr<MediaTreeListener>> m_listeners;
+ QPointer<DeviceSourceProvider> m_sources {};
};
NetworkDeviceModel::NetworkDeviceModel( QObject* parent )
@@ -576,55 +425,3 @@ QVariantList NetworkDeviceModel::getItemsForIndexes(const QModelIndexList & inde
return items;
}
-// NetworkDeviceModel::ListenerCb implementation
-
-void NetworkDeviceModel::ListenerCb::onItemCleared( MediaTreePtr tree, input_item_node_t* node )
-{
- if (node != &tree->root)
- return;
- model->refreshDeviceList( mediaSource, node->pp_children, node->i_children, true );
-}
-
-void NetworkDeviceModel::ListenerCb::onItemAdded( MediaTreePtr tree, input_item_node_t* parent,
- input_item_node_t *const children[],
- size_t count )
-{
- if (parent != &tree->root)
- return;
- model->refreshDeviceList( mediaSource, children, count, false );
-}
-
-void NetworkDeviceModel::ListenerCb::onItemRemoved( MediaTreePtr tree, input_item_node_t* node,
- input_item_node_t *const children[],
- size_t count )
-{
- if (node != &tree->root)
- return;
-
- std::vector<SharedInputItem> itemList;
-
- itemList.reserve( count );
- for ( auto i = 0u; i < count; ++i )
- itemList.emplace_back( children[i]->p_item );
-
- QMetaObject::invokeMethod(model, [model=model, mediaSource=mediaSource, itemList=std::move(itemList)]() {
- model->d_func()->removeItems(itemList, mediaSource);
- }, Qt::QueuedConnection);
-}
-
-void NetworkDeviceModel::refreshDeviceList(MediaSourcePtr mediaSource,
- input_item_node_t * const children[], size_t count,
- bool clear)
-{
- std::vector<SharedInputItem> itemList;
-
- itemList.reserve(count);
- for (size_t i = 0; i < count; i++)
- itemList.emplace_back(children[i]->p_item);
-
- QMetaObject::invokeMethod(this, [this, clear, itemList = std::move(itemList), mediaSource]() mutable
- {
- Q_D(NetworkDeviceModel);
- d->addItems(itemList, mediaSource, clear);
- }, Qt::QueuedConnection);
-}
=====================================
modules/gui/qt/network/networkdevicemodel.hpp
=====================================
@@ -134,7 +134,6 @@ private:
QString m_sourceName; // '*' -> all sources
QString m_name; // source long name
- struct ListenerCb;
Q_DECLARE_PRIVATE(NetworkDeviceModel)
};
=====================================
modules/gui/qt/util/workerthreadset.cpp
=====================================
@@ -0,0 +1,103 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "workerthreadset.hpp"
+
+#include <QTimer>
+
+WorkerThreadSet::~WorkerThreadSet()
+{
+ for (auto worker : workers)
+ {
+ if (!worker.thread)
+ continue;
+
+ finish( worker );
+ }
+}
+
+void WorkerThreadSet::assignToWorkerThread(QObject *obj)
+{
+ auto worker = reserve();
+ obj->moveToThread( worker );
+
+ QObject::connect(obj, &QObject::destroyed, worker, [this, worker]()
+ {
+ unreserve( worker );
+ });
+}
+
+void WorkerThreadSet::finish(Worker &worker)
+{
+ worker.thread->quit();
+ worker.thread->wait();
+ delete worker.thread;
+}
+
+QThread *WorkerThreadSet::reserve()
+{
+ auto itr = std::min_element(std::begin(workers)
+ , std::end(workers)
+ , [](const Worker &l, const Worker &r)
+ {
+ return l.load < r.load;
+ });
+
+ assert(itr != std::end(workers));
+
+ if (!itr->thread)
+ {
+ itr->thread = new QThread;
+ itr->thread->start();
+ }
+
+ itr->load++;
+ itr->inactiveTime.invalidate();
+ return itr->thread;
+}
+
+void WorkerThreadSet::unreserve(QThread *thread)
+{
+ auto itr = std::find_if(std::begin(workers)
+ , std::end(workers)
+ , [thread](const Worker &i) { return i.thread == thread; });
+
+ if (itr == std::end(workers)) return; // impossible?
+
+ const int load = --itr->load;
+ if (load != 0)
+ return;
+
+ itr->inactiveTime.start();
+ QTimer::singleShot(CLEANUP_TIMEOUT, this, &WorkerThreadSet::cleanupInactiveWorker);
+}
+
+void WorkerThreadSet::cleanupInactiveWorker()
+{
+ for (auto &worker : workers)
+ {
+ if ((worker.load == 0)
+ && worker.inactiveTime.hasExpired(MAX_INACTIVE_TIME)
+ && worker.thread)
+ {
+ finish( worker );
+
+ worker.thread = nullptr;
+ }
+ }
+}
=====================================
modules/gui/qt/util/workerthreadset.hpp
=====================================
@@ -0,0 +1,63 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#pragma once
+
+#include <QObject>
+#include <QThread>
+
+// maintains a set of reusable worker threads
+// class is not thread safe and must be accessed from Main thread
+class WorkerThreadSet : public QObject
+{
+ Q_OBJECT
+public:
+ using QObject::QObject;
+
+ ~WorkerThreadSet();
+
+ // changes the thread affinity of 'obj' to a worker thread
+ void assignToWorkerThread(QObject *obj);
+
+private:
+ struct Worker
+ {
+ QThread *thread = nullptr;
+ int load = 0;
+ QElapsedTimer inactiveTime;
+ };
+
+ const static int MAX_WORKER = 2;
+ const static int CLEANUP_TIMEOUT = 10000; // 10seconds
+ const static int MAX_INACTIVE_TIME = 6000; // 6seconds
+
+ void finish(Worker &worker);
+
+ // returns a worker thread after increasing it's load
+ QThread *reserve();
+
+ // reduces load of the previously allocated 'thread'
+ // and makes it available for future operations
+ // threads are automatically freed when they remain
+ // inactive for extended amount of time
+ void unreserve(QThread *thread);
+
+ void cleanupInactiveWorker();
+
+ Worker workers[MAX_WORKER] {};
+};
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/fe8ac5c4fa5708e4a0885cc23c545cf3310317f4...4156b1105c98d33b79c7d1155945d29f3bd95aee
--
This project does not include diff previews in email notifications.
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/fe8ac5c4fa5708e4a0885cc23c545cf3310317f4...4156b1105c98d33b79c7d1155945d29f3bd95aee
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