2021-04-11 17:22:13 +02:00
|
|
|
/**
|
|
|
|
* SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
|
|
|
|
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
2021-04-11 15:26:28 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <QMediaPlayer>
|
2021-05-01 21:35:37 +02:00
|
|
|
#include <QObject>
|
2021-04-11 15:26:28 +02:00
|
|
|
#include <QString>
|
2021-05-01 21:35:37 +02:00
|
|
|
#include <QUrl>
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-06-03 16:23:06 +02:00
|
|
|
#include <KFormat>
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
#include "entry.h"
|
2021-07-04 14:53:42 +02:00
|
|
|
#include "error.h"
|
2021-04-11 15:26:28 +02:00
|
|
|
|
|
|
|
class AudioManagerPrivate;
|
|
|
|
|
|
|
|
class AudioManager : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
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(QMediaPlayer::MediaStatus status READ status NOTIFY statusChanged)
|
|
|
|
Q_PROPERTY(QMediaPlayer::State playbackState READ playbackState NOTIFY playbackStateChanged)
|
|
|
|
Q_PROPERTY(qreal playbackRate READ playbackRate WRITE setPlaybackRate NOTIFY playbackRateChanged)
|
|
|
|
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)
|
|
|
|
Q_PROPERTY(bool canPlay READ canPlay NOTIFY canPlayChanged)
|
|
|
|
Q_PROPERTY(bool canSkipForward READ canSkipForward NOTIFY canSkipForwardChanged)
|
|
|
|
Q_PROPERTY(bool canSkipBackward READ canSkipBackward NOTIFY canSkipBackwardChanged)
|
|
|
|
Q_PROPERTY(bool canGoNext READ canGoNext NOTIFY canGoNextChanged)
|
2021-06-03 16:23:06 +02:00
|
|
|
Q_PROPERTY(QString formattedLeftDuration READ formattedLeftDuration NOTIFY positionChanged)
|
|
|
|
Q_PROPERTY(QString formattedDuration READ formattedDuration NOTIFY durationChanged)
|
|
|
|
Q_PROPERTY(QString formattedPosition READ formattedPosition NOTIFY positionChanged)
|
2022-06-30 08:21:09 +00:00
|
|
|
Q_PROPERTY(qint64 sleepTime READ sleepTime WRITE setSleepTimer RESET stopSleepTimer NOTIFY sleepTimerChanged)
|
|
|
|
Q_PROPERTY(qint64 remainingSleepTime READ remainingSleepTime NOTIFY remainingSleepTimeChanged)
|
|
|
|
Q_PROPERTY(QString formattedRemainingSleepTime READ formattedRemainingSleepTime NOTIFY remainingSleepTimeChanged)
|
2021-04-13 20:51:00 +02:00
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
public:
|
2021-10-29 17:00:52 +02:00
|
|
|
const double MAX_RATE = 1.0;
|
|
|
|
const double MIN_RATE = 2.5;
|
|
|
|
const qint64 SKIP_STEP = 10000;
|
|
|
|
const qint64 SKIP_TRACK_END = 15000;
|
|
|
|
|
2021-05-20 21:42:13 +02:00
|
|
|
static AudioManager &instance()
|
|
|
|
{
|
|
|
|
static AudioManager _instance;
|
|
|
|
return _instance;
|
|
|
|
}
|
2021-04-11 16:19:58 +02:00
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
~AudioManager() override;
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
[[nodiscard]] Entry *entry() const;
|
2021-04-11 15:26:28 +02:00
|
|
|
[[nodiscard]] bool muted() const;
|
|
|
|
[[nodiscard]] qreal volume() const;
|
2021-04-11 23:07:21 +02:00
|
|
|
[[nodiscard]] QUrl source() const;
|
2021-04-11 15:26:28 +02:00
|
|
|
[[nodiscard]] QMediaPlayer::MediaStatus status() const;
|
|
|
|
[[nodiscard]] QMediaPlayer::State playbackState() const;
|
2021-04-11 17:13:44 +02:00
|
|
|
[[nodiscard]] qreal playbackRate() const;
|
2021-04-12 22:18:04 +02:00
|
|
|
[[nodiscard]] qreal minimumPlaybackRate() const;
|
|
|
|
[[nodiscard]] qreal maximumPlaybackRate() const;
|
2021-04-11 15:26:28 +02:00
|
|
|
[[nodiscard]] QMediaPlayer::Error error() const;
|
|
|
|
[[nodiscard]] qint64 duration() const;
|
|
|
|
[[nodiscard]] qint64 position() const;
|
|
|
|
[[nodiscard]] bool seekable() const;
|
2021-04-11 23:07:21 +02:00
|
|
|
[[nodiscard]] bool canPlay() const;
|
2021-04-12 22:18:04 +02:00
|
|
|
[[nodiscard]] bool canPause() const;
|
2021-04-12 22:44:05 +02:00
|
|
|
[[nodiscard]] bool canSkipForward() const;
|
|
|
|
[[nodiscard]] bool canSkipBackward() const;
|
2021-04-13 20:51:00 +02:00
|
|
|
[[nodiscard]] bool canGoNext() const;
|
|
|
|
|
2021-06-03 16:23:06 +02:00
|
|
|
QString formattedDuration() const;
|
|
|
|
QString formattedLeftDuration() const;
|
|
|
|
QString formattedPosition() const;
|
|
|
|
|
2022-06-30 08:21:09 +00:00
|
|
|
qint64 sleepTime() const; // returns originally set sleep time
|
|
|
|
qint64 remainingSleepTime() const; // returns remaining sleep time
|
|
|
|
QString formattedRemainingSleepTime() const;
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
Q_SIGNALS:
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
void entryChanged(Entry *entry);
|
2021-04-11 15:26:28 +02:00
|
|
|
void mutedChanged(bool muted);
|
|
|
|
void volumeChanged();
|
2021-04-11 23:07:21 +02:00
|
|
|
void sourceChanged();
|
2021-04-11 15:26:28 +02:00
|
|
|
void statusChanged(QMediaPlayer::MediaStatus status);
|
|
|
|
void playbackStateChanged(QMediaPlayer::State state);
|
2021-04-11 17:13:44 +02:00
|
|
|
void playbackRateChanged(qreal rate);
|
2021-04-11 15:26:28 +02:00
|
|
|
void errorChanged(QMediaPlayer::Error error);
|
|
|
|
void durationChanged(qint64 duration);
|
|
|
|
void positionChanged(qint64 position);
|
|
|
|
void seekableChanged(bool seekable);
|
|
|
|
void playing();
|
|
|
|
void paused();
|
|
|
|
void stopped();
|
2021-04-12 22:18:04 +02:00
|
|
|
void canPlayChanged();
|
|
|
|
void canPauseChanged();
|
2021-04-12 22:44:05 +02:00
|
|
|
void canSkipForwardChanged();
|
|
|
|
void canSkipBackwardChanged();
|
2021-04-13 20:51:00 +02:00
|
|
|
void canGoNextChanged();
|
|
|
|
|
2022-06-30 08:21:09 +00:00
|
|
|
void sleepTimerChanged(qint64 duration);
|
|
|
|
void remainingSleepTimeChanged(qint64 duration);
|
|
|
|
|
2021-07-14 22:27:52 +02:00
|
|
|
void logError(Error::Type type, const QString &url, const QString &id, const int errorId, const QString &errorString, const QString &title);
|
2021-07-04 14:53:42 +02:00
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
public Q_SLOTS:
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
void setEntry(Entry *entry);
|
2021-04-11 15:26:28 +02:00
|
|
|
void setMuted(bool muted);
|
|
|
|
void setVolume(qreal volume);
|
2021-05-01 21:35:37 +02:00
|
|
|
// void setSource(const QUrl &source); //source should only be set by audiomanager itself
|
2021-04-11 15:26:28 +02:00
|
|
|
void setPosition(qint64 position);
|
2021-04-11 17:13:44 +02:00
|
|
|
void setPlaybackRate(qreal rate);
|
2021-04-11 15:26:28 +02:00
|
|
|
void play();
|
|
|
|
void pause();
|
2021-04-12 22:18:04 +02:00
|
|
|
void playPause();
|
2021-04-11 15:26:28 +02:00
|
|
|
void stop();
|
|
|
|
void seek(qint64 position);
|
2021-04-12 22:44:05 +02:00
|
|
|
void skipBackward();
|
|
|
|
void skipForward();
|
2021-04-13 20:51:00 +02:00
|
|
|
void next();
|
|
|
|
|
2022-06-30 08:21:09 +00:00
|
|
|
void setSleepTimer(qint64 duration);
|
|
|
|
void stopSleepTimer();
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
private Q_SLOTS:
|
|
|
|
|
|
|
|
void mediaStatusChanged();
|
|
|
|
void playerStateChanged();
|
Replace Audio prepare hack by nicer, asynchronous solution
The main bits of this implementation are:
- Start a new track in paused state. We don't care about the actual
media state or player state that QMediaPlayer is reporting. We will
deal with that when the audio actually starts playing.
- If a player position needs to be restored, we set d->m_pendingSeek to
the position that needs to be seeked. We don't actually seek because
we have no idea what state the player is in yet.
- On the positionChanged signal of QMP, and if the media is buffered, we
check if there is pendingSeek value set which is set to a different
value than the current player position. If so, we call
d->m_player.setPosition(). If we have arrived at the correct
position, then we reset d->m_pendingSeek to -1.
- In the position(), duration() and seek() methods, we return sensible
values, even QMP is not. So, we report the duration from the
enclosure, the position from d->m_pendingSeek, and let seek() change
the value of d->m_PendingSeek (if it's not -1) to the new seek
position.
- When there's a pending seek, we set the notifyInterval to shorter
interval to reduce the startup audio glitch as much as possible. We
then reset it to the default of 1000 msec.
This was tested on linux and android.
2021-06-14 16:31:25 +02:00
|
|
|
void playerDurationChanged(qint64 duration);
|
2021-04-11 15:26:28 +02:00
|
|
|
void playerMutedChanged();
|
|
|
|
void playerVolumeChanged();
|
Replace Audio prepare hack by nicer, asynchronous solution
The main bits of this implementation are:
- Start a new track in paused state. We don't care about the actual
media state or player state that QMediaPlayer is reporting. We will
deal with that when the audio actually starts playing.
- If a player position needs to be restored, we set d->m_pendingSeek to
the position that needs to be seeked. We don't actually seek because
we have no idea what state the player is in yet.
- On the positionChanged signal of QMP, and if the media is buffered, we
check if there is pendingSeek value set which is set to a different
value than the current player position. If so, we call
d->m_player.setPosition(). If we have arrived at the correct
position, then we reset d->m_pendingSeek to -1.
- In the position(), duration() and seek() methods, we return sensible
values, even QMP is not. So, we report the duration from the
enclosure, the position from d->m_pendingSeek, and let seek() change
the value of d->m_PendingSeek (if it's not -1) to the new seek
position.
- When there's a pending seek, we set the notifyInterval to shorter
interval to reduce the startup audio glitch as much as possible. We
then reset it to the default of 1000 msec.
This was tested on linux and android.
2021-06-14 16:31:25 +02:00
|
|
|
void savePlayPosition();
|
|
|
|
void prepareAudio();
|
|
|
|
void checkForPendingSeek();
|
2021-04-11 20:30:12 +02:00
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
private:
|
2021-05-20 21:42:13 +02:00
|
|
|
explicit AudioManager(QObject *parent = nullptr);
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
friend class AudioManagerPrivate;
|
|
|
|
|
|
|
|
std::unique_ptr<AudioManagerPrivate> d;
|
2021-06-03 16:23:06 +02:00
|
|
|
KFormat m_kformat;
|
2021-04-11 15:26:28 +02:00
|
|
|
};
|