/**************************************************************************** ** ** 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 "videowidget.h" #include #include #include #include #include Player::Player(QWidget *parent) : QWidget(parent) { //! [create-objs] m_player = new QMediaPlayer(this); m_player->setAudioRole(QAudio::VideoRole); qInfo() << "Supported audio roles:"; for (QAudio::Role role : m_player->supportedAudioRoles()) qInfo() << " " << role; //! [create-objs] //! [2] m_videoWidget = new VideoWidget(this); m_videoWidget->resize(1280, 720); m_player->setVideoOutput(m_videoWidget); m_playlistModel = new PlaylistModel(this); m_playlist = m_playlistModel->playlist(); //! [2] connect(m_player, &QMediaPlayer::durationChanged, this, &Player::durationChanged); connect(m_player, &QMediaPlayer::positionChanged, this, &Player::positionChanged); connect(m_player, QOverload<>::of(&QMediaPlayer::metaDataChanged), this, &Player::metaDataChanged); connect(m_playlist, &QMediaPlaylist::currentIndexChanged, this, &Player::playlistPositionChanged); connect(m_player, &QMediaPlayer::mediaStatusChanged, this, &Player::statusChanged); connect(m_player, &QMediaPlayer::bufferStatusChanged, this, &Player::bufferingProgress); connect(m_player, &QMediaPlayer::videoAvailableChanged, this, &Player::videoAvailableChanged); connect(m_player, QOverload::of(&QMediaPlayer::error), this, &Player::displayErrorMessage); connect(m_player, &QMediaPlayer::stateChanged, this, &Player::stateChanged); m_playlistView = new QListView(this); m_playlistView->setModel(m_playlistModel); m_playlistView->setCurrentIndex(m_playlistModel->index(m_playlist->currentIndex(), 0)); connect(m_playlistView, &QAbstractItemView::activated, this, &Player::jump); m_slider = new QSlider(Qt::Horizontal, this); m_slider->setRange(0, m_player->duration() / 1000); m_labelDuration = new QLabel(this); connect(m_slider, &QSlider::sliderMoved, this, &Player::seek); m_labelHistogram = new QLabel(this); m_labelHistogram->setText("Histogram:"); m_videoHistogram = new HistogramWidget(this); m_audioHistogram = new HistogramWidget(this); QHBoxLayout *histogramLayout = new QHBoxLayout; histogramLayout->addWidget(m_labelHistogram); histogramLayout->addWidget(m_videoHistogram, 1); histogramLayout->addWidget(m_audioHistogram, 2); // ### replace by a monitoring outputs once we have them // m_videoProbe = new QVideoProbe(this); // connect(m_videoProbe, &QVideoProbe::videoFrameProbed, m_videoHistogram, &HistogramWidget::processFrame); // m_videoProbe->setSource(m_player); // m_audioProbe = new QAudioProbe(this); // connect(m_audioProbe, &QAudioProbe::audioBufferProbed, m_audioHistogram, &HistogramWidget::processBuffer); // m_audioProbe->setSource(m_player); QPushButton *openButton = new QPushButton(tr("Open"), this); connect(openButton, &QPushButton::clicked, this, &Player::open); PlayerControls *controls = new PlayerControls(this); controls->setState(m_player->state()); controls->setVolume(m_player->volume()); controls->setMuted(controls->isMuted()); connect(controls, &PlayerControls::play, m_player, &QMediaPlayer::play); connect(controls, &PlayerControls::pause, m_player, &QMediaPlayer::pause); connect(controls, &PlayerControls::stop, m_player, &QMediaPlayer::stop); connect(controls, &PlayerControls::next, m_playlist, &QMediaPlaylist::next); connect(controls, &PlayerControls::previous, this, &Player::previousClicked); connect(controls, &PlayerControls::changeVolume, m_player, &QMediaPlayer::setVolume); connect(controls, &PlayerControls::changeMuting, m_player, &QMediaPlayer::setMuted); connect(controls, &PlayerControls::changeRate, m_player, &QMediaPlayer::setPlaybackRate); connect(controls, &PlayerControls::stop, m_videoWidget, QOverload<>::of(&QVideoWidget::update)); connect(m_player, &QMediaPlayer::stateChanged, controls, &PlayerControls::setState); connect(m_player, &QMediaPlayer::volumeChanged, controls, &PlayerControls::setVolume); connect(m_player, &QMediaPlayer::mutedChanged, controls, &PlayerControls::setMuted); m_fullScreenButton = new QPushButton(tr("FullScreen"), this); m_fullScreenButton->setCheckable(true); m_colorButton = new QPushButton(tr("Color Options..."), this); m_colorButton->setEnabled(false); connect(m_colorButton, &QPushButton::clicked, this, &Player::showColorDialog); m_audioOutput = new QComboBox(this); m_audioOutput->addItem(QString::fromUtf8("Default"), QVariant::fromValue(QAudioDeviceInfo())); for (auto &deviceInfo: QMediaDeviceManager::audioOutputs()) m_audioOutput->addItem(deviceInfo.description(), QVariant::fromValue(deviceInfo)); connect(m_audioOutput, QOverload::of(&QComboBox::activated), this, &Player::audioOutputChanged); QBoxLayout *displayLayout = new QHBoxLayout; displayLayout->addWidget(m_videoWidget, 2); displayLayout->addWidget(m_playlistView); QBoxLayout *controlLayout = new QHBoxLayout; controlLayout->setContentsMargins(0, 0, 0, 0); controlLayout->addWidget(openButton); controlLayout->addStretch(1); controlLayout->addWidget(controls); controlLayout->addStretch(1); controlLayout->addWidget(m_fullScreenButton); controlLayout->addWidget(m_colorButton); controlLayout->addWidget(m_audioOutput); QBoxLayout *layout = new QVBoxLayout; layout->addLayout(displayLayout); QHBoxLayout *hLayout = new QHBoxLayout; hLayout->addWidget(m_slider); hLayout->addWidget(m_labelDuration); layout->addLayout(hLayout); layout->addLayout(controlLayout); layout->addLayout(histogramLayout); #if defined(Q_OS_QNX) // On QNX, the main window doesn't have a title bar (or any other decorations). // Create a status bar for the status information instead. m_statusLabel = new QLabel; m_statusBar = new QStatusBar; m_statusBar->addPermanentWidget(m_statusLabel); m_statusBar->setSizeGripEnabled(false); // Without mouse grabbing, it doesn't work very well. layout->addWidget(m_statusBar); #endif 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); m_playlistView->setEnabled(false); openButton->setEnabled(false); m_colorButton->setEnabled(false); m_fullScreenButton->setEnabled(false); } metaDataChanged(); } Player::~Player() { } bool Player::isPlayerAvailable() const { return m_player->isAvailable(); } void Player::open() { QFileDialog fileDialog(this); fileDialog.setAcceptMode(QFileDialog::AcceptOpen); fileDialog.setWindowTitle(tr("Open Files")); 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 &urls) { for (auto &url: urls) { if (isPlaylist(url)) m_playlist->load(url); else m_playlist->addMedia(url); } } void Player::setCustomAudioRole(const QString &role) { m_player->setCustomAudioRole(role); } void Player::durationChanged(qint64 duration) { m_duration = duration / 1000; m_slider->setMaximum(m_duration); } void Player::positionChanged(qint64 progress) { if (!m_slider->isSliderDown()) m_slider->setValue(progress / 1000); updateDurationInfo(progress / 1000); } void Player::metaDataChanged() { auto metaData = m_player->metaData(); setTrackInfo(QString("%1 - %2") .arg(metaData.value(QMediaMetaData::AlbumArtist).toString()) .arg(metaData.value(QMediaMetaData::Title).toString())); if (m_coverLabel) { QImage cover = metaData.value(QMediaMetaData::CoverArtImage).value(); m_coverLabel->setPixmap(QPixmap::fromImage(cover)); } } void Player::previousClicked() { // Go to previous track if we are within the first 5 seconds of playback // Otherwise, seek to the beginning. if (m_player->position() <= 5000) { m_playlist->previous(); } else { m_player->setPosition(0); } } void Player::jump(const QModelIndex &index) { if (index.isValid()) { m_playlist->setCurrentIndex(index.row()); m_player->play(); } } void Player::playlistPositionChanged(int currentItem) { clearHistogram(); m_playlistView->setCurrentIndex(m_playlistModel->index(currentItem, 0)); m_player->setMedia(m_playlist->currentMedia()); m_player->play(); } void Player::seek(int seconds) { m_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: setStatusInfo(QString()); break; case QMediaPlayer::LoadingMedia: setStatusInfo(tr("Loading...")); break; case QMediaPlayer::BufferingMedia: case QMediaPlayer::BufferedMedia: setStatusInfo(tr("Buffering %1%").arg(m_player->bufferStatus())); break; case QMediaPlayer::StalledMedia: setStatusInfo(tr("Stalled %1%").arg(m_player->bufferStatus())); break; case QMediaPlayer::EndOfMedia: QApplication::alert(this); m_playlist->next(); 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) { if (m_player->mediaStatus() == QMediaPlayer::StalledMedia) setStatusInfo(tr("Stalled %1%").arg(progress)); else setStatusInfo(tr("Buffering %1%").arg(progress)); } void Player::videoAvailableChanged(bool available) { if (!available) { disconnect(m_fullScreenButton, &QPushButton::clicked, m_videoWidget, &QVideoWidget::setFullScreen); disconnect(m_videoWidget, &QVideoWidget::fullScreenChanged, m_fullScreenButton, &QPushButton::setChecked); m_videoWidget->setFullScreen(false); } else { connect(m_fullScreenButton, &QPushButton::clicked, m_videoWidget, &QVideoWidget::setFullScreen); connect(m_videoWidget, &QVideoWidget::fullScreenChanged, m_fullScreenButton, &QPushButton::setChecked); if (m_fullScreenButton->isChecked()) m_videoWidget->setFullScreen(true); } m_colorButton->setEnabled(available); } void Player::setTrackInfo(const QString &info) { m_trackInfo = info; if (m_statusBar) { m_statusBar->showMessage(m_trackInfo); m_statusLabel->setText(m_statusInfo); } else { if (!m_statusInfo.isEmpty()) setWindowTitle(QString("%1 | %2").arg(m_trackInfo).arg(m_statusInfo)); else setWindowTitle(m_trackInfo); } } void Player::setStatusInfo(const QString &info) { m_statusInfo = info; if (m_statusBar) { m_statusBar->showMessage(m_trackInfo); m_statusLabel->setText(m_statusInfo); } else { if (!m_statusInfo.isEmpty()) setWindowTitle(QString("%1 | %2").arg(m_trackInfo).arg(m_statusInfo)); else setWindowTitle(m_trackInfo); } } void Player::displayErrorMessage() { setStatusInfo(m_player->errorString()); } void Player::updateDurationInfo(qint64 currentInfo) { QString tStr; if (currentInfo || m_duration) { QTime currentTime((currentInfo / 3600) % 60, (currentInfo / 60) % 60, currentInfo % 60, (currentInfo * 1000) % 1000); QTime totalTime((m_duration / 3600) % 60, (m_duration / 60) % 60, m_duration % 60, (m_duration * 1000) % 1000); QString format = "mm:ss"; if (m_duration > 3600) format = "hh:mm:ss"; tStr = currentTime.toString(format) + " / " + totalTime.toString(format); } m_labelDuration->setText(tStr); } void Player::showColorDialog() { if (!m_colorDialog) { QSlider *brightnessSlider = new QSlider(Qt::Horizontal); brightnessSlider->setRange(-100, 100); brightnessSlider->setValue(m_videoWidget->brightness()); connect(brightnessSlider, &QSlider::sliderMoved, m_videoWidget, &QVideoWidget::setBrightness); connect(m_videoWidget, &QVideoWidget::brightnessChanged, brightnessSlider, &QSlider::setValue); QSlider *contrastSlider = new QSlider(Qt::Horizontal); contrastSlider->setRange(-100, 100); contrastSlider->setValue(m_videoWidget->contrast()); connect(contrastSlider, &QSlider::sliderMoved, m_videoWidget, &QVideoWidget::setContrast); connect(m_videoWidget, &QVideoWidget::contrastChanged, contrastSlider, &QSlider::setValue); QSlider *hueSlider = new QSlider(Qt::Horizontal); hueSlider->setRange(-100, 100); hueSlider->setValue(m_videoWidget->hue()); connect(hueSlider, &QSlider::sliderMoved, m_videoWidget, &QVideoWidget::setHue); connect(m_videoWidget, &QVideoWidget::hueChanged, hueSlider, &QSlider::setValue); QSlider *saturationSlider = new QSlider(Qt::Horizontal); saturationSlider->setRange(-100, 100); saturationSlider->setValue(m_videoWidget->saturation()); connect(saturationSlider, &QSlider::sliderMoved, m_videoWidget, &QVideoWidget::setSaturation); connect(m_videoWidget, &QVideoWidget::saturationChanged, saturationSlider, &QSlider::setValue); 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); m_colorDialog = new QDialog(this); m_colorDialog->setWindowTitle(tr("Color Options")); m_colorDialog->setLayout(layout); connect(button, &QPushButton::clicked, m_colorDialog, &QDialog::close); } m_colorDialog->show(); } void Player::audioOutputChanged(int index) { auto device = m_audioOutput->itemData(index).value(); m_player->setAudioOutput(device); } void Player::clearHistogram() { QMetaObject::invokeMethod(m_videoHistogram, "processFrame", Qt::QueuedConnection, Q_ARG(QVideoFrame, QVideoFrame())); QMetaObject::invokeMethod(m_audioHistogram, "processBuffer", Qt::QueuedConnection, Q_ARG(QAudioBuffer, QAudioBuffer())); }