Add audiomanager and powermanagementinterface

Both classes are based on classes taken from Elisa.

The audiomanager class will be adapted to add functionality like saving
and restoring play positions and interfacing with MPRIS2.
This commit is contained in:
Bart De Vries 2021-04-11 15:26:28 +02:00
parent 16c052250c
commit 7d94792872
7 changed files with 737 additions and 2 deletions

View File

@ -2,6 +2,11 @@ cmake_minimum_required(VERSION 3.14)
project(Alligator)
# be c++14 compliant
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(KF5_MIN_VERSION "5.75.0")
set(QT_MIN_VERSION "5.15.0")
@ -29,7 +34,7 @@ if (ANDROID)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2)
find_package(OpenSSL REQUIRED)
else()
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Widgets)
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Widgets DBus)
endif()
add_definitions(-DQT_NO_CAST_FROM_ASCII

View File

@ -11,13 +11,15 @@ add_executable(alligator
enclosuredownloadjob.cpp
queuemodel.cpp
datamanager.cpp
audiomanager.cpp
powermanagementinterface.cpp
resources.qrc
)
kconfig_add_kcfg_files(alligator settingsmanager.kcfgc GENERATE_MOC)
target_include_directories(alligator PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(alligator PRIVATE Qt5::Core Qt5::Qml Qt5::Quick Qt5::QuickControls2 Qt5::Sql Qt5::Multimedia KF5::Syndication KF5::CoreAddons KF5::ConfigGui KF5::I18n)
target_link_libraries(alligator PRIVATE Qt5::Core Qt5::Qml Qt5::Quick Qt5::QuickControls2 Qt5::Sql Qt5::Multimedia Qt5::DBus KF5::Syndication KF5::CoreAddons KF5::ConfigGui KF5::I18n)
if(ANDROID)
target_link_libraries(alligator PRIVATE

204
src/audiomanager.cpp Normal file
View File

@ -0,0 +1,204 @@
/*
SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
SPDX-License-Identifier: LGPL-3.0-or-later
*/
#include "audiomanager.h"
#include "powermanagementinterface.h"
#include <QTimer>
#include <QAudio>
class AudioManagerPrivate
{
private:
PowerManagementInterface mPowerInterface;
QMediaPlayer mPlayer;
Entry* entry = nullptr;
friend class AudioManager;
};
AudioManager::AudioManager(QObject *parent) : QObject(parent), d(std::make_unique<AudioManagerPrivate>())
{
connect(&d->mPlayer, &QMediaPlayer::mutedChanged, this, &AudioManager::playerMutedChanged);
connect(&d->mPlayer, &QMediaPlayer::volumeChanged, this, &AudioManager::playerVolumeChanged);
connect(&d->mPlayer, &QMediaPlayer::mediaChanged, this, &AudioManager::sourceChanged);
connect(&d->mPlayer, &QMediaPlayer::mediaStatusChanged, this, &AudioManager::statusChanged);
connect(&d->mPlayer, &QMediaPlayer::mediaStatusChanged, this, &AudioManager::mediaStatusChanged);
connect(&d->mPlayer, &QMediaPlayer::stateChanged, this, &AudioManager::playbackStateChanged);
connect(&d->mPlayer, &QMediaPlayer::stateChanged, this, &AudioManager::playerStateChanged);
connect(&d->mPlayer, QOverload<QMediaPlayer::Error>::of(&QMediaPlayer::error), this, &AudioManager::errorChanged);
connect(&d->mPlayer, &QMediaPlayer::durationChanged, this, &AudioManager::durationChanged);
connect(&d->mPlayer, &QMediaPlayer::positionChanged, this, &AudioManager::positionChanged);
connect(&d->mPlayer, &QMediaPlayer::seekableChanged, this, &AudioManager::seekableChanged);
}
AudioManager::~AudioManager()
{
d->mPowerInterface.setPreventSleep(false);
}
Entry* AudioManager::entry () const
{
return d->entry;
}
bool AudioManager::muted() const
{
return d->mPlayer.isMuted();
}
qreal AudioManager::volume() const
{
auto realVolume = static_cast<qreal>(d->mPlayer.volume() / 100.0);
auto userVolume = static_cast<qreal>(QAudio::convertVolume(realVolume, QAudio::LinearVolumeScale, QAudio::LogarithmicVolumeScale));
return userVolume * 100.0;
}
QUrl AudioManager::source() const
{
return d->mPlayer.media().request().url();
}
QMediaPlayer::Error AudioManager::error() const
{
if (d->mPlayer.error() != QMediaPlayer::NoError) {
qDebug() << "AudioManager::error" << d->mPlayer.errorString();
}
return d->mPlayer.error();
}
qint64 AudioManager::duration() const
{
return d->mPlayer.duration();
}
qint64 AudioManager::position() const
{
return d->mPlayer.position();
}
bool AudioManager::seekable() const
{
return d->mPlayer.isSeekable();
}
QMediaPlayer::State AudioManager::playbackState() const
{
return d->mPlayer.state();
}
QMediaPlayer::MediaStatus AudioManager::status() const
{
return d->mPlayer.mediaStatus();
}
void AudioManager::setEntry(Entry* entry)
{
d->entry = entry;
Q_EMIT entryChanged();
}
void AudioManager::setMuted(bool muted)
{
d->mPlayer.setMuted(muted);
}
void AudioManager::setVolume(qreal volume)
{
qDebug() << "AudioManager::setVolume" << volume;
auto realVolume = static_cast<qreal>(QAudio::convertVolume(volume / 100.0, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale));
d->mPlayer.setVolume(qRound(realVolume * 100));
}
void AudioManager::setSource(const QUrl &source)
{
qDebug() << "AudioManager::setSource" << source;
d->mPlayer.setMedia({source});
}
void AudioManager::setPosition(qint64 position)
{
qDebug() << "AudioManager::setPosition" << position;
d->mPlayer.setPosition(position);
}
void AudioManager::play()
{
qDebug() << "AudioManager::play";
d->mPlayer.play();
}
void AudioManager::pause()
{
qDebug() << "AudioManager::pause";
d->mPlayer.pause();
}
void AudioManager::stop()
{
qDebug() << "AudioManager::stop";
d->mPlayer.stop();
}
void AudioManager::seek(qint64 position)
{
qDebug() << "AudioManager::seek" << position;
d->mPlayer.setPosition(position);
}
void AudioManager::mediaStatusChanged()
{
qDebug() << "AudioManager::mediaStatusChanged" << d->mPlayer.mediaStatus();
}
void AudioManager::playerStateChanged()
{
qDebug() << "AudioManager::playerStateChanged" << d->mPlayer.state();
switch(d->mPlayer.state())
{
case QMediaPlayer::State::StoppedState:
Q_EMIT stopped();
d->mPowerInterface.setPreventSleep(false);
break;
case QMediaPlayer::State::PlayingState:
Q_EMIT playing();
d->mPowerInterface.setPreventSleep(true);
break;
case QMediaPlayer::State::PausedState:
Q_EMIT paused();
d->mPowerInterface.setPreventSleep(false);
break;
}
}
void AudioManager::playerVolumeChanged()
{
qDebug() << "AudioManager::playerVolumeChanged" << d->mPlayer.volume();
QTimer::singleShot(0, [this]() {Q_EMIT volumeChanged();});
}
void AudioManager::playerMutedChanged()
{
qDebug() << "AudioManager::playerMutedChanged";
QTimer::singleShot(0, [this]() {Q_EMIT mutedChanged(muted());});
}

170
src/audiomanager.h Normal file
View File

@ -0,0 +1,170 @@
/*
SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
SPDX-License-Identifier: LGPL-3.0-or-later
*/
#pragma once
#include <QObject>
#include <QUrl>
#include <QMediaPlayer>
#include <QString>
#include <memory>
#include "entry.h"
class AudioManagerPrivate;
class AudioManager : public QObject
{
Q_OBJECT
Q_PROPERTY(bool playerOpen
MEMBER playerOpen)
Q_PROPERTY(Entry* entry
READ entry
WRITE setEntry
NOTIFY entryChanged)
Q_PROPERTY(bool muted
READ muted
WRITE setMuted
NOTIFY mutedChanged)
Q_PROPERTY(qreal volume
READ volume
WRITE setVolume
NOTIFY volumeChanged)
Q_PROPERTY(QUrl source
READ source
WRITE setSource
NOTIFY sourceChanged)
Q_PROPERTY(QMediaPlayer::MediaStatus status
READ status
NOTIFY statusChanged)
Q_PROPERTY(QMediaPlayer::State playbackState
READ playbackState
NOTIFY playbackStateChanged)
Q_PROPERTY(QMediaPlayer::Error error
READ error
NOTIFY errorChanged)
Q_PROPERTY(qint64 duration
READ duration
NOTIFY durationChanged)
Q_PROPERTY(qint64 position
READ position
WRITE setPosition
NOTIFY positionChanged)
Q_PROPERTY(bool seekable
READ seekable
NOTIFY seekableChanged)
public:
static AudioManager &instance()
{
static AudioManager _instance;
return _instance;
}
~AudioManager() override;
[[nodiscard]] Entry* entry() const;
[[nodiscard]] bool muted() const;
[[nodiscard]] qreal volume() const;
[[nodiscard]] QUrl source() const;
[[nodiscard]] QMediaPlayer::MediaStatus status() const;
[[nodiscard]] QMediaPlayer::State playbackState() const;
[[nodiscard]] QMediaPlayer::Error error() const;
[[nodiscard]] qint64 duration() const;
[[nodiscard]] qint64 position() const;
[[nodiscard]] bool seekable() const;
Q_SIGNALS:
void entryChanged();
void mutedChanged(bool muted);
void volumeChanged();
void sourceChanged();
void statusChanged(QMediaPlayer::MediaStatus status);
void playbackStateChanged(QMediaPlayer::State state);
void errorChanged(QMediaPlayer::Error error);
void durationChanged(qint64 duration);
void positionChanged(qint64 position);
void seekableChanged(bool seekable);
void playing();
void paused();
void stopped();
public Q_SLOTS:
void setEntry(Entry* entry);
void setMuted(bool muted);
void setVolume(qreal volume);
void setSource(const QUrl &source);
void setPosition(qint64 position);
void play();
void pause();
void stop();
void seek(qint64 position);
private Q_SLOTS:
void mediaStatusChanged();
void playerStateChanged();
void playerMutedChanged();
void playerVolumeChanged();
private:
explicit AudioManager(QObject *parent = nullptr);
friend class AudioManagerPrivate;
std::unique_ptr<AudioManagerPrivate> d;
bool playerOpen;
};

View File

@ -30,6 +30,7 @@
#include "fetcher.h"
#include "queuemodel.h"
#include "datamanager.h"
#include "audiomanager.h"
#ifdef Q_OS_ANDROID
Q_DECL_EXPORT
@ -67,6 +68,10 @@ int main(int argc, char *argv[])
engine->setObjectOwnership(SettingsManager::self(), QQmlEngine::CppOwnership);
return SettingsManager::self();
});
qmlRegisterSingletonType<AudioManager>("org.kde.alligator", 1, 0, "AudioManager", [](QQmlEngine *engine, QJSEngine *) -> QObject * {
engine->setObjectOwnership(&AudioManager::instance(), QQmlEngine::CppOwnership);
return &AudioManager::instance();
});
qRegisterMetaType<Entry*>("const Entry*"); // "hack" to make qml understand Entry*
QQmlApplicationEngine engine;

View File

@ -0,0 +1,268 @@
/**
* SPDX-FileCopyrightText: 2019 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#include "powermanagementinterface.h"
#include <KLocalizedString>
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QDBusUnixFileDescriptor>
#endif
#if defined Q_OS_WIN
#include <windows.h>
#include <winbase.h>
#endif
#include <QString>
#include <QDebug>
#include <QCoreApplication>
class PowerManagementInterfacePrivate
{
public:
bool mPreventSleep = false;
bool mInhibitedSleep = false;
uint mInhibitSleepCookie = 0;
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
QDBusUnixFileDescriptor mInhibitSleepFileDescriptor;
#endif
};
PowerManagementInterface::PowerManagementInterface(QObject *parent) : QObject(parent), d(std::make_unique<PowerManagementInterfacePrivate>())
{
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
auto sessionBus = QDBusConnection::sessionBus();
sessionBus.connect(QStringLiteral("org.freedesktop.PowerManagement.Inhibit"),
QStringLiteral("/org/freedesktop/PowerManagement/Inhibit"),
QStringLiteral("org.freedesktop.PowerManagement.Inhibit"),
QStringLiteral("HasInhibitChanged"), this, SLOT(hostSleepInhibitChanged()));
#endif
}
PowerManagementInterface::~PowerManagementInterface() = default;
bool PowerManagementInterface::preventSleep() const
{
return d->mPreventSleep;
}
bool PowerManagementInterface::sleepInhibited() const
{
return d->mInhibitedSleep;
}
void PowerManagementInterface::setPreventSleep(bool value)
{
if (d->mPreventSleep == value) {
return;
}
if (value) {
inhibitSleepPlasmaWorkspace();
inhibitSleepGnomeWorkspace();
d->mPreventSleep = true;
} else {
uninhibitSleepPlasmaWorkspace();
uninhibitSleepGnomeWorkspace();
d->mPreventSleep = false;
}
Q_EMIT preventSleepChanged();
}
void PowerManagementInterface::retryInhibitingSleep()
{
if (d->mPreventSleep && !d->mInhibitedSleep) {
inhibitSleepPlasmaWorkspace();
inhibitSleepGnomeWorkspace();
}
}
void PowerManagementInterface::hostSleepInhibitChanged()
{
}
void PowerManagementInterface::inhibitDBusCallFinishedPlasmaWorkspace(QDBusPendingCallWatcher *aWatcher)
{
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
QDBusPendingReply<uint> reply = *aWatcher;
if (reply.isError()) {
} else {
d->mInhibitSleepCookie = reply.argumentAt<0>();
d->mInhibitedSleep = true;
Q_EMIT sleepInhibitedChanged();
}
aWatcher->deleteLater();
#else
Q_UNUSED(aWatcher)
#endif
}
void PowerManagementInterface::uninhibitDBusCallFinishedPlasmaWorkspace(QDBusPendingCallWatcher *aWatcher)
{
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
QDBusPendingReply<> reply = *aWatcher;
if (reply.isError()) {
qDebug() << "PowerManagementInterface::uninhibitDBusCallFinished" << reply.error();
} else {
d->mInhibitedSleep = false;
Q_EMIT sleepInhibitedChanged();
}
aWatcher->deleteLater();
#else
Q_UNUSED(aWatcher)
#endif
}
void PowerManagementInterface::inhibitDBusCallFinishedGnomeWorkspace(QDBusPendingCallWatcher *aWatcher)
{
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
QDBusPendingReply<uint> reply = *aWatcher;
if (reply.isError()) {
qDebug() << "PowerManagementInterface::inhibitDBusCallFinishedGnomeWorkspace" << reply.error();
} else {
d->mInhibitSleepCookie = reply.argumentAt<0>();
d->mInhibitedSleep = true;
Q_EMIT sleepInhibitedChanged();
}
aWatcher->deleteLater();
#else
Q_UNUSED(aWatcher)
#endif
}
void PowerManagementInterface::uninhibitDBusCallFinishedGnomeWorkspace(QDBusPendingCallWatcher *aWatcher)
{
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
QDBusPendingReply<> reply = *aWatcher;
if (reply.isError()) {
qDebug() << "PowerManagementInterface::uninhibitDBusCallFinished" << reply.error();
} else {
d->mInhibitedSleep = false;
Q_EMIT sleepInhibitedChanged();
}
aWatcher->deleteLater();
#else
Q_UNUSED(aWatcher)
#endif
}
void PowerManagementInterface::inhibitSleepPlasmaWorkspace()
{
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
auto sessionBus = QDBusConnection::sessionBus();
auto inhibitCall = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.PowerManagement.Inhibit"),
QStringLiteral("/org/freedesktop/PowerManagement/Inhibit"),
QStringLiteral("org.freedesktop.PowerManagement.Inhibit"),
QStringLiteral("Inhibit"));
inhibitCall.setArguments({{QCoreApplication::applicationName()}, {i18nc("explanation for sleep inhibit during play of music", "Playing Music")}});
auto asyncReply = sessionBus.asyncCall(inhibitCall);
auto replyWatcher = new QDBusPendingCallWatcher(asyncReply, this);
QObject::connect(replyWatcher, &QDBusPendingCallWatcher::finished,
this, &PowerManagementInterface::inhibitDBusCallFinishedPlasmaWorkspace);
#endif
}
void PowerManagementInterface::uninhibitSleepPlasmaWorkspace()
{
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
auto sessionBus = QDBusConnection::sessionBus();
auto uninhibitCall = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.PowerManagement.Inhibit"),
QStringLiteral("/org/freedesktop/PowerManagement/Inhibit"),
QStringLiteral("org.freedesktop.PowerManagement.Inhibit"),
QStringLiteral("UnInhibit"));
uninhibitCall.setArguments({{d->mInhibitSleepCookie}});
auto asyncReply = sessionBus.asyncCall(uninhibitCall);
auto replyWatcher = new QDBusPendingCallWatcher(asyncReply, this);
QObject::connect(replyWatcher, &QDBusPendingCallWatcher::finished,
this, &PowerManagementInterface::uninhibitDBusCallFinishedPlasmaWorkspace);
#endif
}
void PowerManagementInterface::inhibitSleepGnomeWorkspace()
{
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
auto sessionBus = QDBusConnection::sessionBus();
auto inhibitCall = QDBusMessage::createMethodCall(QStringLiteral("org.gnome.SessionManager"),
QStringLiteral("/org/gnome/SessionManager"),
QStringLiteral("org.gnome.SessionManager"),
QStringLiteral("Inhibit"));
inhibitCall.setArguments({{QCoreApplication::applicationName()}, {uint(0)},
{i18nc("explanation for sleep inhibit during play of music", "Playing Music")}, {uint(8)}});
auto asyncReply = sessionBus.asyncCall(inhibitCall);
auto replyWatcher = new QDBusPendingCallWatcher(asyncReply, this);
QObject::connect(replyWatcher, &QDBusPendingCallWatcher::finished,
this, &PowerManagementInterface::inhibitDBusCallFinishedGnomeWorkspace);
#endif
}
void PowerManagementInterface::uninhibitSleepGnomeWorkspace()
{
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
auto sessionBus = QDBusConnection::sessionBus();
auto uninhibitCall = QDBusMessage::createMethodCall(QStringLiteral("org.gnome.SessionManager"),
QStringLiteral("/org/gnome/SessionManager"),
QStringLiteral("org.gnome.SessionManager"),
QStringLiteral("UnInhibit"));
uninhibitCall.setArguments({{d->mInhibitSleepCookie}});
auto asyncReply = sessionBus.asyncCall(uninhibitCall);
auto replyWatcher = new QDBusPendingCallWatcher(asyncReply, this);
QObject::connect(replyWatcher, &QDBusPendingCallWatcher::finished,
this, &PowerManagementInterface::uninhibitDBusCallFinishedGnomeWorkspace);
#endif
}
void PowerManagementInterface::inhibitSleepWindowsWorkspace()
{
#if defined Q_OS_WIN
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
#endif
}
void PowerManagementInterface::uninhibitSleepWindowsWorkspace()
{
#if defined Q_OS_WIN
SetThreadExecutionState(ES_CONTINUOUS);
#endif
}

View File

@ -0,0 +1,81 @@
/**
* SPDX-FileCopyrightText: 2019 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#pragma once
#include <QObject>
#include <memory>
class QDBusPendingCallWatcher;
class PowerManagementInterfacePrivate;
class PowerManagementInterface : public QObject
{
Q_OBJECT
Q_PROPERTY(bool preventSleep
READ preventSleep
WRITE setPreventSleep
NOTIFY preventSleepChanged)
Q_PROPERTY(bool sleepInhibited
READ sleepInhibited
NOTIFY sleepInhibitedChanged)
public:
explicit PowerManagementInterface(QObject *parent = nullptr);
~PowerManagementInterface() override;
[[nodiscard]] bool preventSleep() const;
[[nodiscard]] bool sleepInhibited() const;
Q_SIGNALS:
void preventSleepChanged();
void sleepInhibitedChanged();
public Q_SLOTS:
void setPreventSleep(bool value);
void retryInhibitingSleep();
private Q_SLOTS:
void hostSleepInhibitChanged();
void inhibitDBusCallFinishedPlasmaWorkspace(QDBusPendingCallWatcher *aWatcher);
void uninhibitDBusCallFinishedPlasmaWorkspace(QDBusPendingCallWatcher *aWatcher);
void inhibitDBusCallFinishedGnomeWorkspace(QDBusPendingCallWatcher *aWatcher);
void uninhibitDBusCallFinishedGnomeWorkspace(QDBusPendingCallWatcher *aWatcher);
private:
void inhibitSleepPlasmaWorkspace();
void uninhibitSleepPlasmaWorkspace();
void inhibitSleepGnomeWorkspace();
void uninhibitSleepGnomeWorkspace();
void inhibitSleepWindowsWorkspace();
void uninhibitSleepWindowsWorkspace();
std::unique_ptr<PowerManagementInterfacePrivate> d;
};