1
0
Fork 0
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:
Chris Josten 2021-05-21 15:46:30 +02:00
parent 228f81984b
commit fbc154fb56
16 changed files with 481 additions and 27 deletions

View file

@ -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),

View file

@ -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

View 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