seek: Add support for setting a subtitle URI
[platform/upstream/gstreamer.git] / tests / examples / seek / seek.c
1 /* GStreamer
2  *
3  * seek.c: seeking sample application
4  *
5  * Copyright (C) 2005 Wim Taymans <wim@fluendo.com>
6  *               2006 Stefan Kost <ensonic@users.sf.net>
7  *               2012 Collabora Ltd.
8  *                 Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public
21  * License along with this library; if not, write to the
22  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23  * Boston, MA 02111-1307, USA.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29 /* FIXME 0.11: suppress warnings for deprecated API such as GStaticRecMutex
30  * with newer GTK versions (>= 3.3.0) */
31 #define GDK_DISABLE_DEPRECATION_WARNINGS
32 #define GLIB_DISABLE_DEPRECATION_WARNINGS
33
34 #include <stdlib.h>
35 #include <math.h>
36 #include <glib.h>
37 #include <gtk/gtk.h>
38 #include <gst/gst.h>
39 #include <string.h>
40
41 #include <gdk/gdk.h>
42 #if defined (GDK_WINDOWING_X11)
43 #include <gdk/gdkx.h>
44 #elif defined (GDK_WINDOWING_WIN32)
45 #include <gdk/gdkwin32.h>
46 #elif defined (GDK_WINDOWING_QUARTZ)
47 #include <gdk/gdkquartzwindow.h>
48 #endif
49
50 #include <gst/interfaces/xoverlay.h>
51 #include <gst/interfaces/navigation.h>
52 #include <gst/interfaces/colorbalance.h>
53
54 GST_DEBUG_CATEGORY_STATIC (seek_debug);
55 #define GST_CAT_DEFAULT (seek_debug)
56
57 /* Copied from gst-plugins-base/gst/playback/gstplay-enum.h */
58 typedef enum
59 {
60   GST_PLAY_FLAG_VIDEO = (1 << 0),
61   GST_PLAY_FLAG_AUDIO = (1 << 1),
62   GST_PLAY_FLAG_TEXT = (1 << 2),
63   GST_PLAY_FLAG_VIS = (1 << 3),
64   GST_PLAY_FLAG_SOFT_VOLUME = (1 << 4),
65   GST_PLAY_FLAG_NATIVE_AUDIO = (1 << 5),
66   GST_PLAY_FLAG_NATIVE_VIDEO = (1 << 6),
67   GST_PLAY_FLAG_DOWNLOAD = (1 << 7),
68   GST_PLAY_FLAG_BUFFERING = (1 << 8),
69   GST_PLAY_FLAG_DEINTERLACE = (1 << 9),
70   GST_PLAY_FLAG_SOFT_COLORBALANCE = (1 << 10)
71 } GstPlayFlags;
72
73 /* configuration */
74
75 #define FILL_INTERVAL 100
76 //#define UPDATE_INTERVAL 500
77 //#define UPDATE_INTERVAL 100
78 #define UPDATE_INTERVAL 40
79
80 /* number of milliseconds to play for after a seek */
81 #define SCRUB_TIME 100
82
83 /* timeout for gst_element_get_state() after a seek */
84 #define SEEK_TIMEOUT 40 * GST_MSECOND
85
86 #define DEFAULT_VIDEO_HEIGHT 300
87
88 /* the state to go to when stop is pressed */
89 #define STOP_STATE      GST_STATE_READY
90
91 #define N_GRAD 1000.0
92
93 /* we keep an array of the visualisation entries so that we can easily switch
94  * with the combo box index. */
95 typedef struct
96 {
97   GstElementFactory *factory;
98 } VisEntry;
99
100 typedef struct
101 {
102   /* GTK widgets */
103   GtkWidget *window;
104   GtkWidget *video_combo, *audio_combo, *text_combo, *vis_combo;
105   GtkWidget *video_window;
106
107   GtkWidget *vis_checkbox, *video_checkbox, *audio_checkbox;
108   GtkWidget *text_checkbox, *mute_checkbox, *volume_spinbutton;
109   GtkWidget *soft_volume_checkbox, *native_audio_checkbox,
110       *native_video_checkbox;
111   GtkWidget *download_checkbox, *buffering_checkbox, *deinterlace_checkbox;
112   GtkWidget *soft_colorbalance_checkbox;
113   GtkWidget *video_sink_entry, *audio_sink_entry, *text_sink_entry;
114   GtkWidget *buffer_size_entry, *buffer_duration_entry;
115   GtkWidget *ringbuffer_maxsize_entry, *connection_speed_entry;
116   GtkWidget *av_offset_entry, *subtitle_encoding_entry;
117   GtkWidget *subtitle_fontdesc_button;
118
119   GtkWidget *seek_format_combo, *seek_position_label, *seek_duration_label;
120   GtkWidget *seek_entry;
121
122   GtkWidget *seek_scale, *statusbar;
123   guint status_id;
124
125   GtkWidget *step_format_combo, *step_amount_spinbutton, *step_rate_spinbutton;
126   GtkWidget *shuttle_scale;
127
128   GtkWidget *contrast_scale, *brightness_scale, *hue_scale, *saturation_scale;
129
130   struct
131   {
132     GstNavigationCommand cmd;
133     GtkWidget *button;
134   } navigation_buttons[14];
135
136   guintptr embed_xid;
137
138   /* GStreamer pipeline */
139   GstElement *pipeline;
140
141   GstElement *navigation_element;
142   GstElement *colorbalance_element;
143   GstElement *xoverlay_element;
144
145   /* Settings */
146   gboolean accurate_seek;
147   gboolean keyframe_seek;
148   gboolean loop_seek;
149   gboolean flush_seek;
150   gboolean scrub;
151   gboolean play_scrub;
152   gboolean skip_seek;
153   gdouble rate;
154
155   /* From commandline parameters */
156   gboolean stats;
157   gboolean verbose;
158   const gchar *pipeline_spec;
159   gint pipeline_type;
160   GList *paths, *current_path;
161   GList *sub_paths, *current_sub_path;
162
163   gchar *audiosink_str, *videosink_str;
164
165   /* Internal state */
166   gint64 position, duration;
167
168   gboolean is_live;
169   gboolean buffering;
170   GstBufferingMode mode;
171   gint64 buffering_left;
172   GstState state;
173   guint update_id;
174   guint seek_timeout_id;
175   gulong changed_id;
176   guint fill_id;
177
178   gboolean need_streams;
179   gint n_video, n_audio, n_text;
180
181   GStaticMutex state_mutex;
182
183   GArray *vis_entries;          /* Array of VisEntry structs */
184
185   gboolean shuttling;
186   gdouble shuttle_rate;
187   gdouble play_rate;
188
189   const GstFormatDefinition *seek_format;
190   GList *formats;
191 } SeekApp;
192
193 static void clear_streams (SeekApp * app);
194 static void find_interface_elements (SeekApp * app);
195 static void volume_notify_cb (GstElement * pipeline, GParamSpec * arg,
196     SeekApp * app);
197 static void mute_notify_cb (GstElement * pipeline, GParamSpec * arg,
198     SeekApp * app);
199
200 static void video_sink_activate_cb (GtkEntry * entry, SeekApp * app);
201 static void text_sink_activate_cb (GtkEntry * entry, SeekApp * app);
202 static void audio_sink_activate_cb (GtkEntry * entry, SeekApp * app);
203 static void buffer_size_activate_cb (GtkEntry * entry, SeekApp * app);
204 static void buffer_duration_activate_cb (GtkEntry * entry, SeekApp * app);
205 static void ringbuffer_maxsize_activate_cb (GtkEntry * entry, SeekApp * app);
206 static void connection_speed_activate_cb (GtkEntry * entry, SeekApp * app);
207 static void av_offset_activate_cb (GtkEntry * entry, SeekApp * app);
208 static void subtitle_encoding_activate_cb (GtkEntry * entry, SeekApp * app);
209
210 /* pipeline construction */
211
212 static GstElement *
213 gst_element_factory_make_or_warn (const gchar * type, const gchar * name)
214 {
215   GstElement *element = gst_element_factory_make (type, name);
216
217 #ifndef GST_DISABLE_PARSE
218   if (!element) {
219     /* Try parsing it as a pipeline description */
220     element = gst_parse_bin_from_description (type, TRUE, NULL);
221     if (element) {
222       gst_element_set_name (element, name);
223     }
224   }
225 #endif
226
227   if (!element) {
228     g_warning ("Failed to create element %s of type %s", name, type);
229   }
230
231   return element;
232 }
233
234 static void
235 set_uri_property (GObject * object, const gchar * property,
236     const gchar * location)
237 {
238   gchar *uri;
239
240   /* Add "file://" prefix for convenience */
241   if (location && (g_str_has_prefix (location, "/")
242           || !gst_uri_is_valid (location))) {
243     uri = gst_filename_to_uri (location, NULL);
244     g_print ("Setting URI: %s\n", uri);
245     g_object_set (object, property, uri, NULL);
246     g_free (uri);
247   } else {
248     g_print ("Setting URI: %s\n", location);
249     g_object_set (object, property, location, NULL);
250   }
251 }
252
253 static void
254 playbin_set_uri (GstElement * playbin, const gchar * location,
255     const gchar * sub_location)
256 {
257   set_uri_property (G_OBJECT (playbin), "uri", location);
258   set_uri_property (G_OBJECT (playbin), "suburi", sub_location);
259 }
260
261 static void
262 make_playbin2_pipeline (SeekApp * app, const gchar * location)
263 {
264   GstElement *pipeline;
265
266   app->pipeline = pipeline = gst_element_factory_make ("playbin2", "playbin2");
267   g_assert (pipeline);
268
269   playbin_set_uri (pipeline, location,
270       app->current_sub_path ? app->current_sub_path->data : NULL);
271
272   g_signal_connect (pipeline, "notify::volume", G_CALLBACK (volume_notify_cb),
273       app);
274   g_signal_connect (pipeline, "notify::mute", G_CALLBACK (mute_notify_cb), app);
275
276   app->navigation_element = GST_ELEMENT (gst_object_ref (pipeline));
277   app->colorbalance_element = GST_ELEMENT (gst_object_ref (pipeline));
278 }
279
280 #ifndef GST_DISABLE_PARSE
281 static void
282 make_parselaunch_pipeline (SeekApp * app, const gchar * description)
283 {
284   app->pipeline = gst_parse_launch (description, NULL);
285 }
286 #endif
287
288 typedef struct
289 {
290   const gchar *name;
291   void (*func) (SeekApp * app, const gchar * location);
292 }
293 Pipeline;
294
295 static const Pipeline pipelines[] = {
296   {"playbin2", make_playbin2_pipeline},
297 #ifndef GST_DISABLE_PARSE
298   {"parse-launch", make_parselaunch_pipeline},
299 #endif
300 };
301
302 /* ui callbacks and helpers */
303
304 static gchar *
305 format_value (GtkScale * scale, gdouble value, SeekApp * app)
306 {
307   gint64 real;
308   gint64 seconds;
309   gint64 subseconds;
310
311   real = value * app->duration / N_GRAD;
312   seconds = (gint64) real / GST_SECOND;
313   subseconds = (gint64) real / (GST_SECOND / N_GRAD);
314
315   return g_strdup_printf ("%02" G_GINT64_FORMAT ":%02" G_GINT64_FORMAT ":%02"
316       G_GINT64_FORMAT, seconds / 60, seconds % 60, subseconds % 100);
317 }
318
319 static gchar *
320 shuttle_format_value (GtkScale * scale, gdouble value)
321 {
322   return g_strdup_printf ("%0.*g", gtk_scale_get_digits (scale), value);
323 }
324
325 typedef struct
326 {
327   const gchar *name;
328   const GstFormat format;
329 }
330 seek_format;
331
332 static seek_format seek_formats[] = {
333   {"tim", GST_FORMAT_TIME},
334   {"byt", GST_FORMAT_BYTES},
335   {"buf", GST_FORMAT_BUFFERS},
336   {"def", GST_FORMAT_DEFAULT},
337   {NULL, 0},
338 };
339
340 static void
341 query_positions (SeekApp * app)
342 {
343   gint i = 0;
344
345   g_print ("positions %8.8s: ", GST_ELEMENT_NAME (app->pipeline));
346   while (seek_formats[i].name) {
347     gint64 position, total;
348     GstFormat format;
349
350     format = seek_formats[i].format;
351
352     if (gst_element_query_position (app->pipeline, &format, &position) &&
353         gst_element_query_duration (app->pipeline, &format, &total)) {
354       g_print ("%s %13" G_GINT64_FORMAT " / %13" G_GINT64_FORMAT " | ",
355           seek_formats[i].name, position, total);
356     } else {
357       g_print ("%s %13.13s / %13.13s | ", seek_formats[i].name, "*NA*", "*NA*");
358     }
359     i++;
360   }
361   g_print (" %s\n", GST_ELEMENT_NAME (app->pipeline));
362 }
363
364 static gboolean start_seek (GtkRange * range, GdkEventButton * event,
365     SeekApp * app);
366 static gboolean stop_seek (GtkRange * range, GdkEventButton * event,
367     SeekApp * app);
368 static void seek_cb (GtkRange * range, SeekApp * app);
369
370 static void
371 set_scale (SeekApp * app, gdouble value)
372 {
373   g_signal_handlers_block_by_func (app->seek_scale, start_seek, app);
374   g_signal_handlers_block_by_func (app->seek_scale, stop_seek, app);
375   g_signal_handlers_block_by_func (app->seek_scale, seek_cb, app);
376   gtk_range_set_value (GTK_RANGE (app->seek_scale), value);
377   g_signal_handlers_unblock_by_func (app->seek_scale, start_seek, app);
378   g_signal_handlers_unblock_by_func (app->seek_scale, stop_seek, app);
379   g_signal_handlers_unblock_by_func (app->seek_scale, seek_cb, app);
380   gtk_widget_queue_draw (app->seek_scale);
381 }
382
383 static gboolean
384 update_fill (SeekApp * app)
385 {
386   GstQuery *query;
387
388   query = gst_query_new_buffering (GST_FORMAT_PERCENT);
389   if (gst_element_query (app->pipeline, query)) {
390     gint64 start, stop, buffering_total;
391     GstFormat format;
392     gdouble fill;
393     gboolean busy;
394     gint percent;
395     GstBufferingMode mode;
396     gint avg_in, avg_out;
397     gint64 buffering_left;
398
399     gst_query_parse_buffering_percent (query, &busy, &percent);
400     gst_query_parse_buffering_range (query, &format, &start, &stop,
401         &buffering_total);
402     gst_query_parse_buffering_stats (query, &mode, &avg_in, &avg_out,
403         &buffering_left);
404
405     /* note that we could start the playback when buffering_left < remaining
406      * playback time */
407     GST_DEBUG ("buffering total %" G_GINT64_FORMAT " ms, left %"
408         G_GINT64_FORMAT " ms", buffering_total, buffering_left);
409     GST_DEBUG ("start %" G_GINT64_FORMAT ", stop %" G_GINT64_FORMAT,
410         start, stop);
411
412     if (stop != -1)
413       fill = N_GRAD * stop / GST_FORMAT_PERCENT_MAX;
414     else
415       fill = N_GRAD;
416
417     gtk_range_set_fill_level (GTK_RANGE (app->seek_scale), fill);
418   }
419   gst_query_unref (query);
420
421   return TRUE;
422 }
423
424 static gboolean
425 update_scale (SeekApp * app)
426 {
427   GstFormat format = GST_FORMAT_TIME;
428   gint64 seek_pos, seek_dur;
429   gchar *str;
430
431   //position = 0;
432   //duration = 0;
433
434   gst_element_query_position (app->pipeline, &format, &app->position);
435   gst_element_query_duration (app->pipeline, &format, &app->duration);
436
437   if (app->stats)
438     query_positions (app);
439
440   if (app->position >= app->duration)
441     app->duration = app->position;
442
443   if (app->duration > 0) {
444     set_scale (app, app->position * N_GRAD / app->duration);
445   }
446
447   if (app->seek_format) {
448     format = app->seek_format->value;
449     seek_pos = seek_dur = -1;
450     gst_element_query_position (app->pipeline, &format, &seek_pos);
451     gst_element_query_duration (app->pipeline, &format, &seek_dur);
452
453     str = g_strdup_printf ("%" G_GINT64_FORMAT, seek_pos);
454     gtk_label_set_text (GTK_LABEL (app->seek_position_label), str);
455     g_free (str);
456
457     str = g_strdup_printf ("%" G_GINT64_FORMAT, seek_dur);
458     gtk_label_set_text (GTK_LABEL (app->seek_duration_label), str);
459     g_free (str);
460   }
461
462   return TRUE;
463 }
464
465 static void set_update_scale (SeekApp * app, gboolean active);
466 static void set_update_fill (SeekApp * app, gboolean active);
467
468 static gboolean
469 end_scrub (SeekApp * app)
470 {
471   GST_DEBUG ("end scrub, PAUSE");
472   gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
473   app->seek_timeout_id = 0;
474
475   return FALSE;
476 }
477
478 static gboolean
479 send_event (SeekApp * app, GstEvent * event)
480 {
481   gboolean res = FALSE;
482
483   GST_DEBUG ("send event on element %s", GST_ELEMENT_NAME (app->pipeline));
484   res = gst_element_send_event (app->pipeline, event);
485
486   return res;
487 }
488
489 static void
490 do_seek (SeekApp * app, GstFormat format, gint64 position)
491 {
492   gboolean res = FALSE;
493   GstEvent *s_event;
494   GstSeekFlags flags;
495
496   flags = 0;
497   if (app->flush_seek)
498     flags |= GST_SEEK_FLAG_FLUSH;
499   if (app->accurate_seek)
500     flags |= GST_SEEK_FLAG_ACCURATE;
501   if (app->keyframe_seek)
502     flags |= GST_SEEK_FLAG_KEY_UNIT;
503   if (app->loop_seek)
504     flags |= GST_SEEK_FLAG_SEGMENT;
505   if (app->skip_seek)
506     flags |= GST_SEEK_FLAG_SKIP;
507
508   if (app->rate >= 0) {
509     s_event = gst_event_new_seek (app->rate,
510         format, flags, GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_SET,
511         GST_CLOCK_TIME_NONE);
512     GST_DEBUG ("seek with rate %lf to %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT,
513         app->rate, GST_TIME_ARGS (position), GST_TIME_ARGS (app->duration));
514   } else {
515     s_event = gst_event_new_seek (app->rate,
516         format, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0),
517         GST_SEEK_TYPE_SET, position);
518     GST_DEBUG ("seek with rate %lf to %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT,
519         app->rate, GST_TIME_ARGS (0), GST_TIME_ARGS (position));
520   }
521
522   res = send_event (app, s_event);
523
524   if (res) {
525     if (app->flush_seek) {
526       gst_element_get_state (GST_ELEMENT (app->pipeline), NULL, NULL,
527           SEEK_TIMEOUT);
528     } else {
529       set_update_scale (app, TRUE);
530     }
531   } else {
532     g_print ("seek failed\n");
533     set_update_scale (app, TRUE);
534   }
535 }
536
537 static void
538 seek_cb (GtkRange * range, SeekApp * app)
539 {
540   gint64 real;
541   /* If the timer hasn't expired yet, then the pipeline is running */
542   if (app->play_scrub && app->seek_timeout_id != 0) {
543     GST_DEBUG ("do scrub seek, PAUSED");
544     gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
545   }
546
547   real =
548       gtk_range_get_value (GTK_RANGE (app->seek_scale)) * app->duration /
549       N_GRAD;
550
551   GST_DEBUG ("value=%f, real=%" G_GINT64_FORMAT,
552       gtk_range_get_value (GTK_RANGE (app->seek_scale)), real);
553
554   GST_DEBUG ("do seek");
555   do_seek (app, GST_FORMAT_TIME, real);
556
557   if (app->play_scrub) {
558     GST_DEBUG ("do scrub seek, PLAYING");
559     gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
560
561     if (app->seek_timeout_id == 0) {
562       app->seek_timeout_id =
563           g_timeout_add (SCRUB_TIME, (GSourceFunc) end_scrub, app);
564     }
565   }
566 }
567
568 static void
569 advanced_seek_button_cb (GtkButton * button, SeekApp * app)
570 {
571   GstFormat fmt;
572   gint64 pos;
573   const gchar *text;
574   gchar *endptr;
575
576   fmt = app->seek_format->value;
577
578   text = gtk_entry_get_text (GTK_ENTRY (app->seek_entry));
579
580   pos = g_ascii_strtoll (text, &endptr, 10);
581   if (endptr != text && pos != G_MAXINT64 && pos != G_MININT64) {
582     do_seek (app, fmt, pos);
583   }
584 }
585
586 static void
587 set_update_fill (SeekApp * app, gboolean active)
588 {
589   GST_DEBUG ("fill scale is %d", active);
590
591   if (active) {
592     if (app->fill_id == 0) {
593       app->fill_id =
594           g_timeout_add (FILL_INTERVAL, (GSourceFunc) update_fill, app);
595     }
596   } else {
597     if (app->fill_id) {
598       g_source_remove (app->fill_id);
599       app->fill_id = 0;
600     }
601   }
602 }
603
604 static void
605 set_update_scale (SeekApp * app, gboolean active)
606 {
607   GST_DEBUG ("update scale is %d", active);
608
609   if (active) {
610     if (app->update_id == 0) {
611       app->update_id =
612           g_timeout_add (UPDATE_INTERVAL, (GSourceFunc) update_scale, app);
613     }
614   } else {
615     if (app->update_id) {
616       g_source_remove (app->update_id);
617       app->update_id = 0;
618     }
619   }
620 }
621
622 static gboolean
623 start_seek (GtkRange * range, GdkEventButton * event, SeekApp * app)
624 {
625   if (event->type != GDK_BUTTON_PRESS)
626     return FALSE;
627
628   set_update_scale (app, FALSE);
629
630   if (app->state == GST_STATE_PLAYING && app->flush_seek && app->scrub) {
631     GST_DEBUG ("start scrub seek, PAUSE");
632     gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
633   }
634
635   if (app->changed_id == 0 && app->flush_seek && app->scrub) {
636     app->changed_id =
637         g_signal_connect (app->seek_scale, "value-changed",
638         G_CALLBACK (seek_cb), app);
639   }
640
641   return FALSE;
642 }
643
644 static gboolean
645 stop_seek (GtkRange * range, GdkEventButton * event, SeekApp * app)
646 {
647   if (app->changed_id) {
648     g_signal_handler_disconnect (app->seek_scale, app->changed_id);
649     app->changed_id = 0;
650   }
651
652   if (!app->flush_seek || !app->scrub) {
653     gint64 real;
654
655     GST_DEBUG ("do final seek");
656     real =
657         gtk_range_get_value (GTK_RANGE (app->seek_scale)) * app->duration /
658         N_GRAD;
659     do_seek (app, GST_FORMAT_TIME, real);
660   }
661
662   if (app->seek_timeout_id != 0) {
663     g_source_remove (app->seek_timeout_id);
664     app->seek_timeout_id = 0;
665     /* Still scrubbing, so the pipeline is playing, see if we need PAUSED
666      * instead. */
667     if (app->state == GST_STATE_PAUSED) {
668       GST_DEBUG ("stop scrub seek, PAUSED");
669       gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
670     }
671   } else {
672     if (app->state == GST_STATE_PLAYING) {
673       GST_DEBUG ("stop scrub seek, PLAYING");
674       gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
675     }
676   }
677
678   return FALSE;
679 }
680
681 static void
682 play_cb (GtkButton * button, SeekApp * app)
683 {
684   GstStateChangeReturn ret;
685
686   if (app->state != GST_STATE_PLAYING) {
687     g_print ("PLAY pipeline\n");
688     gtk_statusbar_pop (GTK_STATUSBAR (app->statusbar), app->status_id);
689
690     if (app->pipeline_type == 0) {
691       video_sink_activate_cb (GTK_ENTRY (app->video_sink_entry), app);
692       audio_sink_activate_cb (GTK_ENTRY (app->audio_sink_entry), app);
693       text_sink_activate_cb (GTK_ENTRY (app->text_sink_entry), app);
694       buffer_size_activate_cb (GTK_ENTRY (app->buffer_size_entry), app);
695       buffer_duration_activate_cb (GTK_ENTRY (app->buffer_duration_entry), app);
696       ringbuffer_maxsize_activate_cb (GTK_ENTRY (app->ringbuffer_maxsize_entry),
697           app);
698       connection_speed_activate_cb (GTK_ENTRY (app->connection_speed_entry),
699           app);
700       av_offset_activate_cb (GTK_ENTRY (app->av_offset_entry), app);
701       subtitle_encoding_activate_cb (GTK_ENTRY (app->subtitle_encoding_entry),
702           app);
703     }
704
705     ret = gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
706     switch (ret) {
707       case GST_STATE_CHANGE_FAILURE:
708         goto failed;
709       case GST_STATE_CHANGE_NO_PREROLL:
710         app->is_live = TRUE;
711         break;
712       default:
713         break;
714     }
715     app->state = GST_STATE_PLAYING;
716     gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id,
717         "Playing");
718   }
719
720   return;
721
722 failed:
723   {
724     g_print ("PLAY failed\n");
725     gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id,
726         "Play failed");
727   }
728 }
729
730 static void
731 pause_cb (GtkButton * button, SeekApp * app)
732 {
733   g_static_mutex_lock (&app->state_mutex);
734   if (app->state != GST_STATE_PAUSED) {
735     GstStateChangeReturn ret;
736
737     gtk_statusbar_pop (GTK_STATUSBAR (app->statusbar), app->status_id);
738     g_print ("PAUSE pipeline\n");
739     ret = gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
740     switch (ret) {
741       case GST_STATE_CHANGE_FAILURE:
742         goto failed;
743       case GST_STATE_CHANGE_NO_PREROLL:
744         app->is_live = TRUE;
745         break;
746       default:
747         break;
748     }
749
750     app->state = GST_STATE_PAUSED;
751     gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id,
752         "Paused");
753   }
754   g_static_mutex_unlock (&app->state_mutex);
755
756   return;
757
758 failed:
759   {
760     g_static_mutex_unlock (&app->state_mutex);
761     g_print ("PAUSE failed\n");
762     gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id,
763         "Pause failed");
764   }
765 }
766
767 static void
768 stop_cb (GtkButton * button, SeekApp * app)
769 {
770   if (app->state != STOP_STATE) {
771     GstStateChangeReturn ret;
772     gint i;
773
774     g_print ("READY pipeline\n");
775     gtk_statusbar_pop (GTK_STATUSBAR (app->statusbar), app->status_id);
776
777     g_static_mutex_lock (&app->state_mutex);
778     ret = gst_element_set_state (app->pipeline, STOP_STATE);
779     if (ret == GST_STATE_CHANGE_FAILURE)
780       goto failed;
781
782     app->state = STOP_STATE;
783     gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id,
784         "Stopped");
785     gtk_widget_queue_draw (app->video_window);
786
787     app->is_live = FALSE;
788     app->buffering = FALSE;
789     set_update_scale (app, FALSE);
790     set_scale (app, 0.0);
791     set_update_fill (app, FALSE);
792
793     if (app->pipeline_type == 0)
794       clear_streams (app);
795     g_static_mutex_unlock (&app->state_mutex);
796
797     gtk_widget_set_sensitive (GTK_WIDGET (app->seek_scale), TRUE);
798     for (i = 0; i < G_N_ELEMENTS (app->navigation_buttons); i++)
799       gtk_widget_set_sensitive (app->navigation_buttons[i].button, FALSE);
800   }
801   return;
802
803 failed:
804   {
805     g_static_mutex_unlock (&app->state_mutex);
806     g_print ("STOP failed\n");
807     gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id,
808         "Stop failed");
809   }
810 }
811
812 static void
813 accurate_toggle_cb (GtkToggleButton * button, SeekApp * app)
814 {
815   app->accurate_seek = gtk_toggle_button_get_active (button);
816 }
817
818 static void
819 key_toggle_cb (GtkToggleButton * button, SeekApp * app)
820 {
821   app->keyframe_seek = gtk_toggle_button_get_active (button);
822 }
823
824 static void
825 loop_toggle_cb (GtkToggleButton * button, SeekApp * app)
826 {
827   app->loop_seek = gtk_toggle_button_get_active (button);
828   if (app->state == GST_STATE_PLAYING) {
829     gint64 real;
830
831     real =
832         gtk_range_get_value (GTK_RANGE (app->seek_scale)) * app->duration /
833         N_GRAD;
834     do_seek (app, GST_FORMAT_TIME, real);
835   }
836 }
837
838 static void
839 flush_toggle_cb (GtkToggleButton * button, SeekApp * app)
840 {
841   app->flush_seek = gtk_toggle_button_get_active (button);
842 }
843
844 static void
845 scrub_toggle_cb (GtkToggleButton * button, SeekApp * app)
846 {
847   app->scrub = gtk_toggle_button_get_active (button);
848 }
849
850 static void
851 play_scrub_toggle_cb (GtkToggleButton * button, SeekApp * app)
852 {
853   app->play_scrub = gtk_toggle_button_get_active (button);
854 }
855
856 static void
857 skip_toggle_cb (GtkToggleButton * button, SeekApp * app)
858 {
859   app->skip_seek = gtk_toggle_button_get_active (button);
860   if (app->state == GST_STATE_PLAYING) {
861     gint64 real;
862
863     real =
864         gtk_range_get_value (GTK_RANGE (app->seek_scale)) * app->duration /
865         N_GRAD;
866     do_seek (app, GST_FORMAT_TIME, real);
867   }
868 }
869
870 static void
871 rate_spinbutton_changed_cb (GtkSpinButton * button, SeekApp * app)
872 {
873   gboolean res = FALSE;
874   GstEvent *s_event;
875   GstSeekFlags flags;
876
877   app->rate = gtk_spin_button_get_value (button);
878
879   GST_DEBUG ("rate changed to %lf", app->rate);
880
881   flags = 0;
882   if (app->flush_seek)
883     flags |= GST_SEEK_FLAG_FLUSH;
884   if (app->loop_seek)
885     flags |= GST_SEEK_FLAG_SEGMENT;
886   if (app->accurate_seek)
887     flags |= GST_SEEK_FLAG_ACCURATE;
888   if (app->keyframe_seek)
889     flags |= GST_SEEK_FLAG_KEY_UNIT;
890   if (app->skip_seek)
891     flags |= GST_SEEK_FLAG_SKIP;
892
893   if (app->rate >= 0.0) {
894     s_event = gst_event_new_seek (app->rate,
895         GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, app->position,
896         GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
897   } else {
898     s_event = gst_event_new_seek (app->rate,
899         GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0),
900         GST_SEEK_TYPE_SET, app->position);
901   }
902
903   res = send_event (app, s_event);
904
905   if (res) {
906     if (app->flush_seek) {
907       gst_element_get_state (GST_ELEMENT (app->pipeline), NULL, NULL,
908           SEEK_TIMEOUT);
909     }
910   } else
911     g_print ("seek failed\n");
912 }
913
914 static void
915 update_flag (GstElement * pipeline, GstPlayFlags flag, gboolean state)
916 {
917   gint flags;
918
919   g_print ("%ssetting flag 0x%08x\n", (state ? "" : "un"), flag);
920
921   g_object_get (pipeline, "flags", &flags, NULL);
922   if (state)
923     flags |= flag;
924   else
925     flags &= ~(flag);
926   g_object_set (pipeline, "flags", flags, NULL);
927 }
928
929 static void
930 vis_toggle_cb (GtkToggleButton * button, SeekApp * app)
931 {
932   gboolean state;
933
934   state = gtk_toggle_button_get_active (button);
935   update_flag (app->pipeline, GST_PLAY_FLAG_VIS, state);
936   gtk_widget_set_sensitive (app->vis_combo, state);
937 }
938
939 static void
940 audio_toggle_cb (GtkToggleButton * button, SeekApp * app)
941 {
942   gboolean state;
943
944   state = gtk_toggle_button_get_active (button);
945   update_flag (app->pipeline, GST_PLAY_FLAG_AUDIO, state);
946   gtk_widget_set_sensitive (app->audio_combo, state);
947 }
948
949 static void
950 video_toggle_cb (GtkToggleButton * button, SeekApp * app)
951 {
952   gboolean state;
953
954   state = gtk_toggle_button_get_active (button);
955   update_flag (app->pipeline, GST_PLAY_FLAG_VIDEO, state);
956   gtk_widget_set_sensitive (app->video_combo, state);
957 }
958
959 static void
960 text_toggle_cb (GtkToggleButton * button, SeekApp * app)
961 {
962   gboolean state;
963
964   state = gtk_toggle_button_get_active (button);
965   update_flag (app->pipeline, GST_PLAY_FLAG_TEXT, state);
966   gtk_widget_set_sensitive (app->text_combo, state);
967 }
968
969 static void
970 mute_toggle_cb (GtkToggleButton * button, SeekApp * app)
971 {
972   gboolean mute;
973
974   mute = gtk_toggle_button_get_active (button);
975   g_object_set (app->pipeline, "mute", mute, NULL);
976 }
977
978 static void
979 download_toggle_cb (GtkToggleButton * button, SeekApp * app)
980 {
981   gboolean state;
982
983   state = gtk_toggle_button_get_active (button);
984   update_flag (app->pipeline, GST_PLAY_FLAG_DOWNLOAD, state);
985 }
986
987 static void
988 buffering_toggle_cb (GtkToggleButton * button, SeekApp * app)
989 {
990   gboolean state;
991
992   state = gtk_toggle_button_get_active (button);
993   update_flag (app->pipeline, GST_PLAY_FLAG_BUFFERING, state);
994 }
995
996 static void
997 soft_volume_toggle_cb (GtkToggleButton * button, SeekApp * app)
998 {
999   gboolean state;
1000
1001   state = gtk_toggle_button_get_active (button);
1002   update_flag (app->pipeline, GST_PLAY_FLAG_SOFT_VOLUME, state);
1003 }
1004
1005 static void
1006 native_audio_toggle_cb (GtkToggleButton * button, SeekApp * app)
1007 {
1008   gboolean state;
1009
1010   state = gtk_toggle_button_get_active (button);
1011   update_flag (app->pipeline, GST_PLAY_FLAG_NATIVE_AUDIO, state);
1012 }
1013
1014 static void
1015 native_video_toggle_cb (GtkToggleButton * button, SeekApp * app)
1016 {
1017   gboolean state;
1018
1019   state = gtk_toggle_button_get_active (button);
1020   update_flag (app->pipeline, GST_PLAY_FLAG_NATIVE_VIDEO, state);
1021 }
1022
1023 static void
1024 deinterlace_toggle_cb (GtkToggleButton * button, SeekApp * app)
1025 {
1026   gboolean state;
1027
1028   state = gtk_toggle_button_get_active (button);
1029   update_flag (app->pipeline, GST_PLAY_FLAG_DEINTERLACE, state);
1030 }
1031
1032 static void
1033 soft_colorbalance_toggle_cb (GtkToggleButton * button, SeekApp * app)
1034 {
1035   gboolean state;
1036
1037   state = gtk_toggle_button_get_active (button);
1038   update_flag (app->pipeline, GST_PLAY_FLAG_SOFT_COLORBALANCE, state);
1039 }
1040
1041 static void
1042 clear_streams (SeekApp * app)
1043 {
1044   gint i;
1045
1046   /* remove previous info */
1047   for (i = 0; i < app->n_video; i++)
1048     gtk_combo_box_text_remove (GTK_COMBO_BOX_TEXT (app->video_combo), 0);
1049   for (i = 0; i < app->n_audio; i++)
1050     gtk_combo_box_text_remove (GTK_COMBO_BOX_TEXT (app->audio_combo), 0);
1051   for (i = 0; i < app->n_text; i++)
1052     gtk_combo_box_text_remove (GTK_COMBO_BOX_TEXT (app->text_combo), 0);
1053
1054   app->n_audio = app->n_video = app->n_text = 0;
1055   gtk_widget_set_sensitive (app->video_combo, FALSE);
1056   gtk_widget_set_sensitive (app->audio_combo, FALSE);
1057   gtk_widget_set_sensitive (app->text_combo, FALSE);
1058
1059   app->need_streams = TRUE;
1060 }
1061
1062 static void
1063 update_streams (SeekApp * app)
1064 {
1065   gint i;
1066
1067   if (app->pipeline_type == 0 && app->need_streams) {
1068     GstTagList *tags;
1069     gchar *name, *str;
1070     gint active_idx;
1071     gboolean state;
1072
1073     /* remove previous info */
1074     clear_streams (app);
1075
1076     /* here we get and update the different streams detected by playbin2 */
1077     g_object_get (app->pipeline, "n-video", &app->n_video, NULL);
1078     g_object_get (app->pipeline, "n-audio", &app->n_audio, NULL);
1079     g_object_get (app->pipeline, "n-text", &app->n_text, NULL);
1080
1081     g_print ("video %d, audio %d, text %d\n", app->n_video, app->n_audio,
1082         app->n_text);
1083
1084     active_idx = 0;
1085     for (i = 0; i < app->n_video; i++) {
1086       g_signal_emit_by_name (app->pipeline, "get-video-tags", i, &tags);
1087       if (tags) {
1088         str = gst_structure_to_string ((GstStructure *) tags);
1089         g_print ("video %d: %s\n", i, str);
1090         g_free (str);
1091       }
1092       /* find good name for the label */
1093       name = g_strdup_printf ("video %d", i + 1);
1094       gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (app->video_combo),
1095           name);
1096       g_free (name);
1097     }
1098     state =
1099         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (app->video_checkbox));
1100     gtk_widget_set_sensitive (app->video_combo, state && app->n_video > 0);
1101     gtk_combo_box_set_active (GTK_COMBO_BOX (app->video_combo), active_idx);
1102
1103     active_idx = 0;
1104     for (i = 0; i < app->n_audio; i++) {
1105       g_signal_emit_by_name (app->pipeline, "get-audio-tags", i, &tags);
1106       if (tags) {
1107         str = gst_structure_to_string ((GstStructure *) tags);
1108         g_print ("audio %d: %s\n", i, str);
1109         g_free (str);
1110       }
1111       /* find good name for the label */
1112       name = g_strdup_printf ("audio %d", i + 1);
1113       gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (app->audio_combo),
1114           name);
1115       g_free (name);
1116     }
1117     state =
1118         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (app->audio_checkbox));
1119     gtk_widget_set_sensitive (app->audio_combo, state && app->n_audio > 0);
1120     gtk_combo_box_set_active (GTK_COMBO_BOX (app->audio_combo), active_idx);
1121
1122     active_idx = 0;
1123     for (i = 0; i < app->n_text; i++) {
1124       g_signal_emit_by_name (app->pipeline, "get-text-tags", i, &tags);
1125
1126       name = NULL;
1127       if (tags) {
1128         const GValue *value;
1129
1130         str = gst_structure_to_string ((GstStructure *) tags);
1131         g_print ("text %d: %s\n", i, str);
1132         g_free (str);
1133
1134         /* get the language code if we can */
1135         value = gst_tag_list_get_value_index (tags, GST_TAG_LANGUAGE_CODE, 0);
1136         if (value && G_VALUE_HOLDS_STRING (value)) {
1137           name = g_strdup_printf ("text %s", g_value_get_string (value));
1138         }
1139       }
1140       /* find good name for the label if we didn't use a tag */
1141       if (name == NULL)
1142         name = g_strdup_printf ("text %d", i + 1);
1143
1144       gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (app->text_combo),
1145           name);
1146       g_free (name);
1147     }
1148     state =
1149         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (app->text_checkbox));
1150     gtk_widget_set_sensitive (app->text_combo, state && app->n_text > 0);
1151     gtk_combo_box_set_active (GTK_COMBO_BOX (app->text_combo), active_idx);
1152
1153     app->need_streams = FALSE;
1154   }
1155 }
1156
1157 static void
1158 video_combo_cb (GtkComboBox * combo, SeekApp * app)
1159 {
1160   gint active;
1161
1162   active = gtk_combo_box_get_active (combo);
1163
1164   g_print ("setting current video track %d\n", active);
1165   g_object_set (app->pipeline, "current-video", active, NULL);
1166 }
1167
1168 static void
1169 audio_combo_cb (GtkComboBox * combo, SeekApp * app)
1170 {
1171   gint active;
1172
1173   active = gtk_combo_box_get_active (combo);
1174
1175   g_print ("setting current audio track %d\n", active);
1176   g_object_set (app->pipeline, "current-audio", active, NULL);
1177 }
1178
1179 static void
1180 text_combo_cb (GtkComboBox * combo, SeekApp * app)
1181 {
1182   gint active;
1183
1184   active = gtk_combo_box_get_active (combo);
1185
1186   g_print ("setting current text track %d\n", active);
1187   g_object_set (app->pipeline, "current-text", active, NULL);
1188 }
1189
1190 static gboolean
1191 filter_vis_features (GstPluginFeature * feature, gpointer data)
1192 {
1193   GstElementFactory *f;
1194
1195   if (!GST_IS_ELEMENT_FACTORY (feature))
1196     return FALSE;
1197   f = GST_ELEMENT_FACTORY (feature);
1198   if (!g_strrstr (gst_element_factory_get_klass (f), "Visualization"))
1199     return FALSE;
1200
1201   return TRUE;
1202 }
1203
1204 static void
1205 init_visualization_features (SeekApp * app)
1206 {
1207   GList *list, *walk;
1208
1209   app->vis_entries = g_array_new (FALSE, FALSE, sizeof (VisEntry));
1210
1211   list = gst_registry_feature_filter (gst_registry_get_default (),
1212       filter_vis_features, FALSE, NULL);
1213
1214   for (walk = list; walk; walk = g_list_next (walk)) {
1215     VisEntry entry;
1216     const gchar *name;
1217
1218     entry.factory = GST_ELEMENT_FACTORY (walk->data);
1219     name = gst_element_factory_get_longname (entry.factory);
1220
1221     g_array_append_val (app->vis_entries, entry);
1222     gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (app->vis_combo), name);
1223   }
1224   gtk_combo_box_set_active (GTK_COMBO_BOX (app->vis_combo), 0);
1225 }
1226
1227 static void
1228 vis_combo_cb (GtkComboBox * combo, SeekApp * app)
1229 {
1230   guint index;
1231   VisEntry *entry;
1232   GstElement *element;
1233
1234   /* get the selected index and get the factory for this index */
1235   index = gtk_combo_box_get_active (GTK_COMBO_BOX (app->vis_combo));
1236   if (app->vis_entries->len > 0) {
1237     entry = &g_array_index (app->vis_entries, VisEntry, index);
1238
1239     /* create an instance of the element from the factory */
1240     element = gst_element_factory_create (entry->factory, NULL);
1241     if (!element)
1242       return;
1243
1244     /* set vis plugin for playbin2 */
1245     g_object_set (app->pipeline, "vis-plugin", element, NULL);
1246   }
1247 }
1248
1249 static void
1250 volume_spinbutton_changed_cb (GtkSpinButton * button, SeekApp * app)
1251 {
1252   gdouble volume;
1253
1254   volume = gtk_spin_button_get_value (button);
1255
1256   g_object_set (app->pipeline, "volume", volume, NULL);
1257 }
1258
1259 static gboolean
1260 volume_notify_idle_cb (SeekApp * app)
1261 {
1262   gdouble cur_volume, new_volume;
1263
1264   g_object_get (app->pipeline, "volume", &new_volume, NULL);
1265   cur_volume =
1266       gtk_spin_button_get_value (GTK_SPIN_BUTTON (app->volume_spinbutton));
1267   if (fabs (cur_volume - new_volume) > 0.001) {
1268     g_signal_handlers_block_by_func (app->volume_spinbutton,
1269         volume_spinbutton_changed_cb, app);
1270     gtk_spin_button_set_value (GTK_SPIN_BUTTON (app->volume_spinbutton),
1271         new_volume);
1272     g_signal_handlers_unblock_by_func (app->volume_spinbutton,
1273         volume_spinbutton_changed_cb, app);
1274   }
1275
1276   return FALSE;
1277 }
1278
1279 static void
1280 volume_notify_cb (GstElement * pipeline, GParamSpec * arg, SeekApp * app)
1281 {
1282   /* Do this from the main thread */
1283   g_idle_add ((GSourceFunc) volume_notify_idle_cb, app);
1284 }
1285
1286 static gboolean
1287 mute_notify_idle_cb (SeekApp * app)
1288 {
1289   gboolean cur_mute, new_mute;
1290
1291   g_object_get (app->pipeline, "mute", &new_mute, NULL);
1292   cur_mute =
1293       gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (app->mute_checkbox));
1294   if (cur_mute != new_mute) {
1295     g_signal_handlers_block_by_func (app->mute_checkbox, mute_toggle_cb, app);
1296     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->mute_checkbox),
1297         new_mute);
1298     g_signal_handlers_unblock_by_func (app->mute_checkbox, mute_toggle_cb, app);
1299   }
1300
1301   return FALSE;
1302 }
1303
1304 static void
1305 mute_notify_cb (GstElement * pipeline, GParamSpec * arg, SeekApp * app)
1306 {
1307   /* Do this from the main thread */
1308   g_idle_add ((GSourceFunc) mute_notify_idle_cb, app);
1309 }
1310
1311 static void
1312 shot_cb (GtkButton * button, SeekApp * app)
1313 {
1314   GstBuffer *buffer;
1315   GstCaps *caps;
1316
1317   /* convert to our desired format (RGB24) */
1318   caps = gst_caps_new_simple ("video/x-raw-rgb",
1319       "bpp", G_TYPE_INT, 24, "depth", G_TYPE_INT, 24,
1320       /* Note: we don't ask for a specific width/height here, so that
1321        * videoscale can adjust dimensions from a non-1/1 pixel aspect
1322        * ratio to a 1/1 pixel-aspect-ratio */
1323       "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
1324       "endianness", G_TYPE_INT, G_BIG_ENDIAN,
1325       "red_mask", G_TYPE_INT, 0xff0000,
1326       "green_mask", G_TYPE_INT, 0x00ff00,
1327       "blue_mask", G_TYPE_INT, 0x0000ff, NULL);
1328
1329   /* convert the latest frame to the requested format */
1330   g_signal_emit_by_name (app->pipeline, "convert-frame", caps, &buffer);
1331   gst_caps_unref (caps);
1332
1333   if (buffer) {
1334     GstCaps *caps;
1335     GstStructure *s;
1336     gboolean res;
1337     gint width, height;
1338     GdkPixbuf *pixbuf;
1339     GError *error = NULL;
1340
1341     /* get the snapshot buffer format now. We set the caps on the appsink so
1342      * that it can only be an rgb buffer. The only thing we have not specified
1343      * on the caps is the height, which is dependant on the pixel-aspect-ratio
1344      * of the source material */
1345     caps = GST_BUFFER_CAPS (buffer);
1346     if (!caps) {
1347       g_warning ("could not get snapshot format\n");
1348       goto done;
1349     }
1350     s = gst_caps_get_structure (caps, 0);
1351
1352     /* we need to get the final caps on the buffer to get the size */
1353     res = gst_structure_get_int (s, "width", &width);
1354     res |= gst_structure_get_int (s, "height", &height);
1355     if (!res) {
1356       g_warning ("could not get snapshot dimension\n");
1357       goto done;
1358     }
1359
1360     /* create pixmap from buffer and save, gstreamer video buffers have a stride
1361      * that is rounded up to the nearest multiple of 4 */
1362     pixbuf = gdk_pixbuf_new_from_data (GST_BUFFER_DATA (buffer),
1363         GDK_COLORSPACE_RGB, FALSE, 8, width, height,
1364         GST_ROUND_UP_4 (width * 3), NULL, NULL);
1365
1366     /* save the pixbuf */
1367     gdk_pixbuf_save (pixbuf, "snapshot.png", "png", &error, NULL);
1368
1369   done:
1370     gst_buffer_unref (buffer);
1371   }
1372 }
1373
1374 /* called when the Step button is pressed */
1375 static void
1376 step_cb (GtkButton * button, SeekApp * app)
1377 {
1378   GstEvent *event;
1379   GstFormat format;
1380   guint64 amount;
1381   gdouble rate;
1382   gboolean flush, res;
1383   gint active;
1384
1385   active = gtk_combo_box_get_active (GTK_COMBO_BOX (app->step_format_combo));
1386   amount =
1387       gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON
1388       (app->step_amount_spinbutton));
1389   rate =
1390       gtk_spin_button_get_value (GTK_SPIN_BUTTON (app->step_rate_spinbutton));
1391   flush = TRUE;
1392
1393   switch (active) {
1394     case 0:
1395       format = GST_FORMAT_BUFFERS;
1396       break;
1397     case 1:
1398       format = GST_FORMAT_TIME;
1399       amount *= GST_MSECOND;
1400       break;
1401     default:
1402       format = GST_FORMAT_UNDEFINED;
1403       break;
1404   }
1405
1406   event = gst_event_new_step (format, amount, rate, flush, FALSE);
1407
1408   res = send_event (app, event);
1409
1410   if (!res) {
1411     g_print ("Sending step event failed\n");
1412   }
1413 }
1414
1415 static void
1416 message_received (GstBus * bus, GstMessage * message, SeekApp * app)
1417 {
1418   const GstStructure *s;
1419
1420   switch (GST_MESSAGE_TYPE (message)) {
1421     case GST_MESSAGE_ERROR:
1422       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (app->pipeline),
1423           GST_DEBUG_GRAPH_SHOW_ALL, "seek.error");
1424       break;
1425     case GST_MESSAGE_WARNING:
1426       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (app->pipeline),
1427           GST_DEBUG_GRAPH_SHOW_ALL, "seek.warning");
1428       break;
1429     default:
1430       break;
1431   }
1432
1433   s = gst_message_get_structure (message);
1434   g_print ("message from \"%s\" (%s): ",
1435       GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message))),
1436       gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
1437   if (s) {
1438     gchar *sstr;
1439
1440     sstr = gst_structure_to_string (s);
1441     g_print ("%s\n", sstr);
1442     g_free (sstr);
1443   } else {
1444     g_print ("no message details\n");
1445   }
1446 }
1447
1448 static void
1449 do_shuttle (SeekApp * app)
1450 {
1451   guint64 duration;
1452
1453   if (app->shuttling)
1454     duration = 40 * GST_MSECOND;
1455   else
1456     duration = -1;
1457
1458   gst_element_send_event (app->pipeline,
1459       gst_event_new_step (GST_FORMAT_TIME, duration, app->shuttle_rate, FALSE,
1460           FALSE));
1461 }
1462
1463 static void
1464 msg_sync_step_done (GstBus * bus, GstMessage * message, SeekApp * app)
1465 {
1466   GstFormat format;
1467   guint64 amount;
1468   gdouble rate;
1469   gboolean flush;
1470   gboolean intermediate;
1471   guint64 duration;
1472   gboolean eos;
1473
1474   gst_message_parse_step_done (message, &format, &amount, &rate, &flush,
1475       &intermediate, &duration, &eos);
1476
1477   if (eos) {
1478     g_print ("stepped till EOS\n");
1479     return;
1480   }
1481
1482   if (g_static_mutex_trylock (&app->state_mutex)) {
1483     if (app->shuttling)
1484       do_shuttle (app);
1485     g_static_mutex_unlock (&app->state_mutex);
1486   } else {
1487     /* ignore step messages that come while we are doing a state change */
1488     g_print ("state change is busy\n");
1489   }
1490 }
1491
1492 static void
1493 shuttle_toggled (GtkToggleButton * button, SeekApp * app)
1494 {
1495   gboolean active;
1496
1497   active = gtk_toggle_button_get_active (button);
1498
1499   if (active != app->shuttling) {
1500     app->shuttling = active;
1501     g_print ("shuttling %s\n", app->shuttling ? "active" : "inactive");
1502     if (active) {
1503       app->shuttle_rate = 0.0;
1504       app->play_rate = 1.0;
1505       pause_cb (NULL, app);
1506       gst_element_get_state (app->pipeline, NULL, NULL, -1);
1507     }
1508   }
1509 }
1510
1511 static void
1512 shuttle_rate_switch (SeekApp * app)
1513 {
1514   GstSeekFlags flags;
1515   GstEvent *s_event;
1516   gboolean res;
1517
1518   if (app->state == GST_STATE_PLAYING) {
1519     /* pause when we need to */
1520     pause_cb (NULL, app);
1521     gst_element_get_state (app->pipeline, NULL, NULL, -1);
1522   }
1523
1524   if (app->play_rate == 1.0)
1525     app->play_rate = -1.0;
1526   else
1527     app->play_rate = 1.0;
1528
1529   g_print ("rate changed to %lf %" GST_TIME_FORMAT "\n", app->play_rate,
1530       GST_TIME_ARGS (app->position));
1531
1532   flags = GST_SEEK_FLAG_FLUSH;
1533   flags |= GST_SEEK_FLAG_ACCURATE;
1534
1535   if (app->play_rate >= 0.0) {
1536     s_event = gst_event_new_seek (app->play_rate,
1537         GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, app->position,
1538         GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
1539   } else {
1540     s_event = gst_event_new_seek (app->play_rate,
1541         GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0),
1542         GST_SEEK_TYPE_SET, app->position);
1543   }
1544   res = send_event (app, s_event);
1545   if (res) {
1546     gst_element_get_state (app->pipeline, NULL, NULL, SEEK_TIMEOUT);
1547   } else {
1548     g_print ("seek failed\n");
1549   }
1550 }
1551
1552 static void
1553 shuttle_value_changed (GtkRange * range, SeekApp * app)
1554 {
1555   gdouble rate;
1556
1557   rate = gtk_range_get_value (range);
1558
1559   if (rate == 0.0) {
1560     g_print ("rate 0.0, pause\n");
1561     pause_cb (NULL, app);
1562     gst_element_get_state (app->pipeline, NULL, NULL, -1);
1563   } else {
1564     g_print ("rate changed %0.3g\n", rate);
1565
1566     if ((rate < 0.0 && app->play_rate > 0.0) || (rate > 0.0
1567             && app->play_rate < 0.0)) {
1568       shuttle_rate_switch (app);
1569     }
1570
1571     app->shuttle_rate = ABS (rate);
1572     if (app->state != GST_STATE_PLAYING) {
1573       do_shuttle (app);
1574       play_cb (NULL, app);
1575     }
1576   }
1577 }
1578
1579 static void
1580 colorbalance_value_changed (GtkRange * range, SeekApp * app)
1581 {
1582   const gchar *label;
1583   gdouble val;
1584   gint ival;
1585   GstColorBalanceChannel *channel = NULL;
1586   const GList *channels, *l;
1587
1588   if (range == GTK_RANGE (app->contrast_scale))
1589     label = "CONTRAST";
1590   else if (range == GTK_RANGE (app->brightness_scale))
1591     label = "BRIGHTNESS";
1592   else if (range == GTK_RANGE (app->hue_scale))
1593     label = "HUE";
1594   else if (range == GTK_RANGE (app->saturation_scale))
1595     label = "SATURATION";
1596   else
1597     g_assert_not_reached ();
1598
1599   val = gtk_range_get_value (range);
1600
1601   g_print ("colorbalance %s value changed %lf\n", label, val / N_GRAD);
1602
1603   if (!app->colorbalance_element) {
1604     find_interface_elements (app);
1605     if (!app->colorbalance_element)
1606       return;
1607   }
1608
1609   channels =
1610       gst_color_balance_list_channels (GST_COLOR_BALANCE
1611       (app->colorbalance_element));
1612   for (l = channels; l; l = l->next) {
1613     GstColorBalanceChannel *tmp = l->data;
1614
1615     if (g_strrstr (tmp->label, label)) {
1616       channel = tmp;
1617       break;
1618     }
1619   }
1620
1621   if (!channel)
1622     return;
1623
1624   ival =
1625       (gint) (0.5 + channel->min_value +
1626       (val / N_GRAD) * ((gdouble) channel->max_value -
1627           (gdouble) channel->min_value));
1628   gst_color_balance_set_value (GST_COLOR_BALANCE (app->colorbalance_element),
1629       channel, ival);
1630 }
1631
1632 static void
1633 seek_format_changed_cb (GtkComboBox * box, SeekApp * app)
1634 {
1635   gchar *format_str;
1636   GList *l;
1637   const GstFormatDefinition *format = NULL;
1638
1639   format_str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (box));
1640
1641   for (l = app->formats; l; l = l->next) {
1642     const GstFormatDefinition *tmp = l->data;
1643
1644     if (g_strcmp0 (tmp->nick, format_str) == 0) {
1645       format = tmp;
1646       break;
1647     }
1648   }
1649
1650   if (!format)
1651     goto done;
1652
1653   app->seek_format = format;
1654   update_scale (app);
1655
1656 done:
1657   g_free (format_str);
1658 }
1659
1660 static void
1661 update_formats (SeekApp * app)
1662 {
1663   GstIterator *it;
1664   gboolean done;
1665   GList *l;
1666   gpointer item;
1667   gchar *selected;
1668   gint selected_idx = 0, i;
1669
1670   selected =
1671       gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT
1672       (app->seek_format_combo));
1673   if (selected == NULL)
1674     selected = g_strdup ("time");
1675
1676   it = gst_format_iterate_definitions ();
1677   done = FALSE;
1678
1679   g_list_free (app->formats);
1680   app->formats = NULL;
1681
1682   while (!done) {
1683     switch (gst_iterator_next (it, &item)) {
1684       case GST_ITERATOR_OK:
1685         app->formats = g_list_prepend (app->formats, item);
1686         break;
1687       case GST_ITERATOR_RESYNC:
1688         g_list_free (app->formats);
1689         app->formats = NULL;
1690         gst_iterator_resync (it);
1691         break;
1692       case GST_ITERATOR_ERROR:
1693       case GST_ITERATOR_DONE:
1694       default:
1695         done = TRUE;
1696         break;
1697     }
1698   }
1699
1700   app->formats = g_list_reverse (app->formats);
1701   gst_iterator_free (it);
1702
1703   g_signal_handlers_block_by_func (app->seek_format_combo,
1704       seek_format_changed_cb, app);
1705   gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT (app->seek_format_combo));
1706
1707   for (i = 0, l = app->formats; l; l = l->next, i++) {
1708     const GstFormatDefinition *def = l->data;
1709
1710     gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (app->seek_format_combo),
1711         def->nick);
1712     if (g_strcmp0 (def->nick, selected) == 0)
1713       selected_idx = i;
1714   }
1715   g_signal_handlers_unblock_by_func (app->seek_format_combo,
1716       seek_format_changed_cb, app);
1717
1718   gtk_combo_box_set_active (GTK_COMBO_BOX (app->seek_format_combo),
1719       selected_idx);
1720
1721   g_free (selected);
1722 }
1723
1724 static void
1725 msg_async_done (GstBus * bus, GstMessage * message, SeekApp * app)
1726 {
1727   GST_DEBUG ("async done");
1728
1729   /* Now query all available GstFormats */
1730   update_formats (app);
1731
1732   /* when we get ASYNC_DONE we can query position, duration and other
1733    * properties */
1734   update_scale (app);
1735
1736   /* update the available streams */
1737   update_streams (app);
1738
1739   find_interface_elements (app);
1740 }
1741
1742 static void
1743 msg_state_changed (GstBus * bus, GstMessage * message, SeekApp * app)
1744 {
1745   const GstStructure *s;
1746
1747   s = gst_message_get_structure (message);
1748
1749   /* We only care about state changed on the pipeline */
1750   if (s && GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (app->pipeline)) {
1751     GstState old, new, pending;
1752
1753     gst_message_parse_state_changed (message, &old, &new, &pending);
1754
1755     /* When state of the pipeline changes to paused or playing we start updating scale */
1756     if (new == GST_STATE_PLAYING) {
1757       set_update_scale (app, TRUE);
1758     } else {
1759       set_update_scale (app, FALSE);
1760     }
1761
1762     /* dump graph for (some) pipeline state changes */
1763     {
1764       gchar *dump_name;
1765
1766       dump_name = g_strdup_printf ("seek.%s_%s",
1767           gst_element_state_get_name (old), gst_element_state_get_name (new));
1768
1769       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (app->pipeline),
1770           GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
1771
1772       g_free (dump_name);
1773     }
1774   }
1775 }
1776
1777 static void
1778 msg_segment_done (GstBus * bus, GstMessage * message, SeekApp * app)
1779 {
1780   GstEvent *s_event;
1781   GstSeekFlags flags;
1782   gboolean res;
1783   GstFormat format;
1784
1785   GST_DEBUG ("position is %" GST_TIME_FORMAT, GST_TIME_ARGS (app->position));
1786   gst_message_parse_segment_done (message, &format, &app->position);
1787   GST_DEBUG ("end of segment at %" GST_TIME_FORMAT,
1788       GST_TIME_ARGS (app->position));
1789
1790   flags = 0;
1791   /* in the segment-done callback we never flush as this would not make sense
1792    * for seamless playback. */
1793   if (app->loop_seek)
1794     flags |= GST_SEEK_FLAG_SEGMENT;
1795   if (app->skip_seek)
1796     flags |= GST_SEEK_FLAG_SKIP;
1797
1798   s_event = gst_event_new_seek (app->rate,
1799       GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0),
1800       GST_SEEK_TYPE_SET, app->duration);
1801
1802   GST_DEBUG ("restart loop with rate %lf to 0 / %" GST_TIME_FORMAT,
1803       app->rate, GST_TIME_ARGS (app->duration));
1804
1805   res = send_event (app, s_event);
1806   if (!res)
1807     g_print ("segment seek failed\n");
1808 }
1809
1810 /* in stream buffering mode we PAUSE the pipeline until we receive a 100%
1811  * message */
1812 static void
1813 do_stream_buffering (SeekApp * app, gint percent)
1814 {
1815   gchar *bufstr;
1816
1817   gtk_statusbar_pop (GTK_STATUSBAR (app->statusbar), app->status_id);
1818   bufstr = g_strdup_printf ("Buffering...%d", percent);
1819   gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id, bufstr);
1820   g_free (bufstr);
1821
1822   if (percent == 100) {
1823     /* a 100% message means buffering is done */
1824     app->buffering = FALSE;
1825     /* if the desired state is playing, go back */
1826     if (app->state == GST_STATE_PLAYING) {
1827       /* no state management needed for live pipelines */
1828       if (!app->is_live) {
1829         fprintf (stderr, "Done buffering, setting pipeline to PLAYING ...\n");
1830         gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
1831       }
1832       gtk_statusbar_pop (GTK_STATUSBAR (app->statusbar), app->status_id);
1833       gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id,
1834           "Playing");
1835     }
1836   } else {
1837     /* buffering busy */
1838     if (app->buffering == FALSE && app->state == GST_STATE_PLAYING) {
1839       /* we were not buffering but PLAYING, PAUSE  the pipeline. */
1840       if (!app->is_live) {
1841         fprintf (stderr, "Buffering, setting pipeline to PAUSED ...\n");
1842         gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
1843       }
1844     }
1845     app->buffering = TRUE;
1846   }
1847 }
1848
1849 static void
1850 do_download_buffering (SeekApp * app, gint percent)
1851 {
1852   if (!app->buffering && percent < 100) {
1853     gchar *bufstr;
1854
1855     app->buffering = TRUE;
1856
1857     bufstr = g_strdup_printf ("Downloading...");
1858     gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id, bufstr);
1859     g_free (bufstr);
1860
1861     /* once we get a buffering message, we'll do the fill update */
1862     set_update_fill (app, TRUE);
1863
1864     if (app->state == GST_STATE_PLAYING && !app->is_live) {
1865       fprintf (stderr, "Downloading, setting pipeline to PAUSED ...\n");
1866       gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
1867       /* user has to manually start the playback */
1868       app->state = GST_STATE_PAUSED;
1869     }
1870   }
1871 }
1872
1873 static void
1874 msg_buffering (GstBus * bus, GstMessage * message, SeekApp * app)
1875 {
1876   gint percent;
1877
1878   gst_message_parse_buffering (message, &percent);
1879
1880   /* get more stats */
1881   gst_message_parse_buffering_stats (message, &app->mode, NULL, NULL,
1882       &app->buffering_left);
1883
1884   switch (app->mode) {
1885     case GST_BUFFERING_DOWNLOAD:
1886       do_download_buffering (app, percent);
1887       break;
1888     case GST_BUFFERING_LIVE:
1889       app->is_live = TRUE;
1890     case GST_BUFFERING_TIMESHIFT:
1891     case GST_BUFFERING_STREAM:
1892       do_stream_buffering (app, percent);
1893       break;
1894   }
1895 }
1896
1897 static void
1898 msg_clock_lost (GstBus * bus, GstMessage * message, SeekApp * app)
1899 {
1900   g_print ("clock lost! PAUSE and PLAY to select a new clock\n");
1901   if (app->state == GST_STATE_PLAYING) {
1902     gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
1903     gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
1904   }
1905 }
1906
1907 static gboolean
1908 is_valid_color_balance_element (GstElement * element)
1909 {
1910   GstColorBalance *bal = GST_COLOR_BALANCE (element);
1911   gboolean have_brightness = FALSE;
1912   gboolean have_contrast = FALSE;
1913   gboolean have_hue = FALSE;
1914   gboolean have_saturation = FALSE;
1915   const GList *channels, *l;
1916
1917   channels = gst_color_balance_list_channels (bal);
1918   for (l = channels; l; l = l->next) {
1919     GstColorBalanceChannel *ch = l->data;
1920
1921     if (g_strrstr (ch->label, "BRIGHTNESS"))
1922       have_brightness = TRUE;
1923     else if (g_strrstr (ch->label, "CONTRAST"))
1924       have_contrast = TRUE;
1925     else if (g_strrstr (ch->label, "HUE"))
1926       have_hue = TRUE;
1927     else if (g_strrstr (ch->label, "SATURATION"))
1928       have_saturation = TRUE;
1929   }
1930
1931   return have_brightness && have_contrast && have_hue && have_saturation;
1932 }
1933
1934 static void
1935 find_interface_elements (SeekApp * app)
1936 {
1937   GstIterator *it;
1938   gpointer item;
1939   gboolean done = FALSE, hardware = FALSE;
1940
1941   if (app->pipeline_type == 0)
1942     return;
1943
1944   if (app->navigation_element)
1945     gst_object_unref (app->navigation_element);
1946   app->navigation_element = NULL;
1947
1948   if (app->colorbalance_element)
1949     gst_object_unref (app->colorbalance_element);
1950   app->colorbalance_element = NULL;
1951
1952   app->navigation_element =
1953       gst_bin_get_by_interface (GST_BIN (app->pipeline), GST_TYPE_NAVIGATION);
1954
1955   it = gst_bin_iterate_all_by_interface (GST_BIN (app->pipeline),
1956       GST_TYPE_COLOR_BALANCE);
1957   while (!done) {
1958     switch (gst_iterator_next (it, &item)) {
1959       case GST_ITERATOR_OK:{
1960         GstElement *element = GST_ELEMENT (item);
1961
1962         if (is_valid_color_balance_element (element)) {
1963           if (!app->colorbalance_element) {
1964             app->colorbalance_element =
1965                 GST_ELEMENT_CAST (gst_object_ref (element));
1966             hardware =
1967                 (gst_color_balance_get_balance_type (GST_COLOR_BALANCE
1968                     (element)) == GST_COLOR_BALANCE_HARDWARE);
1969           } else if (!hardware) {
1970             gboolean tmp =
1971                 (gst_color_balance_get_balance_type (GST_COLOR_BALANCE
1972                     (element)) == GST_COLOR_BALANCE_HARDWARE);
1973
1974             if (tmp) {
1975               if (app->colorbalance_element)
1976                 gst_object_unref (app->colorbalance_element);
1977               app->colorbalance_element =
1978                   GST_ELEMENT_CAST (gst_object_ref (element));
1979               hardware = TRUE;
1980             }
1981           }
1982         }
1983
1984         gst_object_unref (element);
1985
1986         if (hardware && app->colorbalance_element)
1987           done = TRUE;
1988         break;
1989       }
1990       case GST_ITERATOR_RESYNC:
1991         gst_iterator_resync (it);
1992         done = FALSE;
1993         hardware = FALSE;
1994         if (app->colorbalance_element)
1995           gst_object_unref (app->colorbalance_element);
1996         app->colorbalance_element = NULL;
1997         break;
1998       case GST_ITERATOR_DONE:
1999       case GST_ITERATOR_ERROR:
2000       default:
2001         done = TRUE;
2002     }
2003   }
2004
2005   gst_iterator_free (it);
2006 }
2007
2008 /* called when Navigation command button is pressed */
2009 static void
2010 navigation_cmd_cb (GtkButton * button, SeekApp * app)
2011 {
2012   GstNavigationCommand cmd = GST_NAVIGATION_COMMAND_INVALID;
2013   gint i;
2014
2015   if (!app->navigation_element) {
2016     find_interface_elements (app);
2017     if (!app->navigation_element)
2018       return;
2019   }
2020
2021   for (i = 0; i < G_N_ELEMENTS (app->navigation_buttons); i++) {
2022     if (app->navigation_buttons[i].button == GTK_WIDGET (button)) {
2023       cmd = app->navigation_buttons[i].cmd;
2024       break;
2025     }
2026   }
2027
2028   if (cmd != GST_NAVIGATION_COMMAND_INVALID)
2029     gst_navigation_send_command (GST_NAVIGATION (app->navigation_element), cmd);
2030 }
2031
2032 #if defined (GDK_WINDOWING_X11) || defined (GDK_WINDOWING_WIN32) || defined (GDK_WINDOWING_QUARTZ)
2033 /* We set the xid here in response to the prepare-xwindow-id message via a
2034  * bus sync handler because we don't know the actual videosink used from the
2035  * start (as we don't know the pipeline, or bin elements such as autovideosink
2036  * or gconfvideosink may be used which create the actual videosink only once
2037  * the pipeline is started) */
2038 static GstBusSyncReply
2039 bus_sync_handler (GstBus * bus, GstMessage * message, SeekApp * app)
2040 {
2041   if ((GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT) &&
2042       gst_structure_has_name (message->structure, "prepare-xwindow-id")) {
2043     GstElement *element = GST_ELEMENT (GST_MESSAGE_SRC (message));
2044
2045     if (app->xoverlay_element)
2046       gst_object_unref (app->xoverlay_element);
2047     app->xoverlay_element = GST_ELEMENT (gst_object_ref (element));
2048
2049     g_print ("got prepare-xwindow-id, setting XID %" G_GUINTPTR_FORMAT "\n",
2050         app->embed_xid);
2051
2052     if (g_object_class_find_property (G_OBJECT_GET_CLASS (element),
2053             "force-aspect-ratio")) {
2054       g_object_set (element, "force-aspect-ratio", TRUE, NULL);
2055     }
2056
2057     /* Should have been initialised from main thread before (can't use
2058      * GDK_WINDOW_XID here with Gtk+ >= 2.18, because the sync handler will
2059      * be called from a streaming thread and GDK_WINDOW_XID maps to more than
2060      * a simple structure lookup with Gtk+ >= 2.18, where 'more' is stuff that
2061      * shouldn't be done from a non-GUI thread without explicit locking).  */
2062     g_assert (app->embed_xid != 0);
2063
2064     gst_x_overlay_set_window_handle (GST_X_OVERLAY (element), app->embed_xid);
2065
2066     find_interface_elements (app);
2067   }
2068   return GST_BUS_PASS;
2069 }
2070 #endif
2071
2072 static gboolean
2073 draw_cb (GtkWidget * widget, cairo_t * cr, SeekApp * app)
2074 {
2075   if (app->state < GST_STATE_PAUSED) {
2076     int width, height;
2077
2078     width = gtk_widget_get_allocated_width (widget);
2079     height = gtk_widget_get_allocated_height (widget);
2080     cairo_set_source_rgb (cr, 0, 0, 0);
2081     cairo_rectangle (cr, 0, 0, width, height);
2082     cairo_fill (cr);
2083     return TRUE;
2084   }
2085
2086   if (app->xoverlay_element)
2087     gst_x_overlay_expose (GST_X_OVERLAY (app->xoverlay_element));
2088
2089   return FALSE;
2090 }
2091
2092 static void
2093 realize_cb (GtkWidget * widget, SeekApp * app)
2094 {
2095   GdkWindow *window = gtk_widget_get_window (widget);
2096
2097   /* This is here just for pedagogical purposes, GDK_WINDOW_XID will call it
2098    * as well */
2099   if (!gdk_window_ensure_native (window))
2100     g_error ("Couldn't create native window needed for GstXOverlay!");
2101
2102 #if defined (GDK_WINDOWING_WIN32)
2103   app->embed_xid = GDK_WINDOW_HWND (window);
2104   g_print ("Window realize: video window HWND = %lu\n", app->embed_xid);
2105 #elif defined (GDK_WINDOWING_QUARTZ)
2106   app->embed_xid = gdk_quartz_window_get_nsview (window);
2107   g_print ("Window realize: video window NSView = %p\n", app->embed_xid);
2108 #elif defined (GDK_WINDOWING_X11)
2109   app->embed_xid = GDK_WINDOW_XID (window);
2110   g_print ("Window realize: video window XID = %" G_GUINTPTR_FORMAT "\n",
2111       app->embed_xid);
2112 #endif
2113 }
2114
2115 static gboolean
2116 button_press_cb (GtkWidget * widget, GdkEventButton * event, SeekApp * app)
2117 {
2118   gtk_widget_grab_focus (widget);
2119
2120   if (app->navigation_element)
2121     gst_navigation_send_mouse_event (GST_NAVIGATION (app->navigation_element),
2122         "mouse-button-press", event->button, event->x, event->y);
2123
2124   return FALSE;
2125 }
2126
2127 static gboolean
2128 button_release_cb (GtkWidget * widget, GdkEventButton * event, SeekApp * app)
2129 {
2130   if (app->navigation_element)
2131     gst_navigation_send_mouse_event (GST_NAVIGATION (app->navigation_element),
2132         "mouse-button-release", event->button, event->x, event->y);
2133
2134   return FALSE;
2135 }
2136
2137 static gboolean
2138 key_press_cb (GtkWidget * widget, GdkEventKey * event, SeekApp * app)
2139 {
2140   if (app->navigation_element)
2141     gst_navigation_send_key_event (GST_NAVIGATION (app->navigation_element),
2142         "key-press", gdk_keyval_name (event->keyval));
2143
2144   return FALSE;
2145 }
2146
2147 static gboolean
2148 key_release_cb (GtkWidget * widget, GdkEventKey * event, SeekApp * app)
2149 {
2150   if (app->navigation_element)
2151     gst_navigation_send_key_event (GST_NAVIGATION (app->navigation_element),
2152         "key-release", gdk_keyval_name (event->keyval));
2153
2154   return FALSE;
2155 }
2156
2157 static gboolean
2158 motion_notify_cb (GtkWidget * widget, GdkEventMotion * event, SeekApp * app)
2159 {
2160   if (app->navigation_element)
2161     gst_navigation_send_mouse_event (GST_NAVIGATION (app->navigation_element),
2162         "mouse-move", 0, event->x, event->y);
2163
2164   return FALSE;
2165 }
2166
2167 static void
2168 msg_eos (GstBus * bus, GstMessage * message, SeekApp * app)
2169 {
2170   message_received (bus, message, app);
2171
2172   /* Set new uri for playerbins and continue playback */
2173   if (app->current_path && app->pipeline_type == 0) {
2174     stop_cb (NULL, app);
2175     app->current_path = g_list_next (app->current_path);
2176     app->current_sub_path = g_list_next (app->current_sub_path);
2177     if (app->current_path) {
2178       playbin_set_uri (app->pipeline, app->current_path->data,
2179           app->current_sub_path ? app->current_sub_path->data : NULL);
2180       play_cb (NULL, app);
2181     }
2182   }
2183 }
2184
2185 static void
2186 msg_step_done (GstBus * bus, GstMessage * message, SeekApp * app)
2187 {
2188   if (!app->shuttling)
2189     message_received (bus, message, app);
2190 }
2191
2192 static void
2193 msg (GstBus * bus, GstMessage * message, SeekApp * app)
2194 {
2195   GstNavigationMessageType nav_type;
2196
2197   nav_type = gst_navigation_message_get_type (message);
2198   switch (nav_type) {
2199     case GST_NAVIGATION_MESSAGE_COMMANDS_CHANGED:{
2200       GstQuery *query;
2201       gboolean res, j;
2202
2203       /* Heuristic to detect if we're dealing with a DVD menu */
2204       query = gst_navigation_query_new_commands ();
2205       res = gst_element_query (GST_ELEMENT (GST_MESSAGE_SRC (message)), query);
2206
2207       for (j = 0; j < G_N_ELEMENTS (app->navigation_buttons); j++)
2208         gtk_widget_set_sensitive (app->navigation_buttons[j].button, FALSE);
2209
2210       if (res) {
2211         gboolean is_menu = FALSE;
2212         guint i, n;
2213
2214         if (gst_navigation_query_parse_commands_length (query, &n)) {
2215           for (i = 0; i < n; i++) {
2216             GstNavigationCommand cmd;
2217
2218             if (!gst_navigation_query_parse_commands_nth (query, i, &cmd))
2219               break;
2220
2221             is_menu |= (cmd == GST_NAVIGATION_COMMAND_ACTIVATE);
2222             is_menu |= (cmd == GST_NAVIGATION_COMMAND_LEFT);
2223             is_menu |= (cmd == GST_NAVIGATION_COMMAND_RIGHT);
2224             is_menu |= (cmd == GST_NAVIGATION_COMMAND_UP);
2225             is_menu |= (cmd == GST_NAVIGATION_COMMAND_DOWN);
2226
2227             for (j = 0; j < G_N_ELEMENTS (app->navigation_buttons); j++) {
2228               if (app->navigation_buttons[j].cmd != cmd)
2229                 continue;
2230
2231               gtk_widget_set_sensitive (app->navigation_buttons[j].button,
2232                   TRUE);
2233             }
2234           }
2235         }
2236
2237         gtk_widget_set_sensitive (GTK_WIDGET (app->seek_scale), !is_menu);
2238       } else {
2239         g_assert_not_reached ();
2240       }
2241
2242       gst_query_unref (query);
2243       message_received (bus, message, app);
2244       break;
2245     }
2246     default:
2247       break;
2248   }
2249 }
2250
2251 static void
2252 connect_bus_signals (SeekApp * app)
2253 {
2254   GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
2255
2256 #if defined (GDK_WINDOWING_X11) || defined (GDK_WINDOWING_WIN32) || defined (GDK_WINDOWING_QUARTZ)
2257   if (app->pipeline_type != 0) {
2258     /* handle prepare-xwindow-id element message synchronously, but only for non-playbin2 */
2259     gst_bus_set_sync_handler (bus, (GstBusSyncHandler) bus_sync_handler, app);
2260   }
2261 #endif
2262
2263   gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
2264   gst_bus_enable_sync_message_emission (bus);
2265
2266   g_signal_connect (bus, "message::state-changed",
2267       G_CALLBACK (msg_state_changed), app);
2268   g_signal_connect (bus, "message::segment-done", G_CALLBACK (msg_segment_done),
2269       app);
2270   g_signal_connect (bus, "message::async-done", G_CALLBACK (msg_async_done),
2271       app);
2272
2273   g_signal_connect (bus, "message::new-clock", G_CALLBACK (message_received),
2274       app);
2275   g_signal_connect (bus, "message::clock-lost", G_CALLBACK (msg_clock_lost),
2276       app);
2277   g_signal_connect (bus, "message::error", G_CALLBACK (message_received), app);
2278   g_signal_connect (bus, "message::warning", G_CALLBACK (message_received),
2279       app);
2280   g_signal_connect (bus, "message::eos", G_CALLBACK (msg_eos), app);
2281   g_signal_connect (bus, "message::tag", G_CALLBACK (message_received), app);
2282   g_signal_connect (bus, "message::element", G_CALLBACK (message_received),
2283       app);
2284   g_signal_connect (bus, "message::segment-done", G_CALLBACK (message_received),
2285       app);
2286   g_signal_connect (bus, "message::buffering", G_CALLBACK (msg_buffering), app);
2287 //  g_signal_connect (bus, "message::step-done", G_CALLBACK (msg_step_done),
2288 //      app);
2289   g_signal_connect (bus, "message::step-start", G_CALLBACK (msg_step_done),
2290       app);
2291   g_signal_connect (bus, "sync-message::step-done",
2292       G_CALLBACK (msg_sync_step_done), app);
2293   g_signal_connect (bus, "message", G_CALLBACK (msg), app);
2294
2295   gst_object_unref (bus);
2296 }
2297
2298 /* Return GList of paths described in location string */
2299 static GList *
2300 handle_wildcards (const gchar * location)
2301 {
2302   GList *res = NULL;
2303   gchar *path = g_path_get_dirname (location);
2304   gchar *pattern = g_path_get_basename (location);
2305   GPatternSpec *pspec = g_pattern_spec_new (pattern);
2306   GDir *dir = g_dir_open (path, 0, NULL);
2307   const gchar *name;
2308
2309   g_print ("matching %s from %s\n", pattern, path);
2310
2311   if (!dir) {
2312     g_print ("opening directory %s failed\n", path);
2313     goto out;
2314   }
2315
2316   while ((name = g_dir_read_name (dir)) != NULL) {
2317     if (g_pattern_match_string (pspec, name)) {
2318       res = g_list_append (res, g_strjoin ("/", path, name, NULL));
2319       g_print ("  found clip %s\n", name);
2320     }
2321   }
2322
2323   g_dir_close (dir);
2324 out:
2325   g_pattern_spec_free (pspec);
2326   g_free (pattern);
2327   g_free (path);
2328
2329   return res;
2330 }
2331
2332 static void
2333 delete_event_cb (GtkWidget * widget, GdkEvent * event, SeekApp * app)
2334 {
2335   stop_cb (NULL, app);
2336   gtk_main_quit ();
2337 }
2338
2339 static void
2340 video_sink_activate_cb (GtkEntry * entry, SeekApp * app)
2341 {
2342   GstElement *sink = NULL;
2343   const gchar *text;
2344
2345   text = gtk_entry_get_text (entry);
2346   if (text != NULL && *text != '\0') {
2347     sink = gst_element_factory_make_or_warn (text, NULL);
2348   }
2349
2350   g_object_set (app->pipeline, "video-sink", sink, NULL);
2351 }
2352
2353 static void
2354 audio_sink_activate_cb (GtkEntry * entry, SeekApp * app)
2355 {
2356   GstElement *sink = NULL;
2357   const gchar *text;
2358
2359   text = gtk_entry_get_text (entry);
2360   if (text != NULL && *text != '\0') {
2361     sink = gst_element_factory_make_or_warn (text, NULL);
2362   }
2363
2364   g_object_set (app->pipeline, "audio-sink", sink, NULL);
2365 }
2366
2367 static void
2368 text_sink_activate_cb (GtkEntry * entry, SeekApp * app)
2369 {
2370   GstElement *sink = NULL;
2371   const gchar *text;
2372
2373   text = gtk_entry_get_text (entry);
2374   if (text != NULL && *text != '\0') {
2375     sink = gst_element_factory_make_or_warn (text, NULL);
2376   }
2377
2378   g_object_set (app->pipeline, "text-sink", sink, NULL);
2379 }
2380
2381 static void
2382 buffer_size_activate_cb (GtkEntry * entry, SeekApp * app)
2383 {
2384   const gchar *text;
2385
2386   text = gtk_entry_get_text (entry);
2387   if (text != NULL && *text != '\0') {
2388     gint v;
2389     gchar *endptr;
2390
2391     v = g_ascii_strtoll (text, &endptr, 10);
2392     if (endptr != text && v != G_MAXINT64 && v != G_MININT64) {
2393       g_object_set (app->pipeline, "buffer-size", v, NULL);
2394     }
2395   }
2396 }
2397
2398 static void
2399 buffer_duration_activate_cb (GtkEntry * entry, SeekApp * app)
2400 {
2401   const gchar *text;
2402
2403   text = gtk_entry_get_text (entry);
2404   if (text != NULL && *text != '\0') {
2405     gint64 v;
2406     gchar *endptr;
2407
2408     v = g_ascii_strtoll (text, &endptr, 10);
2409     if (endptr != text && v != G_MAXINT64 && v != G_MININT64) {
2410       g_object_set (app->pipeline, "buffer-duration", v, NULL);
2411     }
2412   }
2413 }
2414
2415 static void
2416 ringbuffer_maxsize_activate_cb (GtkEntry * entry, SeekApp * app)
2417 {
2418   const gchar *text;
2419
2420   text = gtk_entry_get_text (entry);
2421   if (text != NULL && *text != '\0') {
2422     guint64 v;
2423     gchar *endptr;
2424
2425     v = g_ascii_strtoull (text, &endptr, 10);
2426     if (endptr != text && v != G_MAXUINT64) {
2427       g_object_set (app->pipeline, "ring-buffer-max-size", v, NULL);
2428     }
2429   }
2430 }
2431
2432 static void
2433 connection_speed_activate_cb (GtkEntry * entry, SeekApp * app)
2434 {
2435   const gchar *text;
2436
2437   text = gtk_entry_get_text (entry);
2438   if (text != NULL && *text != '\0') {
2439     guint v;
2440     gchar *endptr;
2441
2442     v = g_ascii_strtoll (text, &endptr, 10);
2443     if (endptr != text && v != G_MAXINT64 && v != G_MININT64) {
2444       g_object_set (app->pipeline, "connection-speed", v, NULL);
2445     }
2446   }
2447 }
2448
2449 static void
2450 subtitle_encoding_activate_cb (GtkEntry * entry, SeekApp * app)
2451 {
2452   const gchar *text;
2453
2454   text = gtk_entry_get_text (entry);
2455   g_object_set (app->pipeline, "subtitle-encoding", text, NULL);
2456 }
2457
2458 static void
2459 subtitle_fontdesc_cb (GtkFontButton * button, SeekApp * app)
2460 {
2461   const gchar *text;
2462
2463   text = gtk_font_button_get_font_name (button);
2464   g_object_set (app->pipeline, "subtitle-font-desc", text, NULL);
2465 }
2466
2467 static void
2468 av_offset_activate_cb (GtkEntry * entry, SeekApp * app)
2469 {
2470   const gchar *text;
2471
2472   text = gtk_entry_get_text (entry);
2473   if (text != NULL && *text != '\0') {
2474     gint64 v;
2475     gchar *endptr;
2476
2477     v = g_ascii_strtoll (text, &endptr, 10);
2478     if (endptr != text && v != G_MAXINT64 && v != G_MININT64) {
2479       g_object_set (app->pipeline, "av-offset", v, NULL);
2480     }
2481   }
2482 }
2483
2484 static void
2485 print_usage (int argc, char **argv)
2486 {
2487   gint i;
2488
2489   g_print ("usage: %s <type> <filename>\n", argv[0]);
2490   g_print ("   possible types:\n");
2491
2492   for (i = 0; i < G_N_ELEMENTS (pipelines); i++) {
2493     g_print ("     %d = %s\n", i, pipelines[i].name);
2494   }
2495 }
2496
2497 static void
2498 create_ui (SeekApp * app)
2499 {
2500   GtkWidget *hbox, *vbox, *seek, *playbin, *step, *navigation, *colorbalance;
2501   GtkWidget *play_button, *pause_button, *stop_button;
2502   GtkAdjustment *adjustment;
2503
2504   /* initialize gui elements ... */
2505   app->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
2506   app->video_window = gtk_drawing_area_new ();
2507   g_signal_connect (app->video_window, "draw", G_CALLBACK (draw_cb), app);
2508   g_signal_connect (app->video_window, "realize", G_CALLBACK (realize_cb), app);
2509   g_signal_connect (app->video_window, "button-press-event",
2510       G_CALLBACK (button_press_cb), app);
2511   g_signal_connect (app->video_window, "button-release-event",
2512       G_CALLBACK (button_release_cb), app);
2513   g_signal_connect (app->video_window, "key-press-event",
2514       G_CALLBACK (key_press_cb), app);
2515   g_signal_connect (app->video_window, "key-release-event",
2516       G_CALLBACK (key_release_cb), app);
2517   g_signal_connect (app->video_window, "motion-notify-event",
2518       G_CALLBACK (motion_notify_cb), app);
2519   gtk_widget_set_can_focus (app->video_window, TRUE);
2520   gtk_widget_set_double_buffered (app->video_window, FALSE);
2521   gtk_widget_add_events (app->video_window,
2522       GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
2523       | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
2524
2525   app->statusbar = gtk_statusbar_new ();
2526   app->status_id =
2527       gtk_statusbar_get_context_id (GTK_STATUSBAR (app->statusbar), "seek");
2528   gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id,
2529       "Stopped");
2530   hbox = gtk_hbox_new (FALSE, 0);
2531   vbox = gtk_vbox_new (FALSE, 0);
2532   gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
2533
2534   /* media controls */
2535   play_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
2536   pause_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PAUSE);
2537   stop_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_STOP);
2538
2539   /* seek expander */
2540   {
2541     GtkWidget *accurate_checkbox, *key_checkbox, *loop_checkbox,
2542         *flush_checkbox;
2543     GtkWidget *scrub_checkbox, *play_scrub_checkbox, *rate_label;
2544     GtkWidget *skip_checkbox, *rate_spinbutton;
2545     GtkWidget *flagtable, *advanced_seek, *advanced_seek_grid;
2546     GtkWidget *duration_label, *position_label, *seek_button;
2547
2548     seek = gtk_expander_new ("seek options");
2549     flagtable = gtk_grid_new ();
2550     gtk_grid_set_row_spacing (GTK_GRID (flagtable), 2);
2551     gtk_grid_set_row_homogeneous (GTK_GRID (flagtable), FALSE);
2552     gtk_grid_set_column_spacing (GTK_GRID (flagtable), 2);
2553     gtk_grid_set_column_homogeneous (GTK_GRID (flagtable), TRUE);
2554
2555     accurate_checkbox = gtk_check_button_new_with_label ("Accurate Seek");
2556     key_checkbox = gtk_check_button_new_with_label ("Key-unit Seek");
2557     loop_checkbox = gtk_check_button_new_with_label ("Loop");
2558     flush_checkbox = gtk_check_button_new_with_label ("Flush");
2559     scrub_checkbox = gtk_check_button_new_with_label ("Scrub");
2560     play_scrub_checkbox = gtk_check_button_new_with_label ("Play Scrub");
2561     skip_checkbox = gtk_check_button_new_with_label ("Play Skip");
2562     rate_spinbutton = gtk_spin_button_new_with_range (-100, 100, 0.1);
2563     gtk_spin_button_set_digits (GTK_SPIN_BUTTON (rate_spinbutton), 3);
2564     rate_label = gtk_label_new ("Rate");
2565
2566     gtk_widget_set_tooltip_text (accurate_checkbox,
2567         "accurate position is requested, this might be considerably slower for some formats");
2568     gtk_widget_set_tooltip_text (key_checkbox,
2569         "seek to the nearest keyframe. This might be faster but less accurate");
2570     gtk_widget_set_tooltip_text (loop_checkbox, "loop playback");
2571     gtk_widget_set_tooltip_text (flush_checkbox,
2572         "flush pipeline after seeking");
2573     gtk_widget_set_tooltip_text (rate_spinbutton,
2574         "define the playback rate, " "negative value trigger reverse playback");
2575     gtk_widget_set_tooltip_text (scrub_checkbox, "show images while seeking");
2576     gtk_widget_set_tooltip_text (play_scrub_checkbox,
2577         "play video while seeking");
2578     gtk_widget_set_tooltip_text (skip_checkbox,
2579         "Skip frames while playing at high frame rates");
2580
2581     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (flush_checkbox), TRUE);
2582     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scrub_checkbox), TRUE);
2583
2584     gtk_spin_button_set_value (GTK_SPIN_BUTTON (rate_spinbutton), app->rate);
2585
2586     g_signal_connect (G_OBJECT (accurate_checkbox), "toggled",
2587         G_CALLBACK (accurate_toggle_cb), app);
2588     g_signal_connect (G_OBJECT (key_checkbox), "toggled",
2589         G_CALLBACK (key_toggle_cb), app);
2590     g_signal_connect (G_OBJECT (loop_checkbox), "toggled",
2591         G_CALLBACK (loop_toggle_cb), app);
2592     g_signal_connect (G_OBJECT (flush_checkbox), "toggled",
2593         G_CALLBACK (flush_toggle_cb), app);
2594     g_signal_connect (G_OBJECT (scrub_checkbox), "toggled",
2595         G_CALLBACK (scrub_toggle_cb), app);
2596     g_signal_connect (G_OBJECT (play_scrub_checkbox), "toggled",
2597         G_CALLBACK (play_scrub_toggle_cb), app);
2598     g_signal_connect (G_OBJECT (skip_checkbox), "toggled",
2599         G_CALLBACK (skip_toggle_cb), app);
2600     g_signal_connect (G_OBJECT (rate_spinbutton), "value-changed",
2601         G_CALLBACK (rate_spinbutton_changed_cb), app);
2602
2603     gtk_grid_attach (GTK_GRID (flagtable), accurate_checkbox, 0, 0, 1, 1);
2604     gtk_grid_attach (GTK_GRID (flagtable), flush_checkbox, 1, 0, 1, 1);
2605     gtk_grid_attach (GTK_GRID (flagtable), loop_checkbox, 2, 0, 1, 1);
2606     gtk_grid_attach (GTK_GRID (flagtable), key_checkbox, 0, 1, 1, 1);
2607     gtk_grid_attach (GTK_GRID (flagtable), scrub_checkbox, 1, 1, 1, 1);
2608     gtk_grid_attach (GTK_GRID (flagtable), play_scrub_checkbox, 2, 1, 1, 1);
2609     gtk_grid_attach (GTK_GRID (flagtable), skip_checkbox, 3, 0, 1, 1);
2610     gtk_grid_attach (GTK_GRID (flagtable), rate_label, 4, 0, 1, 1);
2611     gtk_grid_attach (GTK_GRID (flagtable), rate_spinbutton, 4, 1, 1, 1);
2612
2613     advanced_seek = gtk_frame_new ("Advanced Seek");
2614     advanced_seek_grid = gtk_grid_new ();
2615     gtk_grid_set_row_spacing (GTK_GRID (advanced_seek_grid), 2);
2616     gtk_grid_set_row_homogeneous (GTK_GRID (advanced_seek_grid), FALSE);
2617     gtk_grid_set_column_spacing (GTK_GRID (advanced_seek_grid), 5);
2618     gtk_grid_set_column_homogeneous (GTK_GRID (advanced_seek_grid), FALSE);
2619
2620     app->seek_format_combo = gtk_combo_box_text_new ();
2621     g_signal_connect (app->seek_format_combo, "changed",
2622         G_CALLBACK (seek_format_changed_cb), app);
2623     gtk_grid_attach (GTK_GRID (advanced_seek_grid), app->seek_format_combo, 0,
2624         0, 1, 1);
2625
2626     app->seek_entry = gtk_entry_new ();
2627     gtk_entry_set_width_chars (GTK_ENTRY (app->seek_entry), 12);
2628     gtk_grid_attach (GTK_GRID (advanced_seek_grid), app->seek_entry, 0, 1, 1,
2629         1);
2630
2631     seek_button = gtk_button_new_with_label ("Seek");
2632     g_signal_connect (G_OBJECT (seek_button), "clicked",
2633         G_CALLBACK (advanced_seek_button_cb), app);
2634     gtk_grid_attach (GTK_GRID (advanced_seek_grid), seek_button, 1, 0, 1, 1);
2635
2636     position_label = gtk_label_new ("Position:");
2637     gtk_grid_attach (GTK_GRID (advanced_seek_grid), position_label, 2, 0, 1, 1);
2638     duration_label = gtk_label_new ("Duration:");
2639     gtk_grid_attach (GTK_GRID (advanced_seek_grid), duration_label, 2, 1, 1, 1);
2640
2641     app->seek_position_label = gtk_label_new ("-1");
2642     gtk_grid_attach (GTK_GRID (advanced_seek_grid), app->seek_position_label, 3,
2643         0, 1, 1);
2644     app->seek_duration_label = gtk_label_new ("-1");
2645     gtk_grid_attach (GTK_GRID (advanced_seek_grid), app->seek_duration_label, 3,
2646         1, 1, 1);
2647
2648     gtk_container_add (GTK_CONTAINER (advanced_seek), advanced_seek_grid);
2649     gtk_grid_attach (GTK_GRID (flagtable), advanced_seek, 0, 2, 3, 2);
2650     gtk_container_add (GTK_CONTAINER (seek), flagtable);
2651   }
2652
2653   /* step expander */
2654   {
2655     GtkWidget *hbox;
2656     GtkWidget *step_button, *shuttle_checkbox;
2657
2658     step = gtk_expander_new ("step options");
2659     hbox = gtk_hbox_new (FALSE, 0);
2660
2661     app->step_format_combo = gtk_combo_box_text_new ();
2662     gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (app->step_format_combo),
2663         "frames");
2664     gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (app->step_format_combo),
2665         "time (ms)");
2666     gtk_combo_box_set_active (GTK_COMBO_BOX (app->step_format_combo), 0);
2667     gtk_box_pack_start (GTK_BOX (hbox), app->step_format_combo, FALSE, FALSE,
2668         2);
2669
2670     app->step_amount_spinbutton = gtk_spin_button_new_with_range (1, 1000, 1);
2671     gtk_spin_button_set_digits (GTK_SPIN_BUTTON (app->step_amount_spinbutton),
2672         0);
2673     gtk_spin_button_set_value (GTK_SPIN_BUTTON (app->step_amount_spinbutton),
2674         1.0);
2675     gtk_box_pack_start (GTK_BOX (hbox), app->step_amount_spinbutton, FALSE,
2676         FALSE, 2);
2677
2678     app->step_rate_spinbutton = gtk_spin_button_new_with_range (0.0, 100, 0.1);
2679     gtk_spin_button_set_digits (GTK_SPIN_BUTTON (app->step_rate_spinbutton), 3);
2680     gtk_spin_button_set_value (GTK_SPIN_BUTTON (app->step_rate_spinbutton),
2681         1.0);
2682     gtk_box_pack_start (GTK_BOX (hbox), app->step_rate_spinbutton, FALSE, FALSE,
2683         2);
2684
2685     step_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_FORWARD);
2686     gtk_button_set_label (GTK_BUTTON (step_button), "Step");
2687     gtk_box_pack_start (GTK_BOX (hbox), step_button, FALSE, FALSE, 2);
2688
2689     g_signal_connect (G_OBJECT (step_button), "clicked", G_CALLBACK (step_cb),
2690         app);
2691
2692     /* shuttle scale */
2693     shuttle_checkbox = gtk_check_button_new_with_label ("Shuttle");
2694     gtk_box_pack_start (GTK_BOX (hbox), shuttle_checkbox, FALSE, FALSE, 2);
2695     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (shuttle_checkbox), FALSE);
2696     g_signal_connect (shuttle_checkbox, "toggled", G_CALLBACK (shuttle_toggled),
2697         app);
2698
2699     adjustment =
2700         GTK_ADJUSTMENT (gtk_adjustment_new (0.0, -3.00, 4.0, 0.1, 1.0, 1.0));
2701     app->shuttle_scale = gtk_hscale_new (adjustment);
2702     gtk_scale_set_digits (GTK_SCALE (app->shuttle_scale), 2);
2703     gtk_scale_set_value_pos (GTK_SCALE (app->shuttle_scale), GTK_POS_TOP);
2704     g_signal_connect (app->shuttle_scale, "value-changed",
2705         G_CALLBACK (shuttle_value_changed), app);
2706     g_signal_connect (app->shuttle_scale, "format_value",
2707         G_CALLBACK (shuttle_format_value), app);
2708
2709     gtk_box_pack_start (GTK_BOX (hbox), app->shuttle_scale, TRUE, TRUE, 2);
2710
2711     gtk_container_add (GTK_CONTAINER (step), hbox);
2712   }
2713
2714   /* navigation command expander */
2715   {
2716     GtkWidget *navigation_button;
2717     GtkWidget *grid;
2718     gint i = 0;
2719
2720     navigation = gtk_expander_new ("navigation commands");
2721     grid = gtk_grid_new ();
2722     gtk_grid_set_row_spacing (GTK_GRID (grid), 2);
2723     gtk_grid_set_row_homogeneous (GTK_GRID (grid), FALSE);
2724     gtk_grid_set_column_spacing (GTK_GRID (grid), 2);
2725     gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE);
2726
2727     navigation_button = gtk_button_new_with_label ("Menu 1");
2728     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2729         G_CALLBACK (navigation_cmd_cb), app);
2730     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2731     gtk_widget_set_sensitive (navigation_button, FALSE);
2732     gtk_widget_set_tooltip_text (navigation_button, "DVD Menu");
2733     app->navigation_buttons[i].button = navigation_button;
2734     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU1;
2735
2736     navigation_button = gtk_button_new_with_label ("Menu 2");
2737     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2738         G_CALLBACK (navigation_cmd_cb), app);
2739     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2740     gtk_widget_set_sensitive (navigation_button, FALSE);
2741     gtk_widget_set_tooltip_text (navigation_button, "DVD Title Menu");
2742     app->navigation_buttons[i].button = navigation_button;
2743     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU2;
2744
2745     navigation_button = gtk_button_new_with_label ("Menu 3");
2746     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2747         G_CALLBACK (navigation_cmd_cb), app);
2748     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2749     gtk_widget_set_sensitive (navigation_button, FALSE);
2750     gtk_widget_set_tooltip_text (navigation_button, "DVD Root Menu");
2751     app->navigation_buttons[i].button = navigation_button;
2752     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU3;
2753
2754     navigation_button = gtk_button_new_with_label ("Menu 4");
2755     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2756         G_CALLBACK (navigation_cmd_cb), app);
2757     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2758     gtk_widget_set_sensitive (navigation_button, FALSE);
2759     gtk_widget_set_tooltip_text (navigation_button, "DVD Subpicture Menu");
2760     app->navigation_buttons[i].button = navigation_button;
2761     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU4;
2762
2763     navigation_button = gtk_button_new_with_label ("Menu 5");
2764     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2765         G_CALLBACK (navigation_cmd_cb), app);
2766     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2767     gtk_widget_set_sensitive (navigation_button, FALSE);
2768     gtk_widget_set_tooltip_text (navigation_button, "DVD Audio Menu");
2769     app->navigation_buttons[i].button = navigation_button;
2770     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU5;
2771
2772     navigation_button = gtk_button_new_with_label ("Menu 6");
2773     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2774         G_CALLBACK (navigation_cmd_cb), app);
2775     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2776     gtk_widget_set_sensitive (navigation_button, FALSE);
2777     gtk_widget_set_tooltip_text (navigation_button, "DVD Angle Menu");
2778     app->navigation_buttons[i].button = navigation_button;
2779     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU6;
2780
2781     navigation_button = gtk_button_new_with_label ("Menu 7");
2782     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2783         G_CALLBACK (navigation_cmd_cb), app);
2784     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2785     gtk_widget_set_sensitive (navigation_button, FALSE);
2786     gtk_widget_set_tooltip_text (navigation_button, "DVD Chapter Menu");
2787     app->navigation_buttons[i].button = navigation_button;
2788     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU7;
2789
2790     navigation_button = gtk_button_new_with_label ("Left");
2791     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2792         G_CALLBACK (navigation_cmd_cb), app);
2793     gtk_grid_attach (GTK_GRID (grid), navigation_button, i - 7, 1, 1, 1);
2794     gtk_widget_set_sensitive (navigation_button, FALSE);
2795     app->navigation_buttons[i].button = navigation_button;
2796     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_LEFT;
2797
2798     navigation_button = gtk_button_new_with_label ("Right");
2799     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2800         G_CALLBACK (navigation_cmd_cb), app);
2801     gtk_grid_attach (GTK_GRID (grid), navigation_button, i - 7, 1, 1, 1);
2802     gtk_widget_set_sensitive (navigation_button, FALSE);
2803     app->navigation_buttons[i].button = navigation_button;
2804     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_RIGHT;
2805
2806     navigation_button = gtk_button_new_with_label ("Up");
2807     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2808         G_CALLBACK (navigation_cmd_cb), app);
2809     gtk_grid_attach (GTK_GRID (grid), navigation_button, i - 7, 1, 1, 1);
2810     gtk_widget_set_sensitive (navigation_button, FALSE);
2811     app->navigation_buttons[i].button = navigation_button;
2812     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_UP;
2813
2814     navigation_button = gtk_button_new_with_label ("Down");
2815     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2816         G_CALLBACK (navigation_cmd_cb), app);
2817     gtk_grid_attach (GTK_GRID (grid), navigation_button, i - 7, 1, 1, 1);
2818     gtk_widget_set_sensitive (navigation_button, FALSE);
2819     app->navigation_buttons[i].button = navigation_button;
2820     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_DOWN;
2821
2822     navigation_button = gtk_button_new_with_label ("Activate");
2823     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2824         G_CALLBACK (navigation_cmd_cb), app);
2825     gtk_grid_attach (GTK_GRID (grid), navigation_button, i - 7, 1, 1, 1);
2826     gtk_widget_set_sensitive (navigation_button, FALSE);
2827     app->navigation_buttons[i].button = navigation_button;
2828     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_ACTIVATE;
2829
2830     navigation_button = gtk_button_new_with_label ("Prev. Angle");
2831     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2832         G_CALLBACK (navigation_cmd_cb), app);
2833     gtk_grid_attach (GTK_GRID (grid), navigation_button, i - 7, 1, 1, 1);
2834     gtk_widget_set_sensitive (navigation_button, FALSE);
2835     app->navigation_buttons[i].button = navigation_button;
2836     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_PREV_ANGLE;
2837
2838     navigation_button = gtk_button_new_with_label ("Next. Angle");
2839     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2840         G_CALLBACK (navigation_cmd_cb), app);
2841     gtk_grid_attach (GTK_GRID (grid), navigation_button, i - 7, 1, 1, 1);
2842     gtk_widget_set_sensitive (navigation_button, FALSE);
2843     app->navigation_buttons[i].button = navigation_button;
2844     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_NEXT_ANGLE;
2845
2846     gtk_container_add (GTK_CONTAINER (navigation), grid);
2847   }
2848
2849   /* colorbalance expander */
2850   {
2851     GtkWidget *vbox, *frame;
2852
2853     colorbalance = gtk_expander_new ("color balance options");
2854     vbox = gtk_vbox_new (FALSE, 0);
2855
2856     /* contrast scale */
2857     frame = gtk_frame_new ("Contrast");
2858     adjustment =
2859         GTK_ADJUSTMENT (gtk_adjustment_new (N_GRAD / 2.0, 0.00, N_GRAD, 0.1,
2860             1.0, 1.0));
2861     app->contrast_scale = gtk_hscale_new (adjustment);
2862     gtk_scale_set_draw_value (GTK_SCALE (app->contrast_scale), FALSE);
2863     g_signal_connect (app->contrast_scale, "value-changed",
2864         G_CALLBACK (colorbalance_value_changed), app);
2865     gtk_container_add (GTK_CONTAINER (frame), app->contrast_scale);
2866     gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 2);
2867
2868     /* brightness scale */
2869     frame = gtk_frame_new ("Brightness");
2870     adjustment =
2871         GTK_ADJUSTMENT (gtk_adjustment_new (N_GRAD / 2.0, 0.00, N_GRAD, 0.1,
2872             1.0, 1.0));
2873     app->brightness_scale = gtk_hscale_new (adjustment);
2874     gtk_scale_set_draw_value (GTK_SCALE (app->brightness_scale), FALSE);
2875     g_signal_connect (app->brightness_scale, "value-changed",
2876         G_CALLBACK (colorbalance_value_changed), app);
2877     gtk_container_add (GTK_CONTAINER (frame), app->brightness_scale);
2878     gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 2);
2879
2880     /* hue scale */
2881     frame = gtk_frame_new ("Hue");
2882     adjustment =
2883         GTK_ADJUSTMENT (gtk_adjustment_new (N_GRAD / 2.0, 0.00, N_GRAD, 0.1,
2884             1.0, 1.0));
2885     app->hue_scale = gtk_hscale_new (adjustment);
2886     gtk_scale_set_draw_value (GTK_SCALE (app->hue_scale), FALSE);
2887     g_signal_connect (app->hue_scale, "value-changed",
2888         G_CALLBACK (colorbalance_value_changed), app);
2889     gtk_container_add (GTK_CONTAINER (frame), app->hue_scale);
2890     gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 2);
2891
2892     /* saturation scale */
2893     frame = gtk_frame_new ("Saturation");
2894     adjustment =
2895         GTK_ADJUSTMENT (gtk_adjustment_new (N_GRAD / 2.0, 0.00, N_GRAD, 0.1,
2896             1.0, 1.0));
2897     app->saturation_scale = gtk_hscale_new (adjustment);
2898     gtk_scale_set_draw_value (GTK_SCALE (app->saturation_scale), FALSE);
2899     g_signal_connect (app->saturation_scale, "value-changed",
2900         G_CALLBACK (colorbalance_value_changed), app);
2901     gtk_container_add (GTK_CONTAINER (frame), app->saturation_scale);
2902     gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 2);
2903
2904     gtk_container_add (GTK_CONTAINER (colorbalance), vbox);
2905   }
2906
2907   /* seek bar */
2908   adjustment =
2909       GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.00, N_GRAD, 0.1, 1.0, 1.0));
2910   app->seek_scale = gtk_hscale_new (adjustment);
2911   gtk_scale_set_digits (GTK_SCALE (app->seek_scale), 2);
2912   gtk_scale_set_value_pos (GTK_SCALE (app->seek_scale), GTK_POS_RIGHT);
2913   gtk_range_set_show_fill_level (GTK_RANGE (app->seek_scale), TRUE);
2914   gtk_range_set_fill_level (GTK_RANGE (app->seek_scale), N_GRAD);
2915
2916   g_signal_connect (app->seek_scale, "button_press_event",
2917       G_CALLBACK (start_seek), app);
2918   g_signal_connect (app->seek_scale, "button_release_event",
2919       G_CALLBACK (stop_seek), app);
2920   g_signal_connect (app->seek_scale, "format_value", G_CALLBACK (format_value),
2921       app);
2922
2923   if (app->pipeline_type == 0) {
2924     GtkWidget *pb2vbox, *boxes, *boxes2, *panel, *boxes3;
2925     GtkWidget *volume_label, *shot_button;
2926     GtkWidget *label;
2927
2928     playbin = gtk_expander_new ("playbin2 options");
2929     /* the playbin2 panel controls for the video/audio/subtitle tracks */
2930     panel = gtk_hbox_new (FALSE, 0);
2931     app->video_combo = gtk_combo_box_text_new ();
2932     app->audio_combo = gtk_combo_box_text_new ();
2933     app->text_combo = gtk_combo_box_text_new ();
2934     gtk_widget_set_sensitive (app->video_combo, FALSE);
2935     gtk_widget_set_sensitive (app->audio_combo, FALSE);
2936     gtk_widget_set_sensitive (app->text_combo, FALSE);
2937     gtk_box_pack_start (GTK_BOX (panel), app->video_combo, TRUE, TRUE, 2);
2938     gtk_box_pack_start (GTK_BOX (panel), app->audio_combo, TRUE, TRUE, 2);
2939     gtk_box_pack_start (GTK_BOX (panel), app->text_combo, TRUE, TRUE, 2);
2940     g_signal_connect (G_OBJECT (app->video_combo), "changed",
2941         G_CALLBACK (video_combo_cb), app);
2942     g_signal_connect (G_OBJECT (app->audio_combo), "changed",
2943         G_CALLBACK (audio_combo_cb), app);
2944     g_signal_connect (G_OBJECT (app->text_combo), "changed",
2945         G_CALLBACK (text_combo_cb), app);
2946     /* playbin2 panel for flag checkboxes and volume/mute */
2947     boxes = gtk_grid_new ();
2948     gtk_grid_set_row_spacing (GTK_GRID (boxes), 2);
2949     gtk_grid_set_row_homogeneous (GTK_GRID (boxes), FALSE);
2950     gtk_grid_set_column_spacing (GTK_GRID (boxes), 2);
2951     gtk_grid_set_column_homogeneous (GTK_GRID (boxes), TRUE);
2952
2953     app->video_checkbox = gtk_check_button_new_with_label ("Video");
2954     app->audio_checkbox = gtk_check_button_new_with_label ("Audio");
2955     app->text_checkbox = gtk_check_button_new_with_label ("Text");
2956     app->vis_checkbox = gtk_check_button_new_with_label ("Vis");
2957     app->soft_volume_checkbox = gtk_check_button_new_with_label ("Soft Volume");
2958     app->native_audio_checkbox =
2959         gtk_check_button_new_with_label ("Native Audio");
2960     app->native_video_checkbox =
2961         gtk_check_button_new_with_label ("Native Video");
2962     app->download_checkbox = gtk_check_button_new_with_label ("Download");
2963     app->buffering_checkbox = gtk_check_button_new_with_label ("Buffering");
2964     app->deinterlace_checkbox = gtk_check_button_new_with_label ("Deinterlace");
2965     app->soft_colorbalance_checkbox =
2966         gtk_check_button_new_with_label ("Soft Colorbalance");
2967     app->mute_checkbox = gtk_check_button_new_with_label ("Mute");
2968     volume_label = gtk_label_new ("Volume");
2969     app->volume_spinbutton = gtk_spin_button_new_with_range (0, 10.0, 0.1);
2970
2971     gtk_grid_attach (GTK_GRID (boxes), app->video_checkbox, 0, 0, 1, 1);
2972     gtk_grid_attach (GTK_GRID (boxes), app->audio_checkbox, 1, 0, 1, 1);
2973     gtk_grid_attach (GTK_GRID (boxes), app->text_checkbox, 2, 0, 1, 1);
2974     gtk_grid_attach (GTK_GRID (boxes), app->vis_checkbox, 3, 0, 1, 1);
2975     gtk_grid_attach (GTK_GRID (boxes), app->soft_volume_checkbox, 4, 0, 1, 1);
2976     gtk_grid_attach (GTK_GRID (boxes), app->native_audio_checkbox, 5, 0, 1, 1);
2977     gtk_grid_attach (GTK_GRID (boxes), app->native_video_checkbox, 0, 1, 1, 1);
2978     gtk_grid_attach (GTK_GRID (boxes), app->download_checkbox, 1, 1, 1, 1);
2979     gtk_grid_attach (GTK_GRID (boxes), app->buffering_checkbox, 2, 1, 1, 1);
2980     gtk_grid_attach (GTK_GRID (boxes), app->deinterlace_checkbox, 3, 1, 1, 1);
2981     gtk_grid_attach (GTK_GRID (boxes), app->soft_colorbalance_checkbox, 4, 1, 1,
2982         1);
2983
2984     gtk_grid_attach (GTK_GRID (boxes), app->mute_checkbox, 7, 0, 2, 1);
2985     gtk_grid_attach (GTK_GRID (boxes), volume_label, 6, 1, 1, 1);
2986     gtk_grid_attach (GTK_GRID (boxes), app->volume_spinbutton, 7, 1, 1, 1);
2987
2988     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->video_checkbox),
2989         TRUE);
2990     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->audio_checkbox),
2991         TRUE);
2992     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->text_checkbox), TRUE);
2993     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->vis_checkbox), FALSE);
2994     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->soft_volume_checkbox),
2995         TRUE);
2996     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
2997         (app->native_audio_checkbox), FALSE);
2998     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
2999         (app->native_video_checkbox), FALSE);
3000     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->download_checkbox),
3001         FALSE);
3002     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->buffering_checkbox),
3003         FALSE);
3004     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->deinterlace_checkbox),
3005         FALSE);
3006     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
3007         (app->soft_colorbalance_checkbox), TRUE);
3008     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->mute_checkbox),
3009         FALSE);
3010     gtk_spin_button_set_value (GTK_SPIN_BUTTON (app->volume_spinbutton), 1.0);
3011
3012     g_signal_connect (G_OBJECT (app->video_checkbox), "toggled",
3013         G_CALLBACK (video_toggle_cb), app);
3014     g_signal_connect (G_OBJECT (app->audio_checkbox), "toggled",
3015         G_CALLBACK (audio_toggle_cb), app);
3016     g_signal_connect (G_OBJECT (app->text_checkbox), "toggled",
3017         G_CALLBACK (text_toggle_cb), app);
3018     g_signal_connect (G_OBJECT (app->vis_checkbox), "toggled",
3019         G_CALLBACK (vis_toggle_cb), app);
3020     g_signal_connect (G_OBJECT (app->soft_volume_checkbox), "toggled",
3021         G_CALLBACK (soft_volume_toggle_cb), app);
3022     g_signal_connect (G_OBJECT (app->native_audio_checkbox), "toggled",
3023         G_CALLBACK (native_audio_toggle_cb), app);
3024     g_signal_connect (G_OBJECT (app->native_video_checkbox), "toggled",
3025         G_CALLBACK (native_video_toggle_cb), app);
3026     g_signal_connect (G_OBJECT (app->download_checkbox), "toggled",
3027         G_CALLBACK (download_toggle_cb), app);
3028     g_signal_connect (G_OBJECT (app->buffering_checkbox), "toggled",
3029         G_CALLBACK (buffering_toggle_cb), app);
3030     g_signal_connect (G_OBJECT (app->deinterlace_checkbox), "toggled",
3031         G_CALLBACK (deinterlace_toggle_cb), app);
3032     g_signal_connect (G_OBJECT (app->soft_colorbalance_checkbox), "toggled",
3033         G_CALLBACK (soft_colorbalance_toggle_cb), app);
3034     g_signal_connect (G_OBJECT (app->mute_checkbox), "toggled",
3035         G_CALLBACK (mute_toggle_cb), app);
3036     g_signal_connect (G_OBJECT (app->volume_spinbutton), "value-changed",
3037         G_CALLBACK (volume_spinbutton_changed_cb), app);
3038     /* playbin2 panel for snapshot */
3039     boxes2 = gtk_hbox_new (FALSE, 0);
3040     shot_button = gtk_button_new_from_stock (GTK_STOCK_SAVE);
3041     gtk_widget_set_tooltip_text (shot_button,
3042         "save a screenshot .png in the current directory");
3043     g_signal_connect (G_OBJECT (shot_button), "clicked", G_CALLBACK (shot_cb),
3044         app);
3045     app->vis_combo = gtk_combo_box_text_new ();
3046     g_signal_connect (G_OBJECT (app->vis_combo), "changed",
3047         G_CALLBACK (vis_combo_cb), app);
3048     gtk_widget_set_sensitive (app->vis_combo, FALSE);
3049     gtk_box_pack_start (GTK_BOX (boxes2), shot_button, TRUE, TRUE, 2);
3050     gtk_box_pack_start (GTK_BOX (boxes2), app->vis_combo, TRUE, TRUE, 2);
3051
3052     /* fill the vis combo box and the array of factories */
3053     init_visualization_features (app);
3054
3055     /* Grid with other properties */
3056     boxes3 = gtk_grid_new ();
3057     gtk_grid_set_row_spacing (GTK_GRID (boxes3), 2);
3058     gtk_grid_set_row_homogeneous (GTK_GRID (boxes3), FALSE);
3059     gtk_grid_set_column_spacing (GTK_GRID (boxes3), 2);
3060     gtk_grid_set_column_homogeneous (GTK_GRID (boxes3), TRUE);
3061
3062     label = gtk_label_new ("Video sink");
3063     gtk_grid_attach (GTK_GRID (boxes3), label, 0, 0, 1, 1);
3064     app->video_sink_entry = gtk_entry_new ();
3065     g_signal_connect (app->video_sink_entry, "activate",
3066         G_CALLBACK (video_sink_activate_cb), app);
3067     gtk_grid_attach (GTK_GRID (boxes3), app->video_sink_entry, 0, 1, 1, 1);
3068
3069     label = gtk_label_new ("Audio sink");
3070     gtk_grid_attach (GTK_GRID (boxes3), label, 1, 0, 1, 1);
3071     app->audio_sink_entry = gtk_entry_new ();
3072     g_signal_connect (app->audio_sink_entry, "activate",
3073         G_CALLBACK (audio_sink_activate_cb), app);
3074     gtk_grid_attach (GTK_GRID (boxes3), app->audio_sink_entry, 1, 1, 1, 1);
3075
3076     label = gtk_label_new ("Text sink");
3077     gtk_grid_attach (GTK_GRID (boxes3), label, 2, 0, 1, 1);
3078     app->text_sink_entry = gtk_entry_new ();
3079     g_signal_connect (app->text_sink_entry, "activate",
3080         G_CALLBACK (text_sink_activate_cb), app);
3081     gtk_grid_attach (GTK_GRID (boxes3), app->text_sink_entry, 2, 1, 1, 1);
3082
3083     label = gtk_label_new ("Buffer Size");
3084     gtk_grid_attach (GTK_GRID (boxes3), label, 0, 2, 1, 1);
3085     app->buffer_size_entry = gtk_entry_new ();
3086     gtk_entry_set_text (GTK_ENTRY (app->buffer_size_entry), "-1");
3087     g_signal_connect (app->buffer_size_entry, "activate",
3088         G_CALLBACK (buffer_size_activate_cb), app);
3089     gtk_grid_attach (GTK_GRID (boxes3), app->buffer_size_entry, 0, 3, 1, 1);
3090
3091     label = gtk_label_new ("Buffer Duration");
3092     gtk_grid_attach (GTK_GRID (boxes3), label, 1, 2, 1, 1);
3093     app->buffer_duration_entry = gtk_entry_new ();
3094     gtk_entry_set_text (GTK_ENTRY (app->buffer_duration_entry), "-1");
3095     g_signal_connect (app->buffer_duration_entry, "activate",
3096         G_CALLBACK (buffer_duration_activate_cb), app);
3097     gtk_grid_attach (GTK_GRID (boxes3), app->buffer_duration_entry, 1, 3, 1, 1);
3098
3099     label = gtk_label_new ("Ringbuffer Max Size");
3100     gtk_grid_attach (GTK_GRID (boxes3), label, 2, 2, 1, 1);
3101     app->ringbuffer_maxsize_entry = gtk_entry_new ();
3102     gtk_entry_set_text (GTK_ENTRY (app->ringbuffer_maxsize_entry), "0");
3103     g_signal_connect (app->ringbuffer_maxsize_entry, "activate",
3104         G_CALLBACK (ringbuffer_maxsize_activate_cb), app);
3105     gtk_grid_attach (GTK_GRID (boxes3), app->ringbuffer_maxsize_entry, 2, 3, 1,
3106         1);
3107
3108     label = gtk_label_new ("Connection Speed");
3109     gtk_grid_attach (GTK_GRID (boxes3), label, 3, 2, 1, 1);
3110     app->connection_speed_entry = gtk_entry_new ();
3111     gtk_entry_set_text (GTK_ENTRY (app->connection_speed_entry), "0");
3112     g_signal_connect (app->connection_speed_entry, "activate",
3113         G_CALLBACK (connection_speed_activate_cb), app);
3114     gtk_grid_attach (GTK_GRID (boxes3), app->connection_speed_entry, 3, 3, 1,
3115         1);
3116
3117     label = gtk_label_new ("A/V offset");
3118     gtk_grid_attach (GTK_GRID (boxes3), label, 4, 2, 1, 1);
3119     app->av_offset_entry = gtk_entry_new ();
3120     g_signal_connect (app->av_offset_entry, "activate",
3121         G_CALLBACK (av_offset_activate_cb), app);
3122     gtk_entry_set_text (GTK_ENTRY (app->av_offset_entry), "0");
3123     g_signal_connect (app->av_offset_entry, "activate",
3124         G_CALLBACK (av_offset_activate_cb), app);
3125     gtk_grid_attach (GTK_GRID (boxes3), app->av_offset_entry, 4, 3, 1, 1);
3126
3127     label = gtk_label_new ("Subtitle Encoding");
3128     gtk_grid_attach (GTK_GRID (boxes3), label, 0, 4, 1, 1);
3129     app->subtitle_encoding_entry = gtk_entry_new ();
3130     g_signal_connect (app->subtitle_encoding_entry, "activate",
3131         G_CALLBACK (subtitle_encoding_activate_cb), app);
3132     gtk_grid_attach (GTK_GRID (boxes3), app->subtitle_encoding_entry, 0, 5, 1,
3133         1);
3134
3135     label = gtk_label_new ("Subtitle Fontdesc");
3136     gtk_grid_attach (GTK_GRID (boxes3), label, 1, 4, 1, 1);
3137     app->subtitle_fontdesc_button = gtk_font_button_new ();
3138     g_signal_connect (app->subtitle_fontdesc_button, "font-set",
3139         G_CALLBACK (subtitle_fontdesc_cb), app);
3140     gtk_grid_attach (GTK_GRID (boxes3), app->subtitle_fontdesc_button, 1, 5, 1,
3141         1);
3142
3143     pb2vbox = gtk_vbox_new (FALSE, 0);
3144     gtk_box_pack_start (GTK_BOX (pb2vbox), panel, FALSE, FALSE, 2);
3145     gtk_box_pack_start (GTK_BOX (pb2vbox), boxes, FALSE, FALSE, 2);
3146     gtk_box_pack_start (GTK_BOX (pb2vbox), boxes2, FALSE, FALSE, 2);
3147     gtk_box_pack_start (GTK_BOX (pb2vbox), boxes3, FALSE, FALSE, 2);
3148     gtk_container_add (GTK_CONTAINER (playbin), pb2vbox);
3149   } else {
3150     playbin = NULL;
3151   }
3152
3153   /* do the packing stuff ... */
3154   gtk_window_set_default_size (GTK_WINDOW (app->window), 250, 96);
3155   /* FIXME: can we avoid this for audio only? */
3156   gtk_widget_set_size_request (GTK_WIDGET (app->video_window), -1,
3157       DEFAULT_VIDEO_HEIGHT);
3158   gtk_container_add (GTK_CONTAINER (app->window), vbox);
3159   gtk_box_pack_start (GTK_BOX (vbox), app->video_window, TRUE, TRUE, 2);
3160   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2);
3161   gtk_box_pack_start (GTK_BOX (hbox), play_button, FALSE, FALSE, 2);
3162   gtk_box_pack_start (GTK_BOX (hbox), pause_button, FALSE, FALSE, 2);
3163   gtk_box_pack_start (GTK_BOX (hbox), stop_button, FALSE, FALSE, 2);
3164
3165   gtk_box_pack_start (GTK_BOX (vbox), seek, FALSE, FALSE, 2);
3166   if (playbin)
3167     gtk_box_pack_start (GTK_BOX (vbox), playbin, FALSE, FALSE, 2);
3168   gtk_box_pack_start (GTK_BOX (vbox), step, FALSE, FALSE, 2);
3169   gtk_box_pack_start (GTK_BOX (vbox), navigation, FALSE, FALSE, 2);
3170   gtk_box_pack_start (GTK_BOX (vbox), colorbalance, FALSE, FALSE, 2);
3171   gtk_box_pack_start (GTK_BOX (vbox), gtk_hseparator_new (), FALSE, FALSE, 2);
3172   gtk_box_pack_start (GTK_BOX (vbox), app->seek_scale, FALSE, FALSE, 2);
3173   gtk_box_pack_start (GTK_BOX (vbox), app->statusbar, FALSE, FALSE, 2);
3174
3175   /* connect things ... */
3176   g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb),
3177       app);
3178   g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb),
3179       app);
3180   g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb),
3181       app);
3182
3183   g_signal_connect (G_OBJECT (app->window), "delete-event",
3184       G_CALLBACK (delete_event_cb), app);
3185 }
3186
3187 static void
3188 set_defaults (SeekApp * app)
3189 {
3190   memset (app, 0, sizeof (SeekApp));
3191
3192   app->flush_seek = TRUE;
3193   app->scrub = TRUE;
3194   app->rate = 1.0;
3195
3196   app->position = app->duration = -1;
3197   app->state = GST_STATE_NULL;
3198
3199   app->need_streams = TRUE;
3200
3201   g_static_mutex_init (&app->state_mutex);
3202
3203   app->play_rate = 1.0;
3204 }
3205
3206 static void
3207 reset_app (SeekApp * app)
3208 {
3209   g_free (app->audiosink_str);
3210   g_free (app->videosink_str);
3211
3212   g_list_free (app->formats);
3213
3214   g_static_mutex_free (&app->state_mutex);
3215
3216   if (app->xoverlay_element)
3217     gst_object_unref (app->xoverlay_element);
3218   if (app->navigation_element)
3219     gst_object_unref (app->navigation_element);
3220
3221   g_list_foreach (app->paths, (GFunc) g_free, NULL);
3222   g_list_free (app->paths);
3223   g_list_foreach (app->sub_paths, (GFunc) g_free, NULL);
3224   g_list_free (app->sub_paths);
3225
3226   g_print ("free pipeline\n");
3227   gst_object_unref (app->pipeline);
3228 }
3229
3230 int
3231 main (int argc, char **argv)
3232 {
3233   SeekApp app;
3234   GOptionEntry options[] = {
3235     {"stats", 's', 0, G_OPTION_ARG_NONE, &app.stats,
3236         "Show pad stats", NULL},
3237     {"verbose", 'v', 0, G_OPTION_ARG_NONE, &app.verbose,
3238         "Verbose properties", NULL},
3239     {NULL}
3240   };
3241   GOptionContext *ctx;
3242   GError *err = NULL;
3243
3244   set_defaults (&app);
3245
3246 #if !GLIB_CHECK_VERSION (2, 31, 0)
3247   if (!g_thread_supported ())
3248     g_thread_init (NULL);
3249 #endif
3250
3251   ctx = g_option_context_new ("- test seeking in gsteamer");
3252   g_option_context_add_main_entries (ctx, options, NULL);
3253   g_option_context_add_group (ctx, gst_init_get_option_group ());
3254   g_option_context_add_group (ctx, gtk_get_option_group (TRUE));
3255
3256   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
3257     g_print ("Error initializing: %s\n", err->message);
3258     exit (1);
3259   }
3260
3261   GST_DEBUG_CATEGORY_INIT (seek_debug, "seek", 0, "seek example");
3262
3263   if (argc < 3) {
3264     print_usage (argc, argv);
3265     exit (-1);
3266   }
3267
3268   app.pipeline_type = atoi (argv[1]);
3269
3270   if (app.pipeline_type < 0 || app.pipeline_type >= G_N_ELEMENTS (pipelines)) {
3271     print_usage (argc, argv);
3272     exit (-1);
3273   }
3274
3275   app.pipeline_spec = argv[2];
3276
3277   if (g_path_is_absolute (app.pipeline_spec) &&
3278       (g_strrstr (app.pipeline_spec, "*") != NULL ||
3279           g_strrstr (app.pipeline_spec, "?") != NULL)) {
3280     app.paths = handle_wildcards (app.pipeline_spec);
3281   } else {
3282     app.paths = g_list_prepend (app.paths, g_strdup (app.pipeline_spec));
3283   }
3284
3285   if (!app.paths) {
3286     g_print ("opening %s failed\n", app.pipeline_spec);
3287     exit (-1);
3288   }
3289
3290   app.current_path = app.paths;
3291
3292   if (argc > 3 && argv[3]) {
3293     if (g_path_is_absolute (argv[3]) &&
3294         (g_strrstr (argv[3], "*") != NULL ||
3295             g_strrstr (argv[3], "?") != NULL)) {
3296       app.sub_paths = handle_wildcards (argv[3]);
3297     } else {
3298       app.sub_paths = g_list_prepend (app.sub_paths, g_strdup (argv[3]));
3299     }
3300
3301     if (!app.sub_paths) {
3302       g_print ("opening %s failed\n", argv[3]);
3303       exit (-1);
3304     }
3305
3306     app.current_sub_path = app.sub_paths;
3307   }
3308
3309   pipelines[app.pipeline_type].func (&app, app.current_path->data);
3310   g_assert (app.pipeline);
3311
3312   create_ui (&app);
3313
3314   /* show the gui. */
3315   gtk_widget_show_all (app.window);
3316
3317   /* realize window now so that the video window gets created and we can
3318    * obtain its XID before the pipeline is started up and the videosink
3319    * asks for the XID of the window to render onto */
3320   gtk_widget_realize (app.window);
3321
3322 #if defined (GDK_WINDOWING_X11) || defined (GDK_WINDOWING_WIN32) || defined (GDK_WINDOWING_QUARTZ)
3323   /* we should have the XID now */
3324   g_assert (app.embed_xid != 0);
3325
3326   if (app.pipeline_type == 0) {
3327     gst_x_overlay_set_window_handle (GST_X_OVERLAY (app.pipeline),
3328         app.embed_xid);
3329   }
3330 #endif
3331
3332   if (app.verbose) {
3333     g_signal_connect (app.pipeline, "deep_notify",
3334         G_CALLBACK (gst_object_default_deep_notify), NULL);
3335   }
3336
3337   connect_bus_signals (&app);
3338
3339   gtk_main ();
3340
3341   g_print ("NULL pipeline\n");
3342   gst_element_set_state (app.pipeline, GST_STATE_NULL);
3343
3344   reset_app (&app);
3345
3346   return 0;
3347 }