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 }