From 2eec091ac0bfe3d490fa21347995004eb0714d1b Mon Sep 17 00:00:00 2001 From: Max Paperno Date: Fri, 29 Jun 2018 02:04:45 -0400 Subject: [PATCH] Companion custom functions sound playback fixes (#6000) * [CompanionIcon] Can now add multiple image files to icon for representing different UI states (checked/active/etc). * [Companion][CustomFunctionsPanel] Fix sound file locked after playback (fixes #5222, fixes #5997); Refactor play button (fixes #4929); Create new QMediaPlayer on each play and load in "LowLatency" mode instead of stream. --- companion/src/helpers.cpp | 13 +- companion/src/helpers.h | 1 + companion/src/modeledit/customfunctions.cpp | 129 ++++++++++---------- companion/src/modeledit/customfunctions.h | 9 +- 4 files changed, 78 insertions(+), 74 deletions(-) diff --git a/companion/src/helpers.cpp b/companion/src/helpers.cpp index 8985112a1..b7c0fa9ee 100644 --- a/companion/src/helpers.cpp +++ b/companion/src/helpers.cpp @@ -97,12 +97,17 @@ static QString iconThemeFolder(int theme_set) } CompanionIcon::CompanionIcon(const QString &baseimage) +{ + addImage(baseimage); +} + +void CompanionIcon::addImage(const QString & baseimage, Mode mode, State state) { const QString theme = iconThemeFolder(g.theme()); - addFile(":/themes/"+theme+"/16/"+baseimage, QSize(16,16)); - addFile(":/themes/"+theme+"/24/"+baseimage, QSize(24,24)); - addFile(":/themes/"+theme+"/32/"+baseimage, QSize(32,32)); - addFile(":/themes/"+theme+"/48/"+baseimage, QSize(48,48)); + addFile(":/themes/"+theme+"/16/"+baseimage, QSize(16,16), mode, state); + addFile(":/themes/"+theme+"/24/"+baseimage, QSize(24,24), mode, state); + addFile(":/themes/"+theme+"/32/"+baseimage, QSize(32,32), mode, state); + addFile(":/themes/"+theme+"/48/"+baseimage, QSize(48,48), mode, state); } diff --git a/companion/src/helpers.h b/companion/src/helpers.h index 2891d9573..db2ab7a18 100644 --- a/companion/src/helpers.h +++ b/companion/src/helpers.h @@ -55,6 +55,7 @@ extern const QColor colors[CPN_MAX_CURVES]; class CompanionIcon: public QIcon { public: CompanionIcon(const QString &baseimage); + void addImage(const QString &baseimage, Mode mode = Normal, State state = Off); }; class GVarGroup: public QObject { diff --git a/companion/src/modeledit/customfunctions.cpp b/companion/src/modeledit/customfunctions.cpp index 79d8699f0..8852a5646 100644 --- a/companion/src/modeledit/customfunctions.cpp +++ b/companion/src/modeledit/customfunctions.cpp @@ -109,6 +109,7 @@ CustomFunctionsPanel::CustomFunctionsPanel(QWidget * parent, ModelData * model, s1.report("get scripts"); CompanionIcon playIcon("play.png"); + playIcon.addImage("stop.png", QIcon::Normal, QIcon::On); QStringList headerLabels; headerLabels << "#" << tr("Switch") << tr("Action") << tr("Parameters") << tr("Enable"); @@ -209,12 +210,12 @@ CustomFunctionsPanel::CustomFunctionsPanel(QWidget * parent, ModelData * model, paramLayout->addWidget(fswtchBLcolor[i]); connect(fswtchBLcolor[i], SIGNAL(sliderReleased()), this, SLOT(customFunctionEdited())); - playBT[i] = new QPushButton(this); - playBT[i]->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + playBT[i] = new QToolButton(this); playBT[i]->setProperty("index", i); playBT[i]->setIcon(playIcon); + playBT[i]->setCheckable(true); paramLayout->addWidget(playBT[i]); - connect(playBT[i], SIGNAL(pressed()), this, SLOT(playMusic())); + connect(playBT[i], &QToolButton::clicked, this, &CustomFunctionsPanel::toggleSound); QHBoxLayout * repeatLayout = new QHBoxLayout(); tableLayout->addLayout(i, 4, repeatLayout); @@ -248,6 +249,8 @@ CustomFunctionsPanel::CustomFunctionsPanel(QWidget * parent, ModelData * model, CustomFunctionsPanel::~CustomFunctionsPanel() { + if (mediaPlayer) + stopSound(mediaPlayerCurrent); } void CustomFunctionsPanel::setDataModels() @@ -272,78 +275,70 @@ void CustomFunctionsPanel::setDataModels() void CustomFunctionsPanel::onMediaPlayerStateChanged(QMediaPlayer::State state) { - if (!lock) { - lock = true; - if (state==QMediaPlayer::StoppedState || state==QMediaPlayer::PausedState) { - mediaPlayer->stop(); - if (mediaPlayerCurrent >= 0) { - playBT[mediaPlayerCurrent]->setIcon(CompanionIcon("play.png")); - mediaPlayerCurrent = -1; - } - } - lock = false; - } + if (state != QMediaPlayer::PlayingState) + stopSound(mediaPlayerCurrent); } void CustomFunctionsPanel::onMediaPlayerError(QMediaPlayer::Error error) { - if (!lock) { - lock = true; - if (mediaPlayerCurrent >= 0) { - playBT[mediaPlayerCurrent]->setIcon(CompanionIcon("play.png")); - mediaPlayerCurrent = -1; - } - lock = false; + stopSound(mediaPlayerCurrent); + QMessageBox::critical(this, CPN_STR_TTL_ERROR, tr("Error occurred while trying to play sound, possibly the file is already opened. (Err: %1 [%2])").arg(mediaPlayer ? mediaPlayer->errorString() : "").arg(error)); +} + +bool CustomFunctionsPanel::playSound(int index) +{ + QString path = g.profile[g.id()].sdPath(); + if (!QDir(path).exists()) + return false; // unlikely + + if (mediaPlayer) + stopSound(mediaPlayerCurrent); + + if (firmware->getCapability(VoicesAsNumbers)) { // AVR + path.append(QString("/%1.wav").arg(int(fswtchParam[index]->value()), 4, 10, QChar('0'))); + } + else { + QString lang(generalSettings.ttsLanguage); + if (lang.isEmpty()) + lang = "en"; + path.append(QString("/SOUNDS/%1/%2.wav").arg(lang).arg(fswtchParamArmT[index]->currentText())); + } + if (!QFileInfo::exists(path) || !QFileInfo(path).isReadable()) { + QMessageBox::critical(this, CPN_STR_TTL_ERROR, tr("Unable to find or open sound file:\n%1").arg(path)); + return false; + } + + mediaPlayer = new QMediaPlayer(this, QMediaPlayer::LowLatency); + mediaPlayer->setMedia(QUrl::fromLocalFile(path)); + connect(mediaPlayer, &QMediaPlayer::stateChanged, this, &CustomFunctionsPanel::onMediaPlayerStateChanged); + connect(mediaPlayer, static_cast(&QMediaPlayer::error), this, &CustomFunctionsPanel::onMediaPlayerError); + mediaPlayerCurrent = index; + mediaPlayer->play(); + return true; +} + +void CustomFunctionsPanel::stopSound(int index) +{ + if (index > -1 && index < (int)DIM(playBT)) + playBT[index]->setChecked(false); + mediaPlayerCurrent = -1; + if (mediaPlayer) { + disconnect(mediaPlayer, 0, this, 0); + mediaPlayer->stop(); + mediaPlayer->deleteLater(); + mediaPlayer = nullptr; } } -void CustomFunctionsPanel::playMusic() +void CustomFunctionsPanel::toggleSound(bool play) { - if (!mediaPlayer) { - mediaPlayer = new QMediaPlayer(this); - connect(mediaPlayer, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(onMediaPlayerStateChanged(QMediaPlayer::State))); - connect(mediaPlayer, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(onMediaPlayerError(QMediaPlayer::Error))); - } - - int index = sender()->property("index").toInt(); - QString path = g.profile[g.id()].sdPath(); - QDir qd(path); - QString track; - if (qd.exists()) { - if (firmware->getCapability(VoicesAsNumbers)) { - track = path + QString("/%1.wav").arg(int(fswtchParam[index]->value()), 4, 10, (const QChar)'0'); - } - else { - path.append("/SOUNDS/"); - QString lang = generalSettings.ttsLanguage; - if (lang.isEmpty()) - lang = "en"; - path.append(lang); - if (fswtchParamArmT[index]->currentText() != "----") { - track = path + "/" + fswtchParamArmT[index]->currentText() + ".wav"; - } - } - QFile file(track); - if (!file.exists()) { - QMessageBox::critical(this, CPN_STR_TTL_ERROR, tr("Unable to find sound file %1!").arg(track)); - return; - } - - if (mediaPlayerCurrent == index) { - mediaPlayer->stop(); - playBT[index]->setIcon(CompanionIcon("play.png")); - mediaPlayerCurrent = -1; - } - else { - if (mediaPlayerCurrent >= 0) { - playBT[mediaPlayerCurrent]->setIcon(CompanionIcon("play.png")); - } - mediaPlayerCurrent = index; - mediaPlayer->setMedia(QUrl::fromLocalFile(track)); - mediaPlayer->play(); - playBT[index]->setIcon(CompanionIcon("stop.png")); - } - } + if (!sender() || !sender()->property("index").isValid()) + return; + const int index = sender()->property("index").toInt(); + if (play) + playBT[index]->setChecked(playSound(index)); + else + stopSound(index); } #define CUSTOM_FUNCTION_NUMERIC_PARAM (1<<0) diff --git a/companion/src/modeledit/customfunctions.h b/companion/src/modeledit/customfunctions.h index 8c4c5cd99..a7d3ca8da 100644 --- a/companion/src/modeledit/customfunctions.h +++ b/companion/src/modeledit/customfunctions.h @@ -21,10 +21,11 @@ #ifndef _CUSTOMFUNCTIONS_H_ #define _CUSTOMFUNCTIONS_H_ -#include #include "modeledit.h" #include "eeprominterface.h" +#include + class RawSwitchFilterItemModel; class TimerEdit; @@ -66,7 +67,9 @@ class CustomFunctionsPanel : public GenericPanel void fsw_customContextMenuRequested(QPoint pos); void refreshCustomFunction(int index, bool modified=false); void onChildModified(); - void playMusic(); + bool playSound(int index); + void stopSound(int index); + void toggleSound(bool play); void onMediaPlayerStateChanged(QMediaPlayer::State state); void onMediaPlayerError(QMediaPlayer::Error error); void fswDelete(); @@ -90,7 +93,7 @@ class CustomFunctionsPanel : public GenericPanel QCheckBox * fswtchParamGV[CPN_MAX_SPECIAL_FUNCTIONS]; QDoubleSpinBox * fswtchParam[CPN_MAX_SPECIAL_FUNCTIONS]; TimerEdit * fswtchParamTime[CPN_MAX_SPECIAL_FUNCTIONS]; - QPushButton * playBT[CPN_MAX_SPECIAL_FUNCTIONS]; + QToolButton * playBT[CPN_MAX_SPECIAL_FUNCTIONS]; QComboBox * fswtchParamT[CPN_MAX_SPECIAL_FUNCTIONS]; QComboBox * fswtchParamArmT[CPN_MAX_SPECIAL_FUNCTIONS]; QCheckBox * fswtchEnable[CPN_MAX_SPECIAL_FUNCTIONS];