|
|
|
# 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](https://doc.qt.io/qt-5/qabstractlistmodel.html) 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). Theses 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) throught 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
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
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`
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
qmlRegisterType<MLStuffModel>( uri, versionMajor, versionMinor, "MLStuffModel" );
|
|
|
|
```
|
|
|
|
|
|
|
|
On the QML side a model is instantiated locally and is given the medialibrary as
|
|
|
|
|
|
|
|
```qml
|
|
|
|
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. theses queries will be executed
|
|
|
|
asynchronous, and the result will be provided to you through a callback.
|
|
|
|
|
|
|
|
A sample call:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
//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) |