player.cpp Example File

multimediawidgets/player/player.cpp

  /****************************************************************************
  **
  ** Copyright (C) 2017 The Qt Company Ltd.
  ** Contact: https://www.qt.io/licensing/
  **
  ** This file is part of the examples of the Qt Toolkit.
  **
  ** $QT_BEGIN_LICENSE:BSD$
  ** Commercial License Usage
  ** Licensees holding valid commercial Qt licenses may use this file in
  ** accordance with the commercial license agreement provided with the
  ** Software or, alternatively, in accordance with the terms contained in
  ** a written agreement between you and The Qt Company. For licensing terms
  ** and conditions see https://www.qt.io/terms-conditions. For further
  ** information use the contact form at https://www.qt.io/contact-us.
  **
  ** BSD License Usage
  ** Alternatively, you may use this file under the terms of the BSD license
  ** as follows:
  **
  ** "Redistribution and use in source and binary forms, with or without
  ** modification, are permitted provided that the following conditions are
  ** met:
  **   * Redistributions of source code must retain the above copyright
  **     notice, this list of conditions and the following disclaimer.
  **   * Redistributions in binary form must reproduce the above copyright
  **     notice, this list of conditions and the following disclaimer in
  **     the documentation and/or other materials provided with the
  **     distribution.
  **   * Neither the name of The Qt Company Ltd nor the names of its
  **     contributors may be used to endorse or promote products derived
  **     from this software without specific prior written permission.
  **
  **
  ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
  **
  ** $QT_END_LICENSE$
  **
  ****************************************************************************/

  #include "player.h"

  #include "playercontrols.h"
  #include "playlistmodel.h"

  #include <QMediaService>
  #include <QMediaPlaylist>
  #include <QAudioProbe>
  #include <QMediaMetaData>
  #include <QtWidgets>

  class MySlider : public QSlider
  {
  public:
     MySlider(Qt::Orientation orientation, QWidget *parent = 0) : QSlider(orientation, parent){

     }

  protected:
    void mousePressEvent ( QMouseEvent * event )
    {
      if (event->button() == Qt::LeftButton)
      {
          if (orientation() == Qt::Vertical)
              setValue(minimum() + ((maximum()-minimum()) * (height()-event->y())) / height() ) ;
          else
              setValue(minimum() + ((maximum()-minimum()) * event->x()) / width() ) ;

          event->accept();
      }
      QSlider::mousePressEvent(event);
    }
  };

  Player::Player(QWidget *parent)
      : QWidget(parent)
      , videoWidget(0)
      , coverLabel(0)
      , slider(0)
      , colorDialog(0)
          , vdieoHide(false)
      , wasMaximized(false)
  {
      player = new QMediaPlayer(this);
      // owned by PlaylistModel
      playlist = new QMediaPlaylist();
      player->setPlaylist(playlist);

      connect(player, SIGNAL(durationChanged(qint64)), SLOT(durationChanged(qint64)));
      connect(player, SIGNAL(positionChanged(qint64)), SLOT(positionChanged(qint64)));
      connect(player, SIGNAL(metaDataChanged()), SLOT(metaDataChanged()));
      connect(playlist, SIGNAL(currentIndexChanged(int)), SLOT(playlistPositionChanged(int)));
      connect(player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)),
              this, SLOT(statusChanged(QMediaPlayer::MediaStatus)));
      connect(player, SIGNAL(bufferStatusChanged(int)), this, SLOT(bufferingProgress(int)));
      connect(player, SIGNAL(videoAvailableChanged(bool)), this, SLOT(videoAvailableChanged(bool)));
      connect(player, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(displayErrorMessage()));
      connect(player, &QMediaPlayer::stateChanged, this, &Player::stateChanged);

      videoWidget = new VideoWidget(this);
      player->setVideoOutput(videoWidget);

      playlistModel = new PlaylistModel(this);
      playlistModel->setPlaylist(playlist);

      playlistView = new QListView(this);
      playlistView->setModel(playlistModel);
      playlistView->setCurrentIndex(playlistModel->index(playlist->currentIndex(), 0));

      connect(playlistView, SIGNAL(activated(QModelIndex)), this, SLOT(jump(QModelIndex)));

      slider = new MySlider(Qt::Horizontal, this);
      slider->setRange(0, player->duration() / 1000);
      /* he slider emits the valueChanged() signal only when the user releases the slider. */
      slider->setTracking(false);

      labelDuration = new QLabel(this);
      connect(slider, SIGNAL(valueChanged(int)), this, SLOT(seek(int)));

      QPushButton *openButton = new QPushButton(tr("Open"), this);

      connect(openButton, SIGNAL(clicked()), this, SLOT(open()));

      PlayerControls *controls = new PlayerControls(this);
      controls->setState(player->state());
      controls->setVolume(player->volume());
      controls->setMuted(controls->isMuted());

          connect(controls, SIGNAL(play()), this, SLOT(play()));
      connect(controls, SIGNAL(play()), player, SLOT(play()));
      connect(controls, SIGNAL(pause()), player, SLOT(pause()));
      connect(controls, SIGNAL(stop()), player, SLOT(stop()));
      connect(controls, SIGNAL(next()), playlist, SLOT(next()));
      connect(controls, SIGNAL(previous()), this, SLOT(previousClicked()));
      connect(controls, SIGNAL(changeVolume(int)), player, SLOT(setVolume(int)));
      connect(controls, SIGNAL(changeMuting(bool)), player, SLOT(setMuted(bool)));
      connect(controls, SIGNAL(changeRate(qreal)), player, SLOT(setPlaybackRate(qreal)));

      connect(controls, SIGNAL(stop()), videoWidget, SLOT(update()));

      connect(player, SIGNAL(stateChanged(QMediaPlayer::State)),
              controls, SLOT(setState(QMediaPlayer::State)));
      connect(player, SIGNAL(volumeChanged(int)), controls, SLOT(setVolume(int)));
      connect(player, SIGNAL(mutedChanged(bool)), controls, SLOT(setMuted(bool)));

      fullScreenButton = new QPushButton(tr("FullScreen"), this);
      fullScreenButton->setCheckable(true);

          sinkButton = new QPushButton("EGL", this);
          connect(sinkButton, SIGNAL(clicked()), this, SLOT(changeSink()));

      colorButton = new QPushButton(tr("Color Options..."), this);
      colorButton->setEnabled(false);
      connect(colorButton, SIGNAL(clicked()), this, SLOT(showColorDialog()));

      QBoxLayout *displayLayout = new QHBoxLayout;
      displayLayout->addWidget(videoWidget, 2);
      displayLayout->addWidget(playlistView);

      controlLayout = new QHBoxLayout;
          controlLayout->setMargin(0);
      controlLayout->addWidget(openButton);
      controlLayout->addStretch(1);
      controlLayout->addWidget(controls);
      controlLayout->addStretch(1);
      controlLayout->addWidget(fullScreenButton);
          controlLayout->addWidget(sinkButton);
      controlLayout->addWidget(colorButton);

      layout = new QVBoxLayout;
          layout->addLayout(displayLayout);
      QHBoxLayout *hLayout = new QHBoxLayout;
      hLayout->addWidget(slider);
      hLayout->addWidget(labelDuration);
      layout->addLayout(hLayout);
      layout->addLayout(controlLayout);
          layout->setMargin(5);

      setLayout(layout);

      if (!isPlayerAvailable()) {
          QMessageBox::warning(this, tr("Service not available"),
                               tr("The QMediaPlayer object does not have a valid service.\n"\
                                  "Please check the media service plugins are installed."));

          controls->setEnabled(false);
          playlistView->setEnabled(false);
          openButton->setEnabled(false);
          colorButton->setEnabled(false);
          fullScreenButton->setEnabled(false);
      }

          this->installEventFilter(this);

      metaDataChanged();
  }

  Player::~Player()
  {
  }

  bool Player::isPlayerAvailable() const
  {
      return player->isAvailable();
  }

  void Player::open()
  {
      QFileDialog fileDialog(this);
      fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
      fileDialog.setWindowTitle(tr("Open Files"));
      QStringList supportedMimeTypes = player->supportedMimeTypes();
      if (!supportedMimeTypes.isEmpty()) {
          supportedMimeTypes.append("audio/x-m3u"); // MP3 playlists
          fileDialog.setMimeTypeFilters(supportedMimeTypes);
      }
      fileDialog.setDirectory(QStandardPaths::standardLocations(QStandardPaths::MoviesLocation).value(0, QDir::homePath()));
      if (fileDialog.exec() == QDialog::Accepted)
          addToPlaylist(fileDialog.selectedUrls());
  }

  static bool isPlaylist(const QUrl &url) // Check for ".m3u" playlists.
  {
      if (!url.isLocalFile())
          return false;
      const QFileInfo fileInfo(url.toLocalFile());
      return fileInfo.exists() && !fileInfo.suffix().compare(QLatin1String("m3u"), Qt::CaseInsensitive);
  }

  void Player::addToPlaylist(const QList<QUrl> urls)
  {
      foreach (const QUrl &url, urls) {
          if (isPlaylist(url))
              playlist->load(url);
          else
              playlist->addMedia(url);
      }
  }

  void Player::durationChanged(qint64 duration)
  {
      this->duration = duration/1000;
      slider->setMaximum(duration / 1000);
  }

  void Player::positionChanged(qint64 progress)
  {
      if (!slider->isSliderDown()) {
          slider->blockSignals(true);
          slider->setValue(progress / 1000);
          slider->blockSignals(false);
      }
      updateDurationInfo(progress / 1000);
  }

  void Player::metaDataChanged()
  {
      if (player->isMetaDataAvailable()) {
          setTrackInfo(QString("%1 - %2")
                  .arg(player->metaData(QMediaMetaData::AlbumArtist).toString())
                  .arg(player->metaData(QMediaMetaData::Title).toString()));

          if (coverLabel) {
              QUrl url = player->metaData(QMediaMetaData::CoverArtUrlLarge).value<QUrl>();

              coverLabel->setPixmap(!url.isEmpty()
                      ? QPixmap(url.toString())
                      : QPixmap());
          }
      }
  }

  void Player::previousClicked()
  {
      // Go to previous track if we are within the first 5 seconds of playback
      // Otherwise, seek to the beginning.
      if(player->position() <= 5000)
          playlist->previous();
      else
          player->setPosition(0);
  }

  void Player::jump(const QModelIndex &index)
  {
      if (index.isValid()) {
          playlist->setCurrentIndex(index.row());
          player->play();
      }
  }

  void Player::playlistPositionChanged(int currentItem)
  {
  //    clearHistogram();
      playlistView->setCurrentIndex(playlistModel->index(currentItem, 0));
  }

  void Player::seek(int seconds)
  {
      player->setPosition(seconds * 1000);
  }

  void Player::statusChanged(QMediaPlayer::MediaStatus status)
  {
      handleCursor(status);

      // handle status message
      switch (status) {
      case QMediaPlayer::UnknownMediaStatus:
      case QMediaPlayer::NoMedia:
      case QMediaPlayer::LoadedMedia:
      case QMediaPlayer::BufferingMedia:
      case QMediaPlayer::BufferedMedia:
          setStatusInfo(QString());
          break;
      case QMediaPlayer::LoadingMedia:
          setStatusInfo(tr("Loading..."));
          break;
      case QMediaPlayer::StalledMedia:
          setStatusInfo(tr("Media Stalled"));
          break;
      case QMediaPlayer::EndOfMedia:
          QApplication::alert(this);
          break;
      case QMediaPlayer::InvalidMedia:
          displayErrorMessage();
          break;
      }
  }

  void Player::stateChanged(QMediaPlayer::State state)
  {
  //    if (state == QMediaPlayer::StoppedState)
  //        clearHistogram();
  }

  void Player::handleCursor(QMediaPlayer::MediaStatus status)
  {
  #ifndef QT_NO_CURSOR
      if (status == QMediaPlayer::LoadingMedia ||
          status == QMediaPlayer::BufferingMedia ||
          status == QMediaPlayer::StalledMedia)
          setCursor(QCursor(Qt::BusyCursor));
      else
          unsetCursor();
  #endif
  }

  void Player::bufferingProgress(int progress)
  {
      setStatusInfo(tr("Buffering %4%").arg(progress));
  }

  void Player::videoAvailableChanged(bool available)
  {
      if (!available) {
          disconnect(videoWidget, SIGNAL(setFullScreen(bool)),
                                          this, SLOT(setFullScreen(bool)));
                  disconnect(fullScreenButton, SIGNAL(clicked(bool)),
                                                  this, SLOT(setFullScreen(bool)));
          disconnect(this, SIGNAL(fullscreenChanged(bool)),
                                  fullScreenButton, SLOT(setChecked(bool)));
                  setFullScreen(false);
          } else {
          connect(videoWidget, SIGNAL(setFullScreen(bool)),
                                          this, SLOT(setFullScreen(bool)));
                  connect(fullScreenButton, SIGNAL(clicked(bool)),
                                                  this, SLOT(setFullScreen(bool)));
                  connect(this, SIGNAL(fullscreenChanged(bool)),
                          fullScreenButton, SLOT(setChecked(bool)));

          if (fullScreenButton->isChecked())
                          setFullScreen(true);
          }
      colorButton->setEnabled(available);
  }

  void Player::setTrackInfo(const QString &info)
  {
      trackInfo = info;
      if (!statusInfo.isEmpty())
          setWindowTitle(QString("%1 | %2").arg(trackInfo).arg(statusInfo));
      else
          setWindowTitle(trackInfo);
  }

  void Player::setStatusInfo(const QString &info)
  {
      statusInfo = info;
      if (!statusInfo.isEmpty())
          setWindowTitle(QString("%1 | %2").arg(trackInfo).arg(statusInfo));
      else
          setWindowTitle(trackInfo);
  }

  void Player::displayErrorMessage()
  {
      setStatusInfo(player->errorString());
  }

  void Player::updateDurationInfo(qint64 currentInfo)
  {
      QString tStr;
      if (currentInfo || duration) {
          QTime currentTime((currentInfo/3600)%60, (currentInfo/60)%60, currentInfo%60, (currentInfo*1000)%1000);
          QTime totalTime((duration/3600)%60, (duration/60)%60, duration%60, (duration*1000)%1000);
          QString format = "mm:ss";
          if (duration > 3600)
              format = "hh:mm:ss";
          tStr = currentTime.toString(format) + " / " + totalTime.toString(format);
      }
      labelDuration->setText(tStr);
  }

  void Player::showColorDialog()
  {
      if (!colorDialog) {
          QSlider *brightnessSlider = new QSlider(Qt::Horizontal);
          brightnessSlider->setRange(-100, 100);
          brightnessSlider->setValue(videoWidget->brightness());
          connect(brightnessSlider, SIGNAL(sliderMoved(int)), videoWidget, SLOT(setBrightness(int)));
          connect(videoWidget, SIGNAL(brightnessChanged(int)), brightnessSlider, SLOT(setValue(int)));

          QSlider *contrastSlider = new QSlider(Qt::Horizontal);
          contrastSlider->setRange(-100, 100);
          contrastSlider->setValue(videoWidget->contrast());
          connect(contrastSlider, SIGNAL(sliderMoved(int)), videoWidget, SLOT(setContrast(int)));
          connect(videoWidget, SIGNAL(contrastChanged(int)), contrastSlider, SLOT(setValue(int)));

          QSlider *hueSlider = new QSlider(Qt::Horizontal);
          hueSlider->setRange(-100, 100);
          hueSlider->setValue(videoWidget->hue());
          connect(hueSlider, SIGNAL(sliderMoved(int)), videoWidget, SLOT(setHue(int)));
          connect(videoWidget, SIGNAL(hueChanged(int)), hueSlider, SLOT(setValue(int)));

          QSlider *saturationSlider = new QSlider(Qt::Horizontal);
          saturationSlider->setRange(-100, 100);
          saturationSlider->setValue(videoWidget->saturation());
          connect(saturationSlider, SIGNAL(sliderMoved(int)), videoWidget, SLOT(setSaturation(int)));
          connect(videoWidget, SIGNAL(saturationChanged(int)), saturationSlider, SLOT(setValue(int)));

          QFormLayout *layout = new QFormLayout;
          layout->addRow(tr("Brightness"), brightnessSlider);
          layout->addRow(tr("Contrast"), contrastSlider);
          layout->addRow(tr("Hue"), hueSlider);
          layout->addRow(tr("Saturation"), saturationSlider);

          QPushButton *button = new QPushButton(tr("Close"));
          layout->addRow(button);

          colorDialog = new QDialog(this);
          colorDialog->setWindowTitle(tr("Color Options"));
          colorDialog->setLayout(layout);

          connect(button, SIGNAL(clicked()), colorDialog, SLOT(close()));
      }
      colorDialog->show();
  }

  void Player::play()
  {
      /* don't allow sink change */
      sinkButton->setEnabled(false);
  }

  void Player::changeSink()
  {
      if ( player->state() == QMediaPlayer::StoppedState ) {
          if(sinkButton->text() == "DRM") {
              sinkButton->setText("EGL");
              videoWidget->setSink("EGL");
          } else {
              sinkButton->setText("DRM");
              videoWidget->setSink("DRM");
          }
      }
  }

  bool Player::eventFilter(QObject *obj, QEvent *event) {
      if (event->type() == QEvent::WindowStateChange) {

          /* rkximagesink can not hide itself, so we should do it in
           * player by set its size to zero
           */
          if (isMinimized()) {
              vdieoHide = true;
                          saveSize = QSize(videoWidget->width(), videoWidget->height());
                          videoWidget->resize(0, 0);
          } else if (vdieoHide) {
                  vdieoHide = false;
                  videoWidget->resize(saveSize);
                  }
                  if (isFullScreen()) {
                  if (!wasMaximized) {
                                  emit fullscreenChanged(wasMaximized = true);

                          playlistView->hide();
                                  slider->hide();
                                  labelDuration->hide();

                                  for (int i = 0; i != controlLayout->count(); ++i) {
                                            QWidget* w = controlLayout->itemAt(i)->widget();
                                              if (w != 0) {
                                                              w->setVisible(false); // hides the widget
                                                                    }
                                  }

                                  layout->setMargin(0);
                  }
                  } else {
                      if (wasMaximized) {
                                  emit fullscreenChanged(wasMaximized = false);
                          playlistView->show();
                                  slider->show();
                                  labelDuration->show();

                                  for (int i = 0; i != controlLayout->count(); ++i) {
                                            QWidget* w = controlLayout->itemAt(i)->widget();
                                              if (w != 0) {
                                                              w->setVisible(true); // show the widget
                                                                    }
                                  }

                                  layout->setMargin(5);

                  }
                  }
          } else if (event->type()==QEvent::KeyPress && isFullScreen()) {
                     emit setFullScreen(false);
                     event->accept();
          }

      return QWidget::eventFilter(obj, event);
  }

  void Player::setFullScreen(bool fullscreen)
  {
          if (fullscreen) {
              showFullScreen();
      } else {
          showNormal();
      }
  }

  //void Player::clearHistogram()
  //{
  //    QMetaObject::invokeMethod(videoHistogram, "processFrame", Qt::QueuedConnection, Q_ARG(QVideoFrame, QVideoFrame()));
  //    QMetaObject::invokeMethod(audioHistogram, "processBuffer", Qt::QueuedConnection, Q_ARG(QAudioBuffer, QAudioBuffer()));
  //}