File indexing completed on 2025-02-23 04:34:24

0001 /**
0002  * \file playtoolbar.cpp
0003  * Audio player toolbar.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 24-Aug-2010
0008  *
0009  * Copyright (C) 2010-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "playtoolbar.h"
0028 
0029 #include <QAction>
0030 #include <QLCDNumber>
0031 #include <QHBoxLayout>
0032 #include <QFileInfo>
0033 #include <QApplication>
0034 #include <QStyle>
0035 #include <QLabel>
0036 #include <QSplitter>
0037 #if QT_VERSION >= 0x060200
0038 #include <QAudioOutput>
0039 #else
0040 #include <QMediaPlaylist>
0041 #endif
0042 #include <QSlider>
0043 #include "audioplayer.h"
0044 
0045 namespace {
0046 
0047 const QString zeroTime(QLatin1String(" 0:00"));
0048 
0049 /**
0050  * Event filter for click on time LCD.
0051  */
0052 class TimeLcdClickHandler : public QObject {
0053 public:
0054   /**
0055    * Constructor.
0056    * @param playToolBar play tool bar
0057    */
0058   explicit TimeLcdClickHandler(PlayToolBar* playToolBar)
0059     : QObject(playToolBar), m_playToolBar(playToolBar) {}
0060 
0061   ~TimeLcdClickHandler() override = default;
0062 
0063 protected:
0064   /**
0065    * Event filter function, calls PlayToolBar::toggleTimeDisplayMode().
0066    *
0067    * @param obj watched object
0068    * @param event event for object
0069    *
0070    * @return true if event is filtered.
0071    */
0072   bool eventFilter(QObject* obj, QEvent* event) override;
0073 
0074 private:
0075   Q_DISABLE_COPY(TimeLcdClickHandler)
0076 
0077   PlayToolBar* m_playToolBar;
0078 };
0079 
0080 bool TimeLcdClickHandler::eventFilter(QObject* obj, QEvent* event)
0081 {
0082   if (event->type() == QEvent::MouseButtonRelease) {
0083     m_playToolBar->toggleTimeDisplayMode();
0084     return true;
0085   }
0086   // standard event processing
0087   return QObject::eventFilter(obj, event);
0088 }
0089 
0090 }
0091 
0092 /**
0093  * Constructor.
0094  *
0095  * @param player audio player
0096  * @param parent parent widget
0097  */
0098 PlayToolBar::PlayToolBar(AudioPlayer* player, QWidget* parent)
0099   : QToolBar(parent), m_player(player),
0100     m_timeDisplayMode(TimeDisplayMode::Elapsed)
0101 {
0102   setObjectName(QLatin1String("Kid3Player"));
0103   setWindowTitle(tr("Play"));
0104 
0105   m_playIcon = style()->standardIcon(QStyle::SP_MediaPlay);
0106   m_pauseIcon = style()->standardIcon(QStyle::SP_MediaPause);
0107 
0108   m_playOrPauseAction = new QAction(m_playIcon, tr("Play/Pause"), this);
0109   m_playOrPauseAction->setObjectName(QLatin1String("audio_play"));
0110   m_playOrPauseAction->setShortcut(Qt::Key_MediaPlay);
0111   m_stopAction = new QAction(
0112     style()->standardIcon(QStyle::SP_MediaStop), tr("Stop playback"), this);
0113   m_stopAction->setObjectName(QLatin1String("audio_stop"));
0114   m_stopAction->setShortcut(Qt::Key_MediaStop);
0115   m_previousAction = new QAction(
0116     style()->standardIcon(QStyle::SP_MediaSkipBackward), tr("Previous Track"), this);
0117   m_previousAction->setObjectName(QLatin1String("audio_previous"));
0118   m_previousAction->setShortcut(Qt::Key_MediaPrevious);
0119   m_nextAction = new QAction(
0120     style()->standardIcon(QStyle::SP_MediaSkipForward), tr("Next Track"), this);
0121   m_nextAction->setObjectName(QLatin1String("audio_next"));
0122   m_nextAction->setShortcut(Qt::Key_MediaNext);
0123   auto closeAction = new QAction(
0124     style()->standardIcon(QStyle::SP_TitleBarCloseButton), tr("Close"), this);
0125 
0126   auto splitter = new QSplitter(this);
0127   m_titleLabel = new QLabel(splitter);
0128 
0129   QMediaPlayer* mediaPlayer = m_player->mediaPlayer();
0130   m_seekSlider = new QSlider(Qt::Horizontal, splitter);
0131   m_seekSlider->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
0132   m_seekSlider->setMinimum(0);
0133   m_duration = mediaPlayer->duration();
0134   // Setting a maximum of 0 crashes with Qt 5.4.0 on Mac OS X.
0135   if (int maximum = m_duration / 1000; maximum > 0) {
0136     m_seekSlider->setMaximum(maximum);
0137   }
0138   connect(m_seekSlider, &QAbstractSlider::actionTriggered,
0139           this, &PlayToolBar::seekAction);
0140   m_muteAction = new QAction(
0141         style()->standardIcon(QStyle::SP_MediaVolume), tr("Mute"), this);
0142   m_volumeSlider = new QSlider(Qt::Horizontal, this);
0143   m_volumeSlider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0144   m_volumeSlider->setRange(0, 100);
0145 #if QT_VERSION >= 0x060200
0146   int volume = mediaPlayer->audioOutput()->volume() * 100.0f;
0147 #else
0148   int volume = mediaPlayer->volume();
0149 #endif
0150   m_volumeSlider->setValue(volume);
0151   setVolumeToolTip(volume);
0152   connect(m_volumeSlider, &QAbstractSlider::actionTriggered,
0153           this, &PlayToolBar::volumeAction);
0154 
0155   m_timeLcd = new QLCDNumber(this);
0156   m_timeLcd->setSegmentStyle(QLCDNumber::Flat);
0157   m_timeLcd->setFrameStyle(QFrame::NoFrame);
0158   m_timeLcd->display(zeroTime);
0159   m_timeLcd->setDigitCount(7);
0160   m_timeLcd->installEventFilter(new TimeLcdClickHandler(this));
0161 
0162   addAction(m_playOrPauseAction);
0163   addAction(m_stopAction);
0164   addAction(m_previousAction);
0165   addAction(m_nextAction);
0166   addWidget(splitter);
0167   addAction(m_muteAction);
0168   addWidget(m_volumeSlider);
0169   addWidget(m_timeLcd);
0170   addAction(closeAction);
0171 
0172 #if QT_VERSION >= 0x060200
0173   connect(mediaPlayer, &QMediaPlayer::playbackStateChanged,
0174           this, &PlayToolBar::stateChanged);
0175   connect(mediaPlayer, &QMediaPlayer::errorOccurred,
0176           this, &PlayToolBar::error);
0177   connect(mediaPlayer->audioOutput(), &QAudioOutput::volumeChanged,
0178           this, [this](float volume) { setVolumeToolTip(volume * 100.0f); });
0179 #else
0180   connect(mediaPlayer, &QMediaPlayer::stateChanged,
0181           this, &PlayToolBar::stateChanged);
0182   connect(mediaPlayer, static_cast<void (QMediaPlayer::*)(QMediaPlayer::Error)>(
0183             &QMediaPlayer::error),
0184           this, &PlayToolBar::error);
0185   connect(mediaPlayer, &QMediaPlayer::volumeChanged,
0186           this, &PlayToolBar::setVolumeToolTip);
0187 #endif
0188   connect(mediaPlayer, &QMediaPlayer::durationChanged,
0189           this, &PlayToolBar::durationChanged);
0190   connect(m_muteAction, &QAction::triggered, this, &PlayToolBar::toggleMute);
0191   connect(m_player, &AudioPlayer::positionChanged, this, &PlayToolBar::tick);
0192   connect(m_player, &AudioPlayer::trackChanged,
0193           this, &PlayToolBar::trackChanged);
0194   connect(m_player, &AudioPlayer::aboutToPlay,
0195           this, &PlayToolBar::aboutToPlay);
0196   connect(m_playOrPauseAction, &QAction::triggered,
0197           m_player, &AudioPlayer::playOrPause);
0198   connect(m_stopAction, &QAction::triggered, m_player, &AudioPlayer::stop);
0199   connect(m_previousAction, &QAction::triggered, m_player, &AudioPlayer::previous);
0200   connect(m_nextAction, &QAction::triggered, m_player, &AudioPlayer::next);
0201   connect(closeAction, &QAction::triggered, this, &QWidget::close);
0202 
0203 #ifdef Q_OS_MAC
0204   setStyleSheet(QLatin1String("QToolButton { border: 0; }"));
0205 #endif
0206 }
0207 
0208 /**
0209  * Destructor.
0210   */
0211 PlayToolBar::~PlayToolBar()
0212 {
0213   m_player->stop();
0214   emit closed();
0215 }
0216 
0217 /**
0218  * Stop sound when window is closed.
0219  */
0220 void PlayToolBar::closeEvent(QCloseEvent*)
0221 {
0222   m_player->stop();
0223   emit closed();
0224 }
0225 
0226 /**
0227  * Update displayed time.
0228  *
0229  * @param msec time in ms
0230  */
0231 void PlayToolBar::tick(qint64 msec)
0232 {
0233   qint64 displayedMsecs = msec;
0234   QString sign;
0235   if (m_timeDisplayMode == TimeDisplayMode::Remaining) {
0236     displayedMsecs = qAbs(m_duration - msec);
0237     sign = QLatin1String("-");
0238   }
0239   int hours = displayedMsecs / (60 * 60 * 1000);
0240   int minutes = (displayedMsecs / (60 * 1000)) % 60;
0241   int seconds = (displayedMsecs / 1000) % 60;
0242   if (displayedMsecs % 1000 >= 500) {
0243     ++seconds;
0244   }
0245   if (hours == 0) {
0246     m_timeLcd->display(QString(QLatin1String("%1%2:%3"))
0247                        .arg(sign)
0248                        .arg(minutes, 2, 10, QLatin1Char(' '))
0249                        .arg(seconds, 2, 10, QLatin1Char('0')));
0250   } else {
0251     m_timeLcd->display(QString(QLatin1String("%1%2:%3:%4"))
0252                        .arg(sign)
0253                        .arg(hours, 2, 10, QLatin1Char(' '))
0254                        .arg(minutes, 2, 10, QLatin1Char('0'))
0255                        .arg(seconds, 2, 10, QLatin1Char('0')));
0256   }
0257   if (!m_seekSlider->isSliderDown()) {
0258     m_seekSlider->setValue(msec / 1000);
0259   }
0260 }
0261 
0262 /**
0263  * Update button states when the Phonon state changed.
0264  *
0265  * @param newState new Phonon state
0266  */
0267 void PlayToolBar::stateChanged(int newState)
0268 {
0269   switch (newState) {
0270     case QMediaPlayer::PlayingState:
0271       m_playOrPauseAction->setEnabled(true);
0272       m_playOrPauseAction->setIcon(m_pauseIcon);
0273       m_stopAction->setEnabled(true);
0274       break;
0275     case QMediaPlayer::PausedState:
0276       m_playOrPauseAction->setEnabled(true);
0277       m_playOrPauseAction->setIcon(m_playIcon);
0278       m_stopAction->setEnabled(true);
0279       break;
0280     case QMediaPlayer::StoppedState:
0281       m_playOrPauseAction->setEnabled(true);
0282       m_playOrPauseAction->setIcon(m_playIcon);
0283       m_stopAction->setEnabled(false);
0284       m_timeLcd->display(zeroTime);
0285       break;
0286     default:
0287       m_playOrPauseAction->setEnabled(false);
0288       break;
0289   }
0290 }
0291 
0292 /**
0293  * Update states when a media player error occurs.
0294  *
0295  * @param err error
0296  */
0297 void PlayToolBar::error(QMediaPlayer::Error err)
0298 {
0299   Q_UNUSED(err)
0300   m_playOrPauseAction->setEnabled(false);
0301   m_stopAction->setEnabled(false);
0302   emit errorMessage(m_player->mediaPlayer()->errorString());
0303 }
0304 
0305 /**
0306  * Called when the duration changes.
0307  * @param duration duration in milliseconds
0308  */
0309 void PlayToolBar::durationChanged(qint64 duration)
0310 {
0311   m_duration = duration;
0312   // Setting a maximum of 0 crashes with Qt 5.4.0 on Mac OS X.
0313   if (int maximum = duration / 1000; maximum > 0) {
0314     m_seekSlider->setMaximum(maximum);
0315   }
0316 }
0317 
0318 /**
0319  * Called when the volume changes.
0320  * @param volume current volume (0..100)
0321  */
0322 void PlayToolBar::setVolumeToolTip(int volume)
0323 {
0324   m_volumeSlider->setToolTip(tr("Volume: %1%").arg(volume));
0325 }
0326 
0327 /**
0328  * Set current position in track when slider position is changed.
0329  * @param action slider action
0330  */
0331 void PlayToolBar::seekAction(int action)
0332 {
0333   Q_UNUSED(action);
0334   m_player->setCurrentPosition(m_seekSlider->sliderPosition() * 1000);
0335 }
0336 
0337 /**
0338  * Set volume when slider position is changed.
0339  * @param action slider action
0340  */
0341 void PlayToolBar::volumeAction(int action)
0342 {
0343   Q_UNUSED(action);
0344 #if QT_VERSION >= 0x060200
0345   m_player->mediaPlayer()->audioOutput()->setVolume(
0346         static_cast<float>(m_volumeSlider->sliderPosition()) / 100.0f);
0347 #else
0348   m_player->mediaPlayer()->setVolume(m_volumeSlider->sliderPosition());
0349 #endif
0350 }
0351 
0352 /**
0353  * Toggle muted state.
0354  */
0355 void PlayToolBar::toggleMute()
0356 {
0357 #if QT_VERSION >= 0x060200
0358   bool muted = !m_player->mediaPlayer()->audioOutput()->isMuted();
0359   m_player->mediaPlayer()->audioOutput()->setMuted(muted);
0360 #else
0361   bool muted = !m_player->mediaPlayer()->isMuted();
0362   m_player->mediaPlayer()->setMuted(muted);
0363 #endif
0364   m_muteAction->setIcon(style()->standardIcon(muted
0365       ? QStyle::SP_MediaVolumeMuted : QStyle::SP_MediaVolume));
0366 }
0367 
0368 /**
0369  * Toggle time display mode.
0370  */
0371 void PlayToolBar::toggleTimeDisplayMode()
0372 {
0373   m_timeDisplayMode = m_timeDisplayMode == TimeDisplayMode::Elapsed
0374       ? TimeDisplayMode::Remaining : TimeDisplayMode::Elapsed;
0375 }
0376 
0377 /**
0378  * Get media player actions.
0379  * @return list with named actions for "audio_play", "audio_stop",
0380  * "audio_previous", "audio_next".
0381  */
0382 QList<QAction*> PlayToolBar::mediaActions() const
0383 {
0384   return {
0385       m_playOrPauseAction,
0386       m_stopAction,
0387       m_previousAction,
0388       m_nextAction
0389   };
0390 }
0391 
0392 /**
0393  * Update display and button state when the current track is changed.
0394  *
0395  * @param filePath path of currently played audio file
0396  * @param hasPrevious true if a previous track is available
0397  * @param hasNext true if a next track is available
0398  */
0399 void  PlayToolBar::trackChanged(const QString& filePath,
0400                                 bool hasPrevious, bool hasNext)
0401 {
0402   QFileInfo fi(filePath);
0403   m_titleLabel->setText(fi.fileName());
0404 
0405   m_previousAction->setEnabled(hasPrevious);
0406   m_nextAction->setEnabled(hasNext);
0407 
0408   m_duration = m_player->mediaPlayer()->duration();
0409   // Setting a maximum of 0 crashes with Qt 5.4.0 on Mac OS X.
0410   if (int maximum = m_duration / 1000; maximum > 0) {
0411     m_seekSlider->setMaximum(maximum);
0412   }
0413 }