2021-04-11 17:22:13 +02:00
|
|
|
/**
|
|
|
|
* SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
|
2022-12-15 21:59:19 +01:00
|
|
|
* SPDX-FileCopyrightText: 2021-2023 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"
|
|
|
|
|
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"
|
2023-02-15 16:43:57 +01:00
|
|
|
#include "fetcher.h"
|
2021-09-08 11:46:22 +02:00
|
|
|
#include "models/errorlogmodel.h"
|
2021-04-11 21:08:25 +02:00
|
|
|
#include "settingsmanager.h"
|
2023-10-26 09:52:23 +02:00
|
|
|
#include "utils/networkconnectionmanager.h"
|
2021-04-11 21:08:25 +02:00
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
class AudioManagerPrivate
|
|
|
|
{
|
|
|
|
private:
|
2023-02-16 13:32:52 +01:00
|
|
|
KMediaSession m_player = KMediaSession(QStringLiteral("kasts"), QStringLiteral("org.kde.kasts"));
|
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
|
|
|
{
|
2022-12-15 21:59:19 +01:00
|
|
|
d->m_player.setMpris2PauseInsteadOfStop(true);
|
|
|
|
|
|
|
|
connect(&d->m_player, &KMediaSession::currentBackendChanged, this, &AudioManager::currentBackendChanged);
|
|
|
|
connect(&d->m_player, &KMediaSession::mutedChanged, this, &AudioManager::playerMutedChanged);
|
|
|
|
connect(&d->m_player, &KMediaSession::volumeChanged, this, &AudioManager::playerVolumeChanged);
|
|
|
|
connect(&d->m_player, &KMediaSession::sourceChanged, this, &AudioManager::sourceChanged);
|
|
|
|
connect(&d->m_player, &KMediaSession::mediaStatusChanged, this, &AudioManager::statusChanged);
|
|
|
|
connect(&d->m_player, &KMediaSession::mediaStatusChanged, this, &AudioManager::mediaStatusChanged);
|
|
|
|
connect(&d->m_player, &KMediaSession::playbackStateChanged, this, &AudioManager::playbackStateChanged);
|
|
|
|
connect(&d->m_player, &KMediaSession::playbackRateChanged, this, &AudioManager::playbackRateChanged);
|
|
|
|
connect(&d->m_player, &KMediaSession::errorChanged, this, &AudioManager::errorChanged);
|
|
|
|
connect(&d->m_player, &KMediaSession::durationChanged, this, &AudioManager::playerDurationChanged);
|
|
|
|
connect(&d->m_player, &KMediaSession::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-12-15 21:59:19 +01:00
|
|
|
// connect signals for MPRIS2
|
|
|
|
connect(this, &AudioManager::canSkipForwardChanged, this, [this]() {
|
|
|
|
d->m_player.setCanGoNext(canSkipForward());
|
|
|
|
});
|
|
|
|
connect(this, &AudioManager::canSkipBackwardChanged, this, [this]() {
|
|
|
|
d->m_player.setCanGoPrevious(canSkipBackward());
|
|
|
|
});
|
|
|
|
connect(&d->m_player, &KMediaSession::nextRequested, this, &AudioManager::skipForward);
|
|
|
|
connect(&d->m_player, &KMediaSession::previousRequested, this, &AudioManager::skipBackward);
|
|
|
|
connect(&d->m_player, &KMediaSession::raiseWindowRequested, this, &AudioManager::raiseWindowRequested);
|
|
|
|
connect(&d->m_player, &KMediaSession::quitRequested, this, &AudioManager::quitRequested);
|
|
|
|
|
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);
|
2022-12-15 21:59:19 +01:00
|
|
|
// we'll send custom seekableChanged signal to work around possible backend 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
|
|
|
}
|
|
|
|
|
2023-12-13 19:01:12 +01:00
|
|
|
AudioManager::~AudioManager() = default;
|
2022-12-15 21:59:19 +01:00
|
|
|
|
|
|
|
QString AudioManager::backendName(KMediaSession::MediaBackends backend) const
|
|
|
|
{
|
|
|
|
qCDebug(kastsAudio) << "AudioManager::backendName()";
|
|
|
|
return d->m_player.backendName(backend);
|
|
|
|
}
|
|
|
|
|
|
|
|
KMediaSession::MediaBackends AudioManager::currentBackend() const
|
|
|
|
{
|
|
|
|
qCDebug(kastsAudio) << "AudioManager::currentBackend()";
|
|
|
|
return d->m_player.currentBackend();
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<KMediaSession::MediaBackends> AudioManager::availableBackends() const
|
|
|
|
{
|
|
|
|
qCDebug(kastsAudio) << "AudioManager::availableBackends()";
|
|
|
|
return d->m_player.availableBackends();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
{
|
2022-12-15 21:59:19 +01:00
|
|
|
return d->m_player.muted();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
qreal AudioManager::volume() const
|
|
|
|
{
|
2022-12-15 21:59:19 +01:00
|
|
|
return d->m_player.volume();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QUrl AudioManager::source() const
|
|
|
|
{
|
2022-12-15 21:59:19 +01:00
|
|
|
return d->m_player.source();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
2022-12-15 21:59:19 +01:00
|
|
|
KMediaSession::Error AudioManager::error() const
|
2021-04-11 15:26:28 +02:00
|
|
|
{
|
2022-12-15 21:59:19 +01:00
|
|
|
if (d->m_player.error() != KMediaSession::NoError) {
|
|
|
|
qCDebug(kastsAudio) << "AudioManager::error" << d->m_player.error();
|
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
|
2023-02-15 16:43:57 +01:00
|
|
|
if (!d->m_readyToPlay) {
|
|
|
|
if (d->m_entry && d->m_entry->enclosure()) {
|
|
|
|
return d->m_entry->enclosure()->duration() * 1000;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} else if (d->m_player.duration() > 0) {
|
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 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
|
2023-02-15 16:43:57 +01:00
|
|
|
if (!d->m_readyToPlay) {
|
|
|
|
if (d->m_entry && d->m_entry->enclosure()) {
|
|
|
|
return d->m_entry->enclosure()->playPosition();
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} else if (d->m_pendingSeek != -1) {
|
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 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
|
|
|
}
|
|
|
|
|
2022-12-15 21:59:19 +01:00
|
|
|
KMediaSession::PlaybackState AudioManager::playbackState() const
|
2021-04-11 15:26:28 +02:00
|
|
|
{
|
2022-12-15 21:59:19 +01:00
|
|
|
return d->m_player.playbackState();
|
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
|
|
|
|
{
|
2023-05-11 10:51:14 +02:00
|
|
|
return d->m_player.minimumPlaybackRate();
|
2021-04-12 22:18:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
qreal AudioManager::maximumPlaybackRate() const
|
|
|
|
{
|
2023-05-11 10:51:14 +02:00
|
|
|
return d->m_player.maximumPlaybackRate();
|
2021-04-12 22:18:04 +02:00
|
|
|
}
|
|
|
|
|
2022-09-22 16:59:30 +02:00
|
|
|
bool AudioManager::isStreaming() const
|
|
|
|
{
|
|
|
|
return d->m_isStreaming;
|
|
|
|
}
|
|
|
|
|
2022-12-15 21:59:19 +01:00
|
|
|
KMediaSession::MediaStatus AudioManager::status() const
|
2021-04-11 15:26:28 +02:00
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
return d->m_player.mediaStatus();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
2022-12-15 21:59:19 +01:00
|
|
|
void AudioManager::setCurrentBackend(KMediaSession::MediaBackends backend)
|
|
|
|
{
|
|
|
|
qCDebug(kastsAudio) << "AudioManager::setCurrentBackend(" << backend << ")";
|
|
|
|
|
|
|
|
KMediaSession::PlaybackState currentState = playbackState();
|
|
|
|
qint64 currentRate = playbackRate();
|
|
|
|
|
|
|
|
d->m_player.setCurrentBackend(backend);
|
|
|
|
|
|
|
|
setEntry(d->m_entry);
|
|
|
|
if (currentState == KMediaSession::PlaybackState::PlayingState) {
|
|
|
|
play();
|
|
|
|
}
|
|
|
|
// TODO: Fix restoring the current playback rate
|
|
|
|
setPlaybackRate(currentRate);
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
void AudioManager::setEntry(Entry *entry)
|
2021-04-11 15:26:28 +02:00
|
|
|
{
|
2023-02-15 16:43:57 +01:00
|
|
|
qCDebug(kastsAudio) << "begin AudioManager::setEntry";
|
2023-02-20 11:34:49 +01:00
|
|
|
// First unset current track and save playing state, such that any signal
|
|
|
|
// that still fires doesn't operate on the wrong track.
|
2021-07-06 19:21:57 +02:00
|
|
|
|
2023-02-15 16:43:57 +01:00
|
|
|
// disconnect any pending redirectUrl signals
|
|
|
|
bool signalDisconnect = disconnect(&Fetcher::instance(), &Fetcher::foundRedirectedUrl, this, nullptr);
|
|
|
|
qCDebug(kastsAudio) << "disconnected dangling foundRedirectedUrl signal:" << signalDisconnect;
|
|
|
|
|
2023-02-20 11:34:49 +01:00
|
|
|
// reset any pending seek action and lock position saving
|
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_pendingSeek = -1;
|
2021-04-14 11:32:57 +02:00
|
|
|
d->m_lockPositionSaving = true;
|
2023-02-20 11:34:49 +01:00
|
|
|
|
|
|
|
Entry *oldEntry = d->m_entry;
|
|
|
|
d->m_entry = nullptr;
|
2021-04-29 11:10:09 +02:00
|
|
|
|
2023-02-15 16:43:57 +01:00
|
|
|
// Check if the previous track needs to be marked as read
|
|
|
|
if (oldEntry && !signalDisconnect) {
|
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();
|
2023-06-02 22:45:34 +02:00
|
|
|
if (((duration() > 0) && (position() > 0) && ((duration() - position()) < SettingsManager::self()->markAsPlayedBeforeEnd() * 1000))
|
2022-12-15 21:59:19 +01:00
|
|
|
|| (d->m_player.mediaStatus() == KMediaSession::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);
|
2023-02-20 11:34:49 +01:00
|
|
|
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 = SettingsManager::self()->continuePlayingNextEntry();
|
2023-02-20 11:34:49 +01:00
|
|
|
} else {
|
|
|
|
bool continuePlaying = d->m_continuePlayback; // saving to local bool because it will be overwritten by the stop action
|
|
|
|
stop();
|
|
|
|
d->m_continuePlayback = continuePlaying;
|
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()
|
2023-06-02 14:23:26 +02:00
|
|
|
&& (entry->enclosure()->status() == Enclosure::Downloaded || NetworkConnectionManager::instance().streamingAllowed())) {
|
2021-05-28 22:55:18 +02:00
|
|
|
qCDebug(kastsAudio) << "Going to change source";
|
2023-02-15 16:43:57 +01:00
|
|
|
|
|
|
|
setEntryInfo(entry);
|
|
|
|
|
|
|
|
if (entry->enclosure()->status() == Enclosure::Downloaded) { // i.e. local file
|
2022-09-22 16:59:30 +02:00
|
|
|
if (d->m_isStreaming) {
|
|
|
|
d->m_isStreaming = false;
|
|
|
|
Q_EMIT isStreamingChanged();
|
|
|
|
}
|
2022-12-15 21:59:19 +01:00
|
|
|
|
2023-02-15 16:43:57 +01: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(QUrl::fromLocalFile(entry->enclosure()->path()));
|
|
|
|
} else {
|
|
|
|
// i.e. streaming; we first want to resolve the real URL, following
|
|
|
|
// redirects
|
|
|
|
QUrl loadUrl = QUrl(entry->enclosure()->url());
|
|
|
|
Fetcher::instance().getRedirectedUrl(loadUrl);
|
|
|
|
connect(&Fetcher::instance(), &Fetcher::foundRedirectedUrl, this, [this, entry, loadUrl](const QUrl &oldUrl, const QUrl &newUrl) {
|
|
|
|
qCDebug(kastsAudio) << oldUrl << newUrl;
|
|
|
|
if (loadUrl == oldUrl) {
|
|
|
|
bool signalDisconnect = disconnect(&Fetcher::instance(), &Fetcher::foundRedirectedUrl, this, nullptr);
|
|
|
|
qCDebug(kastsAudio) << "disconnected" << signalDisconnect;
|
|
|
|
|
|
|
|
if (!d->m_isStreaming) {
|
|
|
|
d->m_isStreaming = true;
|
|
|
|
Q_EMIT isStreamingChanged();
|
|
|
|
}
|
2022-12-15 21:59:19 +01:00
|
|
|
|
2023-02-15 16:43:57 +01:00
|
|
|
d->m_entry = entry;
|
|
|
|
Q_EMIT entryChanged(entry);
|
2021-04-11 18:55:09 +02:00
|
|
|
|
2023-02-15 16:43:57 +01: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(newUrl);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2022-12-15 21:59:19 +01:00
|
|
|
|
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();
|
2022-12-15 21:59:19 +01:00
|
|
|
d->m_player.setSource(QUrl());
|
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
|
|
|
|
2022-12-15 21:59:19 +01:00
|
|
|
d->m_player.setVolume(qRound(volume));
|
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()) {
|
2023-06-02 14:23:26 +02:00
|
|
|
if (!NetworkConnectionManager::instance().streamingAllowed()) {
|
2023-11-06 16:50:02 +01:00
|
|
|
if (NetworkConnectionManager::instance().networkReachable()) {
|
|
|
|
qCDebug(kastsAudio) << "Refusing to play: streaming on metered connection not allowed";
|
|
|
|
QString feedUrl, entryId;
|
|
|
|
if (d->m_entry) {
|
|
|
|
feedUrl = d->m_entry->feed()->url();
|
|
|
|
entryId = d->m_entry->id();
|
|
|
|
}
|
|
|
|
Q_EMIT logError(Error::Type::MeteredStreamingNotAllowed, feedUrl, entryId, 0, i18n("Streaming on metered connection not allowed"), QString());
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
qCDebug(kastsAudio) << "Refusing to play: no network connection";
|
|
|
|
QString feedUrl, entryId;
|
|
|
|
if (d->m_entry) {
|
|
|
|
feedUrl = d->m_entry->feed()->url();
|
|
|
|
entryId = d->m_entry->id();
|
|
|
|
}
|
|
|
|
Q_EMIT logError(Error::Type::NoNetwork, feedUrl, entryId, 0, i18n("No network connection"), QString());
|
|
|
|
return;
|
2023-07-27 13:33:41 +02:00
|
|
|
}
|
2022-09-22 16:59:30 +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 start once it's ready
|
|
|
|
d->m_continuePlayback = true;
|
|
|
|
|
2023-02-15 16:43:57 +01:00
|
|
|
if (d->m_readyToPlay) {
|
|
|
|
d->m_player.play();
|
|
|
|
d->m_isSeekable = true;
|
|
|
|
Q_EMIT seekableChanged(d->m_isSeekable);
|
2023-03-15 12:52:24 +01:00
|
|
|
|
|
|
|
if (d->m_entry && d->m_entry->getNew()) {
|
|
|
|
d->m_entry->setNew(false);
|
|
|
|
}
|
2023-02-15 16:43:57 +01:00
|
|
|
}
|
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;
|
|
|
|
|
2023-02-15 16:43:57 +01:00
|
|
|
if (d->m_readyToPlay) {
|
|
|
|
d->m_isSeekable = true;
|
|
|
|
d->m_player.pause();
|
|
|
|
}
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
2021-04-12 22:18:04 +02:00
|
|
|
void AudioManager::playPause()
|
|
|
|
{
|
2023-12-04 13:43:55 +01:00
|
|
|
if (playbackState() == KMediaSession::PlaybackState::PlayingState) {
|
2021-04-12 22:18:04 +02:00
|
|
|
pause();
|
2023-12-04 13:43:55 +01:00
|
|
|
} else {
|
|
|
|
play();
|
|
|
|
}
|
2021-04-12 22:18:04 +02:00
|
|
|
}
|
|
|
|
|
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-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
|
2023-02-27 15:04:01 +01:00
|
|
|
// NOTE: this can also happen while the streaming URL is still resolving, so
|
|
|
|
// we also allow seeking even when the track is not yet readyToPlay.
|
|
|
|
if (d->m_pendingSeek != -1 || !d->m_readyToPlay) {
|
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_pendingSeek = position;
|
|
|
|
Q_EMIT positionChanged(position);
|
2023-02-27 15:04:01 +01:00
|
|
|
} else if (d->m_pendingSeek == -1 && d->m_readyToPlay) {
|
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.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";
|
2023-05-11 07:26:31 +00:00
|
|
|
seek(std::min((position() + (1000 * SettingsManager::skipForward())), 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";
|
2023-05-11 07:26:31 +00:00
|
|
|
seek(std::max((qint64)0, (position() - (1000 * SettingsManager::skipBackward()))));
|
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]);
|
2023-07-27 13:33:41 +02:00
|
|
|
if (next_entry && 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 {
|
2023-06-02 14:23:26 +02:00
|
|
|
if (NetworkConnectionManager::instance().streamingAllowed()) {
|
2022-09-22 16:59:30 +02:00
|
|
|
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()) {
|
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
|
2022-12-15 21:59:19 +01:00
|
|
|
if (d->m_player.mediaStatus() == KMediaSession::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
|
2022-12-15 21:59:19 +01:00
|
|
|
if (d->m_player.mediaStatus() == KMediaSession::InvalidMedia) {
|
2021-04-30 11:31:02 +02:00
|
|
|
// 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();
|
2022-12-15 21:59:19 +01:00
|
|
|
Q_EMIT logError(Error::Type::InvalidMedia, badEntry->feed()->url(), badEntry->id(), KMediaSession::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
|
|
|
}
|
|
|
|
|
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
|
2023-07-27 13:33:41 +02:00
|
|
|
if (d->m_entry && d->m_entry->enclosure()) {
|
|
|
|
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);
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2023-07-27 13:33:41 +02:00
|
|
|
if (d->m_entry && d->m_entry->enclosure()) {
|
2021-04-14 11:32:57 +02:00
|
|
|
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
|
|
|
|
2023-02-15 16:43:57 +01:00
|
|
|
void AudioManager::setEntryInfo(Entry *entry)
|
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
|
|
|
{
|
2023-02-15 16:43:57 +01:00
|
|
|
// Set info for next track in preparation for the actual audio player to be
|
|
|
|
// set up and configured. We set all the info based on what's in the entry
|
|
|
|
// and disable all the controls until the track is ready to be played
|
|
|
|
|
|
|
|
d->m_player.setSource(QUrl());
|
|
|
|
d->m_entry = entry;
|
|
|
|
Q_EMIT entryChanged(entry);
|
|
|
|
|
|
|
|
qint64 newDuration = entry->enclosure()->duration() * 1000;
|
|
|
|
qint64 newPosition = entry->enclosure()->playPosition();
|
|
|
|
if (newPosition > newDuration && newPosition < 0) {
|
|
|
|
newPosition = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit positionChanged and durationChanged signals to make sure that
|
|
|
|
// the GUI can see the faked values (if needed)
|
|
|
|
Q_EMIT durationChanged(newDuration);
|
|
|
|
Q_EMIT positionChanged(newPosition);
|
|
|
|
|
|
|
|
d->m_readyToPlay = false;
|
|
|
|
Q_EMIT canPlayChanged();
|
|
|
|
Q_EMIT canPauseChanged();
|
|
|
|
Q_EMIT canSkipForwardChanged();
|
|
|
|
Q_EMIT canSkipBackwardChanged();
|
|
|
|
Q_EMIT canGoNextChanged();
|
|
|
|
d->m_isSeekable = false;
|
|
|
|
Q_EMIT seekableChanged(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::prepareAudio(const QUrl &loadUrl)
|
|
|
|
{
|
|
|
|
d->m_player.setSource(loadUrl);
|
|
|
|
|
|
|
|
// save the current playing track in the settingsfile for restoring on startup
|
|
|
|
DataManager::instance().setLastPlayingEntry(d->m_entry->id());
|
|
|
|
qCDebug(kastsAudio) << "Changed source to" << d->m_entry->title();
|
|
|
|
|
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.pause();
|
|
|
|
|
|
|
|
qint64 newDuration = duration();
|
|
|
|
|
|
|
|
qint64 startingPosition = d->m_entry->enclosure()->playPosition();
|
|
|
|
qCDebug(kastsAudio) << "Changing position to" << startingPosition / 1000 << "sec";
|
2023-02-27 15:04:01 +01:00
|
|
|
// if a seek is still pending then we don't set the position here
|
|
|
|
// this can happen e.g. if a chapter marker was clicked on a non-playing entry
|
|
|
|
if (d->m_pendingSeek == -1) {
|
|
|
|
if (startingPosition <= newDuration) {
|
|
|
|
d->m_pendingSeek = startingPosition;
|
|
|
|
} else {
|
|
|
|
d->m_pendingSeek = -1;
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
// 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();
|
2023-02-15 16:43:57 +01:00
|
|
|
} else {
|
|
|
|
pause();
|
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;
|
2023-02-15 16:43:57 +01:00
|
|
|
|
|
|
|
// set metadata for MPRIS2
|
|
|
|
updateMetaData();
|
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::checkForPendingSeek()
|
|
|
|
{
|
|
|
|
qint64 position = d->m_player.position();
|
|
|
|
qCDebug(kastsAudio) << "Seek pending?" << d->m_pendingSeek;
|
|
|
|
qCDebug(kastsAudio) << "Current position" << position;
|
|
|
|
|
2024-04-25 13:44:31 +02:00
|
|
|
// FIXME: the LoadedMedia+PlayingState test is a workaround for a broken
|
|
|
|
// qtmultimedia backend; LoadedMedia should never be allowed in the
|
|
|
|
// PlayingState according to docs. Remove this when upstream is fixed.
|
|
|
|
|
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
|
|
|
// Check if we're supposed to skip to a new position
|
2024-04-25 13:44:31 +02:00
|
|
|
if (d->m_pendingSeek != -1
|
|
|
|
&& (d->m_player.mediaStatus() == KMediaSession::BufferedMedia
|
|
|
|
|| (d->m_player.mediaStatus() == KMediaSession::LoadedMedia && d->m_player.playbackState() == KMediaSession::PlayingState))
|
|
|
|
&& d->m_player.duration() > 0) {
|
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 (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
|
2022-12-15 21:59:19 +01:00
|
|
|
if (playbackState() == KMediaSession::PlaybackState::PlayingState) {
|
|
|
|
qint64 seekPosition = d->m_pendingSeek;
|
|
|
|
QTimer::singleShot(0, this, [this, seekPosition]() {
|
|
|
|
d->m_player.setPosition(seekPosition);
|
|
|
|
});
|
|
|
|
}
|
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
|
|
|
} else {
|
|
|
|
qCDebug(kastsAudio) << "Pending position seek has been executed; to position" << d->m_pendingSeek;
|
|
|
|
d->m_pendingSeek = -1;
|
2022-12-15 21:59:19 +01:00
|
|
|
// d->m_player.setNotifyInterval(1000);
|
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-05-28 23:16:35 +02:00
|
|
|
}
|
2021-04-22 16:48:13 +02:00
|
|
|
}
|
2021-06-03 16:23:06 +02:00
|
|
|
|
2022-12-15 21:59:19 +01:00
|
|
|
void AudioManager::updateMetaData()
|
|
|
|
{
|
|
|
|
// set metadata for MPRIS2
|
|
|
|
if (!d->m_entry->title().isEmpty()) {
|
|
|
|
d->m_player.metaData()->setTitle(d->m_entry->title());
|
|
|
|
}
|
|
|
|
// TODO: set URL?? d->m_entry->enclosure()->path();
|
|
|
|
if (!d->m_entry->feed()->name().isEmpty()) {
|
|
|
|
d->m_player.metaData()->setAlbum(d->m_entry->feed()->name());
|
|
|
|
}
|
2023-12-19 09:14:48 +00:00
|
|
|
if (d->m_entry->authors().length() > 0) {
|
|
|
|
d->m_player.metaData()->setArtist(d->m_entry->authors());
|
2022-12-15 21:59:19 +01:00
|
|
|
}
|
|
|
|
if (!d->m_entry->image().isEmpty()) {
|
|
|
|
d->m_player.metaData()->setArtworkUrl(QUrl(d->m_entry->cachedImage()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|