Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Pass a Playlist from C++ to QML (error Unable to assign [undefined] to ::QDeclarativeAudio*)



  • Goodmorning to all,
    Qt version 5.12, in a cross compiled environment for raspberrypi using b2qt.
    I'm trying to make a music Player where I search for songs in system folders, save them in playlists and make them available as model for view. Then in QML I would select the playlist and load it in MediaPlayer.
    Here the code:

    PlaylistModel.h

    #ifndef PLAYLISTMODEL_H
    #define PLAYLISTMODEL_H
    
    #include <QAbstractListModel>
    #include <QHash>
    #include <QDir>
    #include <vector>
    #include <memory>
    #include <QtMultimedia/QMediaPlaylist>
    
    #include "myplaylist.h"
    
    #include "playListModel_global.h"
    
    class PLAYLISTMODEL_EXPORT PlayListModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
    
        enum PlaylistRoles {
            IndexRole = Qt::UserRole + 1,
            IconRole,
            PlaybackRole,
            NextRole,
            PreviousRole,
            SizeRole,
            NameRole
        };
    
        PlayListModel(QDir dirPath, QObject* parent = nullptr);
        PlayListModel(QString dirPath, QObject* parent = nullptr);
    
        QStringList playListNames();
        Q_INVOKABLE QMediaPlaylist& getPlaylistFromIndex(int index);
    
        // QAbstractItemModel interface
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
        QVariant data(const QModelIndex &index = QModelIndex(), int role = Qt::DisplayRole) const override;
        bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::DisplayRole) override;
        QHash<int, QByteArray> roleNames() const override;
    
    private:
        bool isIndexValid(const QModelIndex& index) const;
    
        std::vector<std::unique_ptr<myPlaylist>> mPlayListsList;
    };
    
    #endif // PLAYLISTMODEL_H
    

    PlaylistModel.c

    #include "playlistmodel.h"
    #include "playlistmodel.h"
    
    #include <QDirIterator>
    #include <QMediaContent>
    #include <QImage>
    
    PlayListModel::PlayListModel(QString dirPath, QObject* parent) :
        PlayListModel(QDir(dirPath), parent)
    {}
    
    QStringList PlayListModel::playListNames()
    {
        QStringList lista;
        for(int i=0; i<rowCount(); i++)
        {
            myPlaylist& newPlaylist = *mPlayListsList.at(static_cast<unsigned int>(i));
            lista.append(newPlaylist.getPlaylistName());
        }
        return lista;
    }
    
    PlayListModel::PlayListModel(QDir dirPath, QObject* parent) :
        QAbstractListModel(parent)
    {
        QDirIterator it(dirPath.path(), QDirIterator::Subdirectories);
        while (it.hasNext()) {
            if (QFileInfo(it.filePath()).isDir() & (it.fileName()!=".") & (it.fileName()!=".."))
            {
                bool songFound=false;
                QDir dir(it.filePath());
                qDebug()<<"Analizying folder: "<<it.filePath();
                std::unique_ptr<myPlaylist> newPlaylist(new myPlaylist());
                newPlaylist->setPlaylistName(it.fileName());
    
                QDirIterator song(dir.path(), QDir::Files);
                while(song.hasNext())
                {
                    qDebug()<<"Analizying file: "<<song.filePath();
                    if((QFileInfo(song.filePath()).suffix() == "mp3") |
                        (QFileInfo(song.filePath()).suffix() == "m4a"))
                    {
                        QUrl url = QUrl::fromLocalFile(song.path());
                        newPlaylist->addMedia(QMediaContent(url));
                        songFound=true;
                    }
                    song.next();
                }
    
                if(songFound) {
                    int rowIndex = rowCount();
                    beginInsertRows(QModelIndex(), rowIndex, rowIndex);
                    mPlayListsList.push_back(move(newPlaylist));
                    endInsertRows();
                }
            }
            it.next();
        }
    }
    
    QMediaPlaylist& PlayListModel::getPlaylistFromIndex(int index)
    {
        return *mPlayListsList.at(static_cast<unsigned int>(index));
    }
    
    int PlayListModel::rowCount(const QModelIndex &parent) const
    {
        (void)parent;
        return static_cast<int>(mPlayListsList.size());
    }
    
    QVariant PlayListModel::data(const QModelIndex &index, int role) const
    {
        if (!isIndexValid(index)) {
            return QVariant();
        }
    
        const myPlaylist& album = *mPlayListsList.at(static_cast<unsigned int>(index.row()));
    
        switch (role) {     
            case PlaylistRoles::IconRole:
                return album.getPlaylistImage();
    
            case PlaylistRoles::IndexRole:
                return album.currentIndex();
    
            case PlaylistRoles::PlaybackRole:
                return album.playbackMode();
    
            case PlaylistRoles::SizeRole:
                return album.mediaCount();
    
            case PlaylistRoles::NameRole:
            case Qt::DisplayRole:
                return album.getPlaylistName(); //album.objectName();
    
            case PlaylistRoles::NextRole:
            case PlaylistRoles::PreviousRole:
                return QVariant();
    
            default:
                return QVariant();
        }
    }
    
    bool PlayListModel::setData(const QModelIndex &index, const QVariant &value, int role)
    {
        if (!isIndexValid(index)
                || role != PlaylistRoles::PlaybackRole
                || role != PlaylistRoles::NextRole
                || role != PlaylistRoles::PreviousRole
                || role != PlaylistRoles::NameRole
                || role != PlaylistRoles::IconRole
                || role != PlaylistRoles::IndexRole) {
            return false;
        }
    
        myPlaylist& album = *mPlayListsList.at(static_cast<unsigned int>(index.row()));
    
        switch(role)
        {
            case PlaylistRoles::NameRole:
                album.setPlaylistName(value.toString());
                break;
    
            case PlaylistRoles::IconRole:
                album.setPlaylistImage(value.value<QImage>());
                break;
    
            case PlaylistRoles::PlaybackRole:
                //CurrentItemOnce, CurrentItemInLoop, Sequential, Loop, Random
                album.setPlaybackMode(static_cast<QMediaPlaylist::PlaybackMode>(value.toInt()));
                break;
    
            case PlaylistRoles::NextRole:
                album.next();
                break;
    
            case PlaylistRoles::PreviousRole:
                album.previous();
                break;
    
            case PlaylistRoles::IndexRole:
                album.setCurrentIndex(value.toInt());
                break;
    
            default:
                break;
        }
    
        emit dataChanged(index, index);
        return true;
    }
    
    QHash<int, QByteArray> PlayListModel::roleNames() const
    {
        QHash<int, QByteArray> roles;
        roles[PlaylistRoles::IconRole] = "icon";
        roles[PlaylistRoles::PlaybackRole] = "playback";
        roles[PlaylistRoles::SizeRole] = "size";
        roles[PlaylistRoles::IndexRole] = "currentIndex";
        roles[PlaylistRoles::NameRole] = "name";
        return roles;
    }
    
    bool PlayListModel::isIndexValid(const QModelIndex &index) const
    {
        if (index.row() < 0
                || index.row() >= rowCount()
                || !index.isValid()) {
            return false;
        }
        return true;
    }
    
    

    MusicPage.qml

    import QtQuick 2.6
    import QtQuick.Controls 2.0
    import QtMultimedia 5.9
    import "."
    
    import "../menu"
    
    PageTheme {
        property string playlistName
        property int playlistIndex
    
        toolbarTitle: playlistName + " " + playlistIndex
    
        MediaPlayer
        {
            id: playMusic
            autoLoad: true
            autoPlay: true
            playlist: {playlistModel.getPlaylistFromIndex(playlistIndex)}
        }
    
        MusicController
        {
            songPlayer: {playlistModel.getPlaylistFromIndex(playlistIndex)}
        }
    }
    

    For information the myPlaylist class is:

    #ifndef MYPLAYLIST_H
    #define MYPLAYLIST_H
    
    #include <QtMultimedia/QMediaPlaylist>
    #include <QImage>
    
    class myPlaylist : public QMediaPlaylist
    {
        Q_OBJECT
    public:
    
        explicit myPlaylist(QObject* parent = nullptr);
    
        void setPlaylistName(QString name);
        QString getPlaylistName() const;
    
        void setPlaylistImage(QImage image);
        QImage getPlaylistImage() const;
    
    private:
        QString mName;
        QImage mImage;
    };
    
    #endif // MYPLAYLIST_H
    

    When I select the playlist in a pathView, the MusicPage QML page opens and I get the error Unable to assign [undefined] to ::QDeclarativeAudio*) for the line: playlist: {playlistModel.getPlaylistFromIndex(playlistIndex)}

    What I'm doing wrong? Any suggestions for the whole structure are welcome.

    Thank you in advanced.

    Regards,
    Davidino


  • Moderators

    @davidino said in Pass a Playlist from C++ to QML (error Unable to assign [undefined] to ::QDeclarativeAudio*):

    Q_INVOKABLE QMediaPlaylist& getPlaylistFromIndex(int index);

    You should return a pointer. And in case of unique_ptr, use .get().



  • Heelo @sierdzio ,

    thank you for your reply. I've changed the code as :

    QMediaPlaylist* PlayListModel::getPlaylistFromIndex(int index)
    {
        return mPlayListsList.at(static_cast<unsigned int>(index)).get();
    }
    

    But I get the error Unknown method return type: QMediaPlaylist*
    Any advices?

    For information, I pass the Playlist model object as follow:

        PlayListModel playlistModel("/home/root");
    
        QQmlApplicationEngine engine;
        QQmlContext* context = engine.rootContext();
        context->setContextProperty("playlistModel", &playlistModel);
    

    Thank you.

    Regards.
    Davidino


  • Moderators

    @davidino said in Pass a Playlist from C++ to QML (error Unable to assign [undefined] to ::QDeclarativeAudio*):

    But I get the error Unknown method return type: QMediaPlaylist*

    This should work: Q_DECLARE_METATYPE(QMediaPlaylist*).



  • @sierdzio said in Pass a Playlist from C++ to QML (error Unable to assign [undefined] to ::QDeclarativeAudio*):

    Q_DECLARE_METATYPE(QMediaPlaylist*)

    Hello @sierdzio ,
    thank you for your message. I tried to add Q_DECLARE_METATYPE as per you indication in playlistModel.h:

    Q_DECLARE_METATYPE(QMediaPlaylist*)
    
    class PLAYLISTMODEL_EXPORT PlayListModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
    

    But I still get the error for the line playlistModel.getPlaylistFromIndex(playlistIndex)
    Thank you for your assistance.

    Regards,
    Davidino



  • @davidino said in Pass a Playlist from C++ to QML (error Unable to assign [undefined] to ::QDeclarativeAudio*):

    But I still get the error for the line playlistModel.getPlaylistFromIndex(playlistIndex)

    The problem is that you property is set once/statically and there is no update, you should do something like this:

    PageTheme {
        property string playlistName
        property int playlistIndex
    
        toolbarTitle: playlistName + " " + playlistIndex
        onPlaylistIndexChanged {
            var item = playlistModel.getPlaylistFromIndex(playlistIndex)
            //note: using double ! to check item is not null or undefined (little JavaScript trick)
            if(!!item) {
                playMusic.playlist = item;
                musicCtrl.songPlayer = item;
            }
        }
    
        MediaPlayer {
            id: playMusic
            autoLoad: true
            autoPlay: true
        }
    
        MusicController {
            id: musicCtrl
        }
    }
    


  • Hello @KroMignon ,
    thank you for your answer. Using this method for retrieving the playlist:
    QMediaPlaylist& PlayListModel::getPlaylistFromIndex(int index)
    {
    return *mPlayListsList.at(static_cast<unsigned int>(index));
    }
    I don't get any error as before, but the check (if item is null) always return null.
    As you can see below I modified the code so that the code show the number of songs in the selected playlist and there are songs! I don't get what it could be..

    import "QUItMeterComponent"
    
    import "../menu"
    
    PageTheme {
        property string playlistName
        property int playlistIndex
        property int playlistSize
    
        id: musicPage
        toolbarTitle: playlistName + ": " + playlistSize + " songs"
    
        function changePlaylist() {
            var item = playlistModel.getPlaylistFromIndex(playlistIndex)
            //note: using double ! to check item is not null or undefined (little JavaScript trick)
            if(!!item) {
                playMusic.playlist = item;
                console.log("playlist changed!!");
            }
            else
                console.log("playlist no good!!");
        }
    
        onPlaylistIndexChanged: changePlaylist()
    
        MediaPlayer {
            id: playMusic
            autoLoad: true
            autoPlay: true
            //Component.onCompleted: changePlaylist()
        }
    

    For information, if I tried the function getPlaylistFromIndex returning the pointer as written above, I still get the error Unknown method return type: QMediaPlaylist*.

    Thank you for your assistance.

    Regards,
    Davidino



  • @davidino said in Pass a Playlist from C++ to QML (error Unable to assign [undefined] to ::QDeclarativeAudio*):

    QMediaPlaylist& PlayListModel::getPlaylistFromIndex(int index)

    As @sierdzio already written, to interface with QML you better return a pointer and not a reference. Please respect QML/C++ best practice, this will avoid you many problems!

    have you taken time to read documentation? ==> https://doc.qt.io/qt-5/qtqml-cppintegration-overview.html



  • Hello @KroMignon ,
    thank you for your posts I read several links inside it, however I have to say that since there are many possibilities, it gets complicated as well. I search on the net as well. Here some considerations:
    First, I tried, instead of returning QMediaPlayer*, to return QObject* and QVariant, as explained, but in both cases I got runtime errors.

    It sounds strange that I have to register it with Q_DECLARE_METATYPE(QMediaPlaylist*) considering that it's a QT Object. Could it be a bug as happened here with QAbstractListItem?
    https://bugreports.qt.io/browse/QTBUG-68411

    Despite of all, I'm still getting error Unknown method return type: QMediaPlaylist*..
    I'm thinking to alternative approaches like passing QStringlist of Qurl, which is supported by QML, instead of QMediaplayist.

    Best Regards,
    Davide Brunelli


Log in to reply