Move media objects to dedicated thread to avoid main thread delays

This commit is contained in:
Dan Paulat 2025-05-17 19:08:07 -05:00
parent 38d36d37f6
commit 4ed1fda1ae
2 changed files with 84 additions and 44 deletions

View file

@ -5,13 +5,10 @@
#include <QAudioOutput> #include <QAudioOutput>
#include <QMediaDevices> #include <QMediaDevices>
#include <QMediaPlayer> #include <QMediaPlayer>
#include <QThread>
#include <QUrl> #include <QUrl>
namespace scwx namespace scwx::qt::manager
{
namespace qt
{
namespace manager
{ {
static const std::string logPrefix_ = "scwx::qt::manager::media_manager"; static const std::string logPrefix_ = "scwx::qt::manager::media_manager";
@ -20,46 +17,80 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
class MediaManager::Impl class MediaManager::Impl
{ {
public: public:
explicit Impl(MediaManager* self) : explicit Impl()
self_ {self},
mediaDevices_ {new QMediaDevices(self)},
mediaPlayer_ {new QMediaPlayer(self)},
audioOutput_ {new QAudioOutput(self)}
{ {
logger_->debug("Audio device: {}", mediaParent_ = std::make_unique<QObject>();
audioOutput_->device().description().toStdString()); mediaParent_->moveToThread(&thread_);
mediaPlayer_->setAudioOutput(audioOutput_); thread_.start();
ConnectSignals(); QMetaObject::invokeMethod(
mediaParent_.get(),
[this]()
{
// QObjects are managed by the parent
// NOLINTBEGIN(cppcoreguidelines-owning-memory)
logger_->debug("Creating QMediaDevices");
mediaDevices_ = new QMediaDevices(mediaParent_.get());
logger_->debug("Creating QMediaPlayer");
mediaPlayer_ = new QMediaPlayer(mediaParent_.get());
logger_->debug("Creating QAudioOutput");
audioOutput_ = new QAudioOutput(mediaParent_.get());
// NOLINTEND(cppcoreguidelines-owning-memory)
logger_->debug("Audio device: {}",
audioOutput_->device().description().toStdString());
mediaPlayer_->setAudioOutput(audioOutput_);
ConnectSignals();
});
} }
~Impl() {} ~Impl()
{
// Delete the media parent
mediaParent_.reset();
thread_.quit();
thread_.wait();
}
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
Impl(const Impl&&) = delete;
Impl& operator=(const Impl&&) = delete;
void ConnectSignals(); void ConnectSignals();
MediaManager* self_; QThread thread_ {};
QMediaDevices* mediaDevices_; std::unique_ptr<QObject> mediaParent_ {nullptr};
QMediaPlayer* mediaPlayer_; QMediaDevices* mediaDevices_ {nullptr};
QAudioOutput* audioOutput_; QMediaPlayer* mediaPlayer_ {nullptr};
QAudioOutput* audioOutput_ {nullptr};
}; };
MediaManager::MediaManager() : p(std::make_unique<Impl>(this)) {} MediaManager::MediaManager() : p(std::make_unique<Impl>()) {}
MediaManager::~MediaManager() = default; MediaManager::~MediaManager() = default;
MediaManager::MediaManager(MediaManager&&) noexcept = default;
MediaManager& MediaManager::operator=(MediaManager&&) noexcept = default;
void MediaManager::Impl::ConnectSignals() void MediaManager::Impl::ConnectSignals()
{ {
QObject::connect( QObject::connect(
mediaDevices_, mediaDevices_,
&QMediaDevices::audioOutputsChanged, &QMediaDevices::audioOutputsChanged,
self_, mediaParent_.get(),
[this]() [this]()
{ audioOutput_->setDevice(QMediaDevices::defaultAudioOutput()); }); { audioOutput_->setDevice(QMediaDevices::defaultAudioOutput()); });
QObject::connect(audioOutput_, QObject::connect(audioOutput_,
&QAudioOutput::deviceChanged, &QAudioOutput::deviceChanged,
self_, mediaParent_.get(),
[this]() [this]()
{ {
logger_->debug( logger_->debug(
@ -69,7 +100,7 @@ void MediaManager::Impl::ConnectSignals()
QObject::connect(mediaPlayer_, QObject::connect(mediaPlayer_,
&QMediaPlayer::errorOccurred, &QMediaPlayer::errorOccurred,
self_, mediaParent_.get(),
[](QMediaPlayer::Error error, const QString& errorString) [](QMediaPlayer::Error error, const QString& errorString)
{ {
logger_->error("Error {}: {}", logger_->error("Error {}: {}",
@ -81,30 +112,46 @@ void MediaManager::Impl::ConnectSignals()
void MediaManager::Play(types::AudioFile media) void MediaManager::Play(types::AudioFile media)
{ {
const std::string path = types::GetMediaPath(media); const std::string path = types::GetMediaPath(media);
Play(path);
} }
void MediaManager::Play(const std::string& mediaPath) void MediaManager::Play(const std::string& mediaPath)
{ {
logger_->debug("Playing audio: {}", mediaPath); logger_->debug("Playing audio: {}", mediaPath);
if (p->mediaPlayer_ == nullptr)
{
logger_->warn("Media player is not yet initialized");
return;
}
if (mediaPath.starts_with(':')) if (mediaPath.starts_with(':'))
{ {
p->mediaPlayer_->setSource( QMetaObject::invokeMethod(
p->mediaPlayer_,
&QMediaPlayer::setSource,
QUrl(QString("qrc%1").arg(QString::fromStdString(mediaPath)))); QUrl(QString("qrc%1").arg(QString::fromStdString(mediaPath))));
} }
else else
{ {
p->mediaPlayer_->setSource( QMetaObject::invokeMethod(
p->mediaPlayer_,
&QMediaPlayer::setSource,
QUrl::fromLocalFile(QString::fromStdString(mediaPath))); QUrl::fromLocalFile(QString::fromStdString(mediaPath)));
} }
p->mediaPlayer_->setPosition(0); QMetaObject::invokeMethod(p->mediaPlayer_, &QMediaPlayer::setPosition, 0);
QMetaObject::invokeMethod(p->mediaPlayer_, &QMediaPlayer::play); QMetaObject::invokeMethod(p->mediaPlayer_, &QMediaPlayer::play);
} }
void MediaManager::Stop() void MediaManager::Stop()
{ {
if (p->mediaPlayer_ == nullptr)
{
logger_->warn("Media player is not yet initialized");
return;
}
QMetaObject::invokeMethod(p->mediaPlayer_, &QMediaPlayer::stop); QMetaObject::invokeMethod(p->mediaPlayer_, &QMediaPlayer::stop);
} }
@ -126,6 +173,4 @@ std::shared_ptr<MediaManager> MediaManager::Instance()
return mediaManager; return mediaManager;
} }
} // namespace manager } // namespace scwx::qt::manager
} // namespace qt
} // namespace scwx

View file

@ -4,24 +4,21 @@
#include <memory> #include <memory>
#include <QObject> namespace scwx::qt::manager
namespace scwx
{
namespace qt
{
namespace manager
{ {
class MediaManager : public QObject class MediaManager
{ {
Q_OBJECT
Q_DISABLE_COPY_MOVE(MediaManager)
public: public:
explicit MediaManager(); explicit MediaManager();
~MediaManager(); ~MediaManager();
MediaManager(const MediaManager&) = delete;
MediaManager& operator=(const MediaManager&) = delete;
MediaManager(MediaManager&&) noexcept;
MediaManager& operator=(MediaManager&&) noexcept;
void Play(types::AudioFile media); void Play(types::AudioFile media);
void Play(const std::string& mediaPath); void Play(const std::string& mediaPath);
void Stop(); void Stop();
@ -33,6 +30,4 @@ private:
std::unique_ptr<Impl> p; std::unique_ptr<Impl> p;
}; };
} // namespace manager } // namespace scwx::qt::manager
} // namespace qt
} // namespace scwx