Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC


Commits:
d67fa4ba by Prince Gupta at 2024-07-01T20:55:41+00:00
qml: fix identation

- - - - -
e145e7b1 by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: make MLItemId hashable

- - - - -
20327806 by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: define invalid state of MLItemId

- - - - -
ea002dd8 by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: implement MLMedia class

- - - - -
72aa032c by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: fix networkmediamodel not notifying removed items

- - - - -
86ac0729 by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: store items as uri indexable in NetworkMediaModel

required for efficient implementation for integrating medialibrary data
added in follow up commits

- - - - -
d085c69f by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: implement MLMediaStore

- - - - -
7844853c by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: provide associated media from medialibrary in NetworkMediaModel

- - - - -
5290f1ea by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: use medialibrary thumbnail (if available) in NetworkMediaModel

- - - - -
db8f2ff3 by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: add progress field to NetworkMediaModel data

- - - - -
cb5c3748 by Prince Gupta at 2024-07-01T20:55:41+00:00
qml: show progressbar in NetworkGridItem

- - - - -
23ec44a9 by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: add duration field in NetworkMediaModel

- - - - -
58e030c4 by Prince Gupta at 2024-07-01T20:55:41+00:00
qml: support duration column in BrowseTreeDisplay

- - - - -
31b383f5 by Prince Gupta at 2024-07-01T20:55:41+00:00
qt: optimize data initialization in NetworkMediaModel

eliminates N*M loop [N = NetworkMediaModel::count, M = folders
in NetworkMediaModel] generated to update indexing state of
indexes from medialibrary, use the fact that items are stored as
uri indexable to make this operation linear in size M.

- - - - -


10 changed files:

- modules/gui/qt/Makefile.am
- + modules/gui/qt/medialibrary/mlmedia.hpp
- + modules/gui/qt/medialibrary/mlmediastore.cpp
- + modules/gui/qt/medialibrary/mlmediastore.hpp
- modules/gui/qt/medialibrary/mlqmltypes.hpp
- modules/gui/qt/meson.build
- modules/gui/qt/network/networkmediamodel.cpp
- modules/gui/qt/network/networkmediamodel.hpp
- modules/gui/qt/network/qml/BrowseTreeDisplay.qml
- modules/gui/qt/network/qml/NetworkGridItem.qml


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -172,6 +172,8 @@ libqt_plugin_la_SOURCES = \
        maininterface/windoweffects_module.hpp \
        medialibrary/medialib.cpp \
        medialibrary/medialib.hpp \
+       medialibrary/mlmediastore.cpp \
+       medialibrary/mlmediastore.hpp \
        medialibrary/mlalbum.cpp \
        medialibrary/mlalbum.hpp \
        medialibrary/mlalbummodel.cpp \
@@ -204,6 +206,7 @@ libqt_plugin_la_SOURCES = \
        medialibrary/mlthreadpool.cpp \
        medialibrary/mlthreadpool.hpp \
        medialibrary/mlqmltypes.hpp \
+       medialibrary/mlmedia.hpp \
        medialibrary/mlqueryparams.cpp \
        medialibrary/mlqueryparams.hpp \
        medialibrary/mlrecentsmodel.cpp \
@@ -406,6 +409,7 @@ nodist_libqt_plugin_la_SOURCES = \
        maininterface/videosurface.moc.cpp \
        maininterface/video_window_handler.moc.cpp \
        medialibrary/medialib.moc.cpp \
+       medialibrary/mlmediastore.moc.cpp \
        medialibrary/mlalbummodel.moc.cpp \
        medialibrary/mlalbumtrackmodel.moc.cpp \
        medialibrary/mlartistmodel.moc.cpp \
@@ -415,6 +419,7 @@ nodist_libqt_plugin_la_SOURCES = \
        medialibrary/mlgenremodel.moc.cpp \
        medialibrary/mlthreadpool.moc.cpp \
        medialibrary/mlqmltypes.moc.cpp \
+       medialibrary/mlmedia.moc.cpp \
        medialibrary/mlrecentsmodel.moc.cpp \
        medialibrary/mlrecentsvideomodel.moc.cpp \
        medialibrary/mlurlmodel.moc.cpp \


