701 lines
24 KiB
C++
701 lines
24 KiB
C++
/*
|
|
* Sailfin: a Jellyfin client written using Qt
|
|
* Copyright (C) 2021-2022 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
|
|
*/
|
|
|
|
#include <JellyfinQt/model/playbackmanager.h>
|
|
|
|
#include <QTimer>
|
|
|
|
#include <JellyfinQt/dto/playbackinforesponse.h>
|
|
#include <JellyfinQt/loader/http/userlibrary.h>
|
|
#include <JellyfinQt/loader/http/mediainfo.h>
|
|
#include <JellyfinQt/model/playbackreporter.h>
|
|
#include <JellyfinQt/model/playlist.h>
|
|
#include <JellyfinQt/viewmodel/settings.h>
|
|
|
|
namespace Jellyfin {
|
|
namespace Model {
|
|
|
|
Q_LOGGING_CATEGORY(playbackManager, "jellyfinqt.model.playbackmanager");
|
|
|
|
class PlaybackManagerPrivate {
|
|
Q_DECLARE_PUBLIC(PlaybackManager);
|
|
public:
|
|
PlaybackManagerPrivate(PlaybackManager *q);
|
|
ApiClient *m_apiClient = nullptr;
|
|
|
|
/// Timer used to notify ourselves when we need to preload the next item
|
|
QTimer m_preloadTimer;
|
|
|
|
PlaybackManagerError m_error;
|
|
QString m_errorString;
|
|
QString m_playSessionId;
|
|
QString m_nextPlaySessionId;
|
|
|
|
/// 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 currently playing item
|
|
QSharedPointer<Model::Item> m_item;
|
|
/// The item that will be played next
|
|
QSharedPointer<Model::Item> m_nextItem;
|
|
|
|
PlayerState m_state;
|
|
|
|
Model::Playlist *m_queue = nullptr;
|
|
int m_queueIndex = 0;
|
|
|
|
bool m_resumePlayback = false;
|
|
/// The position in ticks to resume playback from
|
|
qint64 m_resumePosition = 0;
|
|
|
|
bool m_handlePlaystateCommands = true;
|
|
|
|
PlaybackManager *q_ptr;
|
|
|
|
virtual void setItem(QSharedPointer<Model::Item> newItem);
|
|
void skipToItemIndex(int index);
|
|
void setState(PlayerState newState);
|
|
};
|
|
|
|
PlaybackManagerPrivate::PlaybackManagerPrivate(PlaybackManager *q)
|
|
: q_ptr(q) {
|
|
}
|
|
|
|
|
|
void PlaybackManagerPrivate::setItem(QSharedPointer<Model::Item> newItem) {
|
|
Q_Q(PlaybackManager);
|
|
this->m_item = newItem;
|
|
emit q->itemChanged();
|
|
}
|
|
|
|
void PlaybackManagerPrivate::skipToItemIndex(int index) {
|
|
Q_Q(PlaybackManager);
|
|
if (index < m_queue->queueSize()) {
|
|
// Skip until we hit the right number in the queue
|
|
index++;
|
|
while(index != 0) {
|
|
m_queue->next();
|
|
index--;
|
|
}
|
|
} else {
|
|
m_queue->play(index);
|
|
}
|
|
setItem(m_queue->currentItem());
|
|
emit q->hasNextChanged(m_queue->hasNext());
|
|
emit q->hasPreviousChanged(m_queue->hasPrevious());
|
|
|
|
}
|
|
|
|
void PlaybackManagerPrivate::setState(PlayerState newState) {
|
|
Q_Q(PlaybackManager);
|
|
|
|
m_state = newState;
|
|
emit q->playbackStateChanged(newState);
|
|
}
|
|
/*****************************************************************************
|
|
* PlaybackManager *
|
|
*****************************************************************************/
|
|
|
|
PlaybackManager::PlaybackManager(QObject *parent)
|
|
: PlaybackManager(new PlaybackManagerPrivate(this), parent) {
|
|
Q_D(PlaybackManager);
|
|
}
|
|
|
|
PlaybackManager::PlaybackManager(PlaybackManagerPrivate *d, QObject *parent)
|
|
: QObject(parent) {
|
|
QScopedPointer<PlaybackManagerPrivate> foo(d);
|
|
d_ptr.swap(foo);
|
|
d_ptr->m_queue = new Playlist(this);
|
|
}
|
|
|
|
PlaybackManager::~PlaybackManager() {}
|
|
|
|
ApiClient *PlaybackManager::apiClient() const {
|
|
const Q_D(PlaybackManager);
|
|
return d->m_apiClient;
|
|
}
|
|
|
|
void PlaybackManager::setApiClient(ApiClient *apiClient) {
|
|
Q_D(PlaybackManager);
|
|
d->m_apiClient = apiClient;
|
|
}
|
|
|
|
QSharedPointer<Item> PlaybackManager::currentItem() const {
|
|
const Q_D(PlaybackManager);
|
|
return d->m_item;
|
|
}
|
|
|
|
Playlist *PlaybackManager::queue() const {
|
|
const Q_D(PlaybackManager);
|
|
return d->m_queue;
|
|
}
|
|
|
|
int PlaybackManager::queueIndex() const {
|
|
const Q_D(PlaybackManager);
|
|
return d->m_queueIndex;
|
|
}
|
|
|
|
void PlaybackManager::playItemId(const QString &id) {}
|
|
|
|
bool PlaybackManager::resumePlayback() const {
|
|
const Q_D(PlaybackManager);
|
|
return d->m_resumePlayback;
|
|
}
|
|
|
|
void PlaybackManager::setResumePlayback(bool newResumePlayback) {
|
|
Q_D(PlaybackManager);
|
|
d->m_resumePlayback = newResumePlayback;
|
|
emit resumePlaybackChanged(newResumePlayback);
|
|
}
|
|
|
|
int PlaybackManager::audioIndex() const {
|
|
const Q_D(PlaybackManager);
|
|
return d->m_audioIndex;
|
|
}
|
|
|
|
void PlaybackManager::setAudioIndex(int newAudioIndex) {
|
|
Q_D(PlaybackManager);
|
|
d->m_audioIndex = newAudioIndex;
|
|
emit audioIndexChanged(newAudioIndex);
|
|
}
|
|
|
|
int PlaybackManager::subtitleIndex() const {
|
|
const Q_D(PlaybackManager);
|
|
return d->m_subtitleIndex;
|
|
}
|
|
|
|
void PlaybackManager::setSubtitleIndex(int newSubtitleIndex) {
|
|
Q_D(PlaybackManager);
|
|
d->m_subtitleIndex = newSubtitleIndex;
|
|
emit subtitleIndexChanged(newSubtitleIndex);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* LocalPlaybackManagerPrivate *
|
|
*****************************************************************************/
|
|
|
|
class LocalPlaybackManagerPrivate : public PlaybackManagerPrivate {
|
|
Q_DECLARE_PUBLIC(LocalPlaybackManager);
|
|
public:
|
|
explicit LocalPlaybackManagerPrivate(LocalPlaybackManager *q);
|
|
Player *m_mediaPlayer;
|
|
// Properties for making the streaming request.
|
|
QUrl m_streamUrl;
|
|
QUrl m_nextStreamUrl;
|
|
DTO::PlayMethod m_playMethod = DTO::PlayMethod::Transcode;
|
|
|
|
|
|
void setItem(QSharedPointer<Model::Item> newItem) override;
|
|
void setStreamUrl(const QUrl &streamUrl);
|
|
void requestItemUrl(QSharedPointer<Model::Item> item);
|
|
|
|
// slots
|
|
void handlePlaybackInfoResponse(QString itemId, QString mediaType, DTO::PlaybackInfoResponse &response);
|
|
/// Called when we have fetched the playback URL and playSession
|
|
void onItemUrlReceived(const QString &itemId, const QUrl &url, const QString &playSession,
|
|
// Fully specify class to please MOC
|
|
Jellyfin::DTO::PlayMethodClass::Value playMethod);
|
|
/// Called when we have encountered an error
|
|
void onItemErrorReceived(const QString &itemId, const QString &errorString);
|
|
|
|
|
|
|
|
/**
|
|
* @brief Whether to automatically open the livestream of the item;
|
|
*/
|
|
bool m_autoOpen = false;
|
|
PlaybackReporter *m_reporter = nullptr;
|
|
public slots:
|
|
void onPlayerError();
|
|
void onMediaStatusChanged(Jellyfin::Model::MediaStatusClass::Value newMediaStatus);
|
|
};
|
|
|
|
LocalPlaybackManagerPrivate::LocalPlaybackManagerPrivate(LocalPlaybackManager *q)
|
|
: PlaybackManagerPrivate(q),
|
|
m_reporter(new PlaybackReporter()){
|
|
}
|
|
|
|
void LocalPlaybackManagerPrivate::setStreamUrl(const QUrl &streamUrl) {
|
|
Q_Q(LocalPlaybackManager);
|
|
|
|
m_streamUrl = streamUrl;
|
|
Q_ASSERT_X(streamUrl.isValid() || streamUrl.isEmpty(), "setStreamUrl", "StreamURL Jellyfin returned is not valid");
|
|
emit q->streamUrlChanged(m_streamUrl);
|
|
}
|
|
|
|
void LocalPlaybackManagerPrivate::requestItemUrl(QSharedPointer<Model::Item> item) {
|
|
Q_Q(LocalPlaybackManager);
|
|
using ItemUrlLoader = Support::Loader<DTO::PlaybackInfoResponse, Jellyfin::Loader::GetPostedPlaybackInfoParams>;
|
|
ItemUrlLoader *loader = new Jellyfin::Loader::HTTP::GetPostedPlaybackInfoLoader(m_apiClient);
|
|
Jellyfin::Loader::GetPostedPlaybackInfoParams params;
|
|
|
|
|
|
// Check if we'd prefer to transcode if the video file contains multiple audio tracks
|
|
// or if a subtitle track was selected.
|
|
// This has to be done due to the lack of support of selecting audio tracks within QtMultimedia
|
|
bool allowTranscoding = m_apiClient->settings()->allowTranscoding();
|
|
bool transcodePreferred = m_subtitleIndex > 0;
|
|
int audioTracks = 0;
|
|
const QList<DTO::MediaStream> &streams = item->mediaStreams();
|
|
for(int i = 0; i < streams.size(); i++) {
|
|
const DTO::MediaStream &stream = streams[i];
|
|
if (stream.type() == MediaStreamType::Audio) {
|
|
audioTracks++;
|
|
}
|
|
}
|
|
if (audioTracks > 1) {
|
|
transcodePreferred = true;
|
|
}
|
|
|
|
bool forceTranscoding = allowTranscoding && transcodePreferred;
|
|
|
|
QSharedPointer<DTO::PlaybackInfoDto> playbackInfo = QSharedPointer<DTO::PlaybackInfoDto>::create(m_apiClient->deviceProfile());
|
|
params.setItemId(item->jellyfinId());
|
|
params.setUserId(m_apiClient->userId());
|
|
playbackInfo->setEnableDirectPlay(true);
|
|
playbackInfo->setEnableDirectStream(!forceTranscoding);
|
|
playbackInfo->setEnableTranscoding(forceTranscoding || allowTranscoding);
|
|
playbackInfo->setAudioStreamIndex(this->m_audioIndex);
|
|
playbackInfo->setSubtitleStreamIndex(this->m_subtitleIndex);
|
|
params.setBody(playbackInfo);
|
|
|
|
loader->setParameters(params);
|
|
q->connect(loader, &ItemUrlLoader::ready, q, [this, loader, item] {
|
|
DTO::PlaybackInfoResponse result = loader->result();
|
|
handlePlaybackInfoResponse(item->jellyfinId(), item->mediaType(), result);
|
|
loader->deleteLater();
|
|
});
|
|
q->connect(loader, &ItemUrlLoader::error, q, [this, loader, item](QString message) {
|
|
onItemErrorReceived(item->jellyfinId(), message);
|
|
loader->deleteLater();
|
|
});
|
|
loader->load();
|
|
}
|
|
|
|
void LocalPlaybackManagerPrivate::setItem(QSharedPointer<Model::Item> newItem) {
|
|
Q_Q(LocalPlaybackManager);
|
|
if (m_mediaPlayer != nullptr) m_mediaPlayer->stop();
|
|
bool shouldFetchStreamUrl = !newItem.isNull()
|
|
&& ((m_streamUrl.isEmpty() || (!m_item.isNull()
|
|
&& m_item->jellyfinId() != newItem->jellyfinId()))
|
|
|| (m_nextStreamUrl.isEmpty() || (!m_nextItem.isNull()
|
|
&& m_nextItem->jellyfinId() != newItem->jellyfinId())));
|
|
|
|
this->m_item = newItem;
|
|
|
|
if (!newItem.isNull()) {
|
|
if (!newItem->userData().isNull()) {
|
|
m_resumePosition = newItem->userData()->playbackPositionTicks();
|
|
}
|
|
}
|
|
emit q->itemChanged();
|
|
|
|
emit q->hasNextChanged(m_queue->hasNext());
|
|
emit q->hasPreviousChanged(m_queue->hasPrevious());
|
|
|
|
if (m_apiClient == nullptr) {
|
|
|
|
qCWarning(playbackManager) << "apiClient is not set on this playbackmanager instance! Aborting.";
|
|
return;
|
|
}
|
|
// Deinitialize the streamUrl
|
|
if (shouldFetchStreamUrl) {
|
|
qCDebug(playbackManager) << "Fetching streamUrl before playing";
|
|
setStreamUrl(QUrl());
|
|
requestItemUrl(m_item);
|
|
} else {
|
|
qCDebug(playbackManager) << "StreamUrl already fetched, playing!";
|
|
setStreamUrl(m_nextStreamUrl);
|
|
if (m_mediaPlayer != nullptr) m_mediaPlayer->play();
|
|
}
|
|
}
|
|
|
|
void LocalPlaybackManagerPrivate::handlePlaybackInfoResponse(QString itemId, QString mediaType, DTO::PlaybackInfoResponse &response) {
|
|
Q_Q(LocalPlaybackManager);
|
|
//TODO: move the item URL fetching logic out of this function, into MediaSourceInfo?
|
|
QList<DTO::MediaSourceInfo> mediaSources = response.mediaSources();
|
|
QUrl resultingUrl;
|
|
QString playSession = response.playSessionId();
|
|
PlayMethod playMethod = PlayMethod::EnumNotSet;
|
|
bool transcodingAllowed = m_apiClient->settings()->allowTranscoding();
|
|
|
|
for (int i = 0; i < mediaSources.size(); i++) {
|
|
const DTO::MediaSourceInfo &source = mediaSources.at(i);
|
|
|
|
// Check if we'd prefer to transcode if the video file contains multiple audio tracks
|
|
// or if a subtitle track was selected.
|
|
// This has to be done due to the lack of support of selecting audio tracks within QtMultimedia
|
|
bool transcodePreferred = false;
|
|
if (transcodingAllowed) {
|
|
transcodePreferred = m_subtitleIndex > 0;
|
|
int audioTracks = 0;
|
|
const QList<DTO::MediaStream> &streams = source.mediaStreams();
|
|
for (int i = 0; i < streams.size(); i++) {
|
|
DTO::MediaStream stream = streams[i];
|
|
if (stream.type() == MediaStreamType::Audio) {
|
|
audioTracks++;
|
|
}
|
|
}
|
|
if (audioTracks > 1) {
|
|
transcodePreferred = false;
|
|
}
|
|
}
|
|
|
|
|
|
qCDebug(playbackManager()) << "Media source: " << source.name() << "\n"
|
|
<< "Prefer transcoding: " << transcodePreferred << "\n"
|
|
<< "DirectPlay supported: " << source.supportsDirectPlay() << "\n"
|
|
<< "DirectStream supported: " << source.supportsDirectStream() << "\n"
|
|
<< "Transcode supported: " << source.supportsTranscoding() << source.transcodingUrl();
|
|
|
|
if (source.supportsDirectPlay() && QFile::exists(source.path())) {
|
|
resultingUrl = QUrl::fromLocalFile(source.path());
|
|
playMethod = PlayMethod::DirectPlay;
|
|
} else if (source.supportsDirectStream() && !transcodePreferred) {
|
|
if (mediaType == "Video") {
|
|
mediaType.append('s');
|
|
}
|
|
QUrlQuery query;
|
|
query.addQueryItem("mediaSourceId", source.jellyfinId());
|
|
query.addQueryItem("deviceId", m_apiClient->deviceId());
|
|
query.addQueryItem("api_key", m_apiClient->token());
|
|
query.addQueryItem("Static", "True");
|
|
resultingUrl = QUrl(m_apiClient->baseUrl() + "/" + mediaType + "/" + itemId
|
|
+ "/stream." + source.container() + "?" + query.toString(QUrl::EncodeReserved));
|
|
playMethod = PlayMethod::DirectStream;
|
|
} else if (source.supportsTranscoding() && !source.transcodingUrlNull() && transcodingAllowed) {
|
|
qCDebug(playbackManager) << "Transcoding url: " << source.transcodingUrl();
|
|
resultingUrl = QUrl(m_apiClient->baseUrl() + source.transcodingUrl());
|
|
playMethod = PlayMethod::Transcode;
|
|
} else {
|
|
qCDebug(playbackManager) << "No suitable sources for item " << itemId;
|
|
}
|
|
if (!resultingUrl.isEmpty()) break;
|
|
}
|
|
if (resultingUrl.isEmpty()) {
|
|
qCWarning(playbackManager) << "Could not find suitable media source for item " << itemId;
|
|
onItemErrorReceived(itemId, q->tr("Could not find a suitable media source."));
|
|
} else {
|
|
emit q->playMethodChanged(playMethod);
|
|
onItemUrlReceived(itemId, resultingUrl, playSession, playMethod);
|
|
}
|
|
}
|
|
|
|
void LocalPlaybackManagerPrivate::onItemUrlReceived(const QString &itemId, const QUrl &url, const QString &playSession, Jellyfin::DTO::PlayMethodClass::Value playMethod) {
|
|
Q_Q(LocalPlaybackManager);
|
|
Q_UNUSED(playSession)
|
|
qCDebug(playbackManager) << "Item URL received for item" << itemId;
|
|
if (!m_item.isNull() && m_item->jellyfinId() == itemId) {
|
|
// We want to play the item probably right now
|
|
m_playSessionId = playSession;
|
|
m_playMethod = playMethod;
|
|
m_resumePosition = m_item->userData()->playbackPositionTicks();
|
|
setStreamUrl(url);
|
|
qCDebug(playbackManager) << "Starting playback!";
|
|
emit q->playMethodChanged(m_playMethod);
|
|
|
|
// Clear the error string if it is currently set
|
|
if (!m_errorString.isEmpty()) {
|
|
m_errorString.clear();
|
|
emit q->errorStringChanged(m_errorString);
|
|
}
|
|
|
|
if (m_error != PlaybackManagerError::NoError) {
|
|
m_error = PlaybackManagerError::NoError;
|
|
emit q->errorChanged(m_error);
|
|
}
|
|
|
|
m_mediaPlayer->setMedia(url, m_audioIndex, m_subtitleIndex);
|
|
m_mediaPlayer->play(m_resumePosition);
|
|
m_resumePosition = 0;
|
|
} else {
|
|
qDebug() << "Late reply for " << itemId << " received, ignoring";
|
|
}
|
|
}
|
|
|
|
void LocalPlaybackManagerPrivate::onItemErrorReceived(const QString &itemId, const QString &errorString) {
|
|
Q_Q(LocalPlaybackManager);
|
|
qWarning() << "Error while fetching streaming url for " << itemId << ": " << errorString;
|
|
if (!m_item.isNull() && m_item->jellyfinId() == itemId) {
|
|
setStreamUrl(QUrl());
|
|
m_error = PlaybackManagerError::PlaybackInfoError;
|
|
emit q->errorChanged(PlaybackManagerError::PlaybackInfoError);
|
|
m_errorString = errorString;
|
|
emit q->errorStringChanged(errorString);
|
|
}
|
|
}
|
|
|
|
void LocalPlaybackManagerPrivate::onPlayerError() {
|
|
Q_Q(LocalPlaybackManager);
|
|
m_error = PlaybackManagerError::PlayerGeneralError;
|
|
m_errorString = m_mediaPlayer->errorString();
|
|
emit q->errorChanged(m_error);
|
|
emit q->errorStringChanged(m_errorString);
|
|
qWarning() << "Player error: " << m_errorString;
|
|
}
|
|
|
|
void LocalPlaybackManagerPrivate::onMediaStatusChanged(MediaStatus newStatus) {
|
|
Q_Q(LocalPlaybackManager);
|
|
if (m_state == PlayerState::Stopped) return;
|
|
if (newStatus == MediaStatus::Loaded) {
|
|
m_mediaPlayer->play();
|
|
} else if (newStatus == MediaStatus::EndOfMedia) {
|
|
if (m_queue->hasNext() && m_queue->totalSize() > 1) {
|
|
q->next();
|
|
} else {
|
|
// End of the playlist
|
|
setState(PlayerState::Stopped);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* LocalPlaybackManager *
|
|
*****************************************************************************/
|
|
LocalPlaybackManager::LocalPlaybackManager(QObject *parent)
|
|
: PlaybackManager(new LocalPlaybackManagerPrivate(this), parent) {
|
|
Q_D(LocalPlaybackManager);
|
|
d->m_mediaPlayer = new QtMultimediaPlayer(this);
|
|
d->m_reporter->setPlaybackManager(this);
|
|
connect(d->m_mediaPlayer, &Player::positionChanged, this, &LocalPlaybackManager::positionChanged);
|
|
connect(d->m_mediaPlayer, &Player::durationChanged, this, &LocalPlaybackManager::durationChanged);
|
|
connect(d->m_mediaPlayer, &Player::stateChanged, this, &LocalPlaybackManager::playbackStateChanged);
|
|
connect(d->m_mediaPlayer, &Player::seekableChanged, this, &LocalPlaybackManager::seekableChanged);
|
|
connect(d->m_mediaPlayer, &Player::mediaStatusChanged, this, [this, d](MediaStatus newStatus) -> void {
|
|
d->onMediaStatusChanged(newStatus);
|
|
emit mediaStatusChanged(d->m_mediaPlayer->mediaStatus());
|
|
});
|
|
connect(d->m_mediaPlayer, &Player::hasAudioChanged, this, &LocalPlaybackManager::hasAudioChanged);
|
|
connect(d->m_mediaPlayer, &Player::hasVideoChanged, this, &LocalPlaybackManager::hasVideoChanged);
|
|
connect(d->m_mediaPlayer, &Player::errorStringChanged, this, [d]() {
|
|
d->onPlayerError();
|
|
});
|
|
}
|
|
|
|
void LocalPlaybackManager::swap(PlaybackManager &other) {
|
|
Q_UNIMPLEMENTED();
|
|
}
|
|
|
|
Player* LocalPlaybackManager::player() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_mediaPlayer;
|
|
}
|
|
|
|
QString LocalPlaybackManager::sessionId() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_playSessionId;
|
|
}
|
|
|
|
DTO::PlayMethod LocalPlaybackManager::playMethod() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_playMethod;
|
|
}
|
|
|
|
const QUrl& LocalPlaybackManager::streamUrl() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_streamUrl;
|
|
}
|
|
|
|
PlayerState LocalPlaybackManager::playbackState() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_mediaPlayer->state();
|
|
}
|
|
|
|
MediaStatus LocalPlaybackManager::mediaStatus() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_mediaPlayer->mediaStatus();
|
|
}
|
|
|
|
PlaybackManagerError LocalPlaybackManager::error() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_error;
|
|
}
|
|
|
|
const QString &LocalPlaybackManager::errorString() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_errorString;
|
|
}
|
|
|
|
qint64 LocalPlaybackManager::position() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_mediaPlayer->position();
|
|
}
|
|
|
|
qint64 LocalPlaybackManager::duration() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_mediaPlayer->duration();
|
|
}
|
|
|
|
bool LocalPlaybackManager::seekable() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_mediaPlayer->seekable();
|
|
}
|
|
|
|
void LocalPlaybackManager::pause() {
|
|
Q_D(LocalPlaybackManager);
|
|
d->m_mediaPlayer->pause();
|
|
}
|
|
|
|
void LocalPlaybackManager::play() {
|
|
Q_D(LocalPlaybackManager);
|
|
if (d->m_queue->totalSize() > 0) {
|
|
d->m_mediaPlayer->play();
|
|
d->setState(PlayerState::Playing);
|
|
}
|
|
}
|
|
|
|
void LocalPlaybackManager::playItem(QSharedPointer<Model::Item> item) {
|
|
Q_D(LocalPlaybackManager);
|
|
d->m_queue->clearList();
|
|
d->m_queue->appendToList(item);
|
|
d->m_queue->play();
|
|
d->m_queueIndex = 0;
|
|
|
|
d->setItem(item);
|
|
|
|
emit hasNextChanged(d->m_queue->hasNext());
|
|
emit hasPreviousChanged(d->m_queue->hasPrevious());
|
|
d->setState(PlayerState::Playing);
|
|
}
|
|
|
|
void LocalPlaybackManager::playItemId(const QString &itemId) {
|
|
Q_D(PlaybackManager);
|
|
Jellyfin::Loader::HTTP::GetItemLoader *loader = new Jellyfin::Loader::HTTP::GetItemLoader(d->m_apiClient);
|
|
connect(loader, &Support::LoaderBase::error, this, [loader]() {
|
|
// TODO: error handling
|
|
loader->deleteLater();
|
|
});
|
|
connect(loader, &Support::LoaderBase::ready, this, [this, loader](){
|
|
this->playItem(QSharedPointer<Model::Item>::create(loader->result()));
|
|
loader->deleteLater();
|
|
});
|
|
Jellyfin::Loader::GetItemParams params;
|
|
params.setUserId(d->m_apiClient->userId());
|
|
params.setItemId(itemId);
|
|
loader->setParameters(params);
|
|
loader->load();
|
|
}
|
|
|
|
void LocalPlaybackManager::playItemInList(const QList<QSharedPointer<Model::Item>> &items, int index) {
|
|
Q_D(LocalPlaybackManager);
|
|
d->m_queue->clearList();
|
|
d->m_queue->appendToList(items);
|
|
d->m_queue->play(index);
|
|
d->m_queueIndex = index;
|
|
|
|
emit queueIndexChanged(d->m_queueIndex);
|
|
|
|
d->setItem(items.at(index));
|
|
emit hasNextChanged(d->m_queue->hasNext());
|
|
emit hasPreviousChanged(d->m_queue->hasPrevious());
|
|
d->setState(PlayerState::Playing);
|
|
}
|
|
|
|
void LocalPlaybackManager::goTo(int index) {
|
|
Q_D(LocalPlaybackManager);
|
|
d->m_queue->play(index);
|
|
d->m_queueIndex = index;
|
|
emit queueIndexChanged(index);
|
|
|
|
d->setItem(d->m_queue->currentItem());
|
|
emit hasNextChanged(d->m_queue->hasNext());
|
|
emit hasPreviousChanged(d->m_queue->hasPrevious());
|
|
d->setState(PlayerState::Playing);
|
|
}
|
|
|
|
bool LocalPlaybackManager::hasNext() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_queue->hasNext();
|
|
}
|
|
|
|
bool LocalPlaybackManager::hasPrevious() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_queue->hasPrevious();
|
|
}
|
|
|
|
void LocalPlaybackManager::next() {
|
|
Q_D(LocalPlaybackManager);
|
|
d->m_mediaPlayer->stop();
|
|
d->m_mediaPlayer->setMedia(QUrl());
|
|
|
|
if (d->m_nextItem.isNull() || !d->m_queue->nextItem()->sameAs(*d->m_nextItem)) {
|
|
d->setItem(d->m_queue->nextItem());
|
|
d->m_nextStreamUrl = QString();
|
|
d->m_queue->next();
|
|
d->m_nextItem.clear();
|
|
} else {
|
|
d->m_item = d->m_nextItem;
|
|
d->m_streamUrl = d->m_nextStreamUrl;
|
|
|
|
d->m_nextItem.clear();
|
|
d->m_nextStreamUrl = QString();
|
|
|
|
d->m_queue->next();
|
|
d->setItem(d->m_nextItem);
|
|
}
|
|
emit hasNextChanged(d->m_queue->hasNext());
|
|
emit hasPreviousChanged(d->m_queue->hasPrevious());
|
|
}
|
|
|
|
void LocalPlaybackManager::previous() {
|
|
Q_D(LocalPlaybackManager);
|
|
d->m_mediaPlayer->stop();
|
|
d->m_mediaPlayer->seek(0);
|
|
|
|
d->m_item.clear();
|
|
d->m_streamUrl = QString();
|
|
|
|
d->m_nextStreamUrl = d->m_streamUrl;
|
|
d->m_nextItem = d->m_queue->nextItem();
|
|
|
|
d->m_queue->previous();
|
|
d->setItem(d->m_queue->currentItem());
|
|
|
|
emit hasNextChanged(d->m_queue->hasNext());
|
|
emit hasPreviousChanged(d->m_queue->hasPrevious());
|
|
}
|
|
|
|
void LocalPlaybackManager::stop() {
|
|
Q_D(LocalPlaybackManager);
|
|
d->m_queue->clearList();
|
|
d->m_mediaPlayer->stop();
|
|
d->setState(PlayerState::Stopped);
|
|
}
|
|
|
|
void LocalPlaybackManager::seek(qint64 newPosition) {
|
|
Q_D(LocalPlaybackManager);
|
|
d->m_mediaPlayer->seek(newPosition);
|
|
}
|
|
|
|
bool LocalPlaybackManager::hasAudio() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_mediaPlayer->hasAudio();
|
|
}
|
|
|
|
bool LocalPlaybackManager::hasVideo() const {
|
|
const Q_D(LocalPlaybackManager);
|
|
return d->m_mediaPlayer->hasVideo();
|
|
}
|
|
|
|
} // NS Model
|
|
} // NS Jellyfin
|