2021-04-11 17:22:13 +02:00
|
|
|
/**
|
|
|
|
* SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
|
2022-09-22 16:59:30 +02:00
|
|
|
* SPDX-FileCopyrightText: 2021-2022 Bart De Vries <bart@mogwai.be>
|
2021-04-11 17:22:13 +02:00
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
2021-04-11 15:26:28 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "audiomanager.h"
|
|
|
|
|
|
|
|
#include <QAudio>
|
2021-04-11 18:55:09 +02:00
|
|
|
#include <QEventLoop>
|
2021-05-01 21:35:37 +02:00
|
|
|
#include <QTimer>
|
2021-05-02 09:38:48 +02:00
|
|
|
#include <QtMath>
|
2021-05-01 21:35:37 +02:00
|
|
|
#include <algorithm>
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-07-04 14:53:42 +02:00
|
|
|
#include <KLocalizedString>
|
|
|
|
|
2021-05-28 22:55:18 +02:00
|
|
|
#include "audiologging.h"
|
2021-04-11 21:08:25 +02:00
|
|
|
#include "datamanager.h"
|
2021-07-04 14:53:42 +02:00
|
|
|
#include "feed.h"
|
2021-09-08 11:46:22 +02:00
|
|
|
#include "models/errorlogmodel.h"
|
2021-05-01 21:35:37 +02:00
|
|
|
#include "powermanagementinterface.h"
|
2021-04-11 21:08:25 +02:00
|
|
|
#include "settingsmanager.h"
|
|
|
|
|
2022-09-22 16:59:30 +02:00
|
|
|
#include <solidextras/networkstatus.h>
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
class AudioManagerPrivate
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
PowerManagementInterface mPowerInterface;
|
|
|
|
|
2022-09-22 16:59:30 +02:00
|
|
|
SolidExtras::NetworkStatus m_networkStatus;
|
|
|
|
|
2021-04-11 18:11:36 +02:00
|
|
|
QMediaPlayer m_player;
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
Entry *m_entry = nullptr;
|
2021-04-12 22:18:04 +02:00
|
|
|
bool m_readyToPlay = false;
|
2021-04-13 14:31:52 +02:00
|
|
|
bool m_isSeekable = false;
|
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
|
|
|
bool m_continuePlayback = false;
|
2021-04-11 15:26:28 +02:00
|
|
|
|
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
|
|
|
// sort of lock mutex to prevent updating the player position while changing
|
|
|
|
// sources (which will emit lots of playerPositionChanged signals)
|
|
|
|
bool m_lockPositionSaving = false;
|
|
|
|
|
|
|
|
// m_pendingSeek is used to indicate whether a seek action is still pending
|
|
|
|
// * -1 corresponds to no seek action pending
|
|
|
|
// * any positive value indicates that a seek to position=m_pendingSeek is
|
|
|
|
// still pending
|
|
|
|
qint64 m_pendingSeek = -1;
|
2021-04-22 16:48:13 +02:00
|
|
|
|
2022-06-30 08:21:09 +00:00
|
|
|
QTimer *m_sleepTimer = nullptr;
|
|
|
|
qint64 m_sleepTime = -1;
|
|
|
|
qint64 m_remainingSleepTime = -1;
|
|
|
|
|
2022-09-22 16:59:30 +02:00
|
|
|
bool m_isStreaming = false;
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
friend class AudioManager;
|
|
|
|
};
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
AudioManager::AudioManager(QObject *parent)
|
|
|
|
: QObject(parent)
|
|
|
|
, d(std::make_unique<AudioManagerPrivate>())
|
2021-04-11 15:26:28 +02:00
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
connect(&d->m_player, &QMediaPlayer::mutedChanged, this, &AudioManager::playerMutedChanged);
|
|
|
|
connect(&d->m_player, &QMediaPlayer::volumeChanged, this, &AudioManager::playerVolumeChanged);
|
2021-04-11 23:07:21 +02:00
|
|
|
connect(&d->m_player, &QMediaPlayer::mediaChanged, this, &AudioManager::sourceChanged);
|
2021-04-11 18:11:36 +02:00
|
|
|
connect(&d->m_player, &QMediaPlayer::mediaStatusChanged, this, &AudioManager::statusChanged);
|
|
|
|
connect(&d->m_player, &QMediaPlayer::mediaStatusChanged, this, &AudioManager::mediaStatusChanged);
|
|
|
|
connect(&d->m_player, &QMediaPlayer::stateChanged, this, &AudioManager::playbackStateChanged);
|
|
|
|
connect(&d->m_player, &QMediaPlayer::stateChanged, this, &AudioManager::playerStateChanged);
|
|
|
|
connect(&d->m_player, &QMediaPlayer::playbackRateChanged, this, &AudioManager::playbackRateChanged);
|
|
|
|
connect(&d->m_player, QOverload<QMediaPlayer::Error>::of(&QMediaPlayer::error), this, &AudioManager::errorChanged);
|
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
|
|
|
connect(&d->m_player, &QMediaPlayer::durationChanged, this, &AudioManager::playerDurationChanged);
|
2021-04-11 18:11:36 +02:00
|
|
|
connect(&d->m_player, &QMediaPlayer::positionChanged, this, &AudioManager::positionChanged);
|
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
|
|
|
connect(this, &AudioManager::positionChanged, this, &AudioManager::savePlayPosition);
|
2021-04-16 21:42:27 +02:00
|
|
|
|
2022-05-31 22:10:06 +02:00
|
|
|
connect(this, &AudioManager::playbackRateChanged, &DataManager::instance(), &DataManager::playbackRateChanged);
|
2021-04-15 22:51:40 +02:00
|
|
|
connect(&DataManager::instance(), &DataManager::queueEntryMoved, this, &AudioManager::canGoNextChanged);
|
2021-04-16 21:42:27 +02:00
|
|
|
connect(&DataManager::instance(), &DataManager::queueEntryAdded, this, &AudioManager::canGoNextChanged);
|
|
|
|
connect(&DataManager::instance(), &DataManager::queueEntryRemoved, this, &AudioManager::canGoNextChanged);
|
2021-04-13 14:31:52 +02:00
|
|
|
// we'll send custom seekableChanged signal to work around QMediaPlayer glitches
|
2021-04-11 21:08:25 +02:00
|
|
|
|
2021-07-06 17:01:48 +02:00
|
|
|
connect(this, &AudioManager::logError, &ErrorLogModel::instance(), &ErrorLogModel::monitorErrorMessages);
|
|
|
|
|
2021-04-11 21:08:25 +02:00
|
|
|
// Check if an entry was playing when the program was shut down and restore it
|
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
|
|
|
if (DataManager::instance().lastPlayingEntry() != QStringLiteral("none")) {
|
2021-04-17 20:55:01 +02:00
|
|
|
setEntry(DataManager::instance().getEntry(DataManager::instance().lastPlayingEntry()));
|
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
|
|
|
}
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
AudioManager::~AudioManager()
|
|
|
|
{
|
|
|
|
d->mPowerInterface.setPreventSleep(false);
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
Entry *AudioManager::entry() const
|
2021-04-11 15:26:28 +02:00
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
return d->m_entry;
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool AudioManager::muted() const
|
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
return d->m_player.isMuted();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
qreal AudioManager::volume() const
|
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
auto realVolume = static_cast<qreal>(d->m_player.volume() / 100.0);
|
2021-04-11 15:26:28 +02:00
|
|
|
auto userVolume = static_cast<qreal>(QAudio::convertVolume(realVolume, QAudio::LinearVolumeScale, QAudio::LogarithmicVolumeScale));
|
|
|
|
|
|
|
|
return userVolume * 100.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
QUrl AudioManager::source() const
|
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
return d->m_player.media().request().url();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QMediaPlayer::Error AudioManager::error() const
|
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
if (d->m_player.error() != QMediaPlayer::NoError) {
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::error" << d->m_player.errorString();
|
2021-07-27 12:53:21 +03:00
|
|
|
// Some error occurred: probably best to unset the lastPlayingEntry to
|
2021-04-29 11:10:09 +02:00
|
|
|
// avoid a deadlock when starting up again.
|
|
|
|
DataManager::instance().setLastPlayingEntry(QStringLiteral("none"));
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
2021-04-11 18:11:36 +02:00
|
|
|
return d->m_player.error();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
qint64 AudioManager::duration() const
|
|
|
|
{
|
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
|
|
|
// we fake the duration in case the track has not been properly loaded yet
|
|
|
|
if (d->m_player.duration() > 0) {
|
|
|
|
return d->m_player.duration();
|
|
|
|
} else if (d->m_entry && d->m_entry->enclosure()) {
|
|
|
|
return d->m_entry->enclosure()->duration() * 1000;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
qint64 AudioManager::position() const
|
|
|
|
{
|
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
|
|
|
// we fake the player position in case there is still a pending seek
|
|
|
|
if (d->m_pendingSeek != -1) {
|
|
|
|
return d->m_pendingSeek;
|
|
|
|
} else {
|
|
|
|
return d->m_player.position();
|
|
|
|
}
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool AudioManager::seekable() const
|
|
|
|
{
|
2021-04-13 14:31:52 +02:00
|
|
|
return d->m_isSeekable;
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
2021-04-11 23:07:21 +02:00
|
|
|
bool AudioManager::canPlay() const
|
|
|
|
{
|
2021-04-12 22:18:04 +02:00
|
|
|
return (d->m_readyToPlay);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AudioManager::canPause() const
|
|
|
|
{
|
|
|
|
return (d->m_readyToPlay);
|
|
|
|
}
|
|
|
|
|
2021-04-12 22:44:05 +02:00
|
|
|
bool AudioManager::canSkipForward() const
|
2021-04-12 22:18:04 +02:00
|
|
|
{
|
|
|
|
return (d->m_readyToPlay);
|
|
|
|
}
|
|
|
|
|
2021-04-12 22:44:05 +02:00
|
|
|
bool AudioManager::canSkipBackward() const
|
2021-04-12 22:18:04 +02:00
|
|
|
{
|
|
|
|
return (d->m_readyToPlay);
|
2021-04-11 23:07:21 +02:00
|
|
|
}
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
QMediaPlayer::State AudioManager::playbackState() const
|
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
return d->m_player.state();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
2021-04-11 17:13:44 +02:00
|
|
|
qreal AudioManager::playbackRate() const
|
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
return d->m_player.playbackRate();
|
2021-04-11 17:13:44 +02:00
|
|
|
}
|
|
|
|
|
2021-04-12 22:18:04 +02:00
|
|
|
qreal AudioManager::minimumPlaybackRate() const
|
|
|
|
{
|
|
|
|
return MIN_RATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
qreal AudioManager::maximumPlaybackRate() const
|
|
|
|
{
|
|
|
|
return MAX_RATE;
|
|
|
|
}
|
|
|
|
|
2022-09-22 16:59:30 +02:00
|
|
|
bool AudioManager::isStreaming() const
|
|
|
|
{
|
|
|
|
return d->m_isStreaming;
|
|
|
|
}
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
QMediaPlayer::MediaStatus AudioManager::status() const
|
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
return d->m_player.mediaStatus();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
void AudioManager::setEntry(Entry *entry)
|
2021-04-11 15:26:28 +02:00
|
|
|
{
|
2021-07-06 19:21:57 +02:00
|
|
|
// First unset current track, such that any signal that fires doesn't
|
|
|
|
// operate on the wrong track
|
|
|
|
Entry *oldEntry = d->m_entry;
|
|
|
|
d->m_entry = nullptr;
|
|
|
|
|
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
|
|
|
// reset any pending seek action, lock position saving and notify interval
|
|
|
|
d->m_pendingSeek = -1;
|
2021-04-14 11:32:57 +02:00
|
|
|
d->m_lockPositionSaving = true;
|
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
|
|
|
d->m_player.setNotifyInterval(1000);
|
2021-04-29 11:10:09 +02:00
|
|
|
|
2021-04-14 11:32:57 +02:00
|
|
|
// First check if the previous track needs to be marked as read
|
|
|
|
// TODO: make grace time a setting in SettingsManager
|
2021-07-06 19:21:57 +02:00
|
|
|
if (oldEntry) {
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "Checking previous track";
|
2021-05-28 23:16:35 +02:00
|
|
|
qCDebug(kastsAudio) << "Left time" << (duration() - position());
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "MediaStatus" << d->m_player.mediaStatus();
|
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
|
|
|
if (((duration() > 0) && (position() > 0) && ((duration() - position()) < SKIP_TRACK_END)) || (d->m_player.mediaStatus() == QMediaPlayer::EndOfMedia)) {
|
2021-07-06 19:21:57 +02:00
|
|
|
qCDebug(kastsAudio) << "Mark as read:" << oldEntry->title();
|
|
|
|
oldEntry->enclosure()->setPlayPosition(0);
|
2021-10-29 17:00:52 +02:00
|
|
|
oldEntry->setRead(true);
|
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
|
|
|
d->m_continuePlayback = SettingsManager::self()->continuePlayingNextEntry();
|
2021-04-13 22:11:12 +02:00
|
|
|
}
|
2021-04-14 11:32:57 +02:00
|
|
|
}
|
2021-04-29 11:10:09 +02:00
|
|
|
|
|
|
|
// do some checks on the new entry to see whether it's valid and not corrupted
|
2022-09-22 16:59:30 +02:00
|
|
|
if (entry != nullptr && entry->hasEnclosure() && entry->enclosure()
|
|
|
|
&& (entry->enclosure()->status() == Enclosure::Downloaded
|
|
|
|
|| (d->m_networkStatus.connectivity() != SolidExtras::NetworkStatus::No
|
|
|
|
&& (d->m_networkStatus.metered() != SolidExtras::NetworkStatus::Yes || SettingsManager::self()->allowMeteredStreaming())))) {
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "Going to change source";
|
2021-04-11 18:11:36 +02:00
|
|
|
d->m_entry = entry;
|
2021-04-14 11:32:57 +02:00
|
|
|
Q_EMIT entryChanged(entry);
|
2022-09-22 16:59:30 +02:00
|
|
|
QUrl loadUrl;
|
|
|
|
if (entry->enclosure()->status() == Enclosure::Downloaded) {
|
|
|
|
loadUrl = QUrl::fromLocalFile(d->m_entry->enclosure()->path());
|
|
|
|
if (d->m_isStreaming) {
|
|
|
|
d->m_isStreaming = false;
|
|
|
|
Q_EMIT isStreamingChanged();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
loadUrl = QUrl(d->m_entry->enclosure()->url());
|
|
|
|
if (!d->m_isStreaming) {
|
|
|
|
d->m_isStreaming = true;
|
|
|
|
Q_EMIT isStreamingChanged();
|
|
|
|
}
|
|
|
|
}
|
2021-04-21 20:25:01 +02:00
|
|
|
// the gst-pipeline is required to make sure that the pitch is not
|
|
|
|
// changed when speeding up the audio stream
|
2021-04-23 10:55:59 +02:00
|
|
|
// TODO: find a solution for Android (GStreamer not available on android by default)
|
|
|
|
#if !defined Q_OS_ANDROID && !defined Q_OS_WIN
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "use custom pipeline";
|
2022-09-22 16:59:30 +02:00
|
|
|
d->m_player.setMedia(QUrl(QStringLiteral("gst-pipeline: playbin uri=") + loadUrl.toString()
|
2021-05-01 21:35:37 +02:00
|
|
|
+ QStringLiteral(" audio_sink=\"scaletempo ! audioconvert ! audioresample ! autoaudiosink\" video_sink=\"fakevideosink\"")));
|
2021-04-23 10:55:59 +02:00
|
|
|
#else
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "regular audio backend";
|
2022-09-22 16:59:30 +02:00
|
|
|
d->m_player.setMedia(loadUrl);
|
2021-04-23 10:55:59 +02:00
|
|
|
#endif
|
2021-04-12 09:40:18 +02:00
|
|
|
// save the current playing track in the settingsfile for restoring on startup
|
2021-04-17 20:55:01 +02:00
|
|
|
DataManager::instance().setLastPlayingEntry(d->m_entry->id());
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "Changed source to" << d->m_entry->title();
|
2021-04-11 18:55:09 +02:00
|
|
|
|
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
|
|
|
// call method which will try to make sure that the stream will skip to
|
|
|
|
// the previously save position and make sure that the duration and
|
|
|
|
// position are reported correctly
|
|
|
|
prepareAudio();
|
2021-04-12 22:18:04 +02:00
|
|
|
} else {
|
2021-04-17 20:55:01 +02:00
|
|
|
DataManager::instance().setLastPlayingEntry(QStringLiteral("none"));
|
2021-04-14 11:32:57 +02:00
|
|
|
d->m_entry = nullptr;
|
|
|
|
Q_EMIT entryChanged(nullptr);
|
2021-05-22 09:05:33 +00:00
|
|
|
d->m_player.stop();
|
|
|
|
d->m_player.setMedia(nullptr);
|
2021-04-12 22:18:04 +02:00
|
|
|
d->m_readyToPlay = false;
|
2021-04-14 11:32:57 +02:00
|
|
|
Q_EMIT durationChanged(0);
|
|
|
|
Q_EMIT positionChanged(0);
|
2021-04-12 22:18:04 +02:00
|
|
|
Q_EMIT canPlayChanged();
|
|
|
|
Q_EMIT canPauseChanged();
|
2021-04-12 22:44:05 +02:00
|
|
|
Q_EMIT canSkipForwardChanged();
|
|
|
|
Q_EMIT canSkipBackwardChanged();
|
2021-04-13 20:51:00 +02:00
|
|
|
Q_EMIT canGoNextChanged();
|
2021-04-13 14:31:52 +02:00
|
|
|
d->m_isSeekable = false;
|
|
|
|
Q_EMIT seekableChanged(false);
|
2021-04-11 18:11:36 +02:00
|
|
|
}
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::setMuted(bool muted)
|
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
d->m_player.setMuted(muted);
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::setVolume(qreal volume)
|
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::setVolume" << volume;
|
2021-04-11 15:26:28 +02:00
|
|
|
|
|
|
|
auto realVolume = static_cast<qreal>(QAudio::convertVolume(volume / 100.0, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale));
|
2021-04-11 18:11:36 +02:00
|
|
|
d->m_player.setVolume(qRound(realVolume * 100));
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
2021-04-11 18:11:36 +02:00
|
|
|
/*
|
2021-04-11 15:26:28 +02:00
|
|
|
void AudioManager::setSource(const QUrl &source)
|
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
//qCDebug(kastsAudio) << "AudioManager::setSource" << source;
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-04-11 18:11:36 +02:00
|
|
|
d->m_player.setMedia({source});
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
2021-04-11 18:11:36 +02:00
|
|
|
*/
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-04-12 22:18:04 +02:00
|
|
|
void AudioManager::setPosition(qint64 position)
|
2021-04-11 17:13:44 +02:00
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::setPosition" << position;
|
2021-04-11 17:13:44 +02:00
|
|
|
|
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
|
|
|
seek(position);
|
2021-04-11 17:13:44 +02:00
|
|
|
}
|
|
|
|
|
2021-04-12 22:18:04 +02:00
|
|
|
void AudioManager::setPlaybackRate(const qreal rate)
|
2021-04-11 15:26:28 +02:00
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::setPlaybackRate" << rate;
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-04-12 22:18:04 +02:00
|
|
|
d->m_player.setPlaybackRate(rate);
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::play()
|
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::play";
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2022-09-22 16:59:30 +02:00
|
|
|
// if we're streaming, check that we're still connected and check for metered
|
|
|
|
// connection
|
|
|
|
if (isStreaming()) {
|
|
|
|
if (d->m_networkStatus.connectivity() != SolidExtras::NetworkStatus::Yes
|
|
|
|
|| (d->m_networkStatus.metered() != SolidExtras::NetworkStatus::No && !SettingsManager::self()->allowMeteredStreaming())) {
|
|
|
|
qCDebug(kastsAudio) << "Refusing to play: no Connection or streaming on metered connection not allowed";
|
|
|
|
Q_EMIT logError(Error::Type::MeteredStreamingNotAllowed,
|
|
|
|
d->m_entry->feed()->url(),
|
|
|
|
d->m_entry->id(),
|
|
|
|
0,
|
|
|
|
i18n("No connection or streaming on metered connection not allowed"),
|
|
|
|
QString());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
// setting m_continuePlayback will make sure that, if the audio stream is
|
|
|
|
// still being prepared, that the playback will start once it's ready
|
|
|
|
d->m_continuePlayback = true;
|
|
|
|
|
2021-04-11 18:11:36 +02:00
|
|
|
d->m_player.play();
|
2021-04-13 14:31:52 +02:00
|
|
|
d->m_isSeekable = true;
|
|
|
|
Q_EMIT seekableChanged(d->m_isSeekable);
|
2021-04-30 21:45:11 +02:00
|
|
|
d->mPowerInterface.setPreventSleep(true);
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::pause()
|
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::pause";
|
2021-04-11 15:26:28 +02:00
|
|
|
|
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
|
|
|
// setting m_continuePlayback will make sure that, if the audio stream is
|
|
|
|
// still being prepared, that the playback will pause once it's ready
|
|
|
|
d->m_continuePlayback = false;
|
|
|
|
|
2021-04-13 14:31:52 +02:00
|
|
|
d->m_isSeekable = true;
|
2021-04-11 18:11:36 +02:00
|
|
|
d->m_player.pause();
|
2021-04-30 21:45:11 +02:00
|
|
|
d->mPowerInterface.setPreventSleep(false);
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
2021-04-12 22:18:04 +02:00
|
|
|
void AudioManager::playPause()
|
|
|
|
{
|
|
|
|
if (playbackState() == QMediaPlayer::State::PausedState)
|
|
|
|
play();
|
|
|
|
else if (playbackState() == QMediaPlayer::State::PlayingState)
|
|
|
|
pause();
|
|
|
|
}
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
void AudioManager::stop()
|
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::stop";
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-04-11 18:11:36 +02:00
|
|
|
d->m_player.stop();
|
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
|
|
|
d->m_continuePlayback = false;
|
2021-04-13 14:31:52 +02:00
|
|
|
d->m_isSeekable = false;
|
|
|
|
Q_EMIT seekableChanged(d->m_isSeekable);
|
2021-04-30 21:45:11 +02:00
|
|
|
d->mPowerInterface.setPreventSleep(false);
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::seek(qint64 position)
|
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::seek" << position;
|
2021-04-11 15:26:28 +02:00
|
|
|
|
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
|
|
|
// if there is still a pending seek, then we simply update that pending
|
|
|
|
// value, and then manually send the positionChanged signal to have the UI
|
|
|
|
// updated
|
|
|
|
if (d->m_pendingSeek != -1) {
|
|
|
|
d->m_pendingSeek = position;
|
|
|
|
Q_EMIT positionChanged(position);
|
|
|
|
} else {
|
|
|
|
d->m_player.setPosition(position);
|
|
|
|
}
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
2021-04-12 22:44:05 +02:00
|
|
|
void AudioManager::skipForward()
|
2021-04-11 23:07:21 +02:00
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::skipForward";
|
2021-04-12 22:36:17 +02:00
|
|
|
seek(std::min((position() + SKIP_STEP), duration()));
|
2021-04-11 23:07:21 +02:00
|
|
|
}
|
|
|
|
|
2021-04-12 22:44:05 +02:00
|
|
|
void AudioManager::skipBackward()
|
2021-04-11 23:07:21 +02:00
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::skipBackward";
|
2021-04-12 22:36:17 +02:00
|
|
|
seek(std::max((qint64)0, (position() - SKIP_STEP)));
|
2021-04-11 23:07:21 +02:00
|
|
|
}
|
2021-04-12 22:18:04 +02:00
|
|
|
|
2021-04-13 20:51:00 +02:00
|
|
|
bool AudioManager::canGoNext() const
|
|
|
|
{
|
2021-04-14 11:32:57 +02:00
|
|
|
if (d->m_entry) {
|
2021-05-01 21:00:12 +02:00
|
|
|
int index = DataManager::instance().queue().indexOf(d->m_entry->id());
|
2021-04-14 11:32:57 +02:00
|
|
|
if (index >= 0) {
|
|
|
|
// check if there is a next track
|
2021-05-01 21:35:37 +02:00
|
|
|
if (index < DataManager::instance().queue().count() - 1) {
|
|
|
|
Entry *next_entry = DataManager::instance().getEntry(DataManager::instance().queue()[index + 1]);
|
2021-04-14 11:32:57 +02:00
|
|
|
if (next_entry->enclosure()) {
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "Enclosure status" << next_entry->enclosure()->path() << next_entry->enclosure()->status();
|
2021-04-14 11:32:57 +02:00
|
|
|
if (next_entry->enclosure()->status() == Enclosure::Downloaded) {
|
|
|
|
return true;
|
2022-09-22 16:59:30 +02:00
|
|
|
} else {
|
|
|
|
SolidExtras::NetworkStatus networkStatus;
|
|
|
|
if (networkStatus.connectivity() == SolidExtras::NetworkStatus::Yes
|
|
|
|
&& (networkStatus.metered() == SolidExtras::NetworkStatus::No || SettingsManager::self()->allowMeteredStreaming())) {
|
|
|
|
return true;
|
|
|
|
}
|
2021-04-14 11:32:57 +02:00
|
|
|
}
|
2021-04-13 20:51:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::next()
|
|
|
|
{
|
|
|
|
if (canGoNext()) {
|
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
|
|
|
qCDebug(kastsAudio) << "Current playbackStatus before next() is:" << playbackState();
|
|
|
|
d->m_continuePlayback = playbackState() == QMediaPlayer::State::PlayingState;
|
|
|
|
|
2021-05-01 21:00:12 +02:00
|
|
|
int index = DataManager::instance().queue().indexOf(d->m_entry->id());
|
2021-05-28 23:16:35 +02:00
|
|
|
qCDebug(kastsAudio) << "Skipping to" << DataManager::instance().queue()[index + 1];
|
2021-05-01 21:35:37 +02:00
|
|
|
setEntry(DataManager::instance().getEntry(DataManager::instance().queue()[index + 1]));
|
2021-04-13 20:51:00 +02:00
|
|
|
} else {
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "Next track cannot be played, changing entry to nullptr";
|
2021-04-13 20:51:00 +02:00
|
|
|
setEntry(nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
void AudioManager::mediaStatusChanged()
|
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::mediaStatusChanged" << d->m_player.mediaStatus();
|
2021-04-13 20:51:00 +02:00
|
|
|
|
|
|
|
// File has reached the end and has stopped
|
|
|
|
if (d->m_player.mediaStatus() == QMediaPlayer::EndOfMedia) {
|
2021-04-13 22:11:12 +02:00
|
|
|
next();
|
2021-04-13 20:51:00 +02:00
|
|
|
}
|
2021-04-30 11:31:02 +02:00
|
|
|
|
|
|
|
// if there is a problem with the current track, make sure that it's not
|
|
|
|
// loaded again when the application is restarted, skip to next track and
|
|
|
|
// delete the enclosure
|
|
|
|
if (d->m_player.mediaStatus() == QMediaPlayer::InvalidMedia) {
|
|
|
|
// save pointer to this bad entry to allow
|
|
|
|
// us to delete the enclosure after the track has been unloaded
|
2021-05-01 21:35:37 +02:00
|
|
|
Entry *badEntry = d->m_entry;
|
2021-04-30 11:31:02 +02:00
|
|
|
DataManager::instance().setLastPlayingEntry(QStringLiteral("none"));
|
|
|
|
stop();
|
|
|
|
next();
|
2021-07-04 14:53:42 +02:00
|
|
|
if (badEntry && badEntry->enclosure()) {
|
2021-04-30 11:31:02 +02:00
|
|
|
badEntry->enclosure()->deleteFile();
|
2021-07-14 22:27:52 +02:00
|
|
|
Q_EMIT logError(Error::Type::InvalidMedia, badEntry->feed()->url(), badEntry->id(), QMediaPlayer::InvalidMedia, i18n("Invalid Media"), QString());
|
2021-07-04 14:53:42 +02:00
|
|
|
}
|
2021-04-30 11:31:02 +02:00
|
|
|
}
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::playerStateChanged()
|
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::playerStateChanged" << d->m_player.state();
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
switch (d->m_player.state()) {
|
2021-04-11 15:26:28 +02:00
|
|
|
case QMediaPlayer::State::StoppedState:
|
|
|
|
Q_EMIT stopped();
|
|
|
|
d->mPowerInterface.setPreventSleep(false);
|
|
|
|
break;
|
|
|
|
case QMediaPlayer::State::PlayingState:
|
2021-04-30 21:45:11 +02:00
|
|
|
// setPreventSleep is set in play() to avoid it toggling too rapidly
|
|
|
|
// see d->prepareAudioStream() for details
|
2021-04-11 15:26:28 +02:00
|
|
|
Q_EMIT playing();
|
|
|
|
break;
|
|
|
|
case QMediaPlayer::State::PausedState:
|
2021-04-30 21:45:11 +02:00
|
|
|
// setPreventSleep is set in pause() to avoid it toggling too rapidly
|
|
|
|
// see d->prepareAudioStream() for details
|
2021-04-11 15:26:28 +02:00
|
|
|
Q_EMIT paused();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 AudioManager::playerDurationChanged(qint64 duration)
|
|
|
|
{
|
|
|
|
qCDebug(kastsAudio) << "AudioManager::playerDurationChanged" << duration;
|
|
|
|
|
|
|
|
// Check if duration mentioned in enclosure corresponds to real duration
|
|
|
|
if (duration > 0 && (duration / 1000) != d->m_entry->enclosure()->duration()) {
|
|
|
|
qCDebug(kastsAudio) << "Correcting duration of" << d->m_entry->id() << "to" << duration / 1000 << "(was" << d->m_entry->enclosure()->duration() << ")";
|
|
|
|
d->m_entry->enclosure()->setDuration(duration / 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
qint64 correctedDuration = duration;
|
|
|
|
QTimer::singleShot(0, this, [this, correctedDuration]() {
|
|
|
|
Q_EMIT durationChanged(correctedDuration);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
void AudioManager::playerVolumeChanged()
|
|
|
|
{
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "AudioManager::playerVolumeChanged" << d->m_player.volume();
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-05-28 16:54:00 +02:00
|
|
|
QTimer::singleShot(0, this, [this]() {
|
2021-05-01 21:35:37 +02:00
|
|
|
Q_EMIT volumeChanged();
|
|
|
|
});
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::playerMutedChanged()
|
|
|
|
{
|
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
|
|
|
qCDebug(kastsAudio) << "AudioManager::playerMutedChanged" << muted();
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-05-28 16:54:00 +02:00
|
|
|
QTimer::singleShot(0, this, [this]() {
|
2021-05-01 21:35:37 +02:00
|
|
|
Q_EMIT mutedChanged(muted());
|
|
|
|
});
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
2021-04-11 20:30:12 +02:00
|
|
|
|
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 AudioManager::savePlayPosition()
|
2021-04-11 20:30:12 +02:00
|
|
|
{
|
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
|
|
|
qCDebug(kastsAudio) << "AudioManager::savePlayPosition";
|
|
|
|
|
|
|
|
// First check if there is still a pending seek
|
|
|
|
checkForPendingSeek();
|
|
|
|
|
2021-04-14 11:32:57 +02:00
|
|
|
if (!d->m_lockPositionSaving) {
|
|
|
|
if (d->m_entry) {
|
|
|
|
if (d->m_entry->enclosure()) {
|
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
|
|
|
d->m_entry->enclosure()->setPlayPosition(position());
|
2021-04-14 11:32:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << d->m_player.mediaStatus();
|
2021-04-11 20:30:12 +02:00
|
|
|
}
|
2021-04-22 16:48:13 +02:00
|
|
|
|
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 AudioManager::prepareAudio()
|
|
|
|
{
|
|
|
|
d->m_player.pause();
|
|
|
|
|
|
|
|
qint64 newDuration = duration();
|
|
|
|
|
|
|
|
qint64 startingPosition = d->m_entry->enclosure()->playPosition();
|
|
|
|
qCDebug(kastsAudio) << "Changing position to" << startingPosition / 1000 << "sec";
|
|
|
|
if (startingPosition <= newDuration) {
|
|
|
|
d->m_pendingSeek = startingPosition;
|
|
|
|
// Change notify interval temporarily. This will help with reducing the
|
|
|
|
// startup audio glitch to a minimum.
|
|
|
|
d->m_player.setNotifyInterval(50);
|
|
|
|
// do not call d->m_player.setPosition() here since it might start
|
|
|
|
// sending signals with a.o. incorrect duration and position
|
|
|
|
} else {
|
|
|
|
d->m_pendingSeek = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit positionChanged and durationChanged signals to make sure that
|
|
|
|
// the GUI can see the faked values (if needed)
|
|
|
|
qint64 newPosition = position();
|
|
|
|
Q_EMIT durationChanged(newDuration);
|
|
|
|
Q_EMIT positionChanged(newPosition);
|
|
|
|
|
|
|
|
d->m_readyToPlay = true;
|
|
|
|
Q_EMIT canPlayChanged();
|
|
|
|
Q_EMIT canPauseChanged();
|
|
|
|
Q_EMIT canSkipForwardChanged();
|
|
|
|
Q_EMIT canSkipBackwardChanged();
|
|
|
|
Q_EMIT canGoNextChanged();
|
|
|
|
d->m_isSeekable = true;
|
|
|
|
Q_EMIT seekableChanged(true);
|
|
|
|
|
|
|
|
qCDebug(kastsAudio) << "Duration reported by d->m_player" << d->m_player.duration();
|
|
|
|
qCDebug(kastsAudio) << "Duration reported by enclosure (in ms)" << d->m_entry->enclosure()->duration() * 1000;
|
|
|
|
qCDebug(kastsAudio) << "Duration reported by AudioManager" << newDuration;
|
|
|
|
qCDebug(kastsAudio) << "Position reported by d->m_player" << d->m_player.position();
|
|
|
|
qCDebug(kastsAudio) << "Saved position stored in enclosure (in ms)" << startingPosition;
|
|
|
|
qCDebug(kastsAudio) << "Position reported by AudioManager" << newPosition;
|
|
|
|
|
|
|
|
if (d->m_continuePlayback) {
|
|
|
|
// we call play() and not d->m_player.play() because we want to trigger
|
|
|
|
// things like inhibit suspend
|
|
|
|
play();
|
|
|
|
d->m_continuePlayback = false;
|
2021-04-22 16:48:13 +02:00
|
|
|
}
|
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
|
|
|
|
|
|
|
d->m_lockPositionSaving = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::checkForPendingSeek()
|
|
|
|
{
|
|
|
|
qint64 position = d->m_player.position();
|
|
|
|
qCDebug(kastsAudio) << "Seek pending?" << d->m_pendingSeek;
|
|
|
|
qCDebug(kastsAudio) << "Current position" << position;
|
|
|
|
|
|
|
|
// Check if we're supposed to skip to a new position
|
|
|
|
if (d->m_pendingSeek != -1 && d->m_player.mediaStatus() == QMediaPlayer::BufferedMedia && d->m_player.duration() > 0) {
|
|
|
|
if (abs(d->m_pendingSeek - position) > 2000) {
|
|
|
|
qCDebug(kastsAudio) << "Position seek still pending to position" << d->m_pendingSeek;
|
|
|
|
qCDebug(kastsAudio) << "Current reported position and duration" << d->m_player.position() << d->m_player.duration();
|
|
|
|
// be very careful because this setPosition call will trigger
|
|
|
|
// a positionChanged signal, which will be nested, so we call it in
|
|
|
|
// a QTimer::singleShot
|
|
|
|
qint64 seekPosition = d->m_pendingSeek;
|
|
|
|
QTimer::singleShot(0, this, [this, seekPosition]() {
|
|
|
|
d->m_player.setPosition(seekPosition);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
qCDebug(kastsAudio) << "Pending position seek has been executed; to position" << d->m_pendingSeek;
|
|
|
|
d->m_pendingSeek = -1;
|
|
|
|
d->m_player.setNotifyInterval(1000);
|
|
|
|
}
|
2021-05-28 23:16:35 +02:00
|
|
|
}
|
2021-04-22 16:48:13 +02:00
|
|
|
}
|
2021-06-03 16:23:06 +02:00
|
|
|
|
|
|
|
QString AudioManager::formattedDuration() const
|
|
|
|
{
|
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
|
|
|
return m_kformat.formatDuration(duration());
|
2021-06-03 16:23:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QString AudioManager::formattedLeftDuration() const
|
|
|
|
{
|
2022-05-31 22:10:06 +02:00
|
|
|
qreal rate = 1.0;
|
|
|
|
if (SettingsManager::self()->adjustTimeLeft()) {
|
|
|
|
rate = playbackRate();
|
|
|
|
rate = (rate > 0.0) ? rate : 1.0;
|
|
|
|
}
|
|
|
|
qint64 diff = duration() - position();
|
|
|
|
return m_kformat.formatDuration(diff / rate);
|
2021-06-03 16:23:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QString AudioManager::formattedPosition() const
|
|
|
|
{
|
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
|
|
|
return m_kformat.formatDuration(position());
|
2021-06-03 16:23:06 +02:00
|
|
|
}
|
2022-06-30 08:21:09 +00:00
|
|
|
|
|
|
|
qint64 AudioManager::sleepTime() const
|
|
|
|
{
|
|
|
|
if (d->m_sleepTimer) {
|
|
|
|
return d->m_sleepTime;
|
|
|
|
} else {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
qint64 AudioManager::remainingSleepTime() const
|
|
|
|
{
|
|
|
|
if (d->m_sleepTimer) {
|
|
|
|
return d->m_remainingSleepTime;
|
|
|
|
} else {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::setSleepTimer(qint64 duration)
|
|
|
|
{
|
|
|
|
if (duration > 0) {
|
|
|
|
if (d->m_sleepTimer) {
|
|
|
|
stopSleepTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
d->m_sleepTime = duration;
|
|
|
|
d->m_remainingSleepTime = duration;
|
|
|
|
|
|
|
|
d->m_sleepTimer = new QTimer(this);
|
|
|
|
connect(d->m_sleepTimer, &QTimer::timeout, this, [this]() {
|
|
|
|
(d->m_remainingSleepTime)--;
|
|
|
|
if (d->m_remainingSleepTime > 0) {
|
|
|
|
Q_EMIT remainingSleepTimeChanged(remainingSleepTime());
|
|
|
|
} else {
|
|
|
|
pause();
|
|
|
|
stopSleepTimer();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
d->m_sleepTimer->start(1000);
|
|
|
|
|
|
|
|
Q_EMIT sleepTimerChanged(duration);
|
|
|
|
Q_EMIT remainingSleepTimeChanged(remainingSleepTime());
|
|
|
|
} else {
|
|
|
|
stopSleepTimer();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::stopSleepTimer()
|
|
|
|
{
|
|
|
|
if (d->m_sleepTimer) {
|
|
|
|
d->m_sleepTime = -1;
|
|
|
|
d->m_remainingSleepTime = -1;
|
|
|
|
|
|
|
|
delete d->m_sleepTimer;
|
|
|
|
d->m_sleepTimer = nullptr;
|
|
|
|
|
|
|
|
Q_EMIT sleepTimerChanged(-1);
|
|
|
|
Q_EMIT remainingSleepTimeChanged(-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString AudioManager::formattedRemainingSleepTime() const
|
|
|
|
{
|
|
|
|
qint64 timeLeft = remainingSleepTime() * 1000;
|
|
|
|
if (timeLeft < 0) {
|
|
|
|
timeLeft = 0;
|
|
|
|
}
|
|
|
|
return m_kformat.formatDuration(timeLeft);
|
|
|
|
}
|