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 "histogramwidget.h" #include <QMediaService> #include <QMediaPlaylist> #include <QVideoProbe> #include <QAudioProbe> #include <QMediaMetaData> #include <QtWidgets> Player::Player(QWidget *parent) : QWidget(parent) , videoWidget(0) , coverLabel(0) , slider(0) , colorDialog(0) { 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 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:"); videoHistogram = new HistogramWidget(this); audioHistogram = new HistogramWidget(this); QHBoxLayout *histogramLayout = new QHBoxLayout; histogramLayout->addWidget(labelHistogram); histogramLayout->addWidget(videoHistogram, 1); histogramLayout->addWidget(audioHistogram, 2); videoProbe = new QVideoProbe(this); connect(videoProbe, SIGNAL(videoFrameProbed(QVideoFrame)), videoHistogram, SLOT(processFrame(QVideoFrame))); videoProbe->setSource(player); audioProbe = new QAudioProbe(this); connect(audioProbe, SIGNAL(audioBufferProbed(QAudioBuffer)), audioHistogram, SLOT(processBuffer(QAudioBuffer))); audioProbe->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); 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); QBoxLayout *controlLayout = new QHBoxLayout; controlLayout->setMargin(0); controlLayout->addWidget(openButton); controlLayout->addStretch(1); controlLayout->addWidget(controls); controlLayout->addStretch(1); controlLayout->addWidget(fullScreenButton); controlLayout->addWidget(colorButton); 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); colorButton->setEnabled(false); 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) { 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(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); } 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::clearHistogram() { QMetaObject::invokeMethod(videoHistogram, "processFrame", Qt::QueuedConnection, Q_ARG(QVideoFrame, QVideoFrame())); QMetaObject::invokeMethod(audioHistogram, "processBuffer", Qt::QueuedConnection, Q_ARG(QAudioBuffer, QAudioBuffer())); }