2021-04-11 17:22:13 +02:00
|
|
|
/**
|
|
|
|
* SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
|
|
|
|
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
2021-04-11 15:26:28 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#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-04-11 21:08:25 +02:00
|
|
|
#include "datamanager.h"
|
2021-05-01 21:35:37 +02:00
|
|
|
#include "powermanagementinterface.h"
|
2021-04-11 21:08:25 +02:00
|
|
|
#include "settingsmanager.h"
|
|
|
|
|
2021-04-12 22:18:04 +02:00
|
|
|
static const double MAX_RATE = 1.0;
|
|
|
|
static const double MIN_RATE = 2.5;
|
|
|
|
static const qint64 SKIP_STEP = 10000;
|
|
|
|
|
2021-04-11 15:26:28 +02:00
|
|
|
class AudioManagerPrivate
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
PowerManagementInterface mPowerInterface;
|
|
|
|
|
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;
|
2021-05-01 21:35:37 +02:00
|
|
|
bool m_lockPositionSaving =
|
|
|
|
false; // sort of lock mutex to prevent updating the player position while changing sources (which will emit lots of playerPositionChanged signals)
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-04-22 16:48:13 +02:00
|
|
|
void prepareAudioStream();
|
|
|
|
|
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);
|
|
|
|
connect(&d->m_player, &QMediaPlayer::durationChanged, this, &AudioManager::durationChanged);
|
|
|
|
connect(&d->m_player, &QMediaPlayer::positionChanged, this, &AudioManager::positionChanged);
|
2021-04-11 20:30:12 +02:00
|
|
|
connect(&d->m_player, &QMediaPlayer::positionChanged, this, &AudioManager::savePlayPosition);
|
2021-04-16 21:42:27 +02:00
|
|
|
|
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
|
|
|
|
|
|
|
// Check if an entry was playing when the program was shut down and restore it
|
2021-04-17 20:55:01 +02:00
|
|
|
if (DataManager::instance().lastPlayingEntry() != QStringLiteral("none"))
|
|
|
|
setEntry(DataManager::instance().getEntry(DataManager::instance().lastPlayingEntry()));
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
AudioManager::~AudioManager()
|
|
|
|
{
|
|
|
|
d->mPowerInterface.setPreventSleep(false);
|
|
|
|
}
|
|
|
|
|
2021-05-02 09:38:48 +02:00
|
|
|
QString AudioManager::timeString(qint64 timeInMicroSeconds)
|
|
|
|
{
|
|
|
|
QString outputString;
|
|
|
|
outputString += qFloor(timeInMicroSeconds / 3600000) < 10 ? QStringLiteral("0") : QStringLiteral("");
|
|
|
|
outputString += QString::number(qFloor(timeInMicroSeconds / 3600000)) + QStringLiteral(":");
|
|
|
|
outputString += qFloor(timeInMicroSeconds / 60000) % 60 < 10 ? QStringLiteral("0") : QStringLiteral("");
|
|
|
|
outputString += QString::number(qFloor(timeInMicroSeconds / 60000) % 60) + QStringLiteral(":");
|
|
|
|
outputString += qFloor(timeInMicroSeconds / 1000) % 60 < 10 ? QStringLiteral("0") : QStringLiteral("");
|
|
|
|
outputString += QString::number(qFloor(timeInMicroSeconds / 1000) % 60);
|
|
|
|
return outputString;
|
|
|
|
}
|
|
|
|
|
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-04-29 11:10:09 +02:00
|
|
|
qDebug() << "AudioManager::error" << d->m_player.errorString();
|
|
|
|
// Some error occured: probably best to unset the lastPlayingEntry to
|
|
|
|
// 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
|
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
return d->m_player.duration();
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
qint64 AudioManager::position() const
|
|
|
|
{
|
2021-04-11 18:11:36 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
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-04-14 11:32:57 +02:00
|
|
|
d->m_lockPositionSaving = true;
|
2021-05-23 21:09:02 +02:00
|
|
|
bool continuePlayback = false;
|
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
|
|
|
|
if (d->m_entry) {
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "Checking previous track";
|
|
|
|
// qDebug() << "Left time" << (duration()-position());
|
|
|
|
// qDebug() << "MediaStatus" << d->m_player.mediaStatus();
|
|
|
|
if (((duration() - position()) < 15000) || (d->m_player.mediaStatus() == QMediaPlayer::EndOfMedia)) {
|
|
|
|
// qDebug() << "Mark as read:" << d->m_entry->title();
|
2021-04-14 11:32:57 +02:00
|
|
|
d->m_entry->setRead(true);
|
|
|
|
d->m_entry->enclosure()->setPlayPosition(0);
|
2021-04-16 20:38:13 +02:00
|
|
|
d->m_entry->setQueueStatus(false); // i.e. remove from queue TODO: make this a choice in settings
|
2021-05-23 21:09:02 +02:00
|
|
|
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
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << entry->hasEnclosure() << entry->enclosure() << entry->enclosure()->status();
|
2021-04-29 11:10:09 +02:00
|
|
|
|
|
|
|
// do some checks on the new entry to see whether it's valid and not corrupted
|
|
|
|
if (entry != nullptr && entry->hasEnclosure() && entry->enclosure() && entry->enclosure()->status() == Enclosure::Downloaded) {
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "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);
|
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-01 21:35:37 +02:00
|
|
|
// qDebug() << "use custom pipeline";
|
|
|
|
d->m_player.setMedia(QUrl(QStringLiteral("gst-pipeline: playbin uri=file://") + d->m_entry->enclosure()->path()
|
|
|
|
+ QStringLiteral(" audio_sink=\"scaletempo ! audioconvert ! audioresample ! autoaudiosink\" video_sink=\"fakevideosink\"")));
|
2021-04-23 10:55:59 +02:00
|
|
|
#else
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "regular audio backend";
|
|
|
|
d->m_player.setMedia(QUrl(QStringLiteral("file://") + d->m_entry->enclosure()->path()));
|
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-01 21:35:37 +02:00
|
|
|
// qDebug() << "Changed source to" << d->m_entry->title();
|
2021-04-11 18:55:09 +02:00
|
|
|
|
2021-04-22 16:48:13 +02:00
|
|
|
d->prepareAudioStream();
|
2021-04-12 22:18:04 +02:00
|
|
|
d->m_readyToPlay = true;
|
|
|
|
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 = true;
|
|
|
|
Q_EMIT seekableChanged(true);
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "Duration" << d->m_player.duration()/1000 << d->m_entry->enclosure()->duration();
|
2021-04-18 22:21:59 +02:00
|
|
|
// Finally, check if duration mentioned in enclosure corresponds to real duration
|
2021-05-01 21:35:37 +02:00
|
|
|
if ((d->m_player.duration() / 1000) != d->m_entry->enclosure()->duration()) {
|
|
|
|
d->m_entry->enclosure()->setDuration(d->m_player.duration() / 1000);
|
|
|
|
// qDebug() << "Correcting duration of" << d->m_entry->id() << "to" << d->m_player.duration()/1000;
|
2021-04-18 22:21:59 +02:00
|
|
|
}
|
2021-05-23 21:09:02 +02:00
|
|
|
if (continuePlayback) play();
|
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-14 11:32:57 +02:00
|
|
|
// Unlock the position saving lock
|
|
|
|
d->m_lockPositionSaving = false;
|
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-01 21:35:37 +02:00
|
|
|
// qDebug() << "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-04-19 16:49:46 +02:00
|
|
|
//qDebug() << "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-01 21:35:37 +02:00
|
|
|
// qDebug() << "AudioManager::setPosition" << position;
|
2021-04-11 17:13:44 +02:00
|
|
|
|
2021-04-12 22:18:04 +02:00
|
|
|
d->m_player.setPosition(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-01 21:35:37 +02:00
|
|
|
// qDebug() << "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-01 21:35:37 +02:00
|
|
|
// qDebug() << "AudioManager::play";
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-04-22 16:48:13 +02:00
|
|
|
d->prepareAudioStream();
|
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-01 21:35:37 +02:00
|
|
|
// qDebug() << "AudioManager::pause";
|
2021-04-11 15:26:28 +02:00
|
|
|
|
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-01 21:35:37 +02:00
|
|
|
// qDebug() << "AudioManager::stop";
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-04-11 18:11:36 +02:00
|
|
|
d->m_player.stop();
|
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-01 21:35:37 +02:00
|
|
|
// qDebug() << "AudioManager::seek" << position;
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-04-11 18:11:36 +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-01 21:35:37 +02:00
|
|
|
// qDebug() << "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-01 21:35:37 +02:00
|
|
|
// qDebug() << "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-13 22:11:12 +02:00
|
|
|
// TODO: extend with streaming capability
|
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-01 21:35:37 +02:00
|
|
|
// qDebug() << "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;
|
|
|
|
}
|
2021-04-13 20:51:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::next()
|
|
|
|
{
|
|
|
|
if (canGoNext()) {
|
2021-04-14 11:32:57 +02:00
|
|
|
QMediaPlayer::State previousTrackState = playbackState();
|
2021-05-01 21:00:12 +02:00
|
|
|
int index = DataManager::instance().queue().indexOf(d->m_entry->id());
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "Skipping to" << DataManager::instance().queue()[index+1];
|
|
|
|
setEntry(DataManager::instance().getEntry(DataManager::instance().queue()[index + 1]));
|
|
|
|
if (previousTrackState == QMediaPlayer::PlayingState)
|
|
|
|
play();
|
2021-04-13 20:51:00 +02:00
|
|
|
} else {
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "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-01 21:35:37 +02:00
|
|
|
// qDebug() << "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();
|
|
|
|
if (badEntry && badEntry->enclosure())
|
|
|
|
badEntry->enclosure()->deleteFile();
|
|
|
|
// TODO: show error overlay?
|
|
|
|
}
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::playerStateChanged()
|
|
|
|
{
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::playerVolumeChanged()
|
|
|
|
{
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "AudioManager::playerVolumeChanged" << d->m_player.volume();
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
QTimer::singleShot(0, [this]() {
|
|
|
|
Q_EMIT volumeChanged();
|
|
|
|
});
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioManager::playerMutedChanged()
|
|
|
|
{
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "AudioManager::playerMutedChanged";
|
2021-04-11 15:26:28 +02:00
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
QTimer::singleShot(0, [this]() {
|
|
|
|
Q_EMIT mutedChanged(muted());
|
|
|
|
});
|
2021-04-11 15:26:28 +02:00
|
|
|
}
|
2021-04-11 20:30:12 +02:00
|
|
|
|
|
|
|
void AudioManager::savePlayPosition(qint64 position)
|
|
|
|
{
|
2021-04-14 11:32:57 +02:00
|
|
|
if (!d->m_lockPositionSaving) {
|
|
|
|
if (d->m_entry) {
|
|
|
|
if (d->m_entry->enclosure()) {
|
|
|
|
d->m_entry->enclosure()->setPlayPosition(position);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << d->m_player.mediaStatus();
|
2021-04-11 20:30:12 +02:00
|
|
|
}
|
2021-04-22 16:48:13 +02:00
|
|
|
|
|
|
|
void AudioManagerPrivate::prepareAudioStream()
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* What follows is a dirty hack to get the player positioned at the
|
|
|
|
* correct spot. The audio only becomes seekable when the player is
|
|
|
|
* actually playing and the stream is fully buffered. So we start the
|
|
|
|
* playback and then set a timer to wait until the stream becomes
|
|
|
|
* seekable; then switch position and immediately pause the playback.
|
|
|
|
* Unfortunately, this will produce an audible glitch with the current
|
|
|
|
* QMediaPlayer backend.
|
|
|
|
*/
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "voodoo happening";
|
2021-04-22 16:48:13 +02:00
|
|
|
qint64 startingPosition = m_entry->enclosure()->playPosition();
|
|
|
|
m_player.play();
|
|
|
|
if (!m_player.isSeekable()) {
|
|
|
|
QEventLoop loop;
|
|
|
|
QTimer timer;
|
|
|
|
timer.setSingleShot(true);
|
|
|
|
timer.setInterval(2000);
|
2021-05-28 16:49:55 +02:00
|
|
|
loop.connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
|
|
|
loop.connect(&m_player, &QMediaPlayer::seekableChanged, &loop, &QEventLoop::quit);
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "Starting waiting loop";
|
2021-04-22 16:48:13 +02:00
|
|
|
loop.exec();
|
|
|
|
}
|
|
|
|
if (m_player.mediaStatus() != QMediaPlayer::BufferedMedia) {
|
|
|
|
QEventLoop loop;
|
|
|
|
QTimer timer;
|
|
|
|
timer.setSingleShot(true);
|
|
|
|
timer.setInterval(2000);
|
2021-05-28 16:49:55 +02:00
|
|
|
loop.connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
|
|
|
loop.connect(&m_player, &QMediaPlayer::mediaStatusChanged, &loop, &QEventLoop::quit);
|
2021-05-01 21:35:37 +02:00
|
|
|
// qDebug() << "Starting waiting loop on media status" << d->m_player.mediaStatus();
|
2021-04-22 16:48:13 +02:00
|
|
|
loop.exec();
|
2021-05-01 21:35:37 +02:00
|
|
|
} // qDebug() << "Changing position";
|
|
|
|
if (startingPosition > 1000)
|
|
|
|
m_player.setPosition(startingPosition);
|
2021-04-22 16:48:13 +02:00
|
|
|
m_player.pause();
|
|
|
|
}
|