player.cpp Example File

multimediawidgets/player/player.cpp

  /****************************************************************************
  **
  ** Copyright (C) 2015 The Qt Company Ltd.
  ** Contact: http://www.qt.io/licensing/
  **
  ** This file is part of the examples of the Qt Toolkit.
  **
  ** $QT_BEGIN_LICENSE:BSD$
  ** 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 "histogramwidget.h"

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

  Player::Player(QWidget *parent)
      : QWidget(parent)
      , videoWidget(0)
      , coverLabel(0)
      , slider(0)
  #ifndef PLAYER_NO_COLOROPTIONS
      , colorDialog(0)
  #endif
  {
      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()));

      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 QSlider(Qt::Horizontal, this);
      slider->setRange(0, player->duration() / 1000);

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

      labelHistogram = new QLabel(this);
      labelHistogram->setText("Histogram:");
      histogram = new HistogramWidget(this);
      QHBoxLayout *histogramLayout = new QHBoxLayout;
      histogramLayout->addWidget(labelHistogram);
      histogramLayout->addWidget(histogram, 1);

      probe = new QVideoProbe(this);
      connect(probe, SIGNAL(videoFrameProbed(QVideoFrame)), histogram, SLOT(processFrame(QVideoFrame)));
      probe->setSource(player);

      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()), 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);

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

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

      QBoxLayout *controlLayout = new QHBoxLayout;
      controlLayout->setMargin(0);
      controlLayout->addWidget(openButton);
      controlLayout->addStretch(1);
      controlLayout->addWidget(controls);
      controlLayout->addStretch(1);
      controlLayout->addWidget(fullScreenButton);
  #ifndef PLAYER_NO_COLOROPTIONS
      controlLayout->addWidget(colorButton);
  #endif

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

      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);
  #ifndef PLAYER_NO_COLOROPTIONS
          colorButton->setEnabled(false);
  #endif
          fullScreenButton->setEnabled(false);
      }

      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->setValue(progress / 1000);
      }
      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)
  {
      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::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(fullScreenButton, SIGNAL(clicked(bool)),
                      videoWidget, SLOT(setFullScreen(bool)));
          disconnect(videoWidget, SIGNAL(fullScreenChanged(bool)),
                  fullScreenButton, SLOT(setChecked(bool)));
          videoWidget->setFullScreen(false);
      } else {
          connect(fullScreenButton, SIGNAL(clicked(bool)),
                  videoWidget, SLOT(setFullScreen(bool)));
          connect(videoWidget, SIGNAL(fullScreenChanged(bool)),
                  fullScreenButton, SLOT(setChecked(bool)));

          if (fullScreenButton->isChecked())
              videoWidget->setFullScreen(true);
      }
  #ifndef PLAYER_NO_COLOROPTIONS
      colorButton->setEnabled(available);
  #endif
  }

  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);
  }

  #ifndef PLAYER_NO_COLOROPTIONS
  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();
  }
  #endif