Accessing the media library

The medialibrary is an optional component of the Qt interface, if you’re developing a feature that depends on medialibrary component you should ensure that it won’t break the application if you start the application with --no-media-library or if you compile VLC without medialibrary support.

From the QML side you can check that MainCtx.mediaLibraryAvailable is true

From C++ side you can check that MainCtx::hasMediaLibrary() returns true.

Modeling medialibray views

If you need to model a new view from the medialibrary, the usual way is to

Create a class that inherit from MLBaseModel, this class is a QAbstractListModel for the interaction on the QML side.

Loading the data done by implementing a subclass BaseLoader which performs the raw queries on the medialibrary (counting elements, loading a range of elements, loading a single element). These functions are called from a background thread you should not access the model or the GUI from there.

From the qml side, the different properties of the items are access using a role name, the data associated to a role is access thought the itemRoleData, and a mapping between roles ID and their name (string) through the roleNames function

A model can react to events from the medialibrary by overriding the onVlcMlEvent function, this function is called from the Qt thread, so it is safe to acts on the model/UI from there

a simple model might be

class MLStuff : public MLItem {
   //store item data here
};

class MLStuffModel : public MLBaseModel
{
    Q_OBJECT
public:
    enum Roles {
        STUFF_ID = Qt::UserRole + 1,
        STUFF_ROLE_FOO,
        STUFF_ROLE_BAR
    };
    Q_ENUM(Roles);

    //map Roles to their string representation
    QHash<int, QByteArray> roleNames() const override;

protected:
    //get data for a given Role on the object item
    QVariant itemRoleData(MLItem *item, int role) const override;

    //create an Loader instance to load the data
    std::unique_ptr<MLBaseModel::BaseLoader> createLoader() const override;

private:
    // allows to sort using a given role
    vlc_ml_sorting_criteria_t roleToCriteria(int role) const override;

    // react to events from the medialibrary
    virtual void onVlcMlEvent( const MLEvent &event ) override;

    struct Loader : public BaseLoader
    {
        Loader(const MLStuffModel &model) : BaseLoader(model) {}
        // count the number of items
        size_t count(vlc_medialibrary_t* ml) const override;
        // load a range of items
        std::vector<std::unique_ptr<MLItem>> load(vlc_medialibrary_t* ml, size_t index, size_t count) const override;
        // load a single item
        std::unique_ptr<MLItem> loadItemById(vlc_medialibrary_t* ml, MLItemId itemId) const override;
    };
};

the model should be registered in the QML engine using qmlRegisterType in MainUI.cpp

qmlRegisterType<MLStuffModel>( uri, versionMajor, versionMinor, "MLStuffModel" );

On the QML side a model is instantiated locally and is given the medialibrary as

import org.videolan.medialib 0.1

MLStuffModel {
  id: stuffModel
  ctx: MediaLib
}

Querying the medialibrary

Queries should not be made directly on the Qt thread and you should not block the Qt thread while the medialibrary is performing a query.

If you need to query on the medialibrary, you need to use the runOnMLThread endpoint on the MediaLib instance. These queries will be executed asynchronous, and the result will be provided to you through a callback.

A sample call:

    //this structure is used to exchange data between
    struct Ctx {
        bool success;
    };
    uint64_t taskid = m_mediaLib->runOnMLThread<Ctx>(
    //reference on the QObject making the call
    this,
    //this callback is executed in the medialibray thread
    [url, indexed](vlc_medialibrary_t* ml, Ctx& ctx)
    {
        //making whatever calls on the medialibrary
        int res;
        if ( indexed )
            res = vlc_ml_add_folder(  ml, qtu( url ) );
        else
            res = vlc_ml_remove_folder( ml, qtu( url ) );
        ctx.success = (res == VLC_SUCCESS);
    },
    //callback executed on the GUI thread, we waranty that
    //"this" still exists
    [this, indexed](quint64 taskid, Ctx& ctx){
        if (ctx.success)
        {
            m_indexed = indexed;
            emit isIndexedChanged();
        }
    },
    //Optionnal named queue if queries needs to be executed sequentially
    "ML_FOLDER_ADD_QUEUE");
  • The first callback will be called in a background thread, you can perform queries on the medialibrary from there, the second callback will be called on the GUI thread.

  • The ML or UI callback may not be called if the caller is destroyed before the callback is executed. Note that it may be destroyed during the execution of the ML callback, so you should not try to access the caller from this callback, the UI callback warranties that the caller still exists on the main thread.

  • A structure type can be specified to pass data between the two callbacks. Data put inside the structure should adhere to the RAII principle, the structure will be freed by broker, you have no warranty that the UI callback will actually be called.

  • If you need to pass data from the call site, you should pass it though lambda captures. Beware that the object making the call may be destroyed during the execution of the ML callback, so be wary of the life cycle of what you give (usually pass arguments by copy, move or using shared pointers)