1 # Basic tutorial 5: GUI toolkit integration
5 This tutorial shows how to integrate GStreamer in a Graphical User
6 Interface (GUI) toolkit like [GTK+](http://www.gtk.org). Basically,
7 GStreamer takes care of media playback while the GUI toolkit handles
8 user interaction. The most interesting parts are those in which both
9 libraries have to interact: Instructing GStreamer to output video to a
10 GTK+ window and forwarding user actions to GStreamer.
12 In particular, you will learn:
14 - How to tell GStreamer to output video to a particular window
15 (instead of creating its own window).
17 - How to continuously refresh the GUI with information from GStreamer.
19 - How to update the GUI from the multiple threads of GStreamer, an
20 operation forbidden on most GUI toolkits.
22 - A mechanism to subscribe only to the messages you are interested in,
23 instead of being notified of all of them.
27 We are going to build a media player using the
28 [GTK+](http://www.gtk.org/) toolkit, but the concepts apply to other
29 toolkits like [QT](http://qt-project.org/), for example. A minimum
30 knowledge of [GTK+](http://www.gtk.org/) will help understand this
33 The main point is telling GStreamer to output the video to a window of
34 our choice. The specific mechanism depends on the operating system (or
35 rather, on the windowing system), but GStreamer provides a layer of
36 abstraction for the sake of platform independence. This independence
37 comes through the `GstVideoOverlay` interface, that allows the application to
38 tell a video sink the handler of the window that should receive the
41 > ![Information](images/icons/emoticons/information.png)
42 > **GObject interfaces**
44 > A GObject *interface* (which GStreamer uses) is a set of functions that an element can implement. If it does, then it is said to support that particular interface. For example, video sinks usually create their own windows to display video, but, if they are also capable of rendering to an external window, they can choose to implement the `GstVideoOverlay` interface and provide functions to specify this external window. From the application developer point of view, if a certain interface is supported, you can use it and forget about which kind of element is implementing it. Moreover, if you are using `playbin`, it will automatically expose some of the interfaces supported by its internal elements: You can use your interface functions directly on `playbin` without knowing who is implementing them!
46 Another issue is that GUI toolkits usually only allow manipulation of
47 the graphical “widgets” through the main (or application) thread,
48 whereas GStreamer usually spawns multiple threads to take care of
49 different tasks. Calling [GTK+](http://www.gtk.org/) functions from
50 within callbacks will usually fail, because callbacks execute in the
51 calling thread, which does not need to be the main thread. This problem
52 can be solved by posting a message on the GStreamer bus in the callback:
53 The messages will be received by the main thread which will then react
56 Finally, so far we have registered a `handle_message` function that got
57 called every time a message appeared on the bus, which forced us to
58 parse every message to see if it was of interest to us. In this tutorial
59 a different method is used that registers a callback for each kind of
60 message, so there is less parsing and less code overall.
62 ## A media player in GTK+
64 Let's write a very simple media player based on playbin, this time,
67 Copy this code into a text file named `basic-tutorial-5.c` (or find it
68 in the SDK installation).
70 **basic-tutorial-5.c**
77 #include <gst/video/videooverlay.h>
80 #if defined (GDK_WINDOWING_X11)
82 #elif defined (GDK_WINDOWING_WIN32)
83 #include <gdk/gdkwin32.h>
84 #elif defined (GDK_WINDOWING_QUARTZ)
85 #include <gdk/gdkquartz.h>
88 /* Structure to contain all our information, so we can pass it around */
89 typedef struct _CustomData {
90 GstElement *playbin; /* Our one and only pipeline */
92 GtkWidget *slider; /* Slider widget to keep track of current position */
93 GtkWidget *streams_list; /* Text widget to display info about the streams */
94 gulong slider_update_signal_id; /* Signal ID for the slider update signal */
96 GstState state; /* Current state of the pipeline */
97 gint64 duration; /* Duration of the clip, in nanoseconds */
100 /* This function is called when the GUI toolkit creates the physical window that will hold the video.
101 * At this point we can retrieve its handler (which has a different meaning depending on the windowing system)
102 * and pass it to GStreamer through the GstVideoOverlay interface. */
103 static void realize_cb (GtkWidget *widget, CustomData *data) {
104 GdkWindow *window = gtk_widget_get_window (widget);
105 guintptr window_handle;
107 if (!gdk_window_ensure_native (window))
108 g_error ("Couldn't create native window needed for GstVideoOverlay!");
110 /* Retrieve window handler from GDK */
111 #if defined (GDK_WINDOWING_WIN32)
112 window_handle = (guintptr)GDK_WINDOW_HWND (window);
113 #elif defined (GDK_WINDOWING_QUARTZ)
114 window_handle = gdk_quartz_window_get_nsview (window);
115 #elif defined (GDK_WINDOWING_X11)
116 window_handle = GDK_WINDOW_XID (window);
118 /* Pass it to playbin, which implements GstVideoOverlay and will forward it to the video sink */
119 gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->playbin), window_handle);
122 /* This function is called when the PLAY button is clicked */
123 static void play_cb (GtkButton *button, CustomData *data) {
124 gst_element_set_state (data->playbin, GST_STATE_PLAYING);
127 /* This function is called when the PAUSE button is clicked */
128 static void pause_cb (GtkButton *button, CustomData *data) {
129 gst_element_set_state (data->playbin, GST_STATE_PAUSED);
132 /* This function is called when the STOP button is clicked */
133 static void stop_cb (GtkButton *button, CustomData *data) {
134 gst_element_set_state (data->playbin, GST_STATE_READY);
137 /* This function is called when the main window is closed */
138 static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
139 stop_cb (NULL, data);
143 /* This function is called everytime the video window needs to be redrawn (due to damage/exposure,
144 * rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise,
145 * we simply draw a black rectangle to avoid garbage showing up. */
146 static gboolean draw_cb (GtkWidget *widget, cairo_t *cr, CustomData *data) {
147 if (data->state < GST_STATE_PAUSED) {
148 GtkAllocation allocation;
150 /* Cairo is a 2D graphics library which we use here to clean the video window.
151 * It is used by GStreamer for other reasons, so it will always be available to us. */
152 gtk_widget_get_allocation (widget, &allocation);
153 cairo_set_source_rgb (cr, 0, 0, 0);
154 cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
162 /* This function is called when the slider changes its position. We perform a seek to the
163 * new position here. */
164 static void slider_cb (GtkRange *range, CustomData *data) {
165 gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
166 gst_element_seek_simple (data->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
167 (gint64)(value * GST_SECOND));
170 /* This creates all the GTK+ widgets that compose our application, and registers the callbacks */
171 static void create_ui (CustomData *data) {
172 GtkWidget *main_window; /* The uppermost window, containing all other windows */
173 GtkWidget *video_window; /* The drawing area where the video will be shown */
174 GtkWidget *main_box; /* VBox to hold main_hbox and the controls */
175 GtkWidget *main_hbox; /* HBox to hold the video_window and the stream info text widget */
176 GtkWidget *controls; /* HBox to hold the buttons and the slider */
177 GtkWidget *play_button, *pause_button, *stop_button; /* Buttons */
179 main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
180 g_signal_connect (G_OBJECT (main_window), "delete-event", G_CALLBACK (delete_event_cb), data);
182 video_window = gtk_drawing_area_new ();
183 gtk_widget_set_double_buffered (video_window, FALSE);
184 g_signal_connect (video_window, "realize", G_CALLBACK (realize_cb), data);
185 g_signal_connect (video_window, "draw", G_CALLBACK (draw_cb), data);
187 play_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
188 g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb), data);
190 pause_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PAUSE);
191 g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb), data);
193 stop_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_STOP);
194 g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb), data);
196 data->slider = gtk_hscale_new_with_range (0, 100, 1);
197 gtk_scale_set_draw_value (GTK_SCALE (data->slider), 0);
198 data->slider_update_signal_id = g_signal_connect (G_OBJECT (data->slider), "value-changed", G_CALLBACK (slider_cb), data);
200 data->streams_list = gtk_text_view_new ();
201 gtk_text_view_set_editable (GTK_TEXT_VIEW (data->streams_list), FALSE);
203 controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL,, 0);
204 gtk_box_pack_start (GTK_BOX (controls), play_button, FALSE, FALSE, 2);
205 gtk_box_pack_start (GTK_BOX (controls), pause_button, FALSE, FALSE, 2);
206 gtk_box_pack_start (GTK_BOX (controls), stop_button, FALSE, FALSE, 2);
207 gtk_box_pack_start (GTK_BOX (controls), data->slider, TRUE, TRUE, 2);
209 main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL,, 0);
210 gtk_box_pack_start (GTK_BOX (main_hbox), video_window, TRUE, TRUE, 0);
211 gtk_box_pack_start (GTK_BOX (main_hbox), data->streams_list, FALSE, FALSE, 2);
213 main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL,, 0);
214 gtk_box_pack_start (GTK_BOX (main_box), main_hbox, TRUE, TRUE, 0);
215 gtk_box_pack_start (GTK_BOX (main_box), controls, FALSE, FALSE, 0);
216 gtk_container_add (GTK_CONTAINER (main_window), main_box);
217 gtk_window_set_default_size (GTK_WINDOW (main_window), 640, 480);
219 gtk_widget_show_all (main_window);
222 /* This function is called periodically to refresh the GUI */
223 static gboolean refresh_ui (CustomData *data) {
226 /* We do not want to update anything unless we are in the PAUSED or PLAYING states */
227 if (data->state < GST_STATE_PAUSED)
230 /* If we didn't know it yet, query the stream duration */
231 if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
232 if (!gst_element_query_duration (data->playbin, GST_FORMAT_TIME, &data->duration)) {
233 g_printerr ("Could not query current duration.\n");
235 /* Set the range of the slider to the clip duration, in SECONDS */
236 gtk_range_set_range (GTK_RANGE (data->slider), 0, (gdouble)data->duration / GST_SECOND);
240 if (gst_element_query_position (data->playbin, GST_FORMAT_TIME, ¤t)) {
241 /* Block the "value-changed" signal, so the slider_cb function is not called
242 * (which would trigger a seek the user has not requested) */
243 g_signal_handler_block (data->slider, data->slider_update_signal_id);
244 /* Set the position of the slider to the current pipeline positoin, in SECONDS */
245 gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND);
246 /* Re-enable the signal */
247 g_signal_handler_unblock (data->slider, data->slider_update_signal_id);
252 /* This function is called when new metadata is discovered in the stream */
253 static void tags_cb (GstElement *playbin, gint stream, CustomData *data) {
254 /* We are possibly in a GStreamer working thread, so we notify the main
255 * thread of this event through a message in the bus */
256 gst_element_post_message (playbin,
257 gst_message_new_application (GST_OBJECT (playbin),
258 gst_structure_new ("tags-changed", NULL)));
261 /* This function is called when an error message is posted on the bus */
262 static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
266 /* Print error details on the screen */
267 gst_message_parse_error (msg, &err, &debug_info);
268 g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
269 g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
270 g_clear_error (&err);
273 /* Set the pipeline to READY (which stops playback) */
274 gst_element_set_state (data->playbin, GST_STATE_READY);
277 /* This function is called when an End-Of-Stream message is posted on the bus.
278 * We just set the pipeline to READY (which stops playback) */
279 static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
280 g_print ("End-Of-Stream reached.\n");
281 gst_element_set_state (data->playbin, GST_STATE_READY);
284 /* This function is called when the pipeline changes states. We use it to
285 * keep track of the current state. */
286 static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
287 GstState old_state, new_state, pending_state;
288 gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
289 if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
290 data->state = new_state;
291 g_print ("State set to %s\n", gst_element_state_get_name (new_state));
292 if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
293 /* For extra responsiveness, we refresh the GUI as soon as we reach the PAUSED state */
299 /* Extract metadata from all the streams and write it to the text widget in the GUI */
300 static void analyze_streams (CustomData *data) {
303 gchar *str, *total_str;
305 gint n_video, n_audio, n_text;
308 /* Clean current contents of the widget */
309 text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (data->streams_list));
310 gtk_text_buffer_set_text (text, "", -1);
312 /* Read some properties */
313 g_object_get (data->playbin, "n-video", &n_video, NULL);
314 g_object_get (data->playbin, "n-audio", &n_audio, NULL);
315 g_object_get (data->playbin, "n-text", &n_text, NULL);
317 for (i = 0; i < n_video; i++) {
319 /* Retrieve the stream's video tags */
320 g_signal_emit_by_name (data->playbin, "get-video-tags", i, &tags);
322 total_str = g_strdup_printf ("video stream %d:\n", i);
323 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
325 gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
326 total_str = g_strdup_printf (" codec: %s\n", str ? str : "unknown");
327 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
330 gst_tag_list_free (tags);
334 for (i = 0; i < n_audio; i++) {
336 /* Retrieve the stream's audio tags */
337 g_signal_emit_by_name (data->playbin, "get-audio-tags", i, &tags);
339 total_str = g_strdup_printf ("\naudio stream %d:\n", i);
340 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
342 if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str)) {
343 total_str = g_strdup_printf (" codec: %s\n", str);
344 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
348 if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
349 total_str = g_strdup_printf (" language: %s\n", str);
350 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
354 if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate)) {
355 total_str = g_strdup_printf (" bitrate: %d\n", rate);
356 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
359 gst_tag_list_free (tags);
363 for (i = 0; i < n_text; i++) {
365 /* Retrieve the stream's subtitle tags */
366 g_signal_emit_by_name (data->playbin, "get-text-tags", i, &tags);
368 total_str = g_strdup_printf ("\nsubtitle stream %d:\n", i);
369 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
371 if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
372 total_str = g_strdup_printf (" language: %s\n", str);
373 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
377 gst_tag_list_free (tags);
382 /* This function is called when an "application" message is posted on the bus.
383 * Here we retrieve the message posted by the tags_cb callback */
384 static void application_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
385 if (g_strcmp0 (gst_structure_get_name (gst_message_get_structure(msg)), "tags-changed") == 0) {
386 /* If the message is the "tags-changed" (only one we are currently issuing), update
387 * the stream info GUI */
388 analyze_streams (data);
392 int main(int argc, char *argv[]) {
394 GstStateChangeReturn ret;
398 gtk_init (&argc, &argv);
400 /* Initialize GStreamer */
401 gst_init (&argc, &argv);
403 /* Initialize our data structure */
404 memset (&data, 0, sizeof (data));
405 data.duration = GST_CLOCK_TIME_NONE;
407 /* Create the elements */
408 data.playbin = gst_element_factory_make ("playbin", "playbin");
411 g_printerr ("Not all elements could be created.\n");
415 /* Set the URI to play */
416 g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
418 /* Connect to interesting signals in playbin */
419 g_signal_connect (G_OBJECT (data.playbin), "video-tags-changed", (GCallback) tags_cb, &data);
420 g_signal_connect (G_OBJECT (data.playbin), "audio-tags-changed", (GCallback) tags_cb, &data);
421 g_signal_connect (G_OBJECT (data.playbin), "text-tags-changed", (GCallback) tags_cb, &data);
426 /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
427 bus = gst_element_get_bus (data.playbin);
428 gst_bus_add_signal_watch (bus);
429 g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data);
430 g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, &data);
431 g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, &data);
432 g_signal_connect (G_OBJECT (bus), "message::application", (GCallback)application_cb, &data);
433 gst_object_unref (bus);
436 ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
437 if (ret == GST_STATE_CHANGE_FAILURE) {
438 g_printerr ("Unable to set the pipeline to the playing state.\n");
439 gst_object_unref (data.playbin);
443 /* Register a function that GLib will call every second */
444 g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
446 /* Start the GTK main loop. We will not regain control until gtk_main_quit is called. */
450 gst_element_set_state (data.playbin, GST_STATE_NULL);
451 gst_object_unref (data.playbin);
456 > ![Information](images/icons/emoticons/information.png)
459 > If you need help to compile this code, refer to the **Building the tutorials** section for your platform: [Linux](installing-on-linux.md#InstallingonLinux-Build), [Mac OS X](installing-on-mac-osx.md#InstallingonMacOSX-Build) or [Windows](installing-on-windows.md#InstallingonWindows-Build), or use this specific command on Linux:
461 > ``gcc basic-tutorial-5.c -o basic-tutorial-5 `pkg-config --cflags --libs gstreamer-interfaces-1.0 gtk+-3.0 gstreamer-1.0``
463 >If you need help to run this code, refer to the **Running the tutorials** section for your platform: [Linux](installing-on-linux.md#InstallingonLinux-Run), [Mac OS X](installing-on-mac-osx.md#InstallingonMacOSX-Run) or [Windows](installing-on-windows.md#InstallingonWindows-Run).
465 > This tutorial opens a GTK+ window and displays a movie, with accompanying audio. The media is fetched from the Internet, so the window might take a few seconds to appear, depending on your connection speed. The Window has some GTK+ buttons to Pause, Stop and Play the movie, and a slider to show the current position of the stream, which can be dragged to change it. Also, information about the stream is shown on a column at the right edge of the window.
468 > Bear in mind that there is no latency management (buffering), so on slow connections, the movie might stop after a few seconds. See how [](tutorial-basic-streaming.md) solves this issue.
470 > Required libraries: `gstreamer-video-1.0 gtk+-3.0 gstreamer-1.0`
474 Regarding this tutorial's structure, we are not going to use forward
475 function definitions anymore: Functions will be defined before they are
476 used. Also, for clarity of explanation, the order in which the snippets
477 of code are presented will not always match the program order. Use the
478 line numbers to locate the snippets in the complete code.
482 #if defined (GDK_WINDOWING_X11)
483 #include <gdk/gdkx.h>
484 #elif defined (GDK_WINDOWING_WIN32)
485 #include <gdk/gdkwin32.h>
486 #elif defined (GDK_WINDOWING_QUARTZ)
487 #include <gdk/gdkquartzwindow.h>
491 The first thing worth noticing is that we are no longer completely
492 platform-independent. We need to include the appropriate GDK headers for
493 the windowing system we are going to use. Fortunately, there are not
494 that many supported windowing systems, so these three lines often
495 suffice: X11 for Linux, Win32 for Windows and Quartz for Mac OSX.
497 This tutorial is composed mostly of callback functions, which will be
498 called from GStreamer or GTK+, so let's review the `main` function,
499 which registers all these callbacks.
502 int main(int argc, char *argv[]) {
504 GstStateChangeReturn ret;
508 gtk_init (&argc, &argv);
510 /* Initialize GStreamer */
511 gst_init (&argc, &argv);
513 /* Initialize our data structure */
514 memset (&data, 0, sizeof (data));
515 data.duration = GST_CLOCK_TIME_NONE;
517 /* Create the elements */
518 data.playbin = gst_element_factory_make ("playbin", "playbin");
521 g_printerr ("Not all elements could be created.\n");
525 /* Set the URI to play */
526 g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
529 Standard GStreamer initialization and playbin pipeline creation, along
530 with GTK+ initialization. Not much new.
533 /* Connect to interesting signals in playbin */
534 g_signal_connect (G_OBJECT (data.playbin), "video-tags-changed", (GCallback) tags_cb, &data);
535 g_signal_connect (G_OBJECT (data.playbin), "audio-tags-changed", (GCallback) tags_cb, &data);
536 g_signal_connect (G_OBJECT (data.playbin), "text-tags-changed", (GCallback) tags_cb, &data);
539 We are interested in being notified when new tags (metadata) appears on
540 the stream. For simplicity, we are going to handle all kinds of tags
541 (video, audio and text) from the same callback `tags_cb`.
548 All GTK+ widget creation and signal registration happens in this
549 function. It contains only GTK-related function calls, so we will skip
550 over its definition. The signals to which it registers convey user
551 commands, as shown below when reviewing the
555 /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
556 bus = gst_element_get_bus (data.playbin);
557 gst_bus_add_signal_watch (bus);
558 g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data);
559 g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, &data);
560 g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, &data);
561 g_signal_connect (G_OBJECT (bus), "message::application", (GCallback)application_cb, &data);
562 gst_object_unref (bus);
565 In [](tutorial-playback-playbin-usage.md), `gst_bus_add_watch()` is
566 used to register a function that receives every message posted to the
567 GStreamer bus. We can achieve a finer granularity by using signals
568 instead, which allow us to register only to the messages we are
569 interested in. By calling `gst_bus_add_signal_watch()` we instruct the
570 bus to emit a signal every time it receives a message. This signal has
571 the name `message::detail` where *`detail`* is the message that
572 triggered the signal emission. For example, when the bus receives the
573 EOS message, it emits a signal with the name `message::eos`.
575 This tutorial is using the `Signals`'s details to register only to the
576 messages we care about. If we had registered to the `message` signal, we
577 would be notified of every single message, just like
578 `gst_bus_add_watch()` would do.
580 Keep in mind that, in order for the bus watches to work (be it a
581 `gst_bus_add_watch()` or a `gst_bus_add_signal_watch()`), there must be
582 GLib `Main Loop` running. In this case, it is hidden inside the
583 [GTK+](http://www.gtk.org/) main loop.
586 /* Register a function that GLib will call every second */
587 g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
590 Before transferring control to GTK+, we use `g_timeout_add_seconds
591 ()` to register yet another callback, this time with a timeout, so it
592 gets called every second. We are going to use it to refresh the GUI from
593 the `refresh_ui` function.
595 After this, we are done with the setup and can start the GTK+ main loop.
596 We will regain control from our callbacks when interesting things
597 happen. Let's review the callbacks. Each callback has a different
598 signature, depending on who will call it. You can look up the signature
599 (the meaning of the parameters and the return value) in the
600 documentation of the signal.
603 /* This function is called when the GUI toolkit creates the physical window that will hold the video.
604 * At this point we can retrieve its handler (which has a different meaning depending on the windowing system)
605 * and pass it to GStreamer through the GstVideoOverlay interface. */
606 static void realize_cb (GtkWidget *widget, CustomData *data) {
607 GdkWindow *window = gtk_widget_get_window (widget);
608 guintptr window_handle;
610 if (!gdk_window_ensure_native (window))
611 g_error ("Couldn't create native window needed for GstVideoOverlay!");
613 /* Retrieve window handler from GDK */
614 #if defined (GDK_WINDOWING_WIN32)
615 window_handle = (guintptr)GDK_WINDOW_HWND (window);
616 #elif defined (GDK_WINDOWING_QUARTZ)
617 window_handle = gdk_quartz_window_get_nsview (window);
618 #elif defined (GDK_WINDOWING_X11)
619 window_handle = GDK_WINDOW_XID (window);
621 /* Pass it to playbin, which implements GstVideoOverlay and will forward it to the video sink */
622 gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->playbin), window_handle);
626 The code comments talks by itself. At this point in the life cycle of
627 the application, we know the handle (be it an X11's `XID`, a Window's
628 `HWND` or a Quartz's `NSView`) of the window where GStreamer should
629 render the video. We simply retrieve it from the windowing system and
630 pass it to `playbin` through the `GstVideoOverlay` interface using
631 `gst_video_overlay_set_window_handle()`. `playbin` will locate the video
632 sink and pass the handler to it, so it does not create its own window
635 Not much more to see here; `playbin` and the `GstVideoOverlay` really simplify
639 /* This function is called when the PLAY button is clicked */
640 static void play_cb (GtkButton *button, CustomData *data) {
641 gst_element_set_state (data->playbin, GST_STATE_PLAYING);
644 /* This function is called when the PAUSE button is clicked */
645 static void pause_cb (GtkButton *button, CustomData *data) {
646 gst_element_set_state (data->playbin, GST_STATE_PAUSED);
649 /* This function is called when the STOP button is clicked */
650 static void stop_cb (GtkButton *button, CustomData *data) {
651 gst_element_set_state (data->playbin, GST_STATE_READY);
655 These three little callbacks are associated with the PLAY, PAUSE and
656 STOP buttons in the GUI. They simply set the pipeline to the
657 corresponding state. Note that in the STOP state we set the pipeline to
658 `READY`. We could have brought the pipeline all the way down to the
659 `NULL` state, but, the transition would then be a little slower, since some
660 resources (like the audio device) would need to be released and
664 /* This function is called when the main window is closed */
665 static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
666 stop_cb (NULL, data);
671 gtk_main_quit() will eventually make the call to to gtk_main_run()
672 in `main` to terminate, which, in this case, finishes the program. Here,
673 we call it when the main window is closed, after stopping the pipeline
674 (just for the sake of tidiness).
677 /* This function is called everytime the video window needs to be redrawn (due to damage/exposure,
678 * rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise,
679 * we simply draw a black rectangle to avoid garbage showing up. */
680 static gboolean expose_cb (GtkWidget *widget, GdkEventExpose *event, CustomData *data) {
681 if (data->state < GST_STATE_PAUSED) {
682 GtkAllocation allocation;
683 GdkWindow *window = gtk_widget_get_window (widget);
686 /* Cairo is a 2D graphics library which we use here to clean the video window.
687 * It is used by GStreamer for other reasons, so it will always be available to us. */
688 gtk_widget_get_allocation (widget, &allocation);
689 cr = gdk_cairo_create (window);
690 cairo_set_source_rgb (cr, 0, 0, 0);
691 cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
700 When there is data flow (in the `PAUSED` and `PLAYING` states) the video
701 sink takes care of refreshing the content of the video window. In the
702 other cases, however, it will not, so we have to do it. In this example,
703 we just fill the window with a black
707 /* This function is called when the slider changes its position. We perform a seek to the
708 * new position here. */
709 static void slider_cb (GtkRange *range, CustomData *data) {
710 gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
711 gst_element_seek_simple (data->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
712 (gint64)(value * GST_SECOND));
716 This is an example of how a complex GUI element like a seeker bar (or
717 slider that allows seeking) can be very easily implemented thanks to
718 GStreamer and GTK+ collaborating. If the slider has been dragged to a
719 new position, tell GStreamer to seek to that position
720 with `gst_element_seek_simple()` (as seen in [Basic tutorial 4: Time
721 management](tutorial-basic-time-management.md)). The
722 slider has been setup so its value represents seconds.
724 It is worth mentioning that some performance (and responsiveness) can be
725 gained by doing some throttling, this is, not responding to every single
726 user request to seek. Since the seek operation is bound to take some
727 time, it is often nicer to wait half a second (for example) after a seek
728 before allowing another one. Otherwise, the application might look
729 unresponsive if the user drags the slider frantically, which would not
730 allow any seek to complete before a new one is queued.
733 /* This function is called periodically to refresh the GUI */
734 static gboolean refresh_ui (CustomData *data) {
737 /* We do not want to update anything unless we are in the PAUSED or PLAYING states */
738 if (data->state < GST_STATE_PAUSED)
742 This function will move the slider to reflect the current position of
743 the media. First off, if we are not in the `PLAYING` state, we have
744 nothing to do here (plus, position and duration queries will normally
748 /* If we didn't know it yet, query the stream duration */
749 if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
750 if (!gst_element_query_duration (data->playbin, GST_FORMAT_TIME, &data->duration)) {
751 g_printerr ("Could not query current duration.\n");
753 /* Set the range of the slider to the clip duration, in SECONDS */
754 gtk_range_set_range (GTK_RANGE (data->slider), 0, (gdouble)data->duration / GST_SECOND);
759 We recover the duration of the clip if we didn't know it, so we can set
760 the range for the slider.
763 if (gst_element_query_position (data->playbin, GST_FORMAT_TIME, ¤t)) {
764 /* Block the "value-changed" signal, so the slider_cb function is not called
765 * (which would trigger a seek the user has not requested) */
766 g_signal_handler_block (data->slider, data->slider_update_signal_id);
767 /* Set the position of the slider to the current pipeline positoin, in SECONDS */
768 gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND);
769 /* Re-enable the signal */
770 g_signal_handler_unblock (data->slider, data->slider_update_signal_id);
775 We query the current pipeline position, and set the position of the
776 slider accordingly. This would trigger the emission of the
777 `value-changed` signal, which we use to know when the user is dragging
778 the slider. Since we do not want seeks happening unless the user
779 requested them, we disable the `value-changed` signal emission during
780 this operation with `g_signal_handler_block()` and
781 `g_signal_handler_unblock()`.
783 Returning TRUE from this function will keep it called in the future. If
784 we return FALSE, the timer will be
788 /* This function is called when new metadata is discovered in the stream */
789 static void tags_cb (GstElement *playbin, gint stream, CustomData *data) {
790 /* We are possibly in a GStreamer working thread, so we notify the main
791 * thread of this event through a message in the bus */
792 gst_element_post_message (playbin,
793 gst_message_new_application (GST_OBJECT (playbin),
794 gst_structure_new ("tags-changed", NULL)));
798 This is one of the key points of this tutorial. This function will be
799 called when new tags are found in the media, **from a streaming
800 thread**, this is, from a thread other than the application (or main)
801 thread. What we want to do here is to update a GTK+ widget to reflect
802 this new information, but **GTK+ does not allow operating from threads
803 other than the main one**.
805 The solution is to make `playbin` post a message on the bus and return
806 to the calling thread. When appropriate, the main thread will pick up
807 this message and update GTK.
809 `gst_element_post_message()` makes a GStreamer element post the given
810 message to the bus. `gst_message_new_application()` creates a new
811 message of the `APPLICATION` type. GStreamer messages have different
812 types, and this particular type is reserved to the application: it will
813 go through the bus unaffected by GStreamer. The list of types can be
814 found in the `GstMessageType` documentation.
816 Messages can deliver additional information through their embedded
817 `GstStructure`, which is a very flexible data container. Here, we create
818 a new structure with `gst_structure_new`, and name it `tags-changed`, to
819 avoid confusion in case we wanted to send other application messages.
821 Later, once in the main thread, the bus will receive this message and
822 emit the `message::application` signal, which we have associated to the
823 `application_cb` function:
826 /* This function is called when an "application" message is posted on the bus.
827 * Here we retrieve the message posted by the tags_cb callback */
828 static void application_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
829 if (g_strcmp0 (gst_structure_get_name (gst_message_get_structure (msg)), "tags-changed") == 0) {
830 /* If the message is the "tags-changed" (only one we are currently issuing), update
831 * the stream info GUI */
832 analyze_streams (data);
837 Once me made sure it is the `tags-changed` message, we call the
838 `analyze_streams` function, which is also used in [](tutorial-playback-playbin-usage.md) and is
839 more detailed there. It basically recovers the tags from the stream and
840 writes them in a text widget in the GUI.
842 The `error_cb`, `eos_cb` and `state_changed_cb` are not really worth
843 explaining, since they do the same as in all previous tutorials, but
844 from their own function now.
846 And this is it! The amount of code in this tutorial might seem daunting
847 but the required concepts are few and easy. If you have followed the
848 previous tutorials and have a little knowledge of GTK, you probably
849 understood this one can now enjoy your very own media player!
851 ![](attachments/basic-tutorial-5.png)
855 If this media player is not good enough for you, try to change the text
856 widget that displays the information about the streams into a proper
857 list view (or tree view). Then, when the user selects a different
858 stream, make GStreamer switch streams! To switch streams, you will need
859 to read [](tutorial-playback-playbin-usage.md).
863 This tutorial has shown:
865 - How to output the video to a particular window handle
866 using `gst_video_overlay_set_window_handle()`.
868 - How to refresh the GUI periodically by registering a timeout
869 callback with `g_timeout_add_seconds ()`.
871 - How to convey information to the main thread by means of application
872 messages through the bus with `gst_element_post_message()`.
874 - How to be notified only of interesting messages by making the bus
875 emit signals with `gst_bus_add_signal_watch()` and discriminating
876 among all message types using the signal details.
878 This allows you to build a somewhat complete media player with a proper
879 Graphical User Interface.
881 The following basic tutorials keep focusing on other individual
884 It has been a pleasure having you here, and see you soon!