1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "player.h"
52
53#include "playercontrols.h"
54#include "playlistmodel.h"
55#include "histogramwidget.h"
56#include "videowidget.h"
57
58#include <QMediaService>
59#include <QMediaPlaylist>
60#include <QVideoProbe>
61#include <QAudioProbe>
62#include <QMediaMetaData>
63#include <QtWidgets>
64
65Player::Player(QWidget *parent)
66 : QWidget(parent)
67{
68//! [create-objs]
69 m_player = new QMediaPlayer(this);
70 m_player->setAudioRole(QAudio::VideoRole);
71 qInfo() << "Supported audio roles:";
72 for (QAudio::Role role : m_player->supportedAudioRoles())
73 qInfo() << " " << role;
74 // owned by PlaylistModel
75 m_playlist = new QMediaPlaylist();
76 m_player->setPlaylist(m_playlist);
77//! [create-objs]
78
79 connect(sender: m_player, signal: &QMediaPlayer::durationChanged, receiver: this, slot: &Player::durationChanged);
80 connect(sender: m_player, signal: &QMediaPlayer::positionChanged, receiver: this, slot: &Player::positionChanged);
81 connect(sender: m_player, signal: QOverload<>::of(ptr: &QMediaPlayer::metaDataChanged), receiver: this, slot: &Player::metaDataChanged);
82 connect(sender: m_playlist, signal: &QMediaPlaylist::currentIndexChanged, receiver: this, slot: &Player::playlistPositionChanged);
83 connect(sender: m_player, signal: &QMediaPlayer::mediaStatusChanged, receiver: this, slot: &Player::statusChanged);
84 connect(sender: m_player, signal: &QMediaPlayer::bufferStatusChanged, receiver: this, slot: &Player::bufferingProgress);
85 connect(sender: m_player, signal: &QMediaPlayer::videoAvailableChanged, receiver: this, slot: &Player::videoAvailableChanged);
86 connect(sender: m_player, signal: QOverload<QMediaPlayer::Error>::of(ptr: &QMediaPlayer::error), receiver: this, slot: &Player::displayErrorMessage);
87 connect(sender: m_player, signal: &QMediaPlayer::stateChanged, receiver: this, slot: &Player::stateChanged);
88
89//! [2]
90 m_videoWidget = new VideoWidget(this);
91 m_player->setVideoOutput(m_videoWidget);
92
93 m_playlistModel = new PlaylistModel(this);
94 m_playlistModel->setPlaylist(m_playlist);
95//! [2]
96
97 m_playlistView = new QListView(this);
98 m_playlistView->setModel(m_playlistModel);
99 m_playlistView->setCurrentIndex(m_playlistModel->index(row: m_playlist->currentIndex(), column: 0));
100
101 connect(sender: m_playlistView, signal: &QAbstractItemView::activated, receiver: this, slot: &Player::jump);
102
103 m_slider = new QSlider(Qt::Horizontal, this);
104 m_slider->setRange(min: 0, max: m_player->duration() / 1000);
105
106 m_labelDuration = new QLabel(this);
107 connect(sender: m_slider, signal: &QSlider::sliderMoved, receiver: this, slot: &Player::seek);
108
109 m_labelHistogram = new QLabel(this);
110 m_labelHistogram->setText("Histogram:");
111 m_videoHistogram = new HistogramWidget(this);
112 m_audioHistogram = new HistogramWidget(this);
113 QHBoxLayout *histogramLayout = new QHBoxLayout;
114 histogramLayout->addWidget(m_labelHistogram);
115 histogramLayout->addWidget(m_videoHistogram, stretch: 1);
116 histogramLayout->addWidget(m_audioHistogram, stretch: 2);
117
118 m_videoProbe = new QVideoProbe(this);
119 connect(sender: m_videoProbe, signal: &QVideoProbe::videoFrameProbed, receiver: m_videoHistogram, slot: &HistogramWidget::processFrame);
120 m_videoProbe->setSource(m_player);
121
122 m_audioProbe = new QAudioProbe(this);
123 connect(sender: m_audioProbe, signal: &QAudioProbe::audioBufferProbed, receiver: m_audioHistogram, slot: &HistogramWidget::processBuffer);
124 m_audioProbe->setSource(m_player);
125
126 QPushButton *openButton = new QPushButton(tr(s: "Open"), this);
127
128 connect(sender: openButton, signal: &QPushButton::clicked, receiver: this, slot: &Player::open);
129
130 PlayerControls *controls = new PlayerControls(this);
131 controls->setState(m_player->state());
132 controls->setVolume(m_player->volume());
133 controls->setMuted(controls->isMuted());
134
135 connect(sender: controls, signal: &PlayerControls::play, receiver: m_player, slot: &QMediaPlayer::play);
136 connect(sender: controls, signal: &PlayerControls::pause, receiver: m_player, slot: &QMediaPlayer::pause);
137 connect(sender: controls, signal: &PlayerControls::stop, receiver: m_player, slot: &QMediaPlayer::stop);
138 connect(sender: controls, signal: &PlayerControls::next, receiver: m_playlist, slot: &QMediaPlaylist::next);
139 connect(sender: controls, signal: &PlayerControls::previous, receiver: this, slot: &Player::previousClicked);
140 connect(sender: controls, signal: &PlayerControls::changeVolume, receiver: m_player, slot: &QMediaPlayer::setVolume);
141 connect(sender: controls, signal: &PlayerControls::changeMuting, receiver: m_player, slot: &QMediaPlayer::setMuted);
142 connect(sender: controls, signal: &PlayerControls::changeRate, receiver: m_player, slot: &QMediaPlayer::setPlaybackRate);
143 connect(sender: controls, signal: &PlayerControls::stop, receiver: m_videoWidget, slot: QOverload<>::of(ptr: &QVideoWidget::update));
144
145 connect(sender: m_player, signal: &QMediaPlayer::stateChanged, receiver: controls, slot: &PlayerControls::setState);
146 connect(sender: m_player, signal: &QMediaPlayer::volumeChanged, receiver: controls, slot: &PlayerControls::setVolume);
147 connect(sender: m_player, signal: &QMediaPlayer::mutedChanged, receiver: controls, slot: &PlayerControls::setMuted);
148
149 m_fullScreenButton = new QPushButton(tr(s: "FullScreen"), this);
150 m_fullScreenButton->setCheckable(true);
151
152 m_colorButton = new QPushButton(tr(s: "Color Options..."), this);
153 m_colorButton->setEnabled(false);
154 connect(sender: m_colorButton, signal: &QPushButton::clicked, receiver: this, slot: &Player::showColorDialog);
155
156 QBoxLayout *displayLayout = new QHBoxLayout;
157 displayLayout->addWidget(m_videoWidget, stretch: 2);
158 displayLayout->addWidget(m_playlistView);
159
160 QBoxLayout *controlLayout = new QHBoxLayout;
161 controlLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
162 controlLayout->addWidget(openButton);
163 controlLayout->addStretch(stretch: 1);
164 controlLayout->addWidget(controls);
165 controlLayout->addStretch(stretch: 1);
166 controlLayout->addWidget(m_fullScreenButton);
167 controlLayout->addWidget(m_colorButton);
168
169 QBoxLayout *layout = new QVBoxLayout;
170 layout->addLayout(layout: displayLayout);
171 QHBoxLayout *hLayout = new QHBoxLayout;
172 hLayout->addWidget(m_slider);
173 hLayout->addWidget(m_labelDuration);
174 layout->addLayout(layout: hLayout);
175 layout->addLayout(layout: controlLayout);
176 layout->addLayout(layout: histogramLayout);
177#if defined(Q_OS_QNX)
178 // On QNX, the main window doesn't have a title bar (or any other decorations).
179 // Create a status bar for the status information instead.
180 m_statusLabel = new QLabel;
181 m_statusBar = new QStatusBar;
182 m_statusBar->addPermanentWidget(m_statusLabel);
183 m_statusBar->setSizeGripEnabled(false); // Without mouse grabbing, it doesn't work very well.
184 layout->addWidget(m_statusBar);
185#endif
186
187 setLayout(layout);
188
189 if (!isPlayerAvailable()) {
190 QMessageBox::warning(parent: this, title: tr(s: "Service not available"),
191 text: tr(s: "The QMediaPlayer object does not have a valid service.\n"\
192 "Please check the media service plugins are installed."));
193
194 controls->setEnabled(false);
195 m_playlistView->setEnabled(false);
196 openButton->setEnabled(false);
197 m_colorButton->setEnabled(false);
198 m_fullScreenButton->setEnabled(false);
199 }
200
201 metaDataChanged();
202}
203
204Player::~Player()
205{
206}
207
208bool Player::isPlayerAvailable() const
209{
210 return m_player->isAvailable();
211}
212
213void Player::open()
214{
215 QFileDialog fileDialog(this);
216 fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
217 fileDialog.setWindowTitle(tr(s: "Open Files"));
218 QStringList supportedMimeTypes = m_player->supportedMimeTypes();
219 if (!supportedMimeTypes.isEmpty()) {
220 supportedMimeTypes.append(t: "audio/x-m3u"); // MP3 playlists
221 fileDialog.setMimeTypeFilters(supportedMimeTypes);
222 }
223 fileDialog.setDirectory(QStandardPaths::standardLocations(type: QStandardPaths::MoviesLocation).value(i: 0, defaultValue: QDir::homePath()));
224 if (fileDialog.exec() == QDialog::Accepted)
225 addToPlaylist(urls: fileDialog.selectedUrls());
226}
227
228static bool isPlaylist(const QUrl &url) // Check for ".m3u" playlists.
229{
230 if (!url.isLocalFile())
231 return false;
232 const QFileInfo fileInfo(url.toLocalFile());
233 return fileInfo.exists() && !fileInfo.suffix().compare(other: QLatin1String("m3u"), cs: Qt::CaseInsensitive);
234}
235
236void Player::addToPlaylist(const QList<QUrl> &urls)
237{
238 for (auto &url: urls) {
239 if (isPlaylist(url))
240 m_playlist->load(location: url);
241 else
242 m_playlist->addMedia(content: url);
243 }
244}
245
246void Player::setCustomAudioRole(const QString &role)
247{
248 m_player->setCustomAudioRole(role);
249}
250
251void Player::durationChanged(qint64 duration)
252{
253 m_duration = duration / 1000;
254 m_slider->setMaximum(m_duration);
255}
256
257void Player::positionChanged(qint64 progress)
258{
259 if (!m_slider->isSliderDown())
260 m_slider->setValue(progress / 1000);
261
262 updateDurationInfo(currentInfo: progress / 1000);
263}
264
265void Player::metaDataChanged()
266{
267 if (m_player->isMetaDataAvailable()) {
268 setTrackInfo(QString("%1 - %2")
269 .arg(a: m_player->metaData(key: QMediaMetaData::AlbumArtist).toString())
270 .arg(a: m_player->metaData(key: QMediaMetaData::Title).toString()));
271
272 if (m_coverLabel) {
273 QUrl url = m_player->metaData(key: QMediaMetaData::CoverArtUrlLarge).value<QUrl>();
274
275 m_coverLabel->setPixmap(!url.isEmpty()
276 ? QPixmap(url.toString())
277 : QPixmap());
278 }
279 }
280}
281
282void Player::previousClicked()
283{
284 // Go to previous track if we are within the first 5 seconds of playback
285 // Otherwise, seek to the beginning.
286 if (m_player->position() <= 5000)
287 m_playlist->previous();
288 else
289 m_player->setPosition(0);
290}
291
292void Player::jump(const QModelIndex &index)
293{
294 if (index.isValid()) {
295 m_playlist->setCurrentIndex(index.row());
296 m_player->play();
297 }
298}
299
300void Player::playlistPositionChanged(int currentItem)
301{
302 clearHistogram();
303 m_playlistView->setCurrentIndex(m_playlistModel->index(row: currentItem, column: 0));
304}
305
306void Player::seek(int seconds)
307{
308 m_player->setPosition(seconds * 1000);
309}
310
311void Player::statusChanged(QMediaPlayer::MediaStatus status)
312{
313 handleCursor(status);
314
315 // handle status message
316 switch (status) {
317 case QMediaPlayer::UnknownMediaStatus:
318 case QMediaPlayer::NoMedia:
319 case QMediaPlayer::LoadedMedia:
320 setStatusInfo(QString());
321 break;
322 case QMediaPlayer::LoadingMedia:
323 setStatusInfo(tr(s: "Loading..."));
324 break;
325 case QMediaPlayer::BufferingMedia:
326 case QMediaPlayer::BufferedMedia:
327 setStatusInfo(tr(s: "Buffering %1%").arg(a: m_player->bufferStatus()));
328 break;
329 case QMediaPlayer::StalledMedia:
330 setStatusInfo(tr(s: "Stalled %1%").arg(a: m_player->bufferStatus()));
331 break;
332 case QMediaPlayer::EndOfMedia:
333 QApplication::alert(widget: this);
334 break;
335 case QMediaPlayer::InvalidMedia:
336 displayErrorMessage();
337 break;
338 }
339}
340
341void Player::stateChanged(QMediaPlayer::State state)
342{
343 if (state == QMediaPlayer::StoppedState)
344 clearHistogram();
345}
346
347void Player::handleCursor(QMediaPlayer::MediaStatus status)
348{
349#ifndef QT_NO_CURSOR
350 if (status == QMediaPlayer::LoadingMedia ||
351 status == QMediaPlayer::BufferingMedia ||
352 status == QMediaPlayer::StalledMedia)
353 setCursor(QCursor(Qt::BusyCursor));
354 else
355 unsetCursor();
356#endif
357}
358
359void Player::bufferingProgress(int progress)
360{
361 if (m_player->mediaStatus() == QMediaPlayer::StalledMedia)
362 setStatusInfo(tr(s: "Stalled %1%").arg(a: progress));
363 else
364 setStatusInfo(tr(s: "Buffering %1%").arg(a: progress));
365}
366
367void Player::videoAvailableChanged(bool available)
368{
369 if (!available) {
370 disconnect(sender: m_fullScreenButton, signal: &QPushButton::clicked, receiver: m_videoWidget, slot: &QVideoWidget::setFullScreen);
371 disconnect(sender: m_videoWidget, signal: &QVideoWidget::fullScreenChanged, receiver: m_fullScreenButton, slot: &QPushButton::setChecked);
372 m_videoWidget->setFullScreen(false);
373 } else {
374 connect(sender: m_fullScreenButton, signal: &QPushButton::clicked, receiver: m_videoWidget, slot: &QVideoWidget::setFullScreen);
375 connect(sender: m_videoWidget, signal: &QVideoWidget::fullScreenChanged, receiver: m_fullScreenButton, slot: &QPushButton::setChecked);
376
377 if (m_fullScreenButton->isChecked())
378 m_videoWidget->setFullScreen(true);
379 }
380 m_colorButton->setEnabled(available);
381}
382
383void Player::setTrackInfo(const QString &info)
384{
385 m_trackInfo = info;
386
387 if (m_statusBar) {
388 m_statusBar->showMessage(text: m_trackInfo);
389 m_statusLabel->setText(m_statusInfo);
390 } else {
391 if (!m_statusInfo.isEmpty())
392 setWindowTitle(QString("%1 | %2").arg(a: m_trackInfo).arg(a: m_statusInfo));
393 else
394 setWindowTitle(m_trackInfo);
395 }
396}
397
398void Player::setStatusInfo(const QString &info)
399{
400 m_statusInfo = info;
401
402 if (m_statusBar) {
403 m_statusBar->showMessage(text: m_trackInfo);
404 m_statusLabel->setText(m_statusInfo);
405 } else {
406 if (!m_statusInfo.isEmpty())
407 setWindowTitle(QString("%1 | %2").arg(a: m_trackInfo).arg(a: m_statusInfo));
408 else
409 setWindowTitle(m_trackInfo);
410 }
411}
412
413void Player::displayErrorMessage()
414{
415 setStatusInfo(m_player->errorString());
416}
417
418void Player::updateDurationInfo(qint64 currentInfo)
419{
420 QString tStr;
421 if (currentInfo || m_duration) {
422 QTime currentTime((currentInfo / 3600) % 60, (currentInfo / 60) % 60,
423 currentInfo % 60, (currentInfo * 1000) % 1000);
424 QTime totalTime((m_duration / 3600) % 60, (m_duration / 60) % 60,
425 m_duration % 60, (m_duration * 1000) % 1000);
426 QString format = "mm:ss";
427 if (m_duration > 3600)
428 format = "hh:mm:ss";
429 tStr = currentTime.toString(format) + " / " + totalTime.toString(format);
430 }
431 m_labelDuration->setText(tStr);
432}
433
434void Player::showColorDialog()
435{
436 if (!m_colorDialog) {
437 QSlider *brightnessSlider = new QSlider(Qt::Horizontal);
438 brightnessSlider->setRange(min: -100, max: 100);
439 brightnessSlider->setValue(m_videoWidget->brightness());
440 connect(sender: brightnessSlider, signal: &QSlider::sliderMoved, receiver: m_videoWidget, slot: &QVideoWidget::setBrightness);
441 connect(sender: m_videoWidget, signal: &QVideoWidget::brightnessChanged, receiver: brightnessSlider, slot: &QSlider::setValue);
442
443 QSlider *contrastSlider = new QSlider(Qt::Horizontal);
444 contrastSlider->setRange(min: -100, max: 100);
445 contrastSlider->setValue(m_videoWidget->contrast());
446 connect(sender: contrastSlider, signal: &QSlider::sliderMoved, receiver: m_videoWidget, slot: &QVideoWidget::setContrast);
447 connect(sender: m_videoWidget, signal: &QVideoWidget::contrastChanged, receiver: contrastSlider, slot: &QSlider::setValue);
448
449 QSlider *hueSlider = new QSlider(Qt::Horizontal);
450 hueSlider->setRange(min: -100, max: 100);
451 hueSlider->setValue(m_videoWidget->hue());
452 connect(sender: hueSlider, signal: &QSlider::sliderMoved, receiver: m_videoWidget, slot: &QVideoWidget::setHue);
453 connect(sender: m_videoWidget, signal: &QVideoWidget::hueChanged, receiver: hueSlider, slot: &QSlider::setValue);
454
455 QSlider *saturationSlider = new QSlider(Qt::Horizontal);
456 saturationSlider->setRange(min: -100, max: 100);
457 saturationSlider->setValue(m_videoWidget->saturation());
458 connect(sender: saturationSlider, signal: &QSlider::sliderMoved, receiver: m_videoWidget, slot: &QVideoWidget::setSaturation);
459 connect(sender: m_videoWidget, signal: &QVideoWidget::saturationChanged, receiver: saturationSlider, slot: &QSlider::setValue);
460
461 QFormLayout *layout = new QFormLayout;
462 layout->addRow(labelText: tr(s: "Brightness"), field: brightnessSlider);
463 layout->addRow(labelText: tr(s: "Contrast"), field: contrastSlider);
464 layout->addRow(labelText: tr(s: "Hue"), field: hueSlider);
465 layout->addRow(labelText: tr(s: "Saturation"), field: saturationSlider);
466
467 QPushButton *button = new QPushButton(tr(s: "Close"));
468 layout->addRow(widget: button);
469
470 m_colorDialog = new QDialog(this);
471 m_colorDialog->setWindowTitle(tr(s: "Color Options"));
472 m_colorDialog->setLayout(layout);
473
474 connect(sender: button, signal: &QPushButton::clicked, receiver: m_colorDialog, slot: &QDialog::close);
475 }
476 m_colorDialog->show();
477}
478
479void Player::clearHistogram()
480{
481 QMetaObject::invokeMethod(obj: m_videoHistogram, member: "processFrame", type: Qt::QueuedConnection, Q_ARG(QVideoFrame, QVideoFrame()));
482 QMetaObject::invokeMethod(obj: m_audioHistogram, member: "processBuffer", type: Qt::QueuedConnection, Q_ARG(QAudioBuffer, QAudioBuffer()));
483}
484

source code of qtmultimedia/examples/multimediawidgets/player/player.cpp