5 #include <gst/video/videooverlay.h>
8 #if defined (GDK_WINDOWING_X11)
10 #elif defined (GDK_WINDOWING_WIN32)
11 #include <gdk/gdkwin32.h>
12 #elif defined (GDK_WINDOWING_QUARTZ)
13 #include <gdk/gdkquartz.h>
16 /* Structure to contain all our information, so we can pass it around */
17 typedef struct _CustomData {
18 GstElement *playbin; /* Our one and only pipeline */
20 GtkWidget *slider; /* Slider widget to keep track of current position */
21 GtkWidget *streams_list; /* Text widget to display info about the streams */
22 gulong slider_update_signal_id; /* Signal ID for the slider update signal */
24 GstState state; /* Current state of the pipeline */
25 gint64 duration; /* Duration of the clip, in nanoseconds */
28 /* This function is called when the GUI toolkit creates the physical window that will hold the video.
29 * At this point we can retrieve its handler (which has a different meaning depending on the windowing system)
30 * and pass it to GStreamer through the XOverlay interface. */
31 static void realize_cb (GtkWidget *widget, CustomData *data) {
32 GdkWindow *window = gtk_widget_get_window (widget);
33 guintptr window_handle;
35 if (!gdk_window_ensure_native (window))
36 g_error ("Couldn't create native window needed for GstXOverlay!");
38 /* Retrieve window handler from GDK */
39 #if defined (GDK_WINDOWING_WIN32)
40 window_handle = (guintptr)GDK_WINDOW_HWND (window);
41 #elif defined (GDK_WINDOWING_QUARTZ)
42 window_handle = gdk_quartz_window_get_nsview (window);
43 #elif defined (GDK_WINDOWING_X11)
44 window_handle = GDK_WINDOW_XID (window);
46 /* Pass it to playbin, which implements XOverlay and will forward it to the video sink */
47 gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->playbin), window_handle);
50 /* This function is called when the PLAY button is clicked */
51 static void play_cb (GtkButton *button, CustomData *data) {
52 gst_element_set_state (data->playbin, GST_STATE_PLAYING);
55 /* This function is called when the PAUSE button is clicked */
56 static void pause_cb (GtkButton *button, CustomData *data) {
57 gst_element_set_state (data->playbin, GST_STATE_PAUSED);
60 /* This function is called when the STOP button is clicked */
61 static void stop_cb (GtkButton *button, CustomData *data) {
62 gst_element_set_state (data->playbin, GST_STATE_READY);
65 /* This function is called when the main window is closed */
66 static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
71 /* This function is called everytime the video window needs to be redrawn (due to damage/exposure,
72 * rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise,
73 * we simply draw a black rectangle to avoid garbage showing up. */
74 static gboolean draw_cb (GtkWidget *widget, cairo_t *cr, CustomData *data) {
75 if (data->state < GST_STATE_PAUSED) {
76 GtkAllocation allocation;
78 /* Cairo is a 2D graphics library which we use here to clean the video window.
79 * It is used by GStreamer for other reasons, so it will always be available to us. */
80 gtk_widget_get_allocation (widget, &allocation);
81 cairo_set_source_rgb (cr, 0, 0, 0);
82 cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
89 /* This function is called when the slider changes its position. We perform a seek to the
90 * new position here. */
91 static void slider_cb (GtkRange *range, CustomData *data) {
92 gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
93 gst_element_seek_simple (data->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
94 (gint64)(value * GST_SECOND));
97 /* This creates all the GTK+ widgets that compose our application, and registers the callbacks */
98 static void create_ui (CustomData *data) {
99 GtkWidget *main_window; /* The uppermost window, containing all other windows */
100 GtkWidget *video_window; /* The drawing area where the video will be shown */
101 GtkWidget *main_box; /* VBox to hold main_hbox and the controls */
102 GtkWidget *main_hbox; /* HBox to hold the video_window and the stream info text widget */
103 GtkWidget *controls; /* HBox to hold the buttons and the slider */
104 GtkWidget *play_button, *pause_button, *stop_button; /* Buttons */
106 main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
107 g_signal_connect (G_OBJECT (main_window), "delete-event", G_CALLBACK (delete_event_cb), data);
109 video_window = gtk_drawing_area_new ();
110 gtk_widget_set_double_buffered (video_window, FALSE);
111 g_signal_connect (video_window, "realize", G_CALLBACK (realize_cb), data);
112 g_signal_connect (video_window, "draw", G_CALLBACK (draw_cb), data);
114 play_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
115 g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb), data);
117 pause_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PAUSE);
118 g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb), data);
120 stop_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_STOP);
121 g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb), data);
123 data->slider = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 100, 1);
124 gtk_scale_set_draw_value (GTK_SCALE (data->slider), 0);
125 data->slider_update_signal_id = g_signal_connect (G_OBJECT (data->slider), "value-changed", G_CALLBACK (slider_cb), data);
127 data->streams_list = gtk_text_view_new ();
128 gtk_text_view_set_editable (GTK_TEXT_VIEW (data->streams_list), FALSE);
130 controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
131 gtk_box_pack_start (GTK_BOX (controls), play_button, FALSE, FALSE, 2);
132 gtk_box_pack_start (GTK_BOX (controls), pause_button, FALSE, FALSE, 2);
133 gtk_box_pack_start (GTK_BOX (controls), stop_button, FALSE, FALSE, 2);
134 gtk_box_pack_start (GTK_BOX (controls), data->slider, TRUE, TRUE, 2);
136 main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
137 gtk_box_pack_start (GTK_BOX (main_hbox), video_window, TRUE, TRUE, 0);
138 gtk_box_pack_start (GTK_BOX (main_hbox), data->streams_list, FALSE, FALSE, 2);
140 main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
141 gtk_box_pack_start (GTK_BOX (main_box), main_hbox, TRUE, TRUE, 0);
142 gtk_box_pack_start (GTK_BOX (main_box), controls, FALSE, FALSE, 0);
143 gtk_container_add (GTK_CONTAINER (main_window), main_box);
144 gtk_window_set_default_size (GTK_WINDOW (main_window), 640, 480);
146 gtk_widget_show_all (main_window);
149 /* This function is called periodically to refresh the GUI */
150 static gboolean refresh_ui (CustomData *data) {
153 /* We do not want to update anything unless we are in the PAUSED or PLAYING states */
154 if (data->state < GST_STATE_PAUSED)
157 /* If we didn't know it yet, query the stream duration */
158 if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
159 if (!gst_element_query_duration (data->playbin, GST_FORMAT_TIME, &data->duration)) {
160 g_printerr ("Could not query current duration.\n");
162 /* Set the range of the slider to the clip duration, in SECONDS */
163 gtk_range_set_range (GTK_RANGE (data->slider), 0, (gdouble)data->duration / GST_SECOND);
167 if (gst_element_query_position (data->playbin, GST_FORMAT_TIME, ¤t)) {
168 /* Block the "value-changed" signal, so the slider_cb function is not called
169 * (which would trigger a seek the user has not requested) */
170 g_signal_handler_block (data->slider, data->slider_update_signal_id);
171 /* Set the position of the slider to the current pipeline positoin, in SECONDS */
172 gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND);
173 /* Re-enable the signal */
174 g_signal_handler_unblock (data->slider, data->slider_update_signal_id);
179 /* This function is called when new metadata is discovered in the stream */
180 static void tags_cb (GstElement *playbin, gint stream, CustomData *data) {
181 /* We are possibly in a GStreamer working thread, so we notify the main
182 * thread of this event through a message in the bus */
183 gst_element_post_message (playbin,
184 gst_message_new_application (GST_OBJECT (playbin),
185 gst_structure_new_empty ("tags-changed")));
188 /* This function is called when an error message is posted on the bus */
189 static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
193 /* Print error details on the screen */
194 gst_message_parse_error (msg, &err, &debug_info);
195 g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
196 g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
197 g_clear_error (&err);
200 /* Set the pipeline to READY (which stops playback) */
201 gst_element_set_state (data->playbin, GST_STATE_READY);
204 /* This function is called when an End-Of-Stream message is posted on the bus.
205 * We just set the pipeline to READY (which stops playback) */
206 static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
207 g_print ("End-Of-Stream reached.\n");
208 gst_element_set_state (data->playbin, GST_STATE_READY);
211 /* This function is called when the pipeline changes states. We use it to
212 * keep track of the current state. */
213 static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
214 GstState old_state, new_state, pending_state;
215 gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
216 if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
217 data->state = new_state;
218 g_print ("State set to %s\n", gst_element_state_get_name (new_state));
219 if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
220 /* For extra responsiveness, we refresh the GUI as soon as we reach the PAUSED state */
226 /* Extract metadata from all the streams and write it to the text widget in the GUI */
227 static void analyze_streams (CustomData *data) {
230 gchar *str, *total_str;
232 gint n_video, n_audio, n_text;
235 /* Clean current contents of the widget */
236 text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (data->streams_list));
237 gtk_text_buffer_set_text (text, "", -1);
239 /* Read some properties */
240 g_object_get (data->playbin, "n-video", &n_video, NULL);
241 g_object_get (data->playbin, "n-audio", &n_audio, NULL);
242 g_object_get (data->playbin, "n-text", &n_text, NULL);
244 for (i = 0; i < n_video; i++) {
246 /* Retrieve the stream's video tags */
247 g_signal_emit_by_name (data->playbin, "get-video-tags", i, &tags);
249 total_str = g_strdup_printf ("video stream %d:\n", i);
250 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
252 gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
253 total_str = g_strdup_printf (" codec: %s\n", str ? str : "unknown");
254 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
257 gst_tag_list_free (tags);
261 for (i = 0; i < n_audio; i++) {
263 /* Retrieve the stream's audio tags */
264 g_signal_emit_by_name (data->playbin, "get-audio-tags", i, &tags);
266 total_str = g_strdup_printf ("\naudio stream %d:\n", i);
267 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
269 if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str)) {
270 total_str = g_strdup_printf (" codec: %s\n", str);
271 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
275 if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
276 total_str = g_strdup_printf (" language: %s\n", str);
277 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
281 if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate)) {
282 total_str = g_strdup_printf (" bitrate: %d\n", rate);
283 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
286 gst_tag_list_free (tags);
290 for (i = 0; i < n_text; i++) {
292 /* Retrieve the stream's subtitle tags */
293 g_signal_emit_by_name (data->playbin, "get-text-tags", i, &tags);
295 total_str = g_strdup_printf ("\nsubtitle stream %d:\n", i);
296 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
298 if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
299 total_str = g_strdup_printf (" language: %s\n", str);
300 gtk_text_buffer_insert_at_cursor (text, total_str, -1);
304 gst_tag_list_free (tags);
309 /* This function is called when an "application" message is posted on the bus.
310 * Here we retrieve the message posted by the tags_cb callback */
311 static void application_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
312 if (g_strcmp0 (gst_structure_get_name (gst_message_get_structure (msg)), "tags-changed") == 0) {
313 /* If the message is the "tags-changed" (only one we are currently issuing), update
314 * the stream info GUI */
315 analyze_streams (data);
319 int main(int argc, char *argv[]) {
321 GstStateChangeReturn ret;
325 gtk_init (&argc, &argv);
327 /* Initialize GStreamer */
328 gst_init (&argc, &argv);
330 /* Initialize our data structure */
331 memset (&data, 0, sizeof (data));
332 data.duration = GST_CLOCK_TIME_NONE;
334 /* Create the elements */
335 data.playbin = gst_element_factory_make ("playbin", "playbin");
338 g_printerr ("Not all elements could be created.\n");
342 /* Set the URI to play */
343 g_object_set (data.playbin, "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);
345 /* Connect to interesting signals in playbin */
346 g_signal_connect (G_OBJECT (data.playbin), "video-tags-changed", (GCallback) tags_cb, &data);
347 g_signal_connect (G_OBJECT (data.playbin), "audio-tags-changed", (GCallback) tags_cb, &data);
348 g_signal_connect (G_OBJECT (data.playbin), "text-tags-changed", (GCallback) tags_cb, &data);
353 /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
354 bus = gst_element_get_bus (data.playbin);
355 gst_bus_add_signal_watch (bus);
356 g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data);
357 g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, &data);
358 g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, &data);
359 g_signal_connect (G_OBJECT (bus), "message::application", (GCallback)application_cb, &data);
360 gst_object_unref (bus);
363 ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
364 if (ret == GST_STATE_CHANGE_FAILURE) {
365 g_printerr ("Unable to set the pipeline to the playing state.\n");
366 gst_object_unref (data.playbin);
370 /* Register a function that GLib will call every second */
371 g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
373 /* Start the GTK main loop. We will not regain control until gtk_main_quit is called. */
377 gst_element_set_state (data.playbin, GST_STATE_NULL);
378 gst_object_unref (data.playbin);