Commit 5d94bd56 authored by Romain Vimont's avatar Romain Vimont Committed by Thomas Guillem
Browse files

qt: playlist: implement Qt playlist list model and controler



Wrap the playlist, playlist items and media (input items) into C++ classes, for
convenience and automatic memory management.

Use them to implement a list model to be used by a Qt list view.

The playlist model provides information about the play queue the playlist
controler allows to manipulate the playback (play, pause, next, ...).
Co-authored-by: Pierre Lamot's avatarPierre Lamot <pierre@videolabs.io>
Signed-off-by: Thomas Guillem's avatarThomas Guillem <thomas@gllm.fr>
parent d5cfaac8
......@@ -126,6 +126,17 @@ libqt_plugin_la_SOURCES = \
gui/qt/components/playlist_legacy/selector.cpp \
gui/qt/components/playlist_legacy/selector.hpp \
gui/qt/components/playlist_legacy/sorting.h \
gui/qt/components/playlist/media.hpp \
gui/qt/components/playlist/playlist_common.cpp \
gui/qt/components/playlist/playlist_common.hpp \
gui/qt/components/playlist/playlist_controller.cpp \
gui/qt/components/playlist/playlist_controller.hpp \
gui/qt/components/playlist/playlist_controller_p.hpp \
gui/qt/components/playlist/playlist_item.cpp \
gui/qt/components/playlist/playlist_item.hpp \
gui/qt/components/playlist/playlist_model.cpp \
gui/qt/components/playlist/playlist_model.hpp \
gui/qt/components/playlist/playlist_model_p.hpp \
gui/qt/components/sout/profile_selector.cpp \
gui/qt/components/sout/profile_selector.hpp \
gui/qt/components/sout/sout_widgets.cpp \
......@@ -232,6 +243,10 @@ nodist_libqt_plugin_la_SOURCES = \
gui/qt/components/playlist_legacy/playlist.moc.cpp \
gui/qt/components/playlist_legacy/standardpanel.moc.cpp \
gui/qt/components/playlist_legacy/selector.moc.cpp \
gui/qt/components/playlist/playlist_common.moc.cpp \
gui/qt/components/playlist/playlist_item.moc.cpp \
gui/qt/components/playlist/playlist_model.moc.cpp \
gui/qt/components/playlist/playlist_controller.moc.cpp \
gui/qt/components/sout/profile_selector.moc.cpp \
gui/qt/components/sout/sout_widgets.moc.cpp \
gui/qt/util/animators.moc.cpp \
......
/*****************************************************************************
* Copyright (C) 2019 VLC authors and VideoLAN
*
* 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.
*****************************************************************************/
#ifndef VLC_QT_MEDIA_HPP_
#define VLC_QT_MEDIA_HPP_
#include <vlc_cxx_helpers.hpp>
#include <vlc_common.h>
#include <vlc_input_item.h>
#include <QString>
#include <QStringList>
#include "util/qt_dirs.hpp"
namespace vlc {
namespace playlist {
using InputItemPtr = vlc_shared_data_ptr_type(input_item_t,
input_item_Hold,
input_item_Release);
class Media
{
public:
Media(input_item_t *media = nullptr)
{
if (media)
{
/* the media must be unique in the playlist */
ptr.reset(input_item_Copy(media), false);
if (!ptr)
throw std::bad_alloc();
}
}
Media(QString uri, QString name, QStringList* options = nullptr)
{
auto uUri = uri.toUtf8();
auto uName = name.toUtf8();
const char *rawUri = uUri.isNull() ? nullptr : uUri.constData();
const char *rawName = uName.isNull() ? nullptr : uName.constData();
ptr.reset(input_item_New(rawUri, rawName), false);
if (!ptr)
throw std::bad_alloc();
if (options && options->count() > 0)
{
char **ppsz_options = NULL;
int i_options = 0;
ppsz_options = new char *[options->count()];
auto optionDeleter = vlc::wrap_carray<char*>(ppsz_options, [&i_options](char *ptr[]) {
for(int i = 0; i < i_options; i++)
free(ptr[i]);
delete[] ptr;
});
for (int i = 0; i < options->count(); i++)
{
QString option = colon_unescape( options->at(i) );
ppsz_options[i] = strdup(option.toUtf8().constData());
if (!ppsz_options[i])
throw std::bad_alloc();
i_options++;
}
input_item_AddOptions( ptr.get(), i_options, ppsz_options, VLC_INPUT_OPTION_TRUSTED );
}
}
operator bool() const
{
return static_cast<bool>(ptr);
}
input_item_t *raw() const
{
return ptr.get();
}
private:
InputItemPtr ptr;
};
} // namespace playlist
} // namespace vlc
#endif
/*****************************************************************************
* Copyright (C) 2019 VLC authors and VideoLAN
*
* 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 "playlist_common.hpp"
PlaylistPtr::PlaylistPtr()
: m_playlist(nullptr)
{}
PlaylistPtr::PlaylistPtr(vlc_playlist_t* pl)
: m_playlist(pl)
{}
PlaylistPtr::PlaylistPtr(const PlaylistPtr& ptr)
: m_playlist(ptr.m_playlist)
{
}
PlaylistPtr&PlaylistPtr::operator=(const PlaylistPtr& ptr)
{
this->m_playlist = ptr.m_playlist;
return *this;
}
/*****************************************************************************
* Copyright (C) 2019 VLC authors and VideoLAN
*
* 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.
*****************************************************************************/
#ifndef PLAYLIST_COMMON_HPP
#define PLAYLIST_COMMON_HPP
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <QObject>
#include <vlc_playlist.h>
// QObject wrapper to carry playlist ptr through QML
class PlaylistPtr
{
Q_GADGET
public:
PlaylistPtr();
PlaylistPtr(vlc_playlist_t* pl);
PlaylistPtr(const PlaylistPtr& ptr);
PlaylistPtr& operator=(const PlaylistPtr& ptr);
vlc_playlist_t* m_playlist = nullptr;
};
class PlaylistLocker
{
public:
inline PlaylistLocker(vlc_playlist_t* playlist)
: m_playlist(playlist)
{
vlc_playlist_Lock(m_playlist);
}
inline ~PlaylistLocker()
{
vlc_playlist_Unlock(m_playlist);
}
vlc_playlist_t* m_playlist;
};
#endif // PLAYLIST_COMMON_HPP
/*****************************************************************************
* Copyright (C) 2019 VLC authors and VideoLAN
*
* 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.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "playlist_controller.hpp"
#include "playlist_controller_p.hpp"
#include "vlc_player.h"
#include <algorithm>
#include <QVariant>
namespace vlc {
namespace playlist {
static QVector<PlaylistItem> toVec(vlc_playlist_item_t *const items[],
size_t len)
{
QVector<PlaylistItem> vec;
for (size_t i = 0; i < len; ++i)
vec.push_back(items[i]);
return vec;
}
template <typename RAW, typename WRAPPER>
static QVector<RAW> toRaw(const QVector<WRAPPER> &items)
{
QVector<RAW> vec;
int count = items.size();
vec.reserve(count);
for (int i = 0; i < count; ++i)
vec.push_back(items[i].raw());
return vec;
}
extern "C" { // for C callbacks
static void
on_playlist_items_reset(vlc_playlist_t *playlist,
vlc_playlist_item_t *const items[],
size_t len, void *userdata)
{
PlaylistControllerModelPrivate *that = static_cast<PlaylistControllerModelPrivate *>(userdata);
auto vec = toVec(items, len);
size_t totalCount = vlc_playlist_Count(playlist);
that->callAsync([=](){
if (that->m_playlist != playlist)
return;
PlaylistControllerModel* q = that->q_func();
bool empty = vec.size() == 0;
if (that->m_empty != empty)
{
that->m_empty = empty;
emit q->isEmptyChanged(empty);
}
emit q->itemsReset(vec);
if (totalCount != that->m_count)
{
that->m_count = totalCount;
emit q->countChanged(totalCount);
}
});
}
static void
on_playlist_items_added(vlc_playlist_t *playlist, size_t index,
vlc_playlist_item_t *const items[], size_t len,
void *userdata)
{
PlaylistControllerModelPrivate *that = static_cast<PlaylistControllerModelPrivate *>(userdata);
auto vec = toVec(items, len);
size_t totalCount = vlc_playlist_Count(playlist);
that->callAsync([=](){
if (that->m_playlist != playlist)
return;
PlaylistControllerModel* q = that->q_func();
if (that->m_empty && vec.size() > 0)
{
that->m_empty = false;
emit q->isEmptyChanged(that->m_empty);
}
emit q->itemsAdded(index, vec);
if (totalCount != that->m_count)
{
that->m_count = totalCount;
emit q->countChanged(totalCount);
}
});
}
static void
on_playlist_items_moved(vlc_playlist_t *playlist, size_t index, size_t count,
size_t target, void *userdata)
{
PlaylistControllerModelPrivate *that = static_cast<PlaylistControllerModelPrivate *>(userdata);
that->callAsync([=](){
if (that->m_playlist != playlist)
return;
emit that->q_func()->itemsMoved(index, count, target);
});
}
static void
on_playlist_items_removed(vlc_playlist_t *playlist, size_t index, size_t count,
void *userdata)
{
PlaylistControllerModelPrivate *that = static_cast<PlaylistControllerModelPrivate *>(userdata);
size_t totalCount = vlc_playlist_Count(playlist);
bool empty = (totalCount == 0);
that->callAsync([=](){
if (that->m_playlist != playlist)
return;
PlaylistControllerModel* q = that->q_func();
if (that->m_empty != empty)
{
that->m_empty = empty;
emit q->isEmptyChanged(empty);
}
emit q->itemsRemoved(index, count);
if (totalCount != that->m_count)
{
that->m_count = totalCount;
emit q->countChanged(totalCount);
}
});
}
static void
on_playlist_items_updated(vlc_playlist_t *playlist, size_t index,
vlc_playlist_item_t *const items[], size_t len,
void *userdata)
{
PlaylistControllerModelPrivate *that = static_cast<PlaylistControllerModelPrivate *>(userdata);
auto vec = toVec(items, len);
that->callAsync([=](){
if (that->m_playlist != playlist)
return;
emit that->q_func()->itemsUpdated(index, vec);
});
}
static void
on_playlist_playback_repeat_changed(vlc_playlist_t *playlist,
enum vlc_playlist_playback_repeat repeat,
void *userdata)
{
PlaylistControllerModelPrivate *that = static_cast<PlaylistControllerModelPrivate *>(userdata);
that->callAsync([=](){
if (that->m_playlist != playlist)
return;
PlaylistControllerModel::PlaybackRepeat repeatMode = static_cast<PlaylistControllerModel::PlaybackRepeat>(repeat);
if (that->m_repeat != repeatMode )
{
that->m_repeat = repeatMode;
emit that->q_func()->repeatModeChanged(repeatMode);
}
});
}
static void
on_playlist_playback_order_changed(vlc_playlist_t *playlist,
enum vlc_playlist_playback_order order,
void *userdata)
{
PlaylistControllerModelPrivate *that = static_cast<PlaylistControllerModelPrivate *>(userdata);
that->callAsync([=](){
if (that->m_playlist != playlist)
return;
bool isRandom = order == VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM;
if (that->m_random != isRandom)
{
that->m_random = isRandom;
emit that->q_func()->randomChanged(isRandom);
}
});
}
static void
on_playlist_current_item_changed(vlc_playlist_t *playlist, ssize_t index,
void *userdata)
{
PlaylistControllerModelPrivate *that = static_cast<PlaylistControllerModelPrivate *>(userdata);
vlc_playlist_item_t* playlistItem = nullptr;
if (index >= 0)
playlistItem = vlc_playlist_Get(playlist, index);
PlaylistItem newItem{ playlistItem };
that->callAsync([=](){
PlaylistControllerModel* q = that->q_func();
if (that->m_playlist != playlist)
return;
if (that->m_currentIndex != index)
{
that->m_currentIndex = index;
emit q->currentIndexChanged(that->m_currentIndex);
}
that->m_currentItem = newItem;
emit q->currentItemChanged();
});
}
static void
on_playlist_has_prev_changed(vlc_playlist_t *playlist, bool has_prev,
void *userdata)
{
PlaylistControllerModelPrivate *that = static_cast<PlaylistControllerModelPrivate *>(userdata);
that->callAsync([=](){
if (that->m_playlist != playlist)
return;
if (that->m_hasPrev != has_prev)
{
that->m_hasPrev = has_prev;
emit that->q_func()->hasPrevChanged(has_prev);
}
});
}
static void
on_playlist_has_next_changed(vlc_playlist_t *playlist, bool has_next,
void *userdata)
{
PlaylistControllerModelPrivate *that = static_cast<PlaylistControllerModelPrivate *>(userdata);
that->callAsync([=](){
if (that->m_playlist != playlist)
return;
if (that->m_hasNext != has_next)
{
that->m_hasNext = has_next;
emit that->q_func()->hasNextChanged(has_next);
}
});
}
} // extern "C"
static const struct vlc_playlist_callbacks playlist_callbacks = {
/* C++ (before C++20) does not support designated initializers */
on_playlist_items_reset,
on_playlist_items_added,
on_playlist_items_moved,
on_playlist_items_removed,
on_playlist_items_updated,
on_playlist_playback_repeat_changed,
on_playlist_playback_order_changed,
on_playlist_current_item_changed,
on_playlist_has_prev_changed,
on_playlist_has_next_changed,
};
//private API
PlaylistControllerModelPrivate::PlaylistControllerModelPrivate(PlaylistControllerModel* playlistController)
: q_ptr(playlistController)
{
}
PlaylistControllerModelPrivate::~PlaylistControllerModelPrivate()
{
if (m_playlist && m_listener)
{
PlaylistLocker lock(m_playlist);
vlc_playlist_RemoveListener(m_playlist, m_listener);
}
}
//public API
PlaylistControllerModel::PlaylistControllerModel(QObject *parent)
: QObject(parent)
, d_ptr( new PlaylistControllerModelPrivate(this) )
{
}
PlaylistControllerModel::PlaylistControllerModel(vlc_playlist_t *playlist, QObject *parent)
: QObject(parent)
, d_ptr( new PlaylistControllerModelPrivate(this) )
{
setPlaylistPtr(playlist);
}
PlaylistControllerModel::~PlaylistControllerModel()
{
}
PlaylistItem PlaylistControllerModel::getCurrentItem() const
{
Q_D(const PlaylistControllerModel);
return d->m_currentItem;
}
void PlaylistControllerModel::append(const QVariantList& sourceList, bool startPlaying)
{
QVector<Media> mediaList;
std::transform(sourceList.begin(), sourceList.end(),
std::back_inserter(mediaList), [](const QVariant& value) {
if (value.canConvert<QUrl>())
{
auto mrl = value.value<QUrl>();
return Media(mrl.toString(QUrl::None), mrl.fileName());
}
else if (value.canConvert<QString>())
{
auto mrl = value.value<QString>();
return Media(mrl, mrl);
}
return Media{};
});
append(mediaList, startPlaying);
}
void PlaylistControllerModel::insert(unsigned index, const QVariantList& sourceList, bool startPlaying)
{
QVector<Media> mediaList;
std::transform(sourceList.begin(), sourceList.end(),
std::back_inserter(mediaList), [](const QVariant& value) {
if (value.canConvert<QUrl>())
{
auto mrl = value.value<QUrl>();
return Media(mrl.toString(QUrl::None), mrl.fileName());
}
else if (value.canConvert<QString>())
{
auto mrl = value.value<QString>();
return Media(mrl, mrl);
}
return Media{};
});
insert(index, mediaList, startPlaying);
}
void
PlaylistControllerModel::append(const QVector<Media> &media, bool startPlaying)
{
Q_D(PlaylistControllerModel);
PlaylistLocker locker(d->m_playlist);
auto rawMedia = toRaw<input_item_t *>(media);
int ret = vlc_playlist_Append(d->m_playlist,
rawMedia.constData(), rawMedia.size());
if (ret != VLC_SUCCESS)