mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2025-09-04 01:42:44 +00:00
WIP: Playlist support
This commit is contained in:
parent
228f81984b
commit
fbc154fb56
16 changed files with 481 additions and 27 deletions
|
@ -430,7 +430,7 @@ public:
|
|||
}
|
||||
|
||||
// QList-like API
|
||||
T& at(int index) { return m_array.at(index); }
|
||||
const T& at(int index) { return m_array.at(index); }
|
||||
/**
|
||||
* @return the amount of objects in this model.
|
||||
*/
|
||||
|
@ -453,6 +453,10 @@ public:
|
|||
this->endInsertRows();
|
||||
};
|
||||
|
||||
QList<T> mid(int pos, int length = -1) {
|
||||
return m_array.mid(pos, length);
|
||||
}
|
||||
|
||||
void removeAt(int index) {
|
||||
Q_ASSERT(index < size());
|
||||
this->beginRemoveRows(QModelIndex(), index, index);
|
||||
|
|
|
@ -42,6 +42,12 @@ signals:
|
|||
* @param userData The new userData
|
||||
*/
|
||||
void itemUserDataUpdated(const QString &itemId, const DTO::UserItemDataDto &userData);
|
||||
|
||||
/**
|
||||
* @brief The server has requested to display an message to the user
|
||||
* @param message The message to show.
|
||||
*/
|
||||
void displayMessage(const QString &message);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
42
core/include/JellyfinQt/model/playlist.h
Normal file
42
core/include/JellyfinQt/model/playlist.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#ifndef JELLYFIN_MODEL_PLAYLIST_H
|
||||
#define JELLYFIN_MODEL_PLAYLIST_H
|
||||
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
|
||||
|
||||
namespace Jellyfin {
|
||||
namespace Model {
|
||||
|
||||
// Forward declaration
|
||||
class Item;
|
||||
|
||||
class Playlist {
|
||||
public:
|
||||
explicit Playlist();
|
||||
|
||||
/// Start loading data for the next item.
|
||||
void preloadNext();
|
||||
|
||||
|
||||
private:
|
||||
/// Extra data about each itemId that this playlist manages
|
||||
struct ExtendedItem {
|
||||
QSharedPointer<Item> item;
|
||||
/// The url from which this item can be streamed.
|
||||
QUrl url;
|
||||
/// Playsession that should be reported to Jellyfin's server.
|
||||
QString playSession;
|
||||
/// Text to be shown when an error occurred while fetching playback information.
|
||||
QString errorText;
|
||||
};
|
||||
|
||||
QVector<ExtendedItem> list;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // JELLYFIN_MODEL_PLAYLIST_H
|
|
@ -162,12 +162,14 @@ public:
|
|||
extraType,
|
||||
|
||||
// Hand-picked, important ones
|
||||
imageTags
|
||||
imageTags,
|
||||
|
||||
jellyfinExtendModelAfterHere = Qt::UserRole + 300 // Should be enough for now
|
||||
};
|
||||
|
||||
explicit ItemModel (QObject *parent = nullptr);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override {
|
||||
virtual QHash<int, QByteArray> roleNames() const override {
|
||||
return {
|
||||
JFRN(jellyfinId),
|
||||
JFRN(name),
|
||||
|
|
|
@ -94,7 +94,7 @@ public:
|
|||
Q_PROPERTY(QMediaPlayer::State playbackState READ playbackState NOTIFY playbackStateChanged)
|
||||
Q_PROPERTY(qint64 position READ position NOTIFY positionChanged)
|
||||
|
||||
ViewModel::Item *item() const { return m_displayItem.get(); }
|
||||
ViewModel::Item *item() const { return m_displayItem; }
|
||||
void setApiClient(ApiClient *apiClient);
|
||||
|
||||
QString streamUrl() const { return m_streamUrl; }
|
||||
|
@ -134,7 +134,9 @@ signals:
|
|||
void errorStringChanged(const QString &newErrorString);
|
||||
public slots:
|
||||
/**
|
||||
* @brief playItem Plays the item with the given id. This will construct the Jellyfin::Item internally
|
||||
* @brief playItem Replaces the current queue and plays the item with the given id.
|
||||
*
|
||||
* This will construct the Jellyfin::Item internally
|
||||
* and delete it later.
|
||||
* @param itemId The id of the item to play.
|
||||
*/
|
||||
|
@ -166,39 +168,57 @@ private slots:
|
|||
void updatePlaybackInfo();
|
||||
|
||||
private:
|
||||
/// Factor to multiply with when converting from milliseconds to ticks.
|
||||
const static int MS_TICK_FACTOR = 10000;
|
||||
enum PlaybackInfoType { Started, Stopped, Progress };
|
||||
|
||||
QTimer m_updateTimer;
|
||||
ApiClient *m_apiClient = nullptr;
|
||||
QSharedPointer<Model::Item> m_item;
|
||||
QScopedPointer<ViewModel::Item> m_displayItem = QScopedPointer<ViewModel::Item>(new ViewModel::Item());
|
||||
ViewModel::Item *m_displayItem = new ViewModel::Item(this);
|
||||
|
||||
// Properties for making the streaming request.
|
||||
QString m_streamUrl;
|
||||
QString m_playSessionId;
|
||||
/// The index of the mediastreams of the to-be-played item containing the audio
|
||||
int m_audioIndex = 0;
|
||||
/// The index of the mediastreams of the to-be-played item containing subtitles
|
||||
int m_subtitleIndex = -1;
|
||||
/// The position in ticks to resume playback from
|
||||
qint64 m_resumePosition = 0;
|
||||
qint64 m_oldPosition = 0;
|
||||
/// The position in ticks the playback was stopped
|
||||
qint64 m_stopPosition = 0;
|
||||
|
||||
/// Keeps track of latest playback position
|
||||
qint64 m_oldPosition = 0;
|
||||
/**
|
||||
* @brief Whether to automatically open the livestream of the item;
|
||||
*/
|
||||
bool m_autoOpen = false;
|
||||
|
||||
|
||||
// Playback-related members
|
||||
QMediaPlayer::State m_oldState = QMediaPlayer::StoppedState;
|
||||
PlayMethod m_playMethod = Transcode;
|
||||
QMediaPlayer::State m_playbackState = QMediaPlayer::StoppedState;
|
||||
// Pointer to the current media player.
|
||||
/// Pointer to the current media player.
|
||||
QMediaPlayer *m_mediaPlayer = nullptr;
|
||||
|
||||
// There are 2 media players over here, so one is able to preload the next song
|
||||
// before the other starts playing
|
||||
|
||||
/// Media player 1
|
||||
QMediaPlayer *m_mediaPlayer1;
|
||||
/// Media player 2
|
||||
QMediaPlayer *m_mediaPlayer2;
|
||||
ItemModel *m_queue = nullptr;
|
||||
int m_queueIndex = 0;
|
||||
bool m_resumePlayback = true;
|
||||
|
||||
// Helper methods
|
||||
void setItem(ViewModel::Item *newItem);
|
||||
void swapMediaPlayer();
|
||||
|
||||
bool m_qmlIsParsingComponent = false;
|
||||
|
||||
/**
|
||||
* @brief Whether to automatically open the livestream of the item;
|
||||
*/
|
||||
bool m_autoOpen = false;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the URL of the stream to open.
|
||||
|
@ -211,10 +231,7 @@ private:
|
|||
Model::Item *nextItem();
|
||||
void setQueue(ItemModel *itemModel);
|
||||
|
||||
// Factor to multiply with when converting from milliseconds to ticks.
|
||||
const static int MS_TICK_FACTOR = 10000;
|
||||
|
||||
enum PlaybackInfoType { Started, Stopped, Progress };
|
||||
|
||||
/**
|
||||
* @brief Posts the playback information
|
||||
|
@ -222,10 +239,10 @@ private:
|
|||
void postPlaybackInfo(PlaybackInfoType type);
|
||||
|
||||
|
||||
void classBegin() override {
|
||||
m_qmlIsParsingComponent = true;
|
||||
}
|
||||
// QQmlParserListener interface
|
||||
void classBegin() override { m_qmlIsParsingComponent = true; }
|
||||
void componentComplete() override;
|
||||
bool m_qmlIsParsingComponent = false;
|
||||
};
|
||||
|
||||
} // NS ViewModel
|
||||
|
|
139
core/include/JellyfinQt/viewmodel/playlist.h
Normal file
139
core/include/JellyfinQt/viewmodel/playlist.h
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Sailfin: a Jellyfin client written using Qt
|
||||
* Copyright (C) 2021 Chris Josten and the Sailfin Contributors.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef JELLYFIN_VIEWMODEL_PLAYLIST
|
||||
#define JELLYFIN_VIEWMODEL_PLAYLIST
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QAtomicInteger>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
#include <QWaitCondition>
|
||||
#include <QtMultimedia/QMediaPlaylist>
|
||||
|
||||
#include "../dto/playbackinfodto.h"
|
||||
#include "../loader/requesttypes.h"
|
||||
#include "../loader/http/getpostedplaybackinfo.h"
|
||||
#include "itemmodel.h"
|
||||
|
||||
namespace Jellyfin {
|
||||
namespace ViewModel {
|
||||
|
||||
class ItemUrlFetcherThread;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Playlist/queue that can be exposed to the UI. It also containts the playlist-related logic,
|
||||
* which is mostly relevant
|
||||
*/
|
||||
class Playlist : public ItemModel {
|
||||
Q_OBJECT
|
||||
friend class ItemUrlFetcherThread;
|
||||
public:
|
||||
explicit Playlist(ApiClient *apiClient, QObject *parent = nullptr);
|
||||
enum ExtraRoles {
|
||||
Url = ItemModel::RoleNames::jellyfinExtendModelAfterHere + 1,
|
||||
PlaySession,
|
||||
ErrorText
|
||||
};
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override {
|
||||
QHash<int, QByteArray> result(ItemModel::roleNames());
|
||||
result.insert(Url, "url");
|
||||
result.insert(PlaySession, "playSession");
|
||||
result.insert(ErrorText, "errorText");
|
||||
return result;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void onItemsAdded(const QModelIndex &parent, int startIndex, int endIndex);
|
||||
void onItemsMoved(const QModelIndex &parent, int startIndex, int endIndex, const QModelIndex &destination, int destinationRow);
|
||||
void onItemsRemoved(const QModelIndex &parent, int startIndex, int endIndex);
|
||||
void onItemsReset();
|
||||
/// Called when the fetcherThread has fetched the playback URL and playSession
|
||||
void onItemExtraDataReceived(const QString &itemId, const QUrl &url, const QString &playSession);
|
||||
/// Called when the fetcherThread encountered an error
|
||||
void onItemErrorReceived(const QString &itemId, const QString &errorString);
|
||||
private:
|
||||
/// Map from ItemId to ExtraData
|
||||
QHash<QString, ExtraData> m_cache;
|
||||
|
||||
ApiClient *m_apiClient;
|
||||
|
||||
/// Thread that fetches the URLS asynchronously
|
||||
ItemUrlFetcherThread *m_fetcherThread;
|
||||
};
|
||||
|
||||
/// Thread that fetches the Item's stream URL always in the given order they were requested
|
||||
class ItemUrlFetcherThread : public QThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ItemUrlFetcherThread(Playlist *playlist);
|
||||
|
||||
/**
|
||||
* @brief Adds an item to the queue of items that should be requested
|
||||
* @param item The item to fetch the URL of
|
||||
*/
|
||||
void addItemToQueue(const Model::Item item);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Emitted when the url of the item with the itemId has been retrieved.
|
||||
* @param itemId The id of the item of which the URL has been retrieved
|
||||
* @param itemUrl The retrieved url
|
||||
* @param playSession The playsession set by the Jellyfin Server
|
||||
*/
|
||||
void itemUrlFetched(QString itemId, QUrl itemUrl, QString playSession);
|
||||
void itemUrlFetchError(QString itemId, QString errorString);
|
||||
|
||||
void prepareLoaderRequested(QPrivateSignal);
|
||||
public slots:
|
||||
/**
|
||||
* @brief Ask the thread nicely to stop running.
|
||||
*/
|
||||
void cleanlyStop();
|
||||
private slots:
|
||||
void onPrepareLoader();
|
||||
protected:
|
||||
void run() override;
|
||||
private:
|
||||
Playlist *m_parent;
|
||||
Support::Loader<DTO::PlaybackInfoResponse, Jellyfin::Loader::GetPostedPlaybackInfoParams> *m_loader;
|
||||
|
||||
QMutex m_queueModifyMutex;
|
||||
QQueue<const Model::Item&> m_queue;
|
||||
|
||||
QMutex m_urlWaitConditionMutex;
|
||||
/// WaitCondition on which this threads waits until an Item is put into the queue
|
||||
QWaitCondition m_urlWaitCondition;
|
||||
|
||||
QMutex m_waitLoaderPreparedMutex;
|
||||
/// WaitCondition on which this threads waits until the loader has been prepared.
|
||||
QWaitCondition m_waitLoaderPrepared;
|
||||
|
||||
bool m_keepRunning = true;
|
||||
bool m_loaderPrepared = false;
|
||||
};
|
||||
|
||||
} // NS ViewModel
|
||||
} // NS Jellyfin
|
||||
|
||||
#endif //JELLYFIN_VIEWMODEL_PLAYLIST
|
Loading…
Add table
Add a link
Reference in a new issue