161 lines
5.8 KiB
C++
161 lines
5.8 KiB
C++
#include "detailview.h"
|
|
|
|
#include "../../quick/embyimageprovider.h"
|
|
#include "../../quick/quickpagehost.h"
|
|
#include "../../utils/mediaitemutils.h"
|
|
#include "../../utils/playerpreferenceutils.h"
|
|
#include "detailviewmodel.h"
|
|
|
|
#include <config/config_keys.h>
|
|
#include <config/configstore.h>
|
|
#include <models/media/playerlaunchcontext.h>
|
|
#include <qembycore.h>
|
|
#include <services/media/mediaservice.h>
|
|
|
|
#include <QCursor>
|
|
#include <QMetaObject>
|
|
#include <QPointer>
|
|
#include <QQuickItem>
|
|
#include <QQuickWidget>
|
|
#include <QVBoxLayout>
|
|
|
|
#include <qcorotask.h>
|
|
|
|
DetailView::DetailView(QEmbyCore *core, QWidget *parent)
|
|
: BaseView(core, parent) {
|
|
setObjectName(QStringLiteral("detail-view"));
|
|
setAttribute(Qt::WA_StyledBackground, true);
|
|
|
|
auto *layout = new QVBoxLayout(this);
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
layout->setSpacing(0);
|
|
|
|
m_quickHost = new QuickPageHost(this);
|
|
m_viewModel = new DetailViewModel(core, m_quickHost);
|
|
m_quickHost->setContextProperty(QStringLiteral("detailPage"), m_viewModel);
|
|
m_quickHost->addImageProvider(QStringLiteral("emby"),
|
|
new EmbyImageProvider(core));
|
|
m_quickHost->addImageProvider(QStringLiteral("adaptiveicon"),
|
|
new AdaptiveIconProvider());
|
|
m_quickHost->load(
|
|
QUrl(QStringLiteral("qrc:/qt/qml/qEmby/qml/DetailPage.qml")));
|
|
layout->addWidget(m_quickHost);
|
|
|
|
connect(m_quickHost, &QuickPageHost::pageShown, m_viewModel,
|
|
[this]() { m_viewModel->setActive(true); });
|
|
connect(m_quickHost, &QuickPageHost::pageHidden, m_viewModel,
|
|
[this]() { m_viewModel->setActive(false); });
|
|
|
|
connect(m_viewModel, &DetailViewModel::itemActivated, this,
|
|
[this](const MediaItem &item) {
|
|
if (item.type == QLatin1String("Season")) {
|
|
const QString seriesId = item.seriesId.isEmpty()
|
|
? m_viewModel->item().id
|
|
: item.seriesId;
|
|
emit navigateToSeason(seriesId, item.id, item.name);
|
|
} else if (item.type == QLatin1String("BoxSet") ||
|
|
item.type == QLatin1String("Playlist") ||
|
|
item.type == QLatin1String("Folder")) {
|
|
emit navigateToFolder(item.id, item.name);
|
|
} else if (item.type == QLatin1String("Person")) {
|
|
emit navigateToPerson(item.id, item.name);
|
|
} else {
|
|
emit navigateToDetail(item.id, item.name, item);
|
|
}
|
|
});
|
|
connect(m_viewModel, &DetailViewModel::genreActivated, this,
|
|
[this](const QString &genre) {
|
|
emit navigateToFilteredView(QStringLiteral("Genre"), genre);
|
|
});
|
|
connect(m_viewModel, &DetailViewModel::playRequested, this,
|
|
[this](const MediaItem &item, int sourceIndex, bool resume) {
|
|
playSelected(item, sourceIndex, resume);
|
|
});
|
|
connect(m_viewModel, &DetailViewModel::favoriteRequested, this,
|
|
&BaseView::handleFavoriteRequested);
|
|
connect(m_viewModel, &DetailViewModel::playedRequested, this,
|
|
[this](const MediaItem &item, bool played) {
|
|
if (played)
|
|
handleMarkPlayedRequested(item);
|
|
else
|
|
handleMarkUnplayedRequested(item);
|
|
});
|
|
connect(m_viewModel, &DetailViewModel::moreRequested, this,
|
|
[this](const MediaItem &item) {
|
|
handleMoreMenuRequested(item, QCursor::pos());
|
|
});
|
|
}
|
|
|
|
QCoro::Task<void> DetailView::loadItem(const QString &itemId,
|
|
const MediaItem &seedItem) {
|
|
co_await m_viewModel->loadItem(itemId, seedItem);
|
|
}
|
|
|
|
void DetailView::prepareForStackLeave() { m_viewModel->setActive(false); }
|
|
|
|
void DetailView::scrollToTop() {
|
|
if (QQuickItem *root = m_quickHost->quickWidget()->rootObject())
|
|
QMetaObject::invokeMethod(root, "scrollToTop");
|
|
}
|
|
|
|
void DetailView::onMediaItemUpdated(const MediaItem &item) {
|
|
m_viewModel->updateItem(item);
|
|
}
|
|
|
|
void DetailView::beginOptimisticPlayedUpdate() {
|
|
m_viewModel->setActionBusy(true);
|
|
}
|
|
|
|
void DetailView::endOptimisticPlayedUpdate() {
|
|
m_viewModel->setActionBusy(false);
|
|
}
|
|
|
|
QCoro::Task<void> DetailView::playSelected(MediaItem item, int sourceIndex,
|
|
bool resume) {
|
|
if (!m_core || !m_core->mediaService() || item.id.isEmpty())
|
|
co_return;
|
|
|
|
QPointer<DetailView> guard(this);
|
|
try {
|
|
if (item.mediaSources.isEmpty()) {
|
|
const PlaybackInfo info =
|
|
co_await m_core->mediaService()->getPlaybackInfo(item.id);
|
|
if (!guard)
|
|
co_return;
|
|
item.mediaSources = info.mediaSources;
|
|
}
|
|
|
|
QVariant launchData;
|
|
QString sourceId = item.id;
|
|
QString streamUrl;
|
|
if (!item.mediaSources.isEmpty()) {
|
|
sourceIndex = qBound(0, sourceIndex, item.mediaSources.size() - 1);
|
|
MediaSourceInfo selected = item.mediaSources.at(sourceIndex);
|
|
PlayerPreferenceUtils::applyPreferredStreamRules(
|
|
selected,
|
|
ConfigStore::instance()->get<QString>(ConfigKeys::PlayerAudioLang,
|
|
"auto"),
|
|
ConfigStore::instance()->get<QString>(ConfigKeys::PlayerSubLang,
|
|
"auto"));
|
|
sourceId = selected.id;
|
|
PlayerLaunchContext context;
|
|
context.mediaItem = item;
|
|
context.selectedSource = selected;
|
|
launchData = QVariant::fromValue(context);
|
|
streamUrl = m_core->mediaService()->getStreamUrl(item.id, selected);
|
|
} else {
|
|
streamUrl = m_core->mediaService()->getStreamUrl(item.id, sourceId);
|
|
}
|
|
|
|
if (!guard)
|
|
co_return;
|
|
const long long startTicks =
|
|
resume ? item.userData.playbackPositionTicks : 0;
|
|
emit navigateToPlayer(item.id, MediaItemUtils::playbackTitle(item),
|
|
streamUrl, startTicks, launchData);
|
|
} catch (...) {
|
|
if (guard)
|
|
m_viewModel->setTransientError(tr("Unable to prepare playback"));
|
|
}
|
|
}
|