Search code examples
c++qtqmlqtmultimedia

How do I populate a playlist qml type using a c++ model?


I'm building a music player and need to autofill the playlist using this c++ model:

static QStringList pathList;   
int main(int argc, char *argv[]){
// ...
// ...
QDirIterator it("E:/", QStringList() << "*.mp3", QDir::Files, QDirIterator::Subdirectories);
        while (it.hasNext()){
            pathList.append(it.next());
        }
    QQmlContext *ctxt1 = engine.rootContext();
    ctxt1->setContextProperty("pathModel", QVariant::fromValue(pathList)); //used model pathmodel
// ...
// ...
}

and my playlist code on the qml side:

Rectangle{
    width: page.width
    height: page.height
    Audio {
           id: player;
           playlist: Playlist {
               id: playlist
               PlaylistItem { source: "song1.ogg"; } //I want this process to be automated instead of doing it manually
           }
       }
       ListView {
           model: playlist;
           delegate: Text {
               font.pixelSize: 16;
               text: source;
           }
       }
       MouseArea {
           anchors.fill: parent;
           onPressed: {
               if (player.playbackState != Audio.PlayingState) {
                   player.play();
               } else {
                   player.pause();
               }
           }
       }
}

I tried using App list view type but I only managed to play/pause a song on click and couldn't implement functions like autoplay next when a song finishes or stop the current one when another song is selected. The qt documents are not clear on what needs to be done either nor available tutorials or demos


Solution

  • There are several solutions depending on what you want to implement:

    • If you are going to load the list only at the beginning then you should only use a QList <QUrl>:
    // ...
    QList<QUrl> sources;
    QString folder = "E:/"
    QDirIterator it(folder, QStringList() << "*.mp3", QDir::Files, QDirIterator::Subdirectories);
    while (it.hasNext()){
        sources << QUrl::fromLocalFile(it.next());
    }
    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("sources", QVariant::fromValue(sources));
    // ...
    
    // ...
    Audio {
        id: player;
        playlist: Playlist {
            id: playlist
            Component.onCompleted: playlist.addItems(sources)
        }
    }
    // ...
    
    • If you want to add and remove items during the execution of the program then you can use a model:
    class SourceModel: public QStandardItemModel{
        Q_OBJECT
    public:
        enum SourceRoles{
            SourceRole = Qt::UserRole + 1000
        };
        SourceModel(QObject *parent=nullptr):
            QStandardItemModel(parent)
        {
            QHash<int, QByteArray> roles;
            roles[SourceRole] = "source";
            setItemRoleNames(roles);
            connect(this, &QAbstractItemModel::rowsInserted, this, &SourceModel::onRowsInserted);
            connect(this, &QAbstractItemModel::rowsRemoved, this, &SourceModel::onRowsRemoved);
            connect(this, &QAbstractItemModel::modelReset, this, &SourceModel::reset);
        }
        void addSource(const QUrl & url){
            QStandardItem *item = new QStandardItem();
            item->setData(url, SourceRole);
            appendRow(item);
        }
    Q_SIGNALS:
        void sourcesInserted(int first, QList<QUrl> sources);
        void sourcesRemoved(int first, int last);
        void reset();
    private:
        void onRowsInserted(const QModelIndex &parent, int first, int last){
            QList<QUrl> sources;
            for (int i=first; i <= last; ++i) {
                QModelIndex ix = this->index(i, 0, parent);
                QVariant v = data(ix, SourceRole);
                sources << v.toUrl();
            }
            Q_EMIT sourcesInserted(first, sources);
        }
        void onRowsRemoved(const QModelIndex & /*parent*/, int first, int last){
            Q_EMIT sourcesRemoved(first, last);
        }
    };
    
    // ...
    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("source_model", &source_model);
    // ...
    QString folder = "E:/"
    QDirIterator it(folder, QStringList() << "*.mp3", QDir::Files, QDirIterator::Subdirectories);
    while (it.hasNext()){
        source_model.addSource(QUrl::fromLocalFile(it.next()));
    }
    // ...
    
    // ...
    Connections{
        target: source_model
        onSourcesInserted: playlist.insertItems(first, sources)
        onSourcesRemoved: playlist.removeItems(first, last);
        onReset: playlist.clear()
    }
    Audio {
        id: player;
        playlist: Playlist {
            id: playlist
        }
    }
    // ...
    
    • Another option is to use a Q_PROPERTY:
    class SourceManager: public QObject{
        Q_OBJECT
        Q_PROPERTY(QList<QUrl> sources READ sources  WRITE setSources  NOTIFY sourcesChanged)
    public:
        using QObject::QObject;
        QList<QUrl> sources() const{
            return m_sources;
        }
        void setSources(QList<QUrl> sources){
            if (m_sources == sources)
                return;
            m_sources = sources;
            emit sourcesChanged();
        }
    Q_SIGNALS:
        void sourcesChanged();
    private:
        QList<QUrl> m_sources;
    };
    // ...
    SourceManager source_manager;
    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("source_manager", &source_manager);
    // ...
    QList<QUrl> sources;
    QString folder = "E:/"
    QDirIterator it(folder, QStringList() << "*.mp3", QDir::Files, QDirIterator::Subdirectories);
    while (it.hasNext()){
        sources << QUrl::fromLocalFile(it.next());
    }
    source_manager.setSources(sources);
    // ...
    
    // ...
    Connections{
        target: source_manager
        onSourcesChanged: {
            playlist.clear();
            playlist.addItems(source_manager.sources)
        }
    }
    Audio {
        id: player;
        playlist: Playlist {
            id: playlist
        }
    }
    // ...