Update theme submodule
[platform/upstream/gstreamer.git] / basic-media-player.md
1 # Basic Media Player
2
3 ## Goal
4
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
10 documentation.
11
12 In particular, you will learn:
13
14   - How to create a basic pipeline
15   - How to create a video output
16   - Updating the GUI based on playback time
17
18 ## A media player with Qt
19
20 These files are located in the qt-gstreamer SDK's `examples/` directory.
21
22 Due to the length of these samples, they are initially hidden. Click on
23 each file to expand.
24
25 ![](images/icons/grey_arrow_down.gif)CMakeLists.txt
26
27 **CMakeLists.txt**
28
29 ```
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)
35 else()
36     set(CMAKE_AUTOMOC TRUE)
37     macro(automoc4_add_executable)
38         add_executable(${ARGV})
39     endmacro()
40 endif()
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})
47 ```
48
49 ![](images/icons/grey_arrow_down.gif)main.cpp
50
51 **main.cpp**
52
53 ``` c
54 #include "mediaapp.h"
55 #include <QtWidgets/QApplication>
56 #include <QGst/Init>
57 int main(int argc, char *argv[])
58 {
59     QApplication app(argc, argv);
60     QGst::init(&argc, &argv);
61     MediaApp media;
62     media.show();
63     if (argc == 2) {
64         media.openFile(argv[1]);
65     }
66     return app.exec();
67 }
68 ```
69
70 ![](images/icons/grey_arrow_down.gif)mediaapp.h
71
72 **mediaapp.h**
73
74 ``` c
75 #ifndef MEDIAAPP_H
76 #define MEDIAAPP_H
77 #include <QtCore/QTimer>
78 #include <QtWidgets/QWidget>
79 #include <QtWidgets/QStyle>
80 class Player;
81 class QBoxLayout;
82 class QLabel;
83 class QSlider;
84 class QToolButton;
85 class QTimer;
86 class MediaApp : public QWidget
87 {
88     Q_OBJECT
89 public:
90     MediaApp(QWidget *parent = 0);
91     ~MediaApp();
92     void openFile(const QString & fileName);
93 private Q_SLOTS:
94     void open();
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); }
101 protected:
102     void mouseMoveEvent(QMouseEvent *event);
103 private:
104     QToolButton *initButton(QStyle::StandardPixmap icon, const QString & tip,
105                             QObject *dstobj, const char *slot_method, QLayout *layout);
106     void createUI(QBoxLayout *appLayout);
107     QString m_baseDir;
108     Player *m_player;
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;
119 };
120 #endif
121 ```
122
123 ![](images/icons/grey_arrow_down.gif)mediaapp.cpp
124
125 **mediaapp.cpp**
126
127 ``` c
128 #include "mediaapp.h"
129 #include "player.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>
136 #else
137 #include <QtGui/QBoxLayout>
138 #include <QtGui/QFileDialog>
139 #include <QtGui/QToolButton>
140 #include <QtGui/QLabel>
141 #include <QtGui/QSlider>
142 #include <QtGui/QMouseEvent>
143 #endif
144 MediaApp::MediaApp(QWidget *parent)
145     : QWidget(parent)
146 {
147     //create the player
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()));
157     //create the UI
158     QVBoxLayout *appLayout = new QVBoxLayout;
159     appLayout->setContentsMargins(0, 0, 0, 0);
160     createUI(appLayout);
161     setLayout(appLayout);
162     onStateChanged(); //set the controls to their default state
163     setWindowTitle(tr("QtGStreamer example player"));
164     resize(400, 400);
165 }
166 MediaApp::~MediaApp()
167 {
168     delete m_player;
169 }
170 void MediaApp::openFile(const QString & fileName)
171 {
172     m_baseDir = QFileInfo(fileName).path();
173     m_player->stop();
174     m_player->setUri(fileName);
175     m_player->play();
176 }
177 void MediaApp::open()
178 {
179     QString fileName = QFileDialog::getOpenFileName(this, tr("Open a Movie"), m_baseDir);
180     if (!fileName.isEmpty()) {
181         openFile(fileName);
182     }
183 }
184 void MediaApp::toggleFullScreen()
185 {
186     if (isFullScreen()) {
187         setMouseTracking(false);
188         m_player->setMouseTracking(false);
189         m_fullScreenTimer.stop();
190         showControls();
191         showNormal();
192     } else {
193         setMouseTracking(true);
194         m_player->setMouseTracking(true);
195         hideControls();
196         showFullScreen();
197     }
198 }
199 void MediaApp::onStateChanged()
200 {
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) {
212         onPositionChanged();
213     }
214 }
215 /* Called when the positionChanged() is received from the player */
216 void MediaApp::onPositionChanged()
217 {
218     QTime length(0,0);
219     QTime curpos(0,0);
220     if (m_player->state() != QGst::StateReady &&
221         m_player->state() != QGst::StateNull)
222     {
223         length = m_player->length();
224         curpos = m_player->position();
225     }
226     m_positionLabel->setText(curpos.toString("hh:mm:ss.zzz")
227                                         + "/" +
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)));
231     } else {
232         m_positionSlider->setValue(0);
233     }
234     if (curpos != QTime(0,0)) {
235         m_positionLabel->setEnabled(true);
236         m_positionSlider->setEnabled(true);
237     }
238 }
239 /* Called when the user changes the slider's position */
240 void MediaApp::setPosition(int value)
241 {
242     uint length = -m_player->length().msecsTo(QTime(0,0));
243     if (length != 0 && value > 0) {
244         QTime pos(0,0);
245         pos = pos.addMSecs(length * (value / 1000.0));
246         m_player->setPosition(pos);
247     }
248 }
249 void MediaApp::showControls(bool show)
250 {
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);
260 }
261 void MediaApp::mouseMoveEvent(QMouseEvent *event)
262 {
263     Q_UNUSED(event);
264     if (isFullScreen()) {
265         showControls();
266         m_fullScreenTimer.start(3000); //re-hide controls after 3s
267     }
268 }
269 QToolButton *MediaApp::initButton(QStyle::StandardPixmap icon, const QString & tip,
270                                   QObject *dstobj, const char *slot_method, QLayout *layout)
271 {
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);
278     return button;
279 }
280 void MediaApp::createUI(QBoxLayout *appLayout)
281 {
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);
319 }
320 #include "moc_mediaapp.cpp"
321 ```
322
323 ![](images/icons/grey_arrow_down.gif)player.h
324
325 **player.h**
326
327 ``` c
328 #ifndef PLAYER_H
329 #define PLAYER_H
330 #include <QtCore/QTimer>
331 #include <QtCore/QTime>
332 #include <QGst/Pipeline>
333 #include <QGst/Ui/VideoWidget>
334  
335 class Player : public QGst::Ui::VideoWidget
336 {
337     Q_OBJECT
338 public:
339     Player(QWidget *parent = 0);
340     ~Player();
341  
342     void setUri(const QString &uri);
343  
344     QTime position() const;
345     void setPosition(const QTime &pos);
346     int volume() const;
347     QTime length() const;
348     QGst::State state() const;
349  
350 public Q_SLOTS:
351     void play();
352     void pause();
353     void stop();
354     void setVolume(int volume);
355  
356 Q_SIGNALS:
357     void positionChanged();
358     void stateChanged();
359  
360 private:
361     void onBusMessage(const QGst::MessagePtr &message);
362     void handlePipelineStateChange(const QGst::StateChangedMessagePtr &scm);
363  
364     QGst::PipelinePtr m_pipeline;
365     QTimer m_positionTimer;
366 };
367  
368 #endif //PLAYER_H
369 ```
370
371 ![](images/icons/grey_arrow_down.gif)player.cpp
372
373 **player.cpp**
374
375 ``` c
376 #include "player.h"
377 #include <QtCore/QDir>
378 #include <QtCore/QUrl>
379 #include <QGlib/Connect>
380 #include <QGlib/Error>
381 #include <QGst/Pipeline>
382 #include <QGst/ElementFactory>
383 #include <QGst/Bus>
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)
391 {
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()));
395 }
396 Player::~Player()
397 {
398     if (m_pipeline) {
399         m_pipeline->setState(QGst::StateNull);
400         stopPipelineWatch();
401     }
402 }
403 void Player::setUri(const QString & uri)
404 {
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();
409     }
410     if (!m_pipeline) {
411         m_pipeline = QGst::ElementFactory::make("playbin").dynamicCast<QGst::Pipeline>();
412         if (m_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);
419         } else {
420             qCritical() << "Failed to create the pipeline";
421         }
422     }
423     if (m_pipeline) {
424         m_pipeline->setProperty("uri", realUri);
425     }
426 }
427 QTime Player::position() const
428 {
429     if (m_pipeline) {
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();
435     } else {
436         return QTime(0,0);
437     }
438 }
439 void Player::setPosition(const QTime & pos)
440 {
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
445     );
446     m_pipeline->sendEvent(evt);
447 }
448 int Player::volume() const
449 {
450     if (m_pipeline) {
451         QGst::StreamVolumePtr svp =
452             m_pipeline.dynamicCast<QGst::StreamVolume>();
453         if (svp) {
454             return svp->volume(QGst::StreamVolumeFormatCubic) * 10;
455         }
456     }
457     return 0;
458 }
459
460 void Player::setVolume(int volume)
461 {
462     if (m_pipeline) {
463         QGst::StreamVolumePtr svp =
464             m_pipeline.dynamicCast<QGst::StreamVolume>();
465         if(svp) {
466             svp->setVolume((double)volume / 10, QGst::StreamVolumeFormatCubic);
467         }
468     }
469 }
470 QTime Player::length() const
471 {
472     if (m_pipeline) {
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();
478     } else {
479         return QTime(0,0);
480     }
481 }
482 QGst::State Player::state() const
483 {
484     return m_pipeline ? m_pipeline->currentState() : QGst::StateNull;
485 }
486 void Player::play()
487 {
488     if (m_pipeline) {
489         m_pipeline->setState(QGst::StatePlaying);
490     }
491 }
492 void Player::pause()
493 {
494     if (m_pipeline) {
495         m_pipeline->setState(QGst::StatePaused);
496     }
497 }
498 void Player::stop()
499 {
500     if (m_pipeline) {
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();
506     }
507 }
508 void Player::onBusMessage(const QGst::MessagePtr & message)
509 {
510     switch (message->type()) {
511     case QGst::MessageEos: //End of stream. We reached the end of the file.
512         stop();
513         break;
514     case QGst::MessageError: //Some error occurred.
515         qCritical() << message.staticCast<QGst::ErrorMessage>()->error();
516         stop();
517         break;
518     case QGst::MessageStateChanged: //The element in message->source() has changed state
519         if (message->source() == m_pipeline) {
520             handlePipelineStateChange(message.staticCast<QGst::StateChangedMessage>());
521         }
522         break;
523     default:
524         break;
525     }
526 }
527 void Player::handlePipelineStateChange(const QGst::StateChangedMessagePtr & scm)
528 {
529     switch (scm->newState()) {
530     case QGst::StatePlaying:
531         //start the timer when the pipeline starts playing
532         m_positionTimer.start(100);
533         break;
534     case QGst::StatePaused:
535         //stop the timer when the pipeline pauses
536         if(scm->oldState() == QGst::StatePlaying) {
537             m_positionTimer.stop();
538         }
539         break;
540     default:
541         break;
542     }
543     Q_EMIT stateChanged();
544 }
545 #include "moc_player.cpp"
546 ```
547
548 ## Walkthrough
549
550 ### Setting up GStreamer
551
552 We begin by looking at `main()`:
553
554 **main.cpp**
555
556 ``` c
557 int main(int argc, char *argv[])
558 {
559     QApplication app(argc, argv);
560     QGst::init(&argc, &argv);
561     MediaApp media;
562     media.show();
563     if (argc == 2) {
564         media.openFile(argv[1]);
565     }
566     return app.exec();
567 }
568 ```
569
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.
577
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
581 the UI:
582
583 **MediaApp::MediaApp()**
584
585 ``` c
586     //create the player
587     m_player = new Player(this);
588     connect(m_player, SIGNAL(positionChanged()), this, SLOT(onPositionChanged()));
589     connect(m_player, SIGNAL(stateChanged()), this, SLOT(onStateChanged()));
590 ```
591
592 Next, we instruct the `MediaApp` to open the file given on the command
593 line, if any:
594
595 **MediaApp::openFile()**
596
597 ``` c
598 void MediaApp::openFile(const QString & fileName)
599 {
600     m_baseDir = QFileInfo(fileName).path();
601     m_player->stop();
602     m_player->setUri(fileName);
603     m_player->play();
604 }
605 ```
606
607 This in turn instructs the `Player` to construct our GStreamer pipeline:
608
609 **Player::setUri()**
610
611 ``` c
612 void Player::setUri(const QString & uri)
613 {
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();
618     }
619     if (!m_pipeline) {
620         m_pipeline = QGst::ElementFactory::make("playbin").dynamicCast<QGst::Pipeline>();
621         if (m_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);
628         } else {
629             qCritical() << "Failed to create the pipeline";
630         }
631     }
632     if (m_pipeline) {
633         m_pipeline->setProperty("uri", realUri);
634     }
635 }
636 ```
637
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.
642
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:
648
649 **prepare-xwindow-id handling**
650
651 ``` c
652     QGlib::connect(pipeline->bus(), "sync-message",
653                   this, &PipelineWatch::onBusSyncMessage);
654 ...
655 void PipelineWatch::onBusSyncMessage(const MessagePtr & msg)
656 {   
657 ...
658         if (msg->internalStructure()->name() == QLatin1String("prepare-xwindow-id")) {
659             XOverlayPtr overlay = msg->source().dynamicCast<XOverlay>();
660             m_renderer->setVideoSink(overlay);
661         }
662 ```
663
664 Once the pipeline is created, we connect to the bus' message signal (via
665 `QGlib::connect()`) to dispatch state change signals:
666
667 ``` c
668 void Player::onBusMessage(const QGst::MessagePtr & message)
669 {
670     switch (message->type()) {
671     case QGst::MessageEos: //End of stream. We reached the end of the file.
672         stop();
673         break;
674     case QGst::MessageError: //Some error occurred.
675         qCritical() << message.staticCast<QGst::ErrorMessage>()->error();
676         stop();
677         break;
678     case QGst::MessageStateChanged: //The element in message->source() has changed state
679         if (message->source() == m_pipeline) {
680             handlePipelineStateChange(message.staticCast<QGst::StateChangedMessage>());
681         }
682         break;
683     default:
684         break;
685     }
686 }
687 void Player::handlePipelineStateChange(const QGst::StateChangedMessagePtr & scm)
688 {
689     switch (scm->newState()) {
690     case QGst::StatePlaying:
691         //start the timer when the pipeline starts playing
692         m_positionTimer.start(100);
693         break;
694     case QGst::StatePaused:
695         //stop the timer when the pipeline pauses
696         if(scm->oldState() == QGst::StatePlaying) {
697             m_positionTimer.stop();
698         }
699         break;
700     default:
701         break;
702     }
703     Q_EMIT stateChanged();
704 }
705 ```
706
707 Finally, we tell `playbin` what to play by setting the `uri` property:
708
709 ``` c
710 m_pipeline->setProperty("uri", realUri);
711 ```
712
713 ### Starting Playback
714
715 After `Player::setUri()` is called, `MediaApp::openFile()` calls
716 `play()` on the `Player` object:
717
718 **Player::play()**
719
720 ``` c
721 void Player::play()
722 {
723     if (m_pipeline) {
724         m_pipeline->setState(QGst::StatePlaying);
725     }
726 }
727 ```
728
729 The other state control methods are equally simple:
730
731 **Player state functions**
732
733 ``` c
734 void Player::pause()
735 {
736     if (m_pipeline) {
737         m_pipeline->setState(QGst::StatePaused);
738     }
739 }
740 void Player::stop()
741 {
742     if (m_pipeline) {
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();
748     }
749 }
750 ```
751
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`:
754
755 **Player::onBusMessage()**
756
757 ``` c
758 void Player::onBusMessage(const QGst::MessagePtr & message)
759 {
760     switch (message->type()) {
761     case QGst::MessageEos: //End of stream. We reached the end of the file.
762         stop();
763         break;
764     case QGst::MessageError: //Some error occurred.
765         qCritical() << message.staticCast<QGst::ErrorMessage>()->error();
766         stop();
767         break;
768     case QGst::MessageStateChanged: //The element in message->source() has changed state
769         if (message->source() == m_pipeline) {
770             handlePipelineStateChange(message.staticCast<QGst::StateChangedMessage>());
771         }
772         break;
773     default:
774         break;
775     }
776 }
777 ```
778
779 The `stateChanged` signal we connected to earlier is emitted and
780 handled:
781
782 **MediaApp::onStateChanged()**
783
784 ``` c
785 void MediaApp::onStateChanged()
786 {
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) {
798         onPositionChanged();
799     }
800 }
801 ```
802
803 This updates the UI to reflect the current state of the player's
804 pipeline.
805
806 Driven by a
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
809 UI to handle:
810
811 **MediaApp::onPositionChanged()**
812
813 ``` c
814 void MediaApp::onPositionChanged()
815 {
816     QTime length(0,0);
817     QTime curpos(0,0);
818     if (m_player->state() != QGst::StateReady &&
819         m_player->state() != QGst::StateNull)
820     {
821         length = m_player->length();
822         curpos = m_player->position();
823     }
824     m_positionLabel->setText(curpos.toString("hh:mm:ss.zzz")
825                                         + "/" +
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)));
829     } else {
830         m_positionSlider->setValue(0);
831     }
832     if (curpos != QTime(0,0)) {
833         m_positionLabel->setEnabled(true);
834         m_positionSlider->setEnabled(true);
835     }
836 }
837 ```
838
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()`:
842
843 **Player::position()**
844
845 ``` c
846 QTime Player::position() const
847 {
848     if (m_pipeline) {
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();
854     } else {
855         return QTime(0,0);
856     }
857 }
858 ```
859
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
862 example.
863
864 ## Conclusion
865
866 This tutorial has shown:
867
868   - How to create a basic pipeline
869   - How to create a video output
870   - Updating the GUI based on playback time
871
872 It has been a pleasure having you here, and see you soon\!