=====================================
modules/gui/qt/medialibrary/mlmedia.hpp
=====================================
@@ -0,0 +1,82 @@
+/*****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * Authors: Prince Gupta <guptaprince8...@gmail.com>
+ *
+ * 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 "mlqmltypes.hpp"
+#include "util/vlctick.hpp"
+
+#include <QObject>
+
+class MLMedia : public MLItem
+{
+    Q_GADGET
+
+    Q_PROPERTY(MLItemId id READ getId CONSTANT)
+    Q_PROPERTY(QString title READ title CONSTANT FINAL)
+    Q_PROPERTY(QString fileName READ fileName CONSTANT FINAL)
+    Q_PROPERTY(QString smallCover READ smallCover CONSTANT FINAL)
+    Q_PROPERTY(QString bannerCover READ bannerCover CONSTANT FINAL)
+    Q_PROPERTY(VLCTick duration READ duration CONSTANT FINAL)
+    Q_PROPERTY(qreal progress READ progress CONSTANT FINAL)
+
+public:
+    MLMedia() : MLItem {MLItemId()} {}
+
+    MLMedia(const vlc_ml_media_t *media)
+        : MLItem {MLItemId(media->i_id, VLC_ML_PARENT_UNKNOWN)}
+    {
+        const auto getThumbnail = [](const vlc_ml_thumbnail_t & thumbnail)
+        {
+            return (thumbnail.i_status == VLC_ML_THUMBNAIL_STATUS_AVAILABLE)
+                    ? qfu(thumbnail.psz_mrl) : QString {};
+        };
+
+        m_title = qfu(media->psz_title);
+        m_fileName = qfu(media->psz_filename);
+        m_smallCover = getThumbnail(media->thumbnails[VLC_ML_THUMBNAIL_SMALL]);
+        m_bannerCover = 
getThumbnail(media->thumbnails[VLC_ML_THUMBNAIL_BANNER]);
+        m_duration = VLCTick::fromMS(media->i_duration);
+        m_progress = media->f_progress;
+    }
+
+    QString title() const { return m_title; }
+    QString fileName() const { return m_fileName; }
+    QString smallCover() const { return m_smallCover; }
+    QString bannerCover() const { return m_bannerCover; }
+    VLCTick duration() const {  return m_duration; }
+    qreal progress() const { return m_progress; }
+
+    Q_INVOKABLE bool valid() const { return getId().id != INVALID_MLITEMID_ID; 
}
+
+private:
+    QString m_title;
+    QString m_fileName;
+    QString m_smallCover;
+    QString m_bannerCover;
+    VLCTick m_duration;
+    qreal m_progress;
+};
+


=====================================
modules/gui/qt/medialibrary/mlmediastore.cpp
=====================================
@@ -0,0 +1,183 @@
+/*****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * Authors: Prince Gupta <guptaprince8...@gmail.com>
+ *
+ * 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 "mlmediastore.hpp"
+
+#include "mlhelper.hpp"
+#include "medialib.hpp"
+#include "mlmedia.hpp"
+
+static const char *UPDATE_QUEUE = "MLMEDIASTORE_UPDATEQUEUE";
+
+MLMediaStore::~MLMediaStore() = default;
+
+MLMediaStore::MLMediaStore(MediaLib *ml, QObject *parent)
+    : QObject(parent)
+    , m_ml {ml}
+    , m_ml_event_handle( nullptr, [this](vlc_ml_event_callback_t* cb )
+    {
+        assert( m_ml != nullptr );
+        m_ml->unregisterEventListener( cb );
+    })
+{
+    m_ml_event_handle.reset
+    (
+        m_ml->registerEventListener(MLMediaStore::onVlcMlEvent, this)
+    );
+}
+
+void MLMediaStore::insert(const QString &mrl)
+{
+    struct Ctx
+    {
+        MLMedia media;
+    };
+
+    m_files.insert(mrl);
+
+    m_ml->runOnMLThread<Ctx>(this,
+    //ML thread
+    [mrl](vlc_medialibrary_t *ml, Ctx &ctx)
+    {
+        ml_unique_ptr<vlc_ml_media_t> media {vlc_ml_get_media_by_mrl(ml, 
qtu(mrl))};
+        if (media)
+            ctx.media = MLMedia(media.get());
+    },
+    //UI thread
+    [this, mrl](quint64, Ctx &ctx)
+    {
+        if (!ctx.media.valid())
+            return; // failed to get media, TODO: notify??
+
+        setMedia(mrl, std::move(ctx.media));
+    });
+}
+
+void MLMediaStore::remove(const MLItemId &id)
+{
+    if (!m_mrls.contains(id))
+        return;
+
+    const QString mrl = m_mrls[id];
+
+    m_mrls.remove(id);
+    m_files.remove(mrl);
+}
+
+void MLMediaStore::clear()
+{
+    m_mrls.clear();
+}
+
+void MLMediaStore::onVlcMlEvent(void *data, const vlc_ml_event_t *event)
+{
+    auto self = static_cast<MLMediaStore*>(data);
+    switch (event->i_type)
+    {
+    case VLC_ML_EVENT_MEDIA_ADDED:
+    {
+        const vlc_ml_media_t *media = event->creation.p_media;
+        QString mrl;
+        for (const vlc_ml_file_t &file : 
ml_range_iterate<vlc_ml_file_t>(media->p_files))
+        {
+            if (file.i_type == VLC_ML_FILE_TYPE_MAIN)
+            {
+                mrl = QString::fromUtf8(file.psz_mrl);
+                break;
+            }
+        }
+
+        if (mrl.isEmpty())
+            break;
+
+        MLMedia mlMedia (media);
+        QMetaObject::invokeMethod(self, [self, mrl, mlMedia]()
+        {
+            self->setMedia(mrl, mlMedia);
+        });
+
+        break;
+    }
+
+    case VLC_ML_EVENT_MEDIA_UPDATED:
+    {
+        const MLItemId id(event->modification.i_entity_id, 
VLC_ML_PARENT_UNKNOWN);
+        QMetaObject::invokeMethod(self, [self, id] () mutable
+        {
+            self->update(id);
+        });
+
+        break;
+    }
+
+    case VLC_ML_EVENT_MEDIA_DELETED:
+    {
+        const MLItemId id{ event->deletion.i_entity_id, VLC_ML_PARENT_UNKNOWN 
};
+        QMetaObject::invokeMethod(self, [self, id]()
+        {
+            self->remove(id);
+        });
+
+        break;
+    }
+
+    default:
+        break;
+    }
+}
+
+void MLMediaStore::update(const MLItemId &id)
+{
+    if (!m_mrls.contains(id))
+        return;
+
+    struct Ctx
+    {
+        MLMedia media;
+    };
+
+    m_ml->runOnMLThread<Ctx>(this,
+    //ML thread
+    [id](vlc_medialibrary_t *ml, Ctx &ctx)
+    {
+        ml_unique_ptr<vlc_ml_media_t> media {vlc_ml_get_media(ml, id.id)};
+        ctx.media = media ? MLMedia(media.get()) : MLMedia {};
+    },
+    //UI thread
+    [this, id](quint64, Ctx &ctx)
+    {
+        if (!m_mrls.contains(id))
+            return; // item was removed?
+
+        const QString mrl = m_mrls[id];
+        emit updated(mrl, ctx.media);
+    }, UPDATE_QUEUE); // update in a single queue in case there
+                      // is a overlap of same media update
+}
+
+void MLMediaStore::setMedia(const QString &mrl, MLMedia media)
+{
+    if (!m_files.contains(mrl))
+        return;
+
+    m_mrls[media.getId()] = mrl;
+    emit updated(mrl, media);
+}


=====================================
modules/gui/qt/medialibrary/mlmediastore.hpp
=====================================
@@ -0,0 +1,74 @@
+/*****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * Authors: Prince Gupta <guptaprince8...@gmail.com>
+ *
+ * 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 <vlc_media_library.h>
+
+#include <QObject>
+#include <QHash>
+#include <QSet>
+#include <QString>
+
+#include <memory>
+
+#include "mlmedia.hpp"
+
+class MediaLib;
+class MLItemId;
+class MLEvent;
+
+class MLMediaStore : public QObject
+{
+    Q_OBJECT
+
+public:
+    MLMediaStore(MediaLib *ml, QObject *parent = nullptr);
+    ~MLMediaStore();
+
+    void insert(const QString &mrl);
+    void remove(const MLItemId &id);
+    void clear();
+
+    bool contains(const QString &mrl) const;
+
+signals:
+    void updated(const QString &mrl, MLMedia media);
+
+private:
+    static void onVlcMlEvent(void* data
+                             , const vlc_ml_event_t* event);
+
+    // updates associated Media
+    void update(const MLItemId &id);
+
+    void setMedia(const QString &mrl, MLMedia media);
+
+    MediaLib *m_ml;
+
+    std::unique_ptr<vlc_ml_event_callback_t,
+                    std::function<void(vlc_ml_event_callback_t*)>> 
m_ml_event_handle;
+
+    // maintain set of MRL to handle media creation events
+    QSet<QString> m_files;
+
+    // MLEvent doesn't provide media mrl
+    // maintain a map for faster handling of ml events
+    QHash<MLItemId, QString> m_mrls;
+};


=====================================
modules/gui/qt/medialibrary/mlqmltypes.hpp
=====================================
@@ -27,11 +27,13 @@
 #include <vlc_common.h>
 #include <vlc_media_library.h>
 
+static constexpr int64_t INVALID_MLITEMID_ID = 0;
+
 class MLItemId
 {
     Q_GADGET
 public:
-    MLItemId() : id(0), type( VLC_ML_PARENT_UNKNOWN ) {}
+    MLItemId() : id(INVALID_MLITEMID_ID), type( VLC_ML_PARENT_UNKNOWN ) {}
     MLItemId( int64_t i, vlc_ml_parent_type t ) : id( i ), type( t ) {}
     bool operator==( const MLItemId& other ) const
     {
@@ -71,6 +73,12 @@ public:
     }
 };
 
+
+inline size_t qHash(const MLItemId& item, size_t seed = 0)
+{
+    return qHashMulti(seed, item.id, item.type);
+}
+
 class MLItem
 {
 public:


=====================================
modules/gui/qt/meson.build
=====================================
@@ -78,6 +78,7 @@ moc_headers = files(
     'maininterface/videosurface.hpp',
     'maininterface/video_window_handler.hpp',
     'medialibrary/medialib.hpp',
+    'medialibrary/mlmediastore.hpp',
     'medialibrary/mlalbum.hpp',
     'medialibrary/mlalbummodel.hpp',
     'medialibrary/mlalbumtrack.hpp',
@@ -90,6 +91,7 @@ moc_headers = files(
     'medialibrary/mlgenremodel.hpp',
     'medialibrary/mlthreadpool.hpp',
     'medialibrary/mlqmltypes.hpp',
+    'medialibrary/mlmedia.hpp',
     'medialibrary/mlrecentsmodel.hpp',
     'medialibrary/mlrecentsvideomodel.hpp',
     'medialibrary/mlurlmodel.hpp',
@@ -300,6 +302,8 @@ some_sources = files(
     'maininterface/windoweffects_module.hpp',
     'medialibrary/medialib.cpp',
     'medialibrary/medialib.hpp',
+    'medialibrary/mlmediastore.cpp',
+    'medialibrary/mlmediastore.hpp',
     'medialibrary/mlalbum.cpp',
     'medialibrary/mlalbum.hpp',
     'medialibrary/mlalbummodel.cpp',
@@ -332,6 +336,7 @@ some_sources = files(
     'medialibrary/mlthreadpool.cpp',
     'medialibrary/mlthreadpool.hpp',
     'medialibrary/mlqmltypes.hpp',
+    'medialibrary/mlmedia.hpp',
     'medialibrary/mlqueryparams.cpp',
     'medialibrary/mlqueryparams.hpp',
     'medialibrary/mlrecentsmodel.cpp',


=====================================
modules/gui/qt/network/networkmediamodel.cpp
=====================================
@@ -20,6 +20,8 @@
 #include "mediatreelistener.hpp"
 
 #include "maininterface/mainctx.hpp"
+#include "medialibrary/mlmedia.hpp"
+#include "medialibrary/mlmediastore.hpp"
 
 #include "util/locallistbasemodel.hpp"
 
@@ -41,6 +43,7 @@ static const char* const ML_FOLDER_ADD_QUEUE = 
"ML_FOLDER_ADD_QUEUE";
 struct NetworkMediaItem
 {
     QString name;
+    QString uri;
     QUrl mainMrl;
     QString protocol;
     bool indexed;
@@ -50,6 +53,7 @@ struct NetworkMediaItem
     QString artwork;
     qint64 fileSize;
     QDateTime fileModified;
+    MLMedia media;
 };
 
 using NetworkMediaItemPtr = std::shared_ptr<NetworkMediaItem>;
@@ -60,6 +64,20 @@ inline bool isADir(const NetworkMediaItemPtr& x)
     return (x->type == NetworkMediaModel::ItemType::TYPE_DIRECTORY);
 }
 
+int compareMediaDuration(const NetworkMediaItemPtr &l
+                         , const NetworkMediaItemPtr &r)
+{
+    const bool lHasMedia = l->media.valid();
+    const bool rHasMedia = r->media.valid();
+    if (lHasMedia != rHasMedia) return lHasMedia ? 1 : - 1;
+    if (!lHasMedia && !rHasMedia) return 0;
+
+    const auto &lmedia = l->media.duration();
+    const auto &rmedia = r->media.duration();
+    if (lmedia == rmedia) return 0;
+    return lmedia > rmedia ? 1 : -1;
+}
+
 }
 
 // ListCache specialisation
@@ -93,22 +111,39 @@ public:
         return nullptr;
     }
 
-    void setIntexedState(const NetworkMediaItemPtr& item, bool indexed)
+    void mediaUpdated(const QString &mrl, const MLMedia &media)
     {
-        if (item->indexed == indexed)
+        if (!m_items.contains(mrl))
             return;
-        auto it = std::find(m_items.begin(), m_items.end(), item);
-        if (it != m_items.end())
-        {
-            //contruct by copy, don't mutate items in m_items,
-            //we want the change to be notified
-            auto newItem = std::make_shared<NetworkMediaItem>(*item);
-            newItem->indexed = indexed;
-
-            *it = newItem;
-            ++m_revision;
-            invalidateCache();
-        }
+
+        //contruct by copy, don't mutate items in m_items,
+        //we want the change to be notified
+        auto newItem = std::make_shared<NetworkMediaItem>(*m_items[mrl]);
+        newItem->media = media;
+        m_items[mrl] = newItem;
+
+        ++m_revision;
+        invalidateCache();
+    }
+
+    void setIntexedState(const QString &uri, bool indexed)
+    {
+        auto itr = m_items.find(uri);
+        if (itr == m_items.end())
+            return;
+
+        auto &itemPtr = *itr;
+        if (itemPtr->indexed == indexed)
+            return;
+
+        //contruct by copy, don't mutate items in m_items,
+        //we want the change to be notified
+        auto newItem = std::make_shared<NetworkMediaItem>(*itemPtr);
+        newItem->indexed = indexed;
+
+        itemPtr = newItem;
+        ++m_revision;
+        invalidateCache();
     }
 
     void removeItem(const SharedInputItem& node, const 
std::vector<SharedInputItem>& itemsList)
@@ -129,8 +164,15 @@ public:
             if ( it == m_items.end() )
                 continue;
 
+            if (m_MLMedias && (*it)->media.valid())
+                m_MLMedias->remove((*it)->media.getId());
+
             m_items.erase( it );
         }
+
+        // notify updates
+        ++m_revision;
+        invalidateCache();
     }
 
     void refreshMediaList(
@@ -141,7 +183,11 @@ public:
         Q_Q(NetworkMediaModel);
 
         if (clear)
+        {
             m_items.clear();
+            if (m_MLMedias)
+                m_MLMedias->clear();
+        }
 
         std::vector<NetworkMediaItem> items;
         for (const auto& inputItem: children)
@@ -151,13 +197,13 @@ public:
             item->protocol = "";
             item->indexed = false;
             item->type = 
static_cast<NetworkMediaModel::ItemType>(inputItem->i_type);
+            item->uri = QString(inputItem->psz_uri);
             item->mainMrl = (item->type == NetworkMediaModel::TYPE_DIRECTORY 
|| item->type == NetworkMediaModel::TYPE_NODE) ?
                                
QUrl::fromEncoded(QByteArray(inputItem->psz_uri).append('/')) :
                                QUrl::fromEncoded(inputItem->psz_uri);
 
             item->canBeIndexed = canBeIndexed( item->mainMrl , item->type );
 
-
             input_item_t* intputItemRaw = inputItem.get();
             char* str = input_item_GetArtworkURL(intputItemRaw);
             if (str)
@@ -195,21 +241,31 @@ public:
                     bool succeed;
                     bool isIndexed;
                 };
+
+                const QString uri = item->uri;
                 m_mediaLib->runOnMLThread<Ctx>(q,
                     //ML thread
-                    [item](vlc_medialibrary_t* ml, Ctx& ctx){
-                        auto ret = vlc_ml_is_indexed( ml, 
qtu(item->mainMrl.toString( QUrl::FullyEncoded )), &ctx.isIndexed );
+                    [uri](vlc_medialibrary_t* ml, Ctx& ctx){
+                        // Medialibrary requires folders uri to be terminated 
with '/'
+                        const QString mlURI = uri + "/";
+
+                        auto ret = vlc_ml_is_indexed( ml, qtu( mlURI ), 
&ctx.isIndexed );
                         ctx.succeed = (ret == VLC_SUCCESS);
                     },
                     //UI thread
-                    [this, item](quint64, Ctx& ctx){
+                    [this, uri](quint64, Ctx& ctx){
                         if (!ctx.succeed)
                             return;
-                        setIntexedState(item, ctx.isIndexed);
+
+                        setIntexedState(uri, ctx.isIndexed);
                     });
             }
 
-            m_items.push_back(item);
+            m_items[item->uri] = item;
+            if (m_MLMedias && (item->type == NetworkMediaModel::TYPE_FILE))
+            {
+                m_MLMedias->insert(item->uri);
+            }
         }
 
         ++m_revision;
@@ -266,6 +322,21 @@ public:
                     return a->fileModified > b->fileModified;
                 };
         }
+        else if (m_sortCriteria == "duration")
+        {
+            if (m_sortOrder == Qt::SortOrder::DescendingOrder)
+                return [](const NetworkMediaItemPtr& a, const 
NetworkMediaItemPtr& b) -> bool
+                {
+                    if(isADir(a) != isADir(b)) return isADir(a);
+                    return compareMediaDuration(a, b) > 0;
+                };
+            else
+                return [](const NetworkMediaItemPtr& a, const 
NetworkMediaItemPtr& b) -> bool
+                {
+                    if(isADir(a) != isADir(b)) return isADir(a);
+                    return compareMediaDuration(a, b) < 0;
+                };
+        }
         else // m_sortCriteria == "name"
         {
             if (m_sortOrder == Qt::SortOrder::DescendingOrder)
@@ -380,15 +451,14 @@ public:
 protected:
     std::vector<NetworkMediaItemPtr> getModelData(const QString& pattern) 
const override
     {
-        if (pattern.isEmpty())
-            return m_items;
         std::vector<NetworkMediaItemPtr> items;
-        std::copy_if(
-            m_items.cbegin(), m_items.cend(),
-            std::back_inserter(items),
-            [&pattern](const NetworkMediaItemPtr& item){
-                return item->name.contains(pattern, Qt::CaseInsensitive);
-            });
+        items.reserve(m_items.size() / 2);
+        for (const auto &item : m_items)
+        {
+            if (item->name.contains(pattern, Qt::CaseInsensitive))
+                items.push_back(item);
+        }
+
         return items;
     }
 
@@ -397,7 +467,8 @@ public:
     bool m_hasTree = false;
     QSemaphore m_preparseSem;
     std::unique_ptr<MediaTreeListener> m_listener;
-    std::vector<NetworkMediaItemPtr> m_items;
+    QHash<QString, NetworkMediaItemPtr> m_items;
+    std::unique_ptr<MLMediaStore> m_MLMedias;
 };
 
 // NetworkMediaModel::ListenerCb implementation
@@ -459,11 +530,45 @@ QVariant NetworkMediaModel::data( const QModelIndex& 
index, int role ) const
         case NETWORK_TREE:
             return QVariant::fromValue( item->tree );
         case NETWORK_ARTWORK:
-            return item->artwork;
+        {
+            if (!item->artwork.isEmpty())
+                return item->artwork;
+
+            /// XXX: request medialibrary for thumbnail if not available??
+            const MLMedia &media = item->media;
+            if (media.valid())
+            {
+                const QString bannerCover = media.bannerCover();
+                return !bannerCover.isEmpty() ? bannerCover : 
media.smallCover();
+            }
+
+            return {};
+        }
         case NETWORK_FILE_SIZE:
             return item->fileSize;
         case NETWORK_FILE_MODIFIED:
             return item->fileModified;
+        case NETWORK_MEDIA:
+            return item->media.valid()
+                    ? QVariant::fromValue(item->media)
+                    : QVariant {};
+        case NETWORK_MEDIA_PROGRESS:
+        {
+            if (item->media.valid())
+                return item->media.progress();
+
+            return {};
+        }
+        case NETWORK_MEDIA_DURATION:
+        {
+            if (item->media.valid())
+            {
+                const VLCTick duration = item->media.duration();
+                return duration <= 0 ? QVariant {} : 
QVariant::fromValue(duration);
+            }
+
+            return {};
+        }
         default:
             return {};
     }
@@ -481,7 +586,10 @@ QHash<int, QByteArray> NetworkMediaModel::roleNames() const
         { NETWORK_TREE, "tree" },
         { NETWORK_ARTWORK, "artwork" },
         { NETWORK_FILE_SIZE, "fileSizeRaw64" },
-        { NETWORK_FILE_MODIFIED, "fileModified" }
+        { NETWORK_FILE_MODIFIED, "fileModified" },
+        { NETWORK_MEDIA, "media" },
+        { NETWORK_MEDIA_PROGRESS, "progress" },
+        { NETWORK_MEDIA_DURATION, "duration" }
     };
 }
 
@@ -511,27 +619,36 @@ bool NetworkMediaModel::setData( const QModelIndex& idx, 
const QVariant& value,
     if ( item->indexed == enabled )
         return  true;
 
-    QUrl mainMrl = item->mainMrl;
-    struct Ctx {
+    struct Ctx
+    {
         bool succeed;
     };
+
+    const QString uri = item->uri;
+
     d->m_mediaLib->runOnMLThread<Ctx>(this,
     //ML thread
-    [enabled, mainMrl]
+    [enabled, uri]
     (vlc_medialibrary_t* ml, Ctx& ctx){
         int res;
+
+        // Medialibrary requires folders uri to be terminated with '/'
+        const QString mlURI = uri + "/";
+
         if ( enabled )
-            res = vlc_ml_add_folder( ml, qtu( mainMrl.toString( 
QUrl::FullyEncoded ) ) );
+            res = vlc_ml_add_folder( ml, qtu( mlURI ) );
         else
-            res = vlc_ml_remove_folder( ml, qtu( mainMrl.toString( 
QUrl::FullyEncoded ) ) );
+            res = vlc_ml_remove_folder( ml, qtu( mlURI ) );
+
         ctx.succeed = res == VLC_SUCCESS;
     },
     //UI thread
-    [this, item, enabled](qint64, Ctx& ctx){
+    [this, uri, enabled](qint64, Ctx& ctx){
         Q_D(NetworkMediaModel);
         if (!ctx.succeed)
             return;
-        d->setIntexedState(item, enabled);
+
+        d->setIntexedState(uri, enabled);
     },
     ML_FOLDER_ADD_QUEUE);
 
@@ -581,6 +698,17 @@ void NetworkMediaModel::setCtx(MainCtx* ctx)
     m_ctx = ctx;
     d->m_mediaLib = ctx->getMediaLibrary();
 
+    d->m_MLMedias.reset();
+    if (d->m_mediaLib)
+    {
+        d->m_MLMedias.reset(new MLMediaStore(d->m_mediaLib));
+        connect(d->m_MLMedias.get(), &MLMediaStore::updated, this, 
[this](const QString &mrl, const MLMedia &media)
+        {
+            Q_D(NetworkMediaModel);
+            d->mediaUpdated(mrl, media);
+        });
+    }
+
     d->initializeModel();
     emit ctxChanged();
 }


=====================================
modules/gui/qt/network/networkmediamodel.hpp
=====================================
@@ -117,6 +117,9 @@ public:
         NETWORK_ARTWORK,
         NETWORK_FILE_SIZE,
         NETWORK_FILE_MODIFIED,
+        NETWORK_MEDIA,
+        NETWORK_MEDIA_PROGRESS,
+        NETWORK_MEDIA_DURATION,
     };
 
     enum ItemType{


=====================================
modules/gui/qt/network/qml/BrowseTreeDisplay.qml
=====================================
@@ -267,6 +267,18 @@ MainInterface.MainViewLoader {
 
                     showContextButton: true
                 }
+            }, {
+                size: 1,
+
+                model: {
+                    criteria: "duration",
+
+                    text: qsTr("Duration"),
+
+                    showContextButton: true,
+                    headerDelegate: tableColumns.timeHeaderDelegate,
+                    colDelegate: tableColumns.timeColDelegate
+                }
             }]
 
             dragItem: networkDragItem
@@ -307,6 +319,10 @@ MainInterface.MainViewLoader {
             onRightClick: (_,_,globalMousePos) => {
                 contextMenu.popup(selectionModel.selectedIndexes, 
globalMousePos)
             }
+
+            Widgets.TableColumns {
+                id: tableColumns
+            }
         }
     }
 


=====================================
modules/gui/qt/network/qml/NetworkGridItem.qml
=====================================
@@ -23,6 +23,7 @@ import Qt5Compat.GraphicalEffects
 import org.videolan.vlc 0.1
 
 import "qrc:///widgets/" as Widgets
+import "qrc:///util/Helpers.js" as Helpers
 import "qrc:///style/"
 
 Widgets.GridItem {
@@ -78,12 +79,34 @@ Widgets.GridItem {
 
     title: model.name || qsTr("Unknown share")
     subtitle: {
-       if (!model.mrl) {
-         return ""
-       } else if ((model.type === NetworkMediaModel.TYPE_NODE || model.type 
=== NetworkMediaModel.TYPE_DIRECTORY) && model.mrl.toString() === "vlc://nop") {
-         return ""
-      } else {
-         return model.mrl
-      }
+        if (!model.mrl) {
+            return ""
+        } else if ((model.type === NetworkMediaModel.TYPE_NODE || model.type 
=== NetworkMediaModel.TYPE_DIRECTORY) && model.mrl.toString() === "vlc://nop") {
+            return ""
+        } else {
+            return model.mrl
+        }
+    }
+
+    pictureOverlay: Item {
+        width: root.pictureWidth
+        height: root.pictureHeight
+
+        Widgets.VideoProgressBar {
+            id: progressBar
+
+            anchors {
+                bottom: parent.bottom
+                left: parent.left
+                right: parent.right
+            }
+
+            visible: (model.progress ?? - 1) > 0
+
+            radius: root.pictureRadius
+            value:  visible
+                    ? Helpers.clamp(model.progress, 0, 1)
+                    : 0
+        }
     }
 }



View it on GitLab: 
https://code.videolan.org/videolan/vlc/-/compare/8186585a1e1db8ce7b14c61e0123618378042e6e...31b383f5fe84e8ace741b4f28aafa467cc5b0b92

-- 
View it on GitLab: 
https://code.videolan.org/videolan/vlc/-/compare/8186585a1e1db8ce7b14c61e0123618378042e6e...31b383f5fe84e8ace741b4f28aafa467cc5b0b92
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance
_______________________________________________
vlc-commits mailing list
vlc-commits@videolan.org
https://mailman.videolan.org/listinfo/vlc-commits

Reply via email to