Implement our own theme, yay!
[platform/upstream/gstreamer.git] / sdk-basic-tutorial-toolkit-integration.md
1 #  Basic tutorial 5: GUI toolkit integration
2
3 ## Goal
4
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.
11
12 In particular, you will learn:
13
14   - How to tell GStreamer to output video to a particular window
15     (instead of creating its own window).
16
17   - How to continuously refresh the GUI with information from GStreamer.
18
19   - How to update the GUI from the multiple threads of GStreamer, an
20     operation forbidden on most GUI toolkits.
21
22   - A mechanism to subscribe only to the messages you are interested in,
23     instead of being notified of all of them.
24
25 ## Introduction
26
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
31 tutorial.
32
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
39 rendering.
40
41 > ![Information](images/icons/emoticons/information.png)
42 > **GObject interfaces**
43 >
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!
45
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
54 accordingly.
55
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.
61
62 ## A media player in GTK+
63
64 Let's write a very simple media player based on playbin, this time,
65 with a GUI!
66
67 Copy this code into a text file named `basic-tutorial-5.c` (or find it
68 in the SDK installation).
69
70 **basic-tutorial-5.c**
71
72 ``` c
73 #include <string.h>
74
75 #include <gtk/gtk.h>
76 #include <gst/gst.h>
77 #include <gst/video/videooverlay.h>
78
79 #include <gdk/gdk.h>
80 #if defined (GDK_WINDOWING_X11)
81 #include <gdk/gdkx.h>
82 #elif defined (GDK_WINDOWING_WIN32)
83 #include <gdk/gdkwin32.h>
84 #elif defined (GDK_WINDOWING_QUARTZ)
85 #include <gdk/gdkquartz.h>
86 #endif
87
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 */
91
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 */
95
96   GstState state;                 /* Current state of the pipeline */
97   gint64 duration;                /* Duration of the clip, in nanoseconds */
98 } CustomData;
99
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;
106
107   if (!gdk_window_ensure_native (window))
108     g_error ("Couldn't create native window needed for GstVideoOverlay!");
109
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);
117 #endif
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);
120 }
121
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);
125 }
126
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);
130 }
131
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);
135 }
136
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);
140   gtk_main_quit ();
141 }
142
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;
149
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);
155     cairo_fill (cr);
156     cairo_destroy (cr);
157   }
158
159   return FALSE;
160 }
161
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));
168 }
169
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 */
178
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);
181
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);
186
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);
189
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);
192
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);
195
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);
199
200   data->streams_list = gtk_text_view_new ();
201   gtk_text_view_set_editable (GTK_TEXT_VIEW (data->streams_list), FALSE);
202
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);
208
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);
212
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);
218
219   gtk_widget_show_all (main_window);
220 }
221
222 /* This function is called periodically to refresh the GUI */
223 static gboolean refresh_ui (CustomData *data) {
224   gint64 current = -1;
225
226   /* We do not want to update anything unless we are in the PAUSED or PLAYING states */
227   if (data->state < GST_STATE_PAUSED)
228     return TRUE;
229
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");
234     } else {
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);
237     }
238   }
239
240   if (gst_element_query_position (data->playbin, GST_FORMAT_TIME, &current)) {
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);
248   }
249   return TRUE;
250 }
251
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)));
259 }
260
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) {
263   GError *err;
264   gchar *debug_info;
265
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);
271   g_free (debug_info);
272
273   /* Set the pipeline to READY (which stops playback) */
274   gst_element_set_state (data->playbin, GST_STATE_READY);
275 }
276
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);
282 }
283
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 */
294       refresh_ui (data);
295     }
296   }
297 }
298
299 /* Extract metadata from all the streams and write it to the text widget in the GUI */
300 static void analyze_streams (CustomData *data) {
301   gint i;
302   GstTagList *tags;
303   gchar *str, *total_str;
304   guint rate;
305   gint n_video, n_audio, n_text;
306   GtkTextBuffer *text;
307
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);
311
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);
316
317   for (i = 0; i < n_video; i++) {
318     tags = NULL;
319     /* Retrieve the stream's video tags */
320     g_signal_emit_by_name (data->playbin, "get-video-tags", i, &tags);
321     if (tags) {
322       total_str = g_strdup_printf ("video stream %d:\n", i);
323       gtk_text_buffer_insert_at_cursor (text, total_str, -1);
324       g_free (total_str);
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);
328       g_free (total_str);
329       g_free (str);
330       gst_tag_list_free (tags);
331     }
332   }
333
334   for (i = 0; i < n_audio; i++) {
335     tags = NULL;
336     /* Retrieve the stream's audio tags */
337     g_signal_emit_by_name (data->playbin, "get-audio-tags", i, &tags);
338     if (tags) {
339       total_str = g_strdup_printf ("\naudio stream %d:\n", i);
340       gtk_text_buffer_insert_at_cursor (text, total_str, -1);
341       g_free (total_str);
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);
345         g_free (total_str);
346         g_free (str);
347       }
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);
351         g_free (total_str);
352         g_free (str);
353       }
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);
357         g_free (total_str);
358       }
359       gst_tag_list_free (tags);
360     }
361   }
362
363   for (i = 0; i < n_text; i++) {
364     tags = NULL;
365     /* Retrieve the stream's subtitle tags */
366     g_signal_emit_by_name (data->playbin, "get-text-tags", i, &tags);
367     if (tags) {
368       total_str = g_strdup_printf ("\nsubtitle stream %d:\n", i);
369       gtk_text_buffer_insert_at_cursor (text, total_str, -1);
370       g_free (total_str);
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);
374         g_free (total_str);
375         g_free (str);
376       }
377       gst_tag_list_free (tags);
378     }
379   }
380 }
381
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);
389   }
390 }
391
392 int main(int argc, char *argv[]) {
393   CustomData data;
394   GstStateChangeReturn ret;
395   GstBus *bus;
396
397   /* Initialize GTK */
398   gtk_init (&argc, &argv);
399
400   /* Initialize GStreamer */
401   gst_init (&argc, &argv);
402
403   /* Initialize our data structure */
404   memset (&data, 0, sizeof (data));
405   data.duration = GST_CLOCK_TIME_NONE;
406
407   /* Create the elements */
408   data.playbin = gst_element_factory_make ("playbin", "playbin");
409
410   if (!data.playbin) {
411     g_printerr ("Not all elements could be created.\n");
412     return -1;
413   }
414
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);
417
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);
422
423   /* Create the GUI */
424   create_ui (&data);
425
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);
434
435   /* Start playing */
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);
440     return -1;
441   }
442
443   /* Register a function that GLib will call every second */
444   g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
445
446   /* Start the GTK main loop. We will not regain control until gtk_main_quit is called. */
447   gtk_main ();
448
449   /* Free resources */
450   gst_element_set_state (data.playbin, GST_STATE_NULL);
451   gst_object_unref (data.playbin);
452   return 0;
453 }
454 ```
455
456 > ![Information](images/icons/emoticons/information.png)
457 > Need help?
458 >
459 > If you need help to compile this code, refer to the **Building the tutorials**  section for your platform: [Linux](sdk-installing-on-linux.md#InstallingonLinux-Build), [Mac OS X](sdk-installing-on-mac-osx.md#InstallingonMacOSX-Build) or [Windows](sdk-installing-on-windows.md#InstallingonWindows-Build), or use this specific command on Linux:
460 >
461 > ``gcc basic-tutorial-5.c -o basic-tutorial-5 `pkg-config --cflags --libs  gstreamer-interfaces-1.0 gtk+-3.0 gstreamer-1.0``
462 >
463 >If you need help to run this code, refer to the **Running the tutorials** section for your platform: [Linux](sdk-installing-on-linux.md#InstallingonLinux-Run), [Mac OS X](sdk-installing-on-mac-osx.md#InstallingonMacOSX-Run) or [Windows](sdk-installing-on-windows.md#InstallingonWindows-Run).
464 >
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.
466 >
467 >
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 [](sdk-basic-tutorial-streaming.md) solves this issue.
469 >
470 > Required libraries: `gstreamer-video-1.0 gtk+-3.0 gstreamer-1.0`
471
472 ## Walkthrough
473
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.
479
480 ``` c
481 #include <gdk/gdk.h>
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>
488 #endif
489 ```
490
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.
496
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.
500
501 ``` c
502 int main(int argc, char *argv[]) {
503   CustomData data;
504   GstStateChangeReturn ret;
505   GstBus *bus;
506
507   /* Initialize GTK */
508   gtk_init (&argc, &argv);
509
510   /* Initialize GStreamer */
511   gst_init (&argc, &argv);
512
513   /* Initialize our data structure */
514   memset (&data, 0, sizeof (data));
515   data.duration = GST_CLOCK_TIME_NONE;
516
517   /* Create the elements */
518   data.playbin = gst_element_factory_make ("playbin", "playbin");
519
520   if (!data.playbin) {
521     g_printerr ("Not all elements could be created.\n");
522     return -1;
523   }
524
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);
527 ```
528
529 Standard GStreamer initialization and playbin pipeline creation, along
530 with GTK+ initialization. Not much new.
531
532 ``` c
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);
537 ```
538
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`.
542
543 ``` c
544 /* Create the GUI */
545 create_ui (&data);
546 ```
547
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
552 callbacks.
553
554 ``` c
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);
563 ```
564
565 In [](sdk-playback-tutorial-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`.
574
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.
579
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.
584
585 ``` c
586 /* Register a function that GLib will call every second */
587 g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
588 ```
589
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.
594
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.
601
602 ``` c
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;
609
610   if (!gdk_window_ensure_native (window))
611     g_error ("Couldn't create native window needed for GstVideoOverlay!");
612
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);
620 #endif
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);
623 }
624 ```
625
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
633 and uses this one.
634
635 Not much more to see here; `playbin` and the `GstVideoOverlay` really simplify
636 this process a lot!
637
638 ``` c
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);
642 }
643
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);
647 }
648
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);
652 }
653 ```
654
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
661 re-acquired.
662
663 ``` c
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);
667   gtk_main_quit ();
668 }
669 ```
670
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).
675
676 ``` c
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);
684     cairo_t *cr;
685
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);
692     cairo_fill (cr);
693     cairo_destroy (cr);
694   }
695
696   return FALSE;
697 }
698 ```
699
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
704 rectangle.
705
706 ``` c
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));
713 }
714 ```
715
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](sdk-basic-tutorial-time-management.md)). The
722 slider has been setup so its value represents seconds.
723
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.
731
732 ``` c
733 /* This function is called periodically to refresh the GUI */
734 static gboolean refresh_ui (CustomData *data) {
735   gint64 current = -1;
736
737   /* We do not want to update anything unless we are in the PAUSED or PLAYING states */
738   if (data->state < GST_STATE_PAUSED)
739     return TRUE;
740 ```
741
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
745 fail).
746
747 ``` c
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");
752   } else {
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);
755   }
756 }
757 ```
758
759 We recover the duration of the clip if we didn't know it, so we can set
760 the range for the slider.
761
762 ``` c
763 if (gst_element_query_position (data->playbin, GST_FORMAT_TIME, &current)) {
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);
771 }
772 return TRUE;
773 ```
774
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()`.
782
783 Returning TRUE from this function will keep it called in the future. If
784 we return FALSE, the timer will be
785 removed.
786
787 ``` c
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)));
795 }
796 ```
797
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**.
804
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.
808
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.
815
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.
820
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:
824
825 ``` c
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);
833   }
834 }
835 ```
836
837 Once me made sure it is the `tags-changed` message, we call the
838 `analyze_streams` function, which is also used in [](sdk-playback-tutorial-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.
841
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.
845
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!
850
851 ![](attachments/basic-tutorial-5.png)
852
853 ## Exercise
854
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 [](sdk-playback-tutorial-playbin-usage.md).
860
861 ## Conclusion
862
863 This tutorial has shown:
864
865   - How to output the video to a particular window handle
866     using `gst_video_overlay_set_window_handle()`.
867
868   - How to refresh the GUI periodically by registering a timeout
869     callback with `g_timeout_add_seconds ()`.
870
871   - How to convey information to the main thread by means of application
872     messages through the bus with `gst_element_post_message()`.
873
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.
877
878 This allows you to build a somewhat complete media player with a proper
879 Graphical User Interface.
880
881 The following basic tutorials keep focusing on other individual
882 GStreamer topics
883
884 It has been a pleasure having you here, and see you soon!