5 This tutorial shows how to create a basic media player with
6 [Qt](http://qt-project.org/) and
7 [QtGStreamer](http://gstreamer.freedesktop.org/data/doc/gstreamer/head/qt-gstreamer/html/index.html).
8 It assumes that you are already familiar with the basics of Qt and
9 GStreamer. If not, please refer to the other tutorials in this
12 In particular, you will learn:
14 - How to create a basic pipeline
15 - How to create a video output
16 - Updating the GUI based on playback time
18 ## A media player with Qt
20 These files are located in the qt-gstreamer SDK's `examples/` directory.
22 Due to the length of these samples, they are initially hidden. Click on
25 ![](images/icons/grey_arrow_down.gif)CMakeLists.txt
30 project(qtgst-example-player)
31 find_package(QtGStreamer REQUIRED)
32 ## automoc is now a built-in tool since CMake 2.8.6.
33 if (${CMAKE_VERSION} VERSION_LESS "2.8.6")
34 find_package(Automoc4 REQUIRED)
36 set(CMAKE_AUTOMOC TRUE)
37 macro(automoc4_add_executable)
38 add_executable(${ARGV})
41 include_directories(${QTGSTREAMER_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR} ${QT_QTWIDGETS_INCLUDE_DIRS})
42 add_definitions(${QTGSTREAMER_DEFINITIONS})
43 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QTGSTREAMER_FLAGS}")
44 set(player_SOURCES main.cpp player.cpp mediaapp.cpp)
45 automoc4_add_executable(player ${player_SOURCES})
46 target_link_libraries(player ${QTGSTREAMER_UI_LIBRARIES} ${QT_QTOPENGL_LIBRARIES} ${QT_QTWIDGETS_LIBRARIES})
49 ![](images/icons/grey_arrow_down.gif)main.cpp
55 #include <QtWidgets/QApplication>
57 int main(int argc, char *argv[])
59 QApplication app(argc, argv);
60 QGst::init(&argc, &argv);
64 media.openFile(argv[1]);
70 ![](images/icons/grey_arrow_down.gif)mediaapp.h
77 #include <QtCore/QTimer>
78 #include <QtWidgets/QWidget>
79 #include <QtWidgets/QStyle>
86 class MediaApp : public QWidget
90 MediaApp(QWidget *parent = 0);
92 void openFile(const QString & fileName);
95 void toggleFullScreen();
96 void onStateChanged();
97 void onPositionChanged();
98 void setPosition(int position);
99 void showControls(bool show = true);
100 void hideControls() { showControls(false); }
102 void mouseMoveEvent(QMouseEvent *event);
104 QToolButton *initButton(QStyle::StandardPixmap icon, const QString & tip,
105 QObject *dstobj, const char *slot_method, QLayout *layout);
106 void createUI(QBoxLayout *appLayout);
109 QToolButton *m_openButton;
110 QToolButton *m_fullScreenButton;
111 QToolButton *m_playButton;
112 QToolButton *m_pauseButton;
113 QToolButton *m_stopButton;
114 QSlider *m_positionSlider;
115 QSlider *m_volumeSlider;
116 QLabel *m_positionLabel;
117 QLabel *m_volumeLabel;
118 QTimer m_fullScreenTimer;
123 ![](images/icons/grey_arrow_down.gif)mediaapp.cpp
128 #include "mediaapp.h"
130 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
131 #include <QtWidgets/QBoxLayout>
132 #include <QtWidgets/QFileDialog>
133 #include <QtWidgets/QToolButton>
134 #include <QtWidgets/QLabel>
135 #include <QtWidgets/QSlider>
137 #include <QtGui/QBoxLayout>
138 #include <QtGui/QFileDialog>
139 #include <QtGui/QToolButton>
140 #include <QtGui/QLabel>
141 #include <QtGui/QSlider>
142 #include <QtGui/QMouseEvent>
144 MediaApp::MediaApp(QWidget *parent)
148 m_player = new Player(this);
149 connect(m_player, SIGNAL(positionChanged()), this, SLOT(onPositionChanged()));
150 connect(m_player, SIGNAL(stateChanged()), this, SLOT(onStateChanged()));
151 //m_baseDir is used to remember the last directory that was used.
152 //defaults to the current working directory
153 m_baseDir = QLatin1String(".");
154 //this timer (re-)hides the controls after a few seconds when we are in fullscreen mode
155 m_fullScreenTimer.setSingleShot(true);
156 connect(&m_fullScreenTimer, SIGNAL(timeout()), this, SLOT(hideControls()));
158 QVBoxLayout *appLayout = new QVBoxLayout;
159 appLayout->setContentsMargins(0, 0, 0, 0);
161 setLayout(appLayout);
162 onStateChanged(); //set the controls to their default state
163 setWindowTitle(tr("QtGStreamer example player"));
166 MediaApp::~MediaApp()
170 void MediaApp::openFile(const QString & fileName)
172 m_baseDir = QFileInfo(fileName).path();
174 m_player->setUri(fileName);
177 void MediaApp::open()
179 QString fileName = QFileDialog::getOpenFileName(this, tr("Open a Movie"), m_baseDir);
180 if (!fileName.isEmpty()) {
184 void MediaApp::toggleFullScreen()
186 if (isFullScreen()) {
187 setMouseTracking(false);
188 m_player->setMouseTracking(false);
189 m_fullScreenTimer.stop();
193 setMouseTracking(true);
194 m_player->setMouseTracking(true);
199 void MediaApp::onStateChanged()
201 QGst::State newState = m_player->state();
202 m_playButton->setEnabled(newState != QGst::StatePlaying);
203 m_pauseButton->setEnabled(newState == QGst::StatePlaying);
204 m_stopButton->setEnabled(newState != QGst::StateNull);
205 m_positionSlider->setEnabled(newState != QGst::StateNull);
206 m_volumeSlider->setEnabled(newState != QGst::StateNull);
207 m_volumeLabel->setEnabled(newState != QGst::StateNull);
208 m_volumeSlider->setValue(m_player->volume());
209 //if we are in Null state, call onPositionChanged() to restore
210 //the position of the slider and the text on the label
211 if (newState == QGst::StateNull) {
215 /* Called when the positionChanged() is received from the player */
216 void MediaApp::onPositionChanged()
220 if (m_player->state() != QGst::StateReady &&
221 m_player->state() != QGst::StateNull)
223 length = m_player->length();
224 curpos = m_player->position();
226 m_positionLabel->setText(curpos.toString("hh:mm:ss.zzz")
228 length.toString("hh:mm:ss.zzz"));
229 if (length != QTime(0,0)) {
230 m_positionSlider->setValue(curpos.msecsTo(QTime(0,0)) * 1000 / length.msecsTo(QTime(0,0)));
232 m_positionSlider->setValue(0);
234 if (curpos != QTime(0,0)) {
235 m_positionLabel->setEnabled(true);
236 m_positionSlider->setEnabled(true);
239 /* Called when the user changes the slider's position */
240 void MediaApp::setPosition(int value)
242 uint length = -m_player->length().msecsTo(QTime(0,0));
243 if (length != 0 && value > 0) {
245 pos = pos.addMSecs(length * (value / 1000.0));
246 m_player->setPosition(pos);
249 void MediaApp::showControls(bool show)
251 m_openButton->setVisible(show);
252 m_playButton->setVisible(show);
253 m_pauseButton->setVisible(show);
254 m_stopButton->setVisible(show);
255 m_fullScreenButton->setVisible(show);
256 m_positionSlider->setVisible(show);
257 m_volumeSlider->setVisible(show);
258 m_volumeLabel->setVisible(show);
259 m_positionLabel->setVisible(show);
261 void MediaApp::mouseMoveEvent(QMouseEvent *event)
264 if (isFullScreen()) {
266 m_fullScreenTimer.start(3000); //re-hide controls after 3s
269 QToolButton *MediaApp::initButton(QStyle::StandardPixmap icon, const QString & tip,
270 QObject *dstobj, const char *slot_method, QLayout *layout)
272 QToolButton *button = new QToolButton;
273 button->setIcon(style()->standardIcon(icon));
274 button->setIconSize(QSize(36, 36));
275 button->setToolTip(tip);
276 connect(button, SIGNAL(clicked()), dstobj, slot_method);
277 layout->addWidget(button);
280 void MediaApp::createUI(QBoxLayout *appLayout)
282 appLayout->addWidget(m_player);
283 m_positionLabel = new QLabel();
284 m_positionSlider = new QSlider(Qt::Horizontal);
285 m_positionSlider->setTickPosition(QSlider::TicksBelow);
286 m_positionSlider->setTickInterval(10);
287 m_positionSlider->setMaximum(1000);
288 connect(m_positionSlider, SIGNAL(sliderMoved(int)), this, SLOT(setPosition(int)));
289 m_volumeSlider = new QSlider(Qt::Horizontal);
290 m_volumeSlider->setTickPosition(QSlider::TicksLeft);
291 m_volumeSlider->setTickInterval(2);
292 m_volumeSlider->setMaximum(10);
293 m_volumeSlider->setMaximumSize(64,32);
294 connect(m_volumeSlider, SIGNAL(sliderMoved(int)), m_player, SLOT(setVolume(int)));
295 QGridLayout *posLayout = new QGridLayout;
296 posLayout->addWidget(m_positionLabel, 1, 0);
297 posLayout->addWidget(m_positionSlider, 1, 1, 1, 2);
298 appLayout->addLayout(posLayout);
299 QHBoxLayout *btnLayout = new QHBoxLayout;
300 btnLayout->addStretch();
301 m_openButton = initButton(QStyle::SP_DialogOpenButton, tr("Open File"),
302 this, SLOT(open()), btnLayout);
303 m_playButton = initButton(QStyle::SP_MediaPlay, tr("Play"),
304 m_player, SLOT(play()), btnLayout);
305 m_pauseButton = initButton(QStyle::SP_MediaPause, tr("Pause"),
306 m_player, SLOT(pause()), btnLayout);
307 m_stopButton = initButton(QStyle::SP_MediaStop, tr("Stop"),
308 m_player, SLOT(stop()), btnLayout);
309 m_fullScreenButton = initButton(QStyle::SP_TitleBarMaxButton, tr("Fullscreen"),
310 this, SLOT(toggleFullScreen()), btnLayout);
311 btnLayout->addStretch();
312 m_volumeLabel = new QLabel();
313 m_volumeLabel->setPixmap(
314 style()->standardIcon(QStyle::SP_MediaVolume).pixmap(QSize(32, 32),
315 QIcon::Normal, QIcon::On));
316 btnLayout->addWidget(m_volumeLabel);
317 btnLayout->addWidget(m_volumeSlider);
318 appLayout->addLayout(btnLayout);
320 #include "moc_mediaapp.cpp"
323 ![](images/icons/grey_arrow_down.gif)player.h
330 #include <QtCore/QTimer>
331 #include <QtCore/QTime>
332 #include <QGst/Pipeline>
333 #include <QGst/Ui/VideoWidget>
335 class Player : public QGst::Ui::VideoWidget
339 Player(QWidget *parent = 0);
342 void setUri(const QString &uri);
344 QTime position() const;
345 void setPosition(const QTime &pos);
347 QTime length() const;
348 QGst::State state() const;
354 void setVolume(int volume);
357 void positionChanged();
361 void onBusMessage(const QGst::MessagePtr &message);
362 void handlePipelineStateChange(const QGst::StateChangedMessagePtr &scm);
364 QGst::PipelinePtr m_pipeline;
365 QTimer m_positionTimer;
371 ![](images/icons/grey_arrow_down.gif)player.cpp
377 #include <QtCore/QDir>
378 #include <QtCore/QUrl>
379 #include <QGlib/Connect>
380 #include <QGlib/Error>
381 #include <QGst/Pipeline>
382 #include <QGst/ElementFactory>
384 #include <QGst/Message>
385 #include <QGst/Query>
386 #include <QGst/ClockTime>
387 #include <QGst/Event>
388 #include <QGst/StreamVolume>
389 Player::Player(QWidget *parent)
390 : QGst::Ui::VideoWidget(parent)
392 //this timer is used to tell the ui to change its position slider & label
393 //every 100 ms, but only when the pipeline is playing
394 connect(&m_positionTimer, SIGNAL(timeout()), this, SIGNAL(positionChanged()));
399 m_pipeline->setState(QGst::StateNull);
403 void Player::setUri(const QString & uri)
405 QString realUri = uri;
406 //if uri is not a real uri, assume it is a file path
407 if (realUri.indexOf("://") < 0) {
408 realUri = QUrl::fromLocalFile(realUri).toEncoded();
411 m_pipeline = QGst::ElementFactory::make("playbin").dynamicCast<QGst::Pipeline>();
413 //let the video widget watch the pipeline for new video sinks
414 watchPipeline(m_pipeline);
415 //watch the bus for messages
416 QGst::BusPtr bus = m_pipeline->bus();
417 bus->addSignalWatch();
418 QGlib::connect(bus, "message", this, &Player::onBusMessage);
420 qCritical() << "Failed to create the pipeline";
424 m_pipeline->setProperty("uri", realUri);
427 QTime Player::position() const
430 //here we query the pipeline about its position
431 //and we request that the result is returned in time format
432 QGst::PositionQueryPtr query = QGst::PositionQuery::create(QGst::FormatTime);
433 m_pipeline->query(query);
434 return QGst::ClockTime(query->position()).toTime();
439 void Player::setPosition(const QTime & pos)
441 QGst::SeekEventPtr evt = QGst::SeekEvent::create(
442 1.0, QGst::FormatTime, QGst::SeekFlagFlush,
443 QGst::SeekTypeSet, QGst::ClockTime::fromTime(pos),
444 QGst::SeekTypeNone, QGst::ClockTime::None
446 m_pipeline->sendEvent(evt);
448 int Player::volume() const
451 QGst::StreamVolumePtr svp =
452 m_pipeline.dynamicCast<QGst::StreamVolume>();
454 return svp->volume(QGst::StreamVolumeFormatCubic) * 10;
460 void Player::setVolume(int volume)
463 QGst::StreamVolumePtr svp =
464 m_pipeline.dynamicCast<QGst::StreamVolume>();
466 svp->setVolume((double)volume / 10, QGst::StreamVolumeFormatCubic);
470 QTime Player::length() const
473 //here we query the pipeline about the content's duration
474 //and we request that the result is returned in time format
475 QGst::DurationQueryPtr query = QGst::DurationQuery::create(QGst::FormatTime);
476 m_pipeline->query(query);
477 return QGst::ClockTime(query->duration()).toTime();
482 QGst::State Player::state() const
484 return m_pipeline ? m_pipeline->currentState() : QGst::StateNull;
489 m_pipeline->setState(QGst::StatePlaying);
495 m_pipeline->setState(QGst::StatePaused);
501 m_pipeline->setState(QGst::StateNull);
502 //once the pipeline stops, the bus is flushed so we will
503 //not receive any StateChangedMessage about this.
504 //so, to inform the ui, we have to emit this signal manually.
505 Q_EMIT stateChanged();
508 void Player::onBusMessage(const QGst::MessagePtr & message)
510 switch (message->type()) {
511 case QGst::MessageEos: //End of stream. We reached the end of the file.
514 case QGst::MessageError: //Some error occurred.
515 qCritical() << message.staticCast<QGst::ErrorMessage>()->error();
518 case QGst::MessageStateChanged: //The element in message->source() has changed state
519 if (message->source() == m_pipeline) {
520 handlePipelineStateChange(message.staticCast<QGst::StateChangedMessage>());
527 void Player::handlePipelineStateChange(const QGst::StateChangedMessagePtr & scm)
529 switch (scm->newState()) {
530 case QGst::StatePlaying:
531 //start the timer when the pipeline starts playing
532 m_positionTimer.start(100);
534 case QGst::StatePaused:
535 //stop the timer when the pipeline pauses
536 if(scm->oldState() == QGst::StatePlaying) {
537 m_positionTimer.stop();
543 Q_EMIT stateChanged();
545 #include "moc_player.cpp"
550 ### Setting up GStreamer
552 We begin by looking at `main()`:
557 int main(int argc, char *argv[])
559 QApplication app(argc, argv);
560 QGst::init(&argc, &argv);
564 media.openFile(argv[1]);
570 We first initialize QtGStreamer by calling `QGst::init()`, passing
571 `argc` and `argv`. Internally, this ensures that the GLib type system
572 and GStreamer plugin registry is configured and initialized, along with
573 handling helpful environment variables such as `GST_DEBUG` and common
574 command line options. Please see the [Running GStreamer
575 Applications](http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/gst-running.html)
576 section of the core reference manual for details.
578 Construction of the `MediaApp` (derived from
579 [`QApplication`](http://qt-project.org/doc/qt-5.0/qtwidgets/qapplication.html))
580 involves constructing the `Player` object and connecting its signals to
583 **MediaApp::MediaApp()**
587 m_player = new Player(this);
588 connect(m_player, SIGNAL(positionChanged()), this, SLOT(onPositionChanged()));
589 connect(m_player, SIGNAL(stateChanged()), this, SLOT(onStateChanged()));
592 Next, we instruct the `MediaApp` to open the file given on the command
595 **MediaApp::openFile()**
598 void MediaApp::openFile(const QString & fileName)
600 m_baseDir = QFileInfo(fileName).path();
602 m_player->setUri(fileName);
607 This in turn instructs the `Player` to construct our GStreamer pipeline:
612 void Player::setUri(const QString & uri)
614 QString realUri = uri;
615 //if uri is not a real uri, assume it is a file path
616 if (realUri.indexOf("://") < 0) {
617 realUri = QUrl::fromLocalFile(realUri).toEncoded();
620 m_pipeline = QGst::ElementFactory::make("playbin").dynamicCast<QGst::Pipeline>();
622 //let the video widget watch the pipeline for new video sinks
623 watchPipeline(m_pipeline);
624 //watch the bus for messages
625 QGst::BusPtr bus = m_pipeline->bus();
626 bus->addSignalWatch();
627 QGlib::connect(bus, "message", this, &Player::onBusMessage);
629 qCritical() << "Failed to create the pipeline";
633 m_pipeline->setProperty("uri", realUri);
638 Here, we first ensure that the pipeline will receive a proper URI. If
639 `Player::setUri()` is called with `/home/user/some/file.mp3`, the path
640 is modified to `file:///home/user/some/file.mp3`. `playbin` only
641 accepts complete URIs.
643 The pipeline is created via `QGst::ElementFactory::make()`. The
644 `Player` object inherits from the `QGst::Ui::VideoWidget` class, which
645 includes a function to watch for the `prepare-xwindow-id` message, which
646 associates the underlying video sink with a Qt widget used for
647 rendering. For clarity, here is a portion of the implementation:
649 **prepare-xwindow-id handling**
652 QGlib::connect(pipeline->bus(), "sync-message",
653 this, &PipelineWatch::onBusSyncMessage);
655 void PipelineWatch::onBusSyncMessage(const MessagePtr & msg)
658 if (msg->internalStructure()->name() == QLatin1String("prepare-xwindow-id")) {
659 XOverlayPtr overlay = msg->source().dynamicCast<XOverlay>();
660 m_renderer->setVideoSink(overlay);
664 Once the pipeline is created, we connect to the bus' message signal (via
665 `QGlib::connect()`) to dispatch state change signals:
668 void Player::onBusMessage(const QGst::MessagePtr & message)
670 switch (message->type()) {
671 case QGst::MessageEos: //End of stream. We reached the end of the file.
674 case QGst::MessageError: //Some error occurred.
675 qCritical() << message.staticCast<QGst::ErrorMessage>()->error();
678 case QGst::MessageStateChanged: //The element in message->source() has changed state
679 if (message->source() == m_pipeline) {
680 handlePipelineStateChange(message.staticCast<QGst::StateChangedMessage>());
687 void Player::handlePipelineStateChange(const QGst::StateChangedMessagePtr & scm)
689 switch (scm->newState()) {
690 case QGst::StatePlaying:
691 //start the timer when the pipeline starts playing
692 m_positionTimer.start(100);
694 case QGst::StatePaused:
695 //stop the timer when the pipeline pauses
696 if(scm->oldState() == QGst::StatePlaying) {
697 m_positionTimer.stop();
703 Q_EMIT stateChanged();
707 Finally, we tell `playbin` what to play by setting the `uri` property:
710 m_pipeline->setProperty("uri", realUri);
713 ### Starting Playback
715 After `Player::setUri()` is called, `MediaApp::openFile()` calls
716 `play()` on the `Player` object:
724 m_pipeline->setState(QGst::StatePlaying);
729 The other state control methods are equally simple:
731 **Player state functions**
737 m_pipeline->setState(QGst::StatePaused);
743 m_pipeline->setState(QGst::StateNull);
744 //once the pipeline stops, the bus is flushed so we will
745 //not receive any StateChangedMessage about this.
746 //so, to inform the ui, we have to emit this signal manually.
747 Q_EMIT stateChanged();
752 Once the pipeline has entered the playing state, a state change message
753 is emitted on the GStreamer bus which gets picked up by the `Player`:
755 **Player::onBusMessage()**
758 void Player::onBusMessage(const QGst::MessagePtr & message)
760 switch (message->type()) {
761 case QGst::MessageEos: //End of stream. We reached the end of the file.
764 case QGst::MessageError: //Some error occurred.
765 qCritical() << message.staticCast<QGst::ErrorMessage>()->error();
768 case QGst::MessageStateChanged: //The element in message->source() has changed state
769 if (message->source() == m_pipeline) {
770 handlePipelineStateChange(message.staticCast<QGst::StateChangedMessage>());
779 The `stateChanged` signal we connected to earlier is emitted and
782 **MediaApp::onStateChanged()**
785 void MediaApp::onStateChanged()
787 QGst::State newState = m_player->state();
788 m_playButton->setEnabled(newState != QGst::StatePlaying);
789 m_pauseButton->setEnabled(newState == QGst::StatePlaying);
790 m_stopButton->setEnabled(newState != QGst::StateNull);
791 m_positionSlider->setEnabled(newState != QGst::StateNull);
792 m_volumeSlider->setEnabled(newState != QGst::StateNull);
793 m_volumeLabel->setEnabled(newState != QGst::StateNull);
794 m_volumeSlider->setValue(m_player->volume());
795 //if we are in Null state, call onPositionChanged() to restore
796 //the position of the slider and the text on the label
797 if (newState == QGst::StateNull) {
803 This updates the UI to reflect the current state of the player's
807 [`QTimer`](http://qt-project.org/doc/qt-5.0/qtcore/qtimer.html), the
808 `Player` emits the `positionChanged` signal at regular intervals for the
811 **MediaApp::onPositionChanged()**
814 void MediaApp::onPositionChanged()
818 if (m_player->state() != QGst::StateReady &&
819 m_player->state() != QGst::StateNull)
821 length = m_player->length();
822 curpos = m_player->position();
824 m_positionLabel->setText(curpos.toString("hh:mm:ss.zzz")
826 length.toString("hh:mm:ss.zzz"));
827 if (length != QTime(0,0)) {
828 m_positionSlider->setValue(curpos.msecsTo(QTime(0,0)) * 1000 / length.msecsTo(QTime(0,0)));
830 m_positionSlider->setValue(0);
832 if (curpos != QTime(0,0)) {
833 m_positionLabel->setEnabled(true);
834 m_positionSlider->setEnabled(true);
839 The `MediaApp` queries the pipeline via the `Player`'s
840 `position()` method, which submits a position query. This is analogous
841 to `gst_element_query_position()`:
843 **Player::position()**
846 QTime Player::position() const
849 //here we query the pipeline about its position
850 //and we request that the result is returned in time format
851 QGst::PositionQueryPtr query = QGst::PositionQuery::create(QGst::FormatTime);
852 m_pipeline->query(query);
853 return QGst::ClockTime(query->position()).toTime();
860 Due to the way Qt handles signals that cross threads, there is no need
861 to worry about calling UI functions from outside the UI thread in this
866 This tutorial has shown:
868 - How to create a basic pipeline
869 - How to create a video output
870 - Updating the GUI based on playback time
872 It has been a pleasure having you here, and see you soon\!