355 lines
10 KiB
C++

/*
Gwenview: an image viewer
Copyright 2018 Friedrich W. H. Kossebau <kossebau@kde.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "mprismediaplayer2player.h"
#include <config-gwenview.h>
// lib
#include "gwenview_lib_debug.h"
#include <contextmanager.h>
#include <document/documentfactory.h>
#include <imagemetainfomodel.h>
#include <mimetypeutils.h>
#include <semanticinfo/semanticinfodirmodel.h>
#include <semanticinfo/sorteddirmodel.h>
#include <slideshow.h>
// KF
#include <KFileItem>
#include <KLocalizedString>
// Qt
#include <QAction>
#include <QDBusObjectPath>
namespace Gwenview
{
static const double MAX_RATE = 1.0;
static const double MIN_RATE = 1.0;
MprisMediaPlayer2Player::MprisMediaPlayer2Player(const QString &objectDBusPath,
SlideShow *slideShow,
ContextManager *contextManager,
QAction *toggleSlideShowAction,
QAction *fullScreenAction,
QAction *previousAction,
QAction *nextAction,
QObject *parent)
: DBusAbstractAdaptor(objectDBusPath, parent)
, mSlideShow(slideShow)
, mContextManager(contextManager)
, mToggleSlideShowAction(toggleSlideShowAction)
, mFullScreenAction(fullScreenAction)
, mPreviousAction(previousAction)
, mNextAction(nextAction)
, mSlideShowEnabled(mToggleSlideShowAction->isEnabled())
, mPreviousEnabled(mPreviousAction->isEnabled())
, mNextEnabled(mNextAction->isEnabled())
{
updatePlaybackStatus();
connect(mSlideShow, &SlideShow::stateChanged, this, &MprisMediaPlayer2Player::onSlideShowStateChanged);
connect(mSlideShow, &SlideShow::intervalChanged, this, &MprisMediaPlayer2Player::onMetaInfoUpdated);
connect(mContextManager, &ContextManager::currentUrlChanged, this, &MprisMediaPlayer2Player::onCurrentUrlChanged);
connect(mSlideShow->randomAction(), &QAction::toggled, this, &MprisMediaPlayer2Player::onRandomActionToggled);
connect(mToggleSlideShowAction, &QAction::changed, this, &MprisMediaPlayer2Player::onToggleSlideShowActionChanged);
connect(mNextAction, &QAction::changed, this, &MprisMediaPlayer2Player::onNextActionChanged);
connect(mPreviousAction, &QAction::changed, this, &MprisMediaPlayer2Player::onPreviousActionChanged);
}
MprisMediaPlayer2Player::~MprisMediaPlayer2Player() = default;
bool MprisMediaPlayer2Player::updatePlaybackStatus()
{
const QString newStatus = mSlideShow->isRunning() ? QStringLiteral("Playing") : QStringLiteral("Stopped");
const bool changed = (newStatus != mPlaybackStatus);
if (changed) {
mPlaybackStatus = newStatus;
}
return changed;
}
QString MprisMediaPlayer2Player::playbackStatus() const
{
return mPlaybackStatus;
}
bool MprisMediaPlayer2Player::canGoNext() const
{
return mNextEnabled;
}
void MprisMediaPlayer2Player::Next()
{
mNextAction->trigger();
}
bool MprisMediaPlayer2Player::canGoPrevious() const
{
return mPreviousEnabled;
}
void MprisMediaPlayer2Player::Previous()
{
mPreviousAction->trigger();
}
bool MprisMediaPlayer2Player::canPause() const
{
return mSlideShowEnabled;
}
void MprisMediaPlayer2Player::Pause()
{
mSlideShow->pause();
}
void MprisMediaPlayer2Player::PlayPause()
{
mToggleSlideShowAction->trigger();
}
void MprisMediaPlayer2Player::Stop()
{
if (mFullScreenAction->isChecked()) {
mFullScreenAction->trigger();
}
}
bool MprisMediaPlayer2Player::canPlay() const
{
return mSlideShowEnabled;
}
void MprisMediaPlayer2Player::Play()
{
if (mSlideShow->isRunning()) {
return;
}
mToggleSlideShowAction->trigger();
}
double MprisMediaPlayer2Player::volume() const
{
return 0;
}
void MprisMediaPlayer2Player::setVolume(double volume)
{
Q_UNUSED(volume);
}
void MprisMediaPlayer2Player::setShuffle(bool isShuffle)
{
mSlideShow->randomAction()->setChecked(isShuffle);
}
QVariantMap MprisMediaPlayer2Player::metadata() const
{
return mMetaData;
}
qlonglong MprisMediaPlayer2Player::position() const
{
// milliseconds -> microseconds
return mSlideShow->position() * 1000;
}
double MprisMediaPlayer2Player::rate() const
{
return 1.0;
}
void MprisMediaPlayer2Player::setRate(double newRate)
{
Q_UNUSED(newRate);
}
double MprisMediaPlayer2Player::minimumRate() const
{
return MIN_RATE;
}
double MprisMediaPlayer2Player::maximumRate() const
{
return MAX_RATE;
}
bool MprisMediaPlayer2Player::isShuffle() const
{
return mSlideShow->randomAction()->isChecked();
}
bool MprisMediaPlayer2Player::canSeek() const
{
return false;
}
bool MprisMediaPlayer2Player::canControl() const
{
return true;
}
void MprisMediaPlayer2Player::Seek(qlonglong offset)
{
Q_UNUSED(offset);
}
void MprisMediaPlayer2Player::SetPosition(const QDBusObjectPath &trackId, qlonglong pos)
{
Q_UNUSED(trackId);
Q_UNUSED(pos);
}
void MprisMediaPlayer2Player::OpenUri(const QString &uri)
{
Q_UNUSED(uri);
}
void MprisMediaPlayer2Player::onSlideShowStateChanged()
{
if (!updatePlaybackStatus()) {
return;
}
signalPropertyChange(QStringLiteral("Position"), position());
signalPropertyChange(QStringLiteral("PlaybackStatus"), mPlaybackStatus);
}
void MprisMediaPlayer2Player::onCurrentUrlChanged(const QUrl &url)
{
if (url.isEmpty()) {
mCurrentDocument = Document::Ptr();
} else {
mCurrentDocument = DocumentFactory::instance()->load(url);
connect(mCurrentDocument.data(), &Document::metaInfoUpdated, this, &MprisMediaPlayer2Player::onMetaInfoUpdated);
}
onMetaInfoUpdated();
signalPropertyChange(QStringLiteral("Position"), position());
}
void MprisMediaPlayer2Player::onMetaInfoUpdated()
{
QVariantMap updatedMetaData;
if (mCurrentDocument) {
const QUrl url = mCurrentDocument->url();
ImageMetaInfoModel *metaInfoModel = mCurrentDocument->metaInfo();
// We need some unique id mapping to urls. The index in the list is not reliable,
// as images can be added/removed during a running slideshow
// To allow some bidrectional mapping, convert the url to base64 to encode it for
// matching the D-Bus object path spec
const QString slideId = QString::fromLatin1(url.toString().toUtf8().toBase64(QByteArray::OmitTrailingEquals).replace('+', ""));
const QDBusObjectPath trackId(QStringLiteral("/org/kde/gwenview/imagelist/") + slideId);
updatedMetaData.insert(QStringLiteral("mpris:trackid"), QVariant::fromValue<QDBusObjectPath>(trackId));
// TODO: for videos also get and report the length
if (MimeTypeUtils::urlKind(url) != MimeTypeUtils::KIND_VIDEO) {
// convert seconds in microseconds
const auto duration = qlonglong(mSlideShow->interval() * 1000000);
updatedMetaData.insert(QStringLiteral("mpris:length"), duration);
}
// TODO: update on metadata changes, given user can edit most of this data
const QString name = metaInfoModel->getValueForKey(QStringLiteral("General.Name"));
updatedMetaData.insert(QStringLiteral("xesam:title"), name);
const QString comment = metaInfoModel->getValueForKey(QStringLiteral("General.Comment"));
if (!comment.isEmpty()) {
updatedMetaData.insert(QStringLiteral("xesam:comment"), comment);
}
updatedMetaData.insert(QStringLiteral("xesam:url"), url.toString());
// slight bending of semantics :)
const KFileItem folderItem(mContextManager->currentDirUrl());
updatedMetaData.insert(QStringLiteral("xesam:album"), folderItem.text());
// TODO: hook up with thumbnail cache and pass that as arturl
// updatedMetaData.insert(QStringLiteral("mpris:artUrl"), url.toString());
#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
const QModelIndex index = mContextManager->dirModel()->indexForUrl(url);
if (index.isValid()) {
const double rating = index.data(SemanticInfoDirModel::RatingRole).toInt() / 10.0;
updatedMetaData.insert(QStringLiteral("xesam:userRating"), rating);
}
#endif
// consider export of other metadata where mapping works
}
if (updatedMetaData != mMetaData) {
mMetaData = updatedMetaData;
signalPropertyChange(QStringLiteral("Metadata"), mMetaData);
}
}
void MprisMediaPlayer2Player::onRandomActionToggled(bool checked)
{
signalPropertyChange(QStringLiteral("Shuffle"), checked);
}
void MprisMediaPlayer2Player::onToggleSlideShowActionChanged()
{
const bool isEnabled = mToggleSlideShowAction->isEnabled();
if (mSlideShowEnabled == isEnabled) {
return;
}
mSlideShowEnabled = isEnabled;
const bool playbackStatusChanged = updatePlaybackStatus();
signalPropertyChange(QStringLiteral("CanPlay"), mSlideShowEnabled);
signalPropertyChange(QStringLiteral("CanPause"), mSlideShowEnabled);
if (playbackStatusChanged) {
signalPropertyChange(QStringLiteral("Position"), position());
signalPropertyChange(QStringLiteral("PlaybackStatus"), mPlaybackStatus);
}
}
void MprisMediaPlayer2Player::onNextActionChanged()
{
const bool isEnabled = mNextAction->isEnabled();
if (mNextEnabled == isEnabled) {
return;
}
mNextEnabled = isEnabled;
signalPropertyChange(QStringLiteral("CanGoNext"), mNextEnabled);
}
void MprisMediaPlayer2Player::onPreviousActionChanged()
{
const bool isEnabled = mPreviousAction->isEnabled();
if (mPreviousEnabled == isEnabled) {
return;
}
mPreviousEnabled = isEnabled;
signalPropertyChange(QStringLiteral("CanGoPrevious"), mPreviousEnabled);
}
}
#include "moc_mprismediaplayer2player.cpp"