mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2025-09-05 18:22:46 +00:00
Moved playback logic to C++-side (and refractoring)
This commit is contained in:
parent
895731ae38
commit
f7bca333c8
35 changed files with 1063 additions and 449 deletions
|
@ -25,7 +25,16 @@ namespace Jellyfin {
|
|||
namespace DTO {
|
||||
|
||||
const QRegularExpression JsonSerializable::m_listExpression = QRegularExpression("^QList<\\s*([a-zA-Z0-9]*)\\s*\\*?\\s*>$");
|
||||
const QRegularExpression JsonSerializable::m_hashExpression = QRegularExpression("^QHash<\\s*([a-zA-Z0-9]*)\\s*\\*?\\s*,\\s*([a-zA-Z0-9]*)\\s*\\*?\\s*>$");
|
||||
JsonSerializable::JsonSerializable(QObject *parent) : QObject(parent) { }
|
||||
JsonSerializable::~JsonSerializable() {
|
||||
if (parent() == nullptr) {
|
||||
qDebug() << "Deleting" << metaObject()->className() << ", parent: nullptr, ownership: " << QQmlEngine::objectOwnership(this);
|
||||
} else {
|
||||
qDebug() << "Deleting" << metaObject()->className() << ", parent: " << parent()->metaObject()->className()
|
||||
<< ", ownership: " << QQmlEngine::objectOwnership(this);
|
||||
}
|
||||
}
|
||||
|
||||
void JsonSerializable::deserialize(const QJsonObject &jObj) {
|
||||
const QMetaObject *obj = this->metaObject();
|
||||
|
@ -95,45 +104,55 @@ QVariant JsonSerializable::jsonToVariant(QMetaProperty prop, const QJsonValue &v
|
|||
JsonHelper::convertToCamelCase(QJsonValueRef(&tmp, 0));
|
||||
return QVariant(innerObj);
|
||||
} else {
|
||||
return deserializeQobject(innerObj, prop);
|
||||
return deserializeQObject(innerObj, prop);
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant JsonSerializable::deserializeQobject(const QJsonObject &innerObj, const QMetaProperty &prop) {
|
||||
QVariant JsonSerializable::deserializeQObject(const QJsonObject &innerObj, const QMetaProperty &prop) {
|
||||
int typeNo = prop.userType();
|
||||
const QMetaObject *metaType = QMetaType::metaObjectForType(prop.userType());
|
||||
if (metaType == nullptr) {
|
||||
// Try to determine if the type is a qlist
|
||||
QRegularExpressionMatch match = m_listExpression.match(prop.typeName());
|
||||
if (match.hasMatch()) {
|
||||
// It is a qList! Now extract the inner type
|
||||
// There should be an easier way, shouldn't there?
|
||||
QString listType = match.captured(1).prepend("Jellyfin::DTO::").append("*");
|
||||
// UGLY CODE HERE WE COME
|
||||
typeNo = QMetaType::type(listType.toUtf8());
|
||||
if (typeNo == QMetaType::UnknownType) {
|
||||
qDebug() << "Unknown type: " << listType;
|
||||
return QVariant();
|
||||
}
|
||||
metaType = QMetaType::metaObjectForType(typeNo);
|
||||
} else {
|
||||
qDebug() << "No metaObject for " << prop.typeName() << ", " << prop.type() << ", " << prop.userType();
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
QObject *deserializedInnerObj = metaType->newInstance();
|
||||
deserializedInnerObj->setParent(this);
|
||||
if (JsonSerializable *ser = dynamic_cast<JsonSerializable *>(deserializedInnerObj)) {
|
||||
qDebug() << "Deserializing user type " << deserializedInnerObj->metaObject()->className();
|
||||
ser->deserialize(innerObj);
|
||||
return QVariant(typeNo, &ser);
|
||||
const QMetaObject *metaType = QMetaType::metaObjectForType(prop.userType());
|
||||
if (metaType == nullptr) {
|
||||
// Try to determine if the type is a qlist
|
||||
QRegularExpressionMatch listMatch = m_listExpression.match(prop.typeName());
|
||||
QRegularExpressionMatch hashMatch = m_hashExpression.match(prop.typeName());
|
||||
if (listMatch.hasMatch()) {
|
||||
// It is a qList! Now extract the inner type
|
||||
// There should be an easier way, shouldn't there?
|
||||
QString listType = listMatch.captured(1);
|
||||
typeNo = findTypeIdForProperty(listType);
|
||||
metaType = QMetaType::metaObjectForType(typeNo);
|
||||
} else {
|
||||
deserializedInnerObj->deleteLater();
|
||||
qDebug() << "Object is not a serializable one!";
|
||||
qDebug() << "No metaObject for " << prop.typeName() << ", " << prop.type() << ", " << prop.userType();
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
QObject *deserializedInnerObj = metaType->newInstance();
|
||||
deserializedInnerObj->setParent(this);
|
||||
if (JsonSerializable *ser = dynamic_cast<JsonSerializable *>(deserializedInnerObj)) {
|
||||
// qDebug() << "Deserializing user type " << deserializedInnerObj->metaObject()->className();
|
||||
ser->deserialize(innerObj);
|
||||
return QVariant(typeNo, &ser);
|
||||
} else {
|
||||
deserializedInnerObj->deleteLater();
|
||||
qDebug() << "Object is not a serializable one!";
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int JsonSerializable::findTypeIdForProperty(QString type) {
|
||||
// UGLY CODE HERE WE COME
|
||||
// We assume the type is either in no namespace (Qt Types) or in the Jellyfin::DTO namespace.
|
||||
int typeNo = QMetaType::type(type.toUtf8());
|
||||
if (typeNo == QMetaType::UnknownType) {
|
||||
typeNo = QMetaType::type(type.prepend("Jellyfin::DTO::").append("*").toUtf8());
|
||||
if (typeNo == QMetaType::UnknownType) {
|
||||
qDebug() << "Unknown type: " << type;
|
||||
return typeNo;
|
||||
}
|
||||
}
|
||||
return typeNo;
|
||||
}
|
||||
|
||||
QJsonObject JsonSerializable::serialize(bool capitalize) const {
|
||||
|
@ -220,6 +239,13 @@ void RemoteData::setApiClient(ApiClient *newApiClient) {
|
|||
reload();
|
||||
}
|
||||
|
||||
void RemoteData::setExtraFields(const QStringList &extraFields) {
|
||||
if (extraFields != m_extraFields) {
|
||||
emit extraFieldsChanged(extraFields);
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteData::reload() {
|
||||
if (!canReload() || m_apiClient == nullptr) {
|
||||
setStatus(Uninitialised);
|
||||
|
@ -227,7 +253,11 @@ void RemoteData::reload() {
|
|||
} else {
|
||||
setStatus(Loading);
|
||||
}
|
||||
QNetworkReply *rep = m_apiClient->get(getDataUrl());
|
||||
QUrlQuery params;
|
||||
if (m_extraFields.length() > 0) {
|
||||
params.addQueryItem("fields", m_extraFields.join(","));
|
||||
}
|
||||
QNetworkReply *rep = m_apiClient->get(getDataUrl() + QStringLiteral("?") + params.toString(QUrl::EncodeReserved));
|
||||
connect(rep, &QNetworkReply::finished, this, [this, rep]() {
|
||||
rep->deleteLater();
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ QString Item::getDataUrl() const {
|
|||
}
|
||||
|
||||
bool Item::canReload() const {
|
||||
return !m_id.isNull();
|
||||
return !m_id.isNull() && m_apiClient != nullptr;
|
||||
}
|
||||
|
||||
void Item::setJellyfinId(QString newId) {
|
||||
|
@ -54,7 +54,7 @@ void Item::setJellyfinId(QString newId) {
|
|||
}
|
||||
}
|
||||
|
||||
void Item::onUserDataChanged(const QString &itemId, QSharedPointer<UserData> userData) {
|
||||
void Item::onUserDataChanged(const QString &itemId, UserData *userData) {
|
||||
if (itemId != m_id || m_userData == nullptr) return;
|
||||
m_userData->onUpdated(userData);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ void UserData::updateOnServer() {
|
|||
//TODO: implement
|
||||
}
|
||||
|
||||
void UserData::onUpdated(QSharedPointer<UserData> other) {
|
||||
void UserData::onUpdated(UserData *other) {
|
||||
// The reason I'm not using setLikes and similar is that they don't work with std::nullopt,
|
||||
// since QML does not like it.
|
||||
// THe other reason is that the setLikes method will send a post request to the server, to update the contents
|
||||
|
|
|
@ -45,7 +45,7 @@ void ApiClient::addBaseRequestHeaders(QNetworkRequest &request, const QString &p
|
|||
request.setRawHeader("Accept", "application/json;"); // profile=\"CamelCase\"");
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Sailfin/%1").arg(version()));
|
||||
QString url = this->m_baseUrl + path;
|
||||
if (!params.isEmpty()) url += "?" + params.toString();
|
||||
if (!params.isEmpty()) url += "?" + params.toString(QUrl::EncodeReserved);
|
||||
request.setUrl(url);
|
||||
}
|
||||
|
||||
|
@ -263,7 +263,7 @@ void ApiClient::defaultNetworkErrorHandler(QNetworkReply::NetworkError error) {
|
|||
rep->deleteLater();
|
||||
}
|
||||
|
||||
void ApiClient::onUserDataChanged(const QString &itemId, QSharedPointer<UserData> userData) {
|
||||
void ApiClient::onUserDataChanged(const QString &itemId, UserData *userData) {
|
||||
emit userDataChanged(itemId, userData);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,81 +19,70 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
|
||||
#include "JellyfinQt/apimodel.h"
|
||||
|
||||
#include "JellyfinQt/DTO/item.h"
|
||||
#include "JellyfinQt/DTO/userdata.h"
|
||||
#include "JellyfinQt/DTO/user.h"
|
||||
|
||||
namespace Jellyfin {
|
||||
ApiModel::ApiModel(QString path, bool hasRecordResponse, bool addUserId, QObject *parent)
|
||||
: QAbstractListModel (parent),
|
||||
|
||||
// BaseApiModel
|
||||
|
||||
BaseApiModel::BaseApiModel(QString path, bool hasRecordResponse, bool addUserId, QObject *parent)
|
||||
: QAbstractListModel(parent),
|
||||
m_path(path),
|
||||
m_hasRecordResponse(hasRecordResponse),
|
||||
m_addUserId(addUserId){
|
||||
m_addUserId(addUserId) {
|
||||
|
||||
}
|
||||
|
||||
void ApiModel::reload() {
|
||||
void BaseApiModel::setApiClient(ApiClient *apiClient) {
|
||||
m_apiClient = apiClient;
|
||||
emit apiClientChanged(m_apiClient);
|
||||
}
|
||||
|
||||
void BaseApiModel::setLimit(int newLimit) {
|
||||
m_limit = newLimit;
|
||||
emit limitChanged(newLimit);
|
||||
if (m_apiClient != nullptr && !m_isBeingParsed) {
|
||||
load(LOAD_MORE);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseApiModel::reload() {
|
||||
this->setStatus(Loading);
|
||||
m_startIndex = 0;
|
||||
load(RELOAD);
|
||||
}
|
||||
|
||||
void ApiModel::load(LoadType type) {
|
||||
|
||||
void BaseApiModel::load(LoadType type) {
|
||||
qDebug() << (type == RELOAD ? "RELOAD" : "LOAD_MORE");
|
||||
if (m_apiClient == nullptr) {
|
||||
qWarning() << "Please set the apiClient property before (re)loading";
|
||||
return;
|
||||
}
|
||||
if (m_path.contains("{{user}}")) {
|
||||
m_path = m_path.replace("{{user}}", m_apiClient->userId());
|
||||
}
|
||||
if (m_path.contains("{{show}}") && !m_show.isEmpty()) {
|
||||
m_path = m_path.replace("{{show}}", m_show);
|
||||
}
|
||||
|
||||
QString path(m_path);
|
||||
replacePathPlaceholders(path);
|
||||
QUrlQuery query;
|
||||
if (m_limit >= 0) {
|
||||
query.addQueryItem("Limit", QString::number(m_limit));
|
||||
} else {
|
||||
query.addQueryItem("Limit", QString::number(DEFAULT_LIMIT));
|
||||
}
|
||||
if (m_startIndex > 0) {
|
||||
query.addQueryItem("StartIndex", QString::number(m_startIndex));
|
||||
}
|
||||
if (!m_parentId.isEmpty()) {
|
||||
query.addQueryItem("ParentId", m_parentId);
|
||||
}
|
||||
if (!m_sortBy.empty()) {
|
||||
query.addQueryItem("SortBy", m_sortBy.join(","));
|
||||
}
|
||||
if (m_sortOrder != Unspecified) {
|
||||
query.addQueryItem("SortOrder", m_sortOrder == Ascending ? "Ascending" : "Descending");
|
||||
}
|
||||
if (!m_imageTypes.empty()) {
|
||||
query.addQueryItem("ImageTypes", m_imageTypes.join(","));
|
||||
}
|
||||
if (!m_includeItemTypes.empty()) {
|
||||
query.addQueryItem("IncludeItemTypes", m_includeItemTypes.join(","));
|
||||
}
|
||||
if (!m_fields.empty()) {
|
||||
query.addQueryItem("Fields", m_fields.join(","));
|
||||
}
|
||||
if (!m_seasonId.isEmpty()) {
|
||||
query.addQueryItem("seasonId", m_seasonId);
|
||||
}
|
||||
if (m_addUserId) {
|
||||
query.addQueryItem("userId", m_apiClient->userId());
|
||||
}
|
||||
if (m_recursive) {
|
||||
query.addQueryItem("Recursive", "true");
|
||||
}
|
||||
addQueryParameters(query);
|
||||
QNetworkReply *rep = m_apiClient->get(m_path, query);
|
||||
|
||||
QNetworkReply *rep = m_apiClient->get(path, query);
|
||||
connect(rep, &QNetworkReply::finished, this, [this, type, rep]() {
|
||||
qDebug() << rep->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << ": " << rep->request().url();
|
||||
QJsonDocument doc = QJsonDocument::fromJson(rep->readAll());
|
||||
if (doc.isNull()) {
|
||||
qWarning() << "JSON parse error";
|
||||
this->setStatus(Error);
|
||||
}
|
||||
if (!m_hasRecordResponse) {
|
||||
if (!doc.isArray()) {
|
||||
qWarning() << "Object is not an array!";
|
||||
this->setStatus(Error);
|
||||
return;
|
||||
}
|
||||
this->m_array = doc.array();
|
||||
QJsonArray items = doc.array();
|
||||
setModelData(items);
|
||||
} else {
|
||||
if (!doc.isObject()) {
|
||||
qWarning() << "Object is not an object!";
|
||||
|
@ -125,42 +114,109 @@ void ApiModel::load(LoadType type) {
|
|||
QJsonArray items = obj["Items"].toArray();
|
||||
switch(type) {
|
||||
case RELOAD:
|
||||
this->m_array = items;
|
||||
setModelData(items);
|
||||
break;
|
||||
case LOAD_MORE:
|
||||
this->beginInsertRows(QModelIndex(), m_array.size(), m_array.size() + items.size() - 1);
|
||||
// QJsonArray apparently doesn't allow concatenating lists like QList or std::vector
|
||||
for (auto it = items.begin(); it != items.end(); it++) {
|
||||
JsonHelper::convertToCamelCase(*it);
|
||||
}
|
||||
foreach (const QJsonValue &val, items) {
|
||||
m_array.append(val);
|
||||
}
|
||||
this->endInsertRows();
|
||||
appendModelData(items);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (type == RELOAD) {
|
||||
generateFields();
|
||||
}
|
||||
this->setStatus(Ready);
|
||||
rep->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void ApiModel::generateFields() {
|
||||
if (m_array.size() == 0) return;
|
||||
this->beginResetModel();
|
||||
void BaseApiModel::addQueryParameters(QUrlQuery &query) {
|
||||
if (m_limit >= 0) {
|
||||
query.addQueryItem("Limit", QString::number(m_limit));
|
||||
} else {
|
||||
query.addQueryItem("Limit", QString::number(DEFAULT_LIMIT));
|
||||
}
|
||||
if (m_startIndex > 0) {
|
||||
query.addQueryItem("StartIndex", QString::number(m_startIndex));
|
||||
}
|
||||
if (!m_sortBy.empty()) {
|
||||
query.addQueryItem("SortBy", m_sortBy.join(","));
|
||||
}
|
||||
if (m_sortOrder != Unspecified) {
|
||||
query.addQueryItem("SortOrder", m_sortOrder == Ascending ? "Ascending" : "Descending");
|
||||
}
|
||||
if (!m_fields.empty()) {
|
||||
query.addQueryItem("Fields", m_fields.join(","));
|
||||
}
|
||||
if (m_addUserId) {
|
||||
query.addQueryItem("userId", m_apiClient->userId());
|
||||
}
|
||||
}
|
||||
|
||||
void BaseApiModel::replacePathPlaceholders(QString &path) {
|
||||
if (path.contains("{{user}}")) {
|
||||
path = path.replace("{{user}}", m_apiClient->userId());
|
||||
}
|
||||
}
|
||||
|
||||
void BaseApiModel::classBegin() {
|
||||
m_isBeingParsed = true;
|
||||
}
|
||||
|
||||
void BaseApiModel::componentComplete() {
|
||||
m_isBeingParsed = false;
|
||||
}
|
||||
|
||||
// ApiModel
|
||||
template <class T>
|
||||
ApiModel<T>::ApiModel(QString path, bool hasRecordResponse, bool addUserId, QObject *parent)
|
||||
: BaseApiModel(path, hasRecordResponse, addUserId, parent) {
|
||||
// If based on QObject, we know our role names before the first request
|
||||
generateFields();
|
||||
}
|
||||
|
||||
template <>
|
||||
ApiModel<QJsonValue>::ApiModel(QString path, bool hasRecordResponse, bool addUserId, QObject *parent)
|
||||
: BaseApiModel(path, hasRecordResponse, addUserId, parent) {
|
||||
// But we only know our role names after our first request.
|
||||
}
|
||||
|
||||
|
||||
template <class T>
|
||||
T *ApiModel<T>::deserializeResult(QJsonValueRef source) {
|
||||
T *result = new T(static_cast<BaseApiModel *>(this));
|
||||
result->deserialize(source.toObject());
|
||||
return result;
|
||||
}
|
||||
|
||||
template <>
|
||||
QJsonValue *ApiModel<QJsonValue>::deserializeResult(QJsonValueRef source) {
|
||||
QJsonValue *result = new QJsonValue(source);
|
||||
JsonHelper::convertToCamelCase(*result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void ApiModel<T>::generateFields() {
|
||||
const QMetaObject *obj = &T::staticMetaObject;
|
||||
m_roles[Qt::UserRole + 1] = "qtObject";
|
||||
for (int i = 0; i < obj->propertyCount(); i++) {
|
||||
QMetaProperty property = obj->property(i);
|
||||
m_roles.insert(Qt::UserRole + 2 + i, property.name());
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void ApiModel<QJsonValue>::generateFields() {
|
||||
// We can only generate field names if there is a first item. Redefining role names later leads to
|
||||
// unexpected results, so prevent it as well.
|
||||
if (m_array.size() == 0 || m_roles.size() > 0) return;
|
||||
int i = Qt::UserRole + 1;
|
||||
if (!m_array[0].isObject()) {
|
||||
if (!m_array[0]->isObject()) {
|
||||
qWarning() << "Iterator is not an object?";
|
||||
return;
|
||||
}
|
||||
// Walks over the keys in the first record and adds them to the rolenames.
|
||||
// This assumes the back-end has the same keys for every record. I could technically
|
||||
// go over all records to be really sure, but no-one got time for a O(n²) algorithm, so
|
||||
// go over all records to be really sure, but no-one got time for a O(n) algorithm, so
|
||||
// this heuristic hopefully suffices.
|
||||
QJsonObject ob = m_array[0].toObject();
|
||||
QJsonObject ob = m_array[0]->toObject();
|
||||
for (auto jt = ob.begin(); jt != ob.end(); jt++) {
|
||||
QString keyName = jt.key();
|
||||
keyName[0] = keyName[0].toLower();
|
||||
|
@ -169,20 +225,78 @@ void ApiModel::generateFields() {
|
|||
m_roles.insert(i++, keyArr);
|
||||
}
|
||||
}
|
||||
for (auto it = m_array.begin(); it != m_array.end(); it++){
|
||||
JsonHelper::convertToCamelCase(*it);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void ApiModel<T>::setModelData(QJsonArray &data) {
|
||||
this->beginResetModel();
|
||||
for (T* value : m_array) {
|
||||
value->deleteLater();
|
||||
}
|
||||
m_array.clear();
|
||||
for(QJsonValueRef value : data) {
|
||||
m_array.append(deserializeResult(value));
|
||||
}
|
||||
this->endResetModel();
|
||||
}
|
||||
|
||||
QVariant ApiModel::data(const QModelIndex &index, int role) const {
|
||||
template <>
|
||||
void ApiModel<QJsonValue>::setModelData(QJsonArray &data) {
|
||||
generateFields();
|
||||
this->beginResetModel();
|
||||
for (QJsonValue* value : m_array) {
|
||||
delete value;
|
||||
}
|
||||
m_array.clear();
|
||||
for(QJsonValueRef value : data) {
|
||||
m_array.append(deserializeResult(value));
|
||||
}
|
||||
this->endResetModel();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void ApiModel<T>::appendModelData(QJsonArray &data) {
|
||||
this->beginInsertRows(QModelIndex(), m_array.size(), m_array.size() + data.size() - 1);
|
||||
// QJsonArray apparently doesn't allow concatenating lists like QList or std::vector
|
||||
for (auto it = data.begin(); it != data.end(); it++) {
|
||||
JsonHelper::convertToCamelCase(*it);
|
||||
}
|
||||
for(QJsonValueRef val : data) {
|
||||
m_array.append(deserializeResult(val));
|
||||
}
|
||||
this->endInsertRows();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
QVariant ApiModel<T>::data(const QModelIndex &index, int role) const {
|
||||
// Ignore roles we don't know
|
||||
if (role <= Qt::UserRole || role >= Qt::UserRole + m_roles.size()) return QVariant();
|
||||
// Ignore invalid indices.
|
||||
if (!index.isValid()) return QVariant();
|
||||
|
||||
|
||||
QJsonObject obj = m_array.at(index.row()).toObject();
|
||||
T* obj = m_array.at(index.row());
|
||||
// m_roleNames[role] == "qtObject"
|
||||
if (role == Qt::UserRole + 1) {
|
||||
return QVariant::fromValue(obj);
|
||||
}
|
||||
|
||||
const QString &key = m_roles[role];
|
||||
|
||||
if (role - Qt::UserRole - 2 < obj->metaObject()->propertyCount() ) {
|
||||
return obj->property(key.toLocal8Bit());
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
template <>
|
||||
QVariant ApiModel<QJsonValue>::data(const QModelIndex &index, int role) const {
|
||||
// Ignore roles we don't know
|
||||
if (role <= Qt::UserRole || role >= Qt::UserRole + m_roles.size()) return QVariant();
|
||||
// Ignore invalid indices.
|
||||
if (!index.isValid()) return QVariant();
|
||||
|
||||
|
||||
QJsonObject obj = m_array.at(index.row())->toObject();
|
||||
|
||||
const QString &key = m_roles[role];
|
||||
|
||||
|
@ -192,7 +306,9 @@ QVariant ApiModel::data(const QModelIndex &index, int role) const {
|
|||
return QVariant();
|
||||
}
|
||||
|
||||
bool ApiModel::canFetchMore(const QModelIndex &parent) const {
|
||||
|
||||
template <class T>
|
||||
bool ApiModel<T>::canFetchMore(const QModelIndex &parent) const {
|
||||
if (parent.isValid()) return false;
|
||||
switch(m_status) {
|
||||
case Uninitialised:
|
||||
|
@ -208,50 +324,99 @@ bool ApiModel::canFetchMore(const QModelIndex &parent) const {
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ApiModel::fetchMore(const QModelIndex &parent) {
|
||||
template <class T>
|
||||
void ApiModel<T>::fetchMore(const QModelIndex &parent) {
|
||||
if (parent.isValid()) return;
|
||||
this->setStatus(LoadingMore);
|
||||
load(LOAD_MORE);
|
||||
}
|
||||
|
||||
void ApiModel::addQueryParameters(QUrlQuery &query) { Q_UNUSED(query)}
|
||||
template <class T>
|
||||
void ApiModel<T>::addQueryParameters(QUrlQuery &query) {
|
||||
BaseApiModel::addQueryParameters(query);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void ApiModel<T>::replacePathPlaceholders(QString &path) {
|
||||
BaseApiModel::replacePathPlaceholders(path);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void ApiModel<T>::insert(int index, T* object) {
|
||||
Q_ASSERT(index >=0 && index <= size());
|
||||
this->beginInsertRows(QModelIndex(), index, index);
|
||||
m_array.insert(index, object);
|
||||
this->endInsertRows();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void ApiModel<T>::removeAt(int index) {
|
||||
this->beginRemoveRows(QModelIndex(), index, index);
|
||||
m_array.removeAt(index);
|
||||
this->endRemoveRows();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void ApiModel<T>::removeOne(T* object) {
|
||||
int idx = m_array.indexOf(object);
|
||||
if (idx >= 0) {
|
||||
removeAt(idx);
|
||||
}
|
||||
}
|
||||
|
||||
// Itemmodel
|
||||
|
||||
ItemModel::ItemModel(QString path, bool hasRecordFields, bool replaceUser, QObject *parent)
|
||||
: ApiModel (path, hasRecordFields, replaceUser, parent){
|
||||
connect(this, &ApiModel::apiClientChanged, this, [this](ApiClient *newApiClient) {
|
||||
connect(newApiClient, &ApiClient::userDataChanged, this, &ItemModel::onUserDataChanged);
|
||||
QObject::connect(this, &BaseApiModel::apiClientChanged, static_cast<BaseApiModel *>(this), [this](ApiClient *newApiClient) {
|
||||
QObject::connect(newApiClient, &ApiClient::userDataChanged, this, &ItemModel::onUserDataChanged);
|
||||
});
|
||||
}
|
||||
|
||||
void ItemModel::onUserDataChanged(const QString &itemId, QSharedPointer<DTO::UserData> userData) {
|
||||
void ItemModel::onUserDataChanged(const QString &itemId, DTO::UserData *userData) {
|
||||
int i = 0;
|
||||
for (QJsonValueRef val: m_array) {
|
||||
QJsonObject item = val.toObject();
|
||||
if (item.contains("id") && item["id"].toString() == itemId) {
|
||||
if (item.contains("userData")) {
|
||||
QModelIndex cell = this->index(i);
|
||||
item["userData"] = userData->serialize(false);
|
||||
val = item;
|
||||
this->dataChanged(cell, cell);
|
||||
}
|
||||
for (Item *val: m_array) {
|
||||
if (val->userData() != nullptr && val->jellyfinId() == itemId) {
|
||||
QModelIndex cell = this->index(i);
|
||||
val->userData()->onUpdated(userData);
|
||||
this->dataChanged(cell, cell);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void ItemModel::addQueryParameters(QUrlQuery &query) {
|
||||
ApiModel<Item>::addQueryParameters(query);
|
||||
if (!m_parentId.isEmpty()) {
|
||||
query.addQueryItem("ParentId", m_parentId);
|
||||
}
|
||||
if (!m_imageTypes.empty()) {
|
||||
query.addQueryItem("ImageTypes", m_imageTypes.join(","));
|
||||
}
|
||||
if (!m_includeItemTypes.empty()) {
|
||||
query.addQueryItem("IncludeItemTypes", m_includeItemTypes.join(","));
|
||||
}
|
||||
if (!m_seasonId.isEmpty()) {
|
||||
query.addQueryItem("seasonId", m_seasonId);
|
||||
}
|
||||
if (m_recursive) {
|
||||
query.addQueryItem("Recursive", "true");
|
||||
}
|
||||
}
|
||||
|
||||
void ItemModel::replacePathPlaceholders(QString &path) {
|
||||
ApiModel::replacePathPlaceholders(path);
|
||||
if (path.contains("{{show}}") && !m_show.isEmpty()) {
|
||||
path = m_path.replace("{{show}}", m_show);
|
||||
}
|
||||
}
|
||||
|
||||
PublicUserModel::PublicUserModel(QObject *parent)
|
||||
: ApiModel ("/users/public", false, false, parent) { }
|
||||
|
||||
UserViewModel::UserViewModel(QObject *parent)
|
||||
: ApiModel ("/Users/{{user}}/Views", true, false, parent) {}
|
||||
: ItemModel ("/Users/{{user}}/Views", true, false, parent) {}
|
||||
|
||||
UserItemModel::UserItemModel(QObject *parent)
|
||||
: ItemModel ("/Users/{{user}}/Items", true, false, parent) {}
|
||||
|
@ -272,7 +437,7 @@ ShowEpisodesModel::ShowEpisodesModel(QObject *parent)
|
|||
: ItemModel ("/Shows/{{show}}/Episodes", true, true, parent) {}
|
||||
|
||||
void registerModels(const char *URI) {
|
||||
qmlRegisterUncreatableType<ApiModel>(URI, 1, 0, "ApiModel", "Is enum and base class");
|
||||
qmlRegisterUncreatableType<BaseApiModel>(URI, 1, 0, "ApiModel", "Is enum and base class");
|
||||
qmlRegisterUncreatableType<SortOptions>(URI, 1, 0, "SortOptions", "Is enum");
|
||||
qmlRegisterType<PublicUserModel>(URI, 1, 0, "PublicUserModel");
|
||||
qmlRegisterType<UserViewModel>(URI, 1, 0, "UserViewModel");
|
||||
|
|
|
@ -51,6 +51,37 @@ void convertToCamelCase(QJsonValueRef val) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
void convertToCamelCase(QJsonValue &val) {
|
||||
switch(val.type()) {
|
||||
case QJsonValue::Object: {
|
||||
QJsonObject obj = val.toObject();
|
||||
for(const QString &key: obj.keys()) {
|
||||
QJsonValueRef ref = obj[key];
|
||||
convertToCamelCase(ref);
|
||||
obj[convertToCamelCaseHelper(key)] = ref;
|
||||
if (key[0].isLower() || !key[0].isLetter()) {
|
||||
obj[key] = ref;
|
||||
} else {
|
||||
obj[convertToCamelCaseHelper(key)] = ref;
|
||||
obj.remove(key);
|
||||
}
|
||||
}
|
||||
val = obj;
|
||||
break;
|
||||
}
|
||||
case QJsonValue::Array: {
|
||||
QJsonArray arr = val.toArray();
|
||||
for (auto it = arr.begin(); it != arr.end(); it++) {
|
||||
convertToCamelCase(*it);
|
||||
}
|
||||
val = arr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString convertToCamelCaseHelper(const QString &str) {
|
||||
QString res(str);
|
||||
|
|
|
@ -19,25 +19,29 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
|
||||
#include "JellyfinQt/playbackmanager.h"
|
||||
|
||||
#include "JellyfinQt/apimodel.h"
|
||||
|
||||
#include "JellyfinQt/DTO/dto.h"
|
||||
#include "JellyfinQt/DTO/userdata.h"
|
||||
|
||||
namespace Jellyfin {
|
||||
class ItemModel;
|
||||
|
||||
PlaybackManager::PlaybackManager(QObject *parent)
|
||||
: QObject(parent) {
|
||||
: QObject(parent), m_mediaPlayer1(new QMediaPlayer(this)), m_mediaPlayer2(new QMediaPlayer(this)) {
|
||||
// Set up connections.
|
||||
swapMediaPlayer();
|
||||
m_updateTimer.setInterval(10000); // 10 seconds
|
||||
m_updateTimer.setSingleShot(false);
|
||||
connect(&m_updateTimer, &QTimer::timeout, this, &PlaybackManager::updatePlaybackInfo);
|
||||
}
|
||||
|
||||
void PlaybackManager::fetchStreamUrl() {
|
||||
if (m_item == nullptr || m_apiClient == nullptr) {
|
||||
void PlaybackManager::fetchStreamUrl(const Item *item, bool autoOpen, const FetchCallback &callback) {
|
||||
if (item == nullptr || m_apiClient == nullptr) {
|
||||
qDebug() << "Item or apiClient not set";
|
||||
return;
|
||||
}
|
||||
m_resumePosition = 0;
|
||||
if (m_resumePlayback && !m_item->property("userData").isNull()) {
|
||||
if (m_resumePlayback && !item->property("userData").isNull()) {
|
||||
UserData* userData = qvariant_cast<UserData *>(m_item->property("userData"));
|
||||
if (userData != nullptr) {
|
||||
m_resumePosition = userData->playbackPositionTicks();
|
||||
|
@ -47,16 +51,16 @@ void PlaybackManager::fetchStreamUrl() {
|
|||
params.addQueryItem("UserId", m_apiClient->userId());
|
||||
params.addQueryItem("StartTimeTicks", QString::number(m_resumePosition));
|
||||
params.addQueryItem("IsPlayback", "true");
|
||||
params.addQueryItem("AutoOpenLiveStream", this->m_autoOpen ? "true" : "false");
|
||||
params.addQueryItem("MediaSourceId", this->m_item->jellyfinId());
|
||||
params.addQueryItem("AutoOpenLiveStream", autoOpen? "true" : "false");
|
||||
params.addQueryItem("MediaSourceId", item->jellyfinId());
|
||||
params.addQueryItem("SubtitleStreamIndex", QString::number(m_subtitleIndex));
|
||||
params.addQueryItem("AudioStreamIndex", QString::number(m_audioIndex));
|
||||
|
||||
QJsonObject root;
|
||||
root["DeviceProfile"] = m_apiClient->playbackDeviceProfile();
|
||||
|
||||
QNetworkReply *rep = m_apiClient->post("/Items/" + this->m_item->jellyfinId() + "/PlaybackInfo", QJsonDocument(root), params);
|
||||
connect(rep, &QNetworkReply::finished, this, [this, rep]() {
|
||||
QNetworkReply *rep = m_apiClient->post("/Items/" + item->jellyfinId() + "/PlaybackInfo", QJsonDocument(root), params);
|
||||
connect(rep, &QNetworkReply::finished, this, [this, rep, callback]() {
|
||||
QJsonObject root = QJsonDocument::fromJson(rep->readAll()).object();
|
||||
this->m_playSessionId = root["PlaySessionId"].toString();
|
||||
qDebug() << "Session id: " << this->m_playSessionId;
|
||||
|
@ -81,25 +85,35 @@ void PlaybackManager::fetchStreamUrl() {
|
|||
}
|
||||
QString streamUrl = this->m_apiClient->baseUrl() + "/" + mediaType + "/" + m_item->jellyfinId() + "/stream."
|
||||
+ firstMediaSource["Container"].toString() + "?" + query.toString(QUrl::EncodeReserved);
|
||||
setStreamUrl(streamUrl);
|
||||
this->m_playMethod = DirectPlay;
|
||||
callback(QUrl(streamUrl), DirectPlay);
|
||||
} else if (firstMediaSource["SupportsTranscoding"].toBool() && !firstMediaSource["TranscodingUrl"].isNull()) {
|
||||
QString streamUrl = this->m_apiClient->baseUrl()
|
||||
+ firstMediaSource["TranscodingUrl"].toString();
|
||||
|
||||
this->m_playMethod = Transcode;
|
||||
setStreamUrl(streamUrl);
|
||||
callback(QUrl(streamUrl), Transcode);
|
||||
} else {
|
||||
qDebug() << "No stream url found";
|
||||
return;
|
||||
}
|
||||
qDebug() << "Found stream url: " << this->m_streamUrl;
|
||||
}
|
||||
|
||||
rep->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void PlaybackManager::fetchAndSetStreamUrl(const Item *item) {
|
||||
fetchStreamUrl(item, m_autoOpen, [this, item](QUrl &&url, PlayMethod playbackMethod) {
|
||||
if (m_item == item) {
|
||||
setStreamUrl(url.toString());
|
||||
m_playMethod = playbackMethod;
|
||||
emit playMethodChanged(m_playMethod);
|
||||
m_mediaPlayer->setMedia(QMediaContent(url));
|
||||
m_mediaPlayer->play();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void PlaybackManager::setItem(Item *newItem) {
|
||||
if (m_mediaPlayer != nullptr) m_mediaPlayer->stop();
|
||||
|
||||
|
@ -109,8 +123,6 @@ void PlaybackManager::setItem(Item *newItem) {
|
|||
}
|
||||
this->m_item = newItem;
|
||||
emit itemChanged(newItem);
|
||||
// Don't try to start fetching when we're not completely parsed yet.
|
||||
if (m_qmlIsParsingComponent) return;
|
||||
|
||||
if (m_apiClient == nullptr) {
|
||||
qWarning() << "apiClient is not set on this MediaSource instance! Aborting.";
|
||||
|
@ -130,27 +142,33 @@ void PlaybackManager::setItem(Item *newItem) {
|
|||
newItem->setParent(this);
|
||||
}
|
||||
if (m_item->status() == RemoteData::Ready) {
|
||||
fetchStreamUrl();
|
||||
fetchAndSetStreamUrl(m_item);
|
||||
} else {
|
||||
connect(m_item, &RemoteData::ready, [this]() -> void {
|
||||
fetchStreamUrl();
|
||||
fetchAndSetStreamUrl(m_item);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PlaybackManager::setStreamUrl(const QString &streamUrl) {
|
||||
this->m_streamUrl = streamUrl;
|
||||
// Inspired by PHP naming schemes
|
||||
QUrl realStreamUrl(streamUrl);
|
||||
Q_ASSERT_X(realStreamUrl.isValid(), "setStreamUrl", "StreamURL Jellyfin returned is not valid");
|
||||
if (m_mediaPlayer != nullptr) {
|
||||
m_mediaPlayer->setMedia(QMediaContent(realStreamUrl));
|
||||
}
|
||||
emit streamUrlChanged(streamUrl);
|
||||
}
|
||||
|
||||
void PlaybackManager::setPlaybackState(QMediaPlayer::State newState) {
|
||||
if (newState != m_playbackState) {
|
||||
m_playbackState = newState;
|
||||
emit playbackStateChanged(newState);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaybackManager::mediaPlayerPositionChanged(qint64 position) {
|
||||
emit positionChanged(position);
|
||||
if (position == 0 && m_oldPosition != 0) {
|
||||
// Save the old position when stop gets called. The QMediaPlayer will try to set
|
||||
// position to 0 when stopped, but we don't want to report that to Jellyfin. We
|
||||
|
@ -171,6 +189,7 @@ void PlaybackManager::mediaPlayerStateChanged(QMediaPlayer::State newState) {
|
|||
// We've stopped playing the media. Post a stop signal.
|
||||
m_updateTimer.stop();
|
||||
postPlaybackInfo(Stopped);
|
||||
setPlaybackState(QMediaPlayer::StoppedState);
|
||||
} else {
|
||||
postPlaybackInfo(Progress);
|
||||
}
|
||||
|
@ -178,8 +197,10 @@ void PlaybackManager::mediaPlayerStateChanged(QMediaPlayer::State newState) {
|
|||
}
|
||||
|
||||
void PlaybackManager::mediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus newStatus) {
|
||||
emit mediaStatusChanged(newStatus);
|
||||
if (newStatus == QMediaPlayer::LoadedMedia) {
|
||||
m_mediaPlayer->play();
|
||||
setPlaybackState(playbackState());
|
||||
if (m_resumePlayback) {
|
||||
qDebug() << "Resuming playback by seeking to " << (m_resumePosition / MS_TICK_FACTOR);
|
||||
m_mediaPlayer->setPosition(m_resumePosition / MS_TICK_FACTOR);
|
||||
|
@ -187,24 +208,9 @@ void PlaybackManager::mediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus ne
|
|||
}
|
||||
}
|
||||
|
||||
void PlaybackManager::setMediaPlayer(QObject *qmlMediaPlayer) {
|
||||
if (m_mediaPlayer != nullptr) {
|
||||
// Clean up the old media player.
|
||||
disconnect(m_mediaPlayer, &QMediaPlayer::stateChanged, this, &PlaybackManager::mediaPlayerStateChanged);
|
||||
disconnect(m_mediaPlayer, &QMediaPlayer::positionChanged, this, &PlaybackManager::mediaPlayerPositionChanged);
|
||||
disconnect(m_mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, &PlaybackManager::mediaPlayerMediaStatusChanged);
|
||||
}
|
||||
|
||||
m_qmlMediaPlayer = qmlMediaPlayer;
|
||||
if (qmlMediaPlayer != nullptr) {
|
||||
m_mediaPlayer = qvariant_cast<QMediaPlayer *>(qmlMediaPlayer->property("mediaObject"));
|
||||
Q_ASSERT_X(m_mediaPlayer != nullptr, "setMediaPlayer", "The mediaPlayer property must contain a qml MediaPlayer with the mediaObject property");
|
||||
|
||||
// Connect signals from the new media player
|
||||
connect(m_mediaPlayer, &QMediaPlayer::stateChanged, this, &PlaybackManager::mediaPlayerStateChanged);
|
||||
connect(m_mediaPlayer, &QMediaPlayer::positionChanged, this, &PlaybackManager::mediaPlayerPositionChanged);
|
||||
connect(m_mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, &PlaybackManager::mediaPlayerMediaStatusChanged);
|
||||
}
|
||||
void PlaybackManager::mediaPlayerError(QMediaPlayer::Error error) {
|
||||
emit errorChanged(error);
|
||||
emit errorStringChanged(m_mediaPlayer->errorString());
|
||||
}
|
||||
|
||||
void PlaybackManager::updatePlaybackInfo() {
|
||||
|
@ -213,17 +219,45 @@ void PlaybackManager::updatePlaybackInfo() {
|
|||
|
||||
void PlaybackManager::playItem(const QString &itemId) {
|
||||
Item *newItem = new Item(itemId, m_apiClient, this);
|
||||
QString parentId = newItem->parentId();
|
||||
setItem(newItem);
|
||||
ItemModel *queue = new UserItemModel(this);
|
||||
setQueue(queue);
|
||||
connect(newItem, &Item::ready, this, [this, queue, parentId](){
|
||||
queue->setParentId(parentId);
|
||||
queue->setLimit(10000);
|
||||
queue->setApiClient(m_apiClient);
|
||||
queue->reload();
|
||||
});
|
||||
connect(queue, &BaseApiModel::ready, this, [this, queue, newItem]() {
|
||||
for (int i = 0; i < queue->size(); i++) {
|
||||
if (queue->at(i)->jellyfinId() == newItem->jellyfinId()) {
|
||||
m_queueIndex = i;
|
||||
emit queueIndexChanged(m_queueIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
setPlaybackState(QMediaPlayer::PlayingState);
|
||||
}
|
||||
|
||||
void PlaybackManager::playItemInList(ItemModel *playlist, int itemIdx) {
|
||||
playlist->setParent(this);
|
||||
setQueue(playlist);
|
||||
m_queueIndex = itemIdx;
|
||||
emit queueIndexChanged(m_queueIndex);
|
||||
setItem(playlist->at(itemIdx));
|
||||
}
|
||||
|
||||
void PlaybackManager::next() {
|
||||
Q_UNIMPLEMENTED();
|
||||
Q_UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
void PlaybackManager::previous() {
|
||||
Q_UNIMPLEMENTED();
|
||||
Q_UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
|
||||
void PlaybackManager::postPlaybackInfo(PlaybackInfoType type) {
|
||||
QJsonObject root;
|
||||
|
||||
|
@ -272,15 +306,62 @@ void PlaybackManager::postPlaybackInfo(PlaybackInfoType type) {
|
|||
m_apiClient->setDefaultErrorHandler(rep);
|
||||
}
|
||||
|
||||
void PlaybackManager::swapMediaPlayer() {
|
||||
if (m_mediaPlayer != nullptr) {
|
||||
disconnect(m_mediaPlayer, &QMediaPlayer::stateChanged, this, &PlaybackManager::mediaPlayerStateChanged);
|
||||
disconnect(m_mediaPlayer, &QMediaPlayer::positionChanged, this, &PlaybackManager::mediaPlayerPositionChanged);
|
||||
disconnect(m_mediaPlayer, &QMediaPlayer::durationChanged, this, &PlaybackManager::durationChanged);
|
||||
disconnect(m_mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, &PlaybackManager::mediaPlayerMediaStatusChanged);
|
||||
disconnect(m_mediaPlayer, &QMediaPlayer::videoAvailableChanged, this, &PlaybackManager::hasVideoChanged);
|
||||
// I do not like the complicated overload cast
|
||||
disconnect(m_mediaPlayer, SIGNAL(error(QMediaPlayer::error)), this, SLOT(mediaPlayerError(QmediaPlayer::error)));
|
||||
}
|
||||
if (m_mediaPlayer == m_mediaPlayer1) {
|
||||
m_mediaPlayer = m_mediaPlayer2;
|
||||
emit mediaPlayerChanged(m_mediaPlayer);
|
||||
} else {
|
||||
m_mediaPlayer = m_mediaPlayer1;
|
||||
emit mediaPlayerChanged(m_mediaPlayer);
|
||||
}
|
||||
connect(m_mediaPlayer, &QMediaPlayer::stateChanged, this, &PlaybackManager::mediaPlayerStateChanged);
|
||||
connect(m_mediaPlayer, &QMediaPlayer::positionChanged, this, &PlaybackManager::mediaPlayerPositionChanged);
|
||||
connect(m_mediaPlayer, &QMediaPlayer::durationChanged, this, &PlaybackManager::durationChanged);
|
||||
connect(m_mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, &PlaybackManager::mediaPlayerMediaStatusChanged);
|
||||
connect(m_mediaPlayer, &QMediaPlayer::videoAvailableChanged, this, &PlaybackManager::hasVideoChanged);
|
||||
// I do not like the complicated overload cast
|
||||
connect(m_mediaPlayer, SIGNAL(error(QMediaPlayer::error)), this, SLOT(mediaPlayerError(QmediaPlayer::error)));
|
||||
}
|
||||
|
||||
Item *PlaybackManager::nextItem() {
|
||||
if (m_queue == nullptr) return nullptr;
|
||||
// TODO: shuffle etc.
|
||||
if (m_queueIndex < m_queue->size()) {
|
||||
return m_queue->at(m_queueIndex + 1);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PlaybackManager::setQueue(ItemModel *model) {
|
||||
if (m_queue != nullptr) {
|
||||
if (QQmlEngine::objectOwnership(m_queue) == QQmlEngine::CppOwnership) {
|
||||
m_queue->deleteLater();
|
||||
} else {
|
||||
m_queue->setParent(nullptr);
|
||||
}
|
||||
}
|
||||
m_queue = model;
|
||||
emit queueChanged(m_queue);
|
||||
}
|
||||
|
||||
void PlaybackManager::componentComplete() {
|
||||
if (m_apiClient == nullptr) qWarning() << "No ApiClient set for PlaybackManager";
|
||||
m_qmlIsParsingComponent = false;
|
||||
if (m_item != nullptr) {
|
||||
if (m_item->status() == RemoteData::Ready) {
|
||||
fetchStreamUrl();
|
||||
fetchAndSetStreamUrl(m_item);
|
||||
} else {
|
||||
connect(m_item, &RemoteData::ready, [this]() -> void {
|
||||
fetchStreamUrl();
|
||||
fetchAndSetStreamUrl(m_item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,9 +105,11 @@ void WebSocket::textMessageReceived(const QString &message) {
|
|||
}
|
||||
QJsonArray userDataList = data2["UserDataList"].toArray();
|
||||
for (QJsonValue val: userDataList) {
|
||||
QSharedPointer<DTO::UserData> userData(new DTO::UserData, &QObject::deleteLater);
|
||||
UserData* userData =new DTO::UserData;
|
||||
userData->deserialize(val.toObject());
|
||||
userData->setParent(this);
|
||||
m_apiClient->onUserDataChanged(userData->itemId(), userData);
|
||||
userData->deleteLater();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue