Merge branch 'master' into 0.11
[platform/upstream/gst-plugins-base.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/video/videooverlay.h>
51 #include <gst/video/colorbalance.h>
52 #include <gst/interfaces/navigation.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 *overlay_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   GMutex 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_playbin_pipeline (SeekApp * app, const gchar * location)
263 {
264   GstElement *pipeline;
265
266   app->pipeline = pipeline = gst_element_factory_make ("playbin", "playbin");
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   {"playbin", make_playbin_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_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_mutex_unlock (&app->state_mutex);
755
756   return;
757
758 failed:
759   {
760     g_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_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_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_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 playbin */
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 (),
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 playbin */
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   GstSample *sample = NULL;
1315   GstCaps *caps;
1316
1317   GST_DEBUG ("taking snapshot");
1318
1319   /* convert to our desired format (RGB24) */
1320   caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING, "RGB",
1321       /* Note: we don't ask for a specific width/height here, so that
1322        * videoscale can adjust dimensions from a non-1/1 pixel aspect
1323        * ratio to a 1/1 pixel-aspect-ratio */
1324       "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL);
1325
1326   /* convert the latest sample to the requested format */
1327   g_signal_emit_by_name (app->pipeline, "convert-sample", caps, &sample);
1328   gst_caps_unref (caps);
1329
1330   if (sample) {
1331     GstBuffer *buffer;
1332     GstCaps *caps;
1333     GstStructure *s;
1334     gboolean res;
1335     gint width, height;
1336     GdkPixbuf *pixbuf;
1337     GError *error = NULL;
1338     GstMapInfo map;
1339
1340     /* get the snapshot buffer format now. We set the caps on the appsink so
1341      * that it can only be an rgb buffer. The only thing we have not specified
1342      * on the caps is the height, which is dependant on the pixel-aspect-ratio
1343      * of the source material */
1344     caps = gst_sample_get_caps (sample);
1345     if (!caps) {
1346       g_warning ("could not get snapshot format\n");
1347       goto done;
1348     }
1349     s = gst_caps_get_structure (caps, 0);
1350
1351     /* we need to get the final caps on the buffer to get the size */
1352     res = gst_structure_get_int (s, "width", &width);
1353     res |= gst_structure_get_int (s, "height", &height);
1354     if (!res) {
1355       g_warning ("could not get snapshot dimension\n");
1356       goto done;
1357     }
1358
1359     /* create pixmap from buffer and save, gstreamer video buffers have a stride
1360      * that is rounded up to the nearest multiple of 4 */
1361     buffer = gst_sample_get_buffer (sample);
1362     gst_buffer_map (buffer, &map, GST_MAP_READ);
1363     pixbuf = gdk_pixbuf_new_from_data (map.data,
1364         GDK_COLORSPACE_RGB, FALSE, 8, width, height,
1365         GST_ROUND_UP_4 (width * 3), NULL, NULL);
1366
1367     /* save the pixbuf */
1368     gdk_pixbuf_save (pixbuf, "snapshot.png", "png", &error, NULL);
1369     gst_buffer_unmap (buffer, &map);
1370
1371   done:
1372     gst_sample_unref (sample);
1373   }
1374 }
1375
1376 /* called when the Step button is pressed */
1377 static void
1378 step_cb (GtkButton * button, SeekApp * app)
1379 {
1380   GstEvent *event;
1381   GstFormat format;
1382   guint64 amount;
1383   gdouble rate;
1384   gboolean flush, res;
1385   gint active;
1386
1387   active = gtk_combo_box_get_active (GTK_COMBO_BOX (app->step_format_combo));
1388   amount =
1389       gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON
1390       (app->step_amount_spinbutton));
1391   rate =
1392       gtk_spin_button_get_value (GTK_SPIN_BUTTON (app->step_rate_spinbutton));
1393   flush = TRUE;
1394
1395   switch (active) {
1396     case 0:
1397       format = GST_FORMAT_BUFFERS;
1398       break;
1399     case 1:
1400       format = GST_FORMAT_TIME;
1401       amount *= GST_MSECOND;
1402       break;
1403     default:
1404       format = GST_FORMAT_UNDEFINED;
1405       break;
1406   }
1407
1408   event = gst_event_new_step (format, amount, rate, flush, FALSE);
1409
1410   res = send_event (app, event);
1411
1412   if (!res) {
1413     g_print ("Sending step event failed\n");
1414   }
1415 }
1416
1417 static void
1418 message_received (GstBus * bus, GstMessage * message, SeekApp * app)
1419 {
1420   const GstStructure *s;
1421
1422   switch (GST_MESSAGE_TYPE (message)) {
1423     case GST_MESSAGE_ERROR:
1424       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (app->pipeline),
1425           GST_DEBUG_GRAPH_SHOW_ALL, "seek.error");
1426       break;
1427     case GST_MESSAGE_WARNING:
1428       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (app->pipeline),
1429           GST_DEBUG_GRAPH_SHOW_ALL, "seek.warning");
1430       break;
1431     default:
1432       break;
1433   }
1434
1435   s = gst_message_get_structure (message);
1436   g_print ("message from \"%s\" (%s): ",
1437       GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message))),
1438       gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
1439   if (s) {
1440     gchar *sstr;
1441
1442     sstr = gst_structure_to_string (s);
1443     g_print ("%s\n", sstr);
1444     g_free (sstr);
1445   } else {
1446     g_print ("no message details\n");
1447   }
1448 }
1449
1450 static void
1451 do_shuttle (SeekApp * app)
1452 {
1453   guint64 duration;
1454
1455   if (app->shuttling)
1456     duration = 40 * GST_MSECOND;
1457   else
1458     duration = -1;
1459
1460   gst_element_send_event (app->pipeline,
1461       gst_event_new_step (GST_FORMAT_TIME, duration, app->shuttle_rate, FALSE,
1462           FALSE));
1463 }
1464
1465 static void
1466 msg_sync_step_done (GstBus * bus, GstMessage * message, SeekApp * app)
1467 {
1468   GstFormat format;
1469   guint64 amount;
1470   gdouble rate;
1471   gboolean flush;
1472   gboolean intermediate;
1473   guint64 duration;
1474   gboolean eos;
1475
1476   gst_message_parse_step_done (message, &format, &amount, &rate, &flush,
1477       &intermediate, &duration, &eos);
1478
1479   if (eos) {
1480     g_print ("stepped till EOS\n");
1481     return;
1482   }
1483
1484   if (g_mutex_trylock (&app->state_mutex)) {
1485     if (app->shuttling)
1486       do_shuttle (app);
1487     g_mutex_unlock (&app->state_mutex);
1488   } else {
1489     /* ignore step messages that come while we are doing a state change */
1490     g_print ("state change is busy\n");
1491   }
1492 }
1493
1494 static void
1495 shuttle_toggled (GtkToggleButton * button, SeekApp * app)
1496 {
1497   gboolean active;
1498
1499   active = gtk_toggle_button_get_active (button);
1500
1501   if (active != app->shuttling) {
1502     app->shuttling = active;
1503     g_print ("shuttling %s\n", app->shuttling ? "active" : "inactive");
1504     if (active) {
1505       app->shuttle_rate = 0.0;
1506       app->play_rate = 1.0;
1507       pause_cb (NULL, app);
1508       gst_element_get_state (app->pipeline, NULL, NULL, -1);
1509     }
1510   }
1511 }
1512
1513 static void
1514 shuttle_rate_switch (SeekApp * app)
1515 {
1516   GstSeekFlags flags;
1517   GstEvent *s_event;
1518   gboolean res;
1519
1520   if (app->state == GST_STATE_PLAYING) {
1521     /* pause when we need to */
1522     pause_cb (NULL, app);
1523     gst_element_get_state (app->pipeline, NULL, NULL, -1);
1524   }
1525
1526   if (app->play_rate == 1.0)
1527     app->play_rate = -1.0;
1528   else
1529     app->play_rate = 1.0;
1530
1531   g_print ("rate changed to %lf %" GST_TIME_FORMAT "\n", app->play_rate,
1532       GST_TIME_ARGS (app->position));
1533
1534   flags = GST_SEEK_FLAG_FLUSH;
1535   flags |= GST_SEEK_FLAG_ACCURATE;
1536
1537   if (app->play_rate >= 0.0) {
1538     s_event = gst_event_new_seek (app->play_rate,
1539         GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, app->position,
1540         GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
1541   } else {
1542     s_event = gst_event_new_seek (app->play_rate,
1543         GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0),
1544         GST_SEEK_TYPE_SET, app->position);
1545   }
1546   res = send_event (app, s_event);
1547   if (res) {
1548     gst_element_get_state (app->pipeline, NULL, NULL, SEEK_TIMEOUT);
1549   } else {
1550     g_print ("seek failed\n");
1551   }
1552 }
1553
1554 static void
1555 shuttle_value_changed (GtkRange * range, SeekApp * app)
1556 {
1557   gdouble rate;
1558
1559   rate = gtk_range_get_value (range);
1560
1561   if (rate == 0.0) {
1562     g_print ("rate 0.0, pause\n");
1563     pause_cb (NULL, app);
1564     gst_element_get_state (app->pipeline, NULL, NULL, -1);
1565   } else {
1566     g_print ("rate changed %0.3g\n", rate);
1567
1568     if ((rate < 0.0 && app->play_rate > 0.0) || (rate > 0.0
1569             && app->play_rate < 0.0)) {
1570       shuttle_rate_switch (app);
1571     }
1572
1573     app->shuttle_rate = ABS (rate);
1574     if (app->state != GST_STATE_PLAYING) {
1575       do_shuttle (app);
1576       play_cb (NULL, app);
1577     }
1578   }
1579 }
1580
1581 static void
1582 colorbalance_value_changed (GtkRange * range, SeekApp * app)
1583 {
1584   const gchar *label;
1585   gdouble val;
1586   gint ival;
1587   GstColorBalanceChannel *channel = NULL;
1588   const GList *channels, *l;
1589
1590   if (range == GTK_RANGE (app->contrast_scale))
1591     label = "CONTRAST";
1592   else if (range == GTK_RANGE (app->brightness_scale))
1593     label = "BRIGHTNESS";
1594   else if (range == GTK_RANGE (app->hue_scale))
1595     label = "HUE";
1596   else if (range == GTK_RANGE (app->saturation_scale))
1597     label = "SATURATION";
1598   else
1599     g_assert_not_reached ();
1600
1601   val = gtk_range_get_value (range);
1602
1603   g_print ("colorbalance %s value changed %lf\n", label, val / N_GRAD);
1604
1605   if (!app->colorbalance_element) {
1606     find_interface_elements (app);
1607     if (!app->colorbalance_element)
1608       return;
1609   }
1610
1611   channels =
1612       gst_color_balance_list_channels (GST_COLOR_BALANCE
1613       (app->colorbalance_element));
1614   for (l = channels; l; l = l->next) {
1615     GstColorBalanceChannel *tmp = l->data;
1616
1617     if (g_strrstr (tmp->label, label)) {
1618       channel = tmp;
1619       break;
1620     }
1621   }
1622
1623   if (!channel)
1624     return;
1625
1626   ival =
1627       (gint) (0.5 + channel->min_value +
1628       (val / N_GRAD) * ((gdouble) channel->max_value -
1629           (gdouble) channel->min_value));
1630   gst_color_balance_set_value (GST_COLOR_BALANCE (app->colorbalance_element),
1631       channel, ival);
1632 }
1633
1634 static void
1635 seek_format_changed_cb (GtkComboBox * box, SeekApp * app)
1636 {
1637   gchar *format_str;
1638   GList *l;
1639   const GstFormatDefinition *format = NULL;
1640
1641   format_str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (box));
1642
1643   for (l = app->formats; l; l = l->next) {
1644     const GstFormatDefinition *tmp = l->data;
1645
1646     if (g_strcmp0 (tmp->nick, format_str) == 0) {
1647       format = tmp;
1648       break;
1649     }
1650   }
1651
1652   if (!format)
1653     goto done;
1654
1655   app->seek_format = format;
1656   update_scale (app);
1657
1658 done:
1659   g_free (format_str);
1660 }
1661
1662 static void
1663 update_formats (SeekApp * app)
1664 {
1665   GstIterator *it;
1666   gboolean done;
1667   GList *l;
1668   GValue item = { 0, };
1669   gchar *selected;
1670   gint selected_idx = 0, i;
1671
1672   selected =
1673       gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT
1674       (app->seek_format_combo));
1675   if (selected == NULL)
1676     selected = g_strdup ("time");
1677
1678   it = gst_format_iterate_definitions ();
1679   done = FALSE;
1680
1681   g_list_free (app->formats);
1682   app->formats = NULL;
1683
1684   while (!done) {
1685     switch (gst_iterator_next (it, &item)) {
1686       case GST_ITERATOR_OK:{
1687         GstFormatDefinition *def = g_value_get_pointer (&item);
1688
1689         app->formats = g_list_prepend (app->formats, def);
1690         g_value_reset (&item);
1691         break;
1692       }
1693       case GST_ITERATOR_RESYNC:
1694         g_list_free (app->formats);
1695         app->formats = NULL;
1696         gst_iterator_resync (it);
1697         break;
1698       case GST_ITERATOR_ERROR:
1699       case GST_ITERATOR_DONE:
1700       default:
1701         done = TRUE;
1702         break;
1703     }
1704   }
1705   g_value_unset (&item);
1706
1707   app->formats = g_list_reverse (app->formats);
1708   gst_iterator_free (it);
1709
1710   g_signal_handlers_block_by_func (app->seek_format_combo,
1711       seek_format_changed_cb, app);
1712   gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT (app->seek_format_combo));
1713
1714   for (i = 0, l = app->formats; l; l = l->next, i++) {
1715     const GstFormatDefinition *def = l->data;
1716
1717     gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (app->seek_format_combo),
1718         def->nick);
1719     if (g_strcmp0 (def->nick, selected) == 0)
1720       selected_idx = i;
1721   }
1722   g_signal_handlers_unblock_by_func (app->seek_format_combo,
1723       seek_format_changed_cb, app);
1724
1725   gtk_combo_box_set_active (GTK_COMBO_BOX (app->seek_format_combo),
1726       selected_idx);
1727
1728   g_free (selected);
1729 }
1730
1731 static void
1732 msg_async_done (GstBus * bus, GstMessage * message, SeekApp * app)
1733 {
1734   GST_DEBUG ("async done");
1735
1736   /* Now query all available GstFormats */
1737   update_formats (app);
1738
1739   /* when we get ASYNC_DONE we can query position, duration and other
1740    * properties */
1741   update_scale (app);
1742
1743   /* update the available streams */
1744   update_streams (app);
1745
1746   find_interface_elements (app);
1747 }
1748
1749 static void
1750 msg_state_changed (GstBus * bus, GstMessage * message, SeekApp * app)
1751 {
1752   const GstStructure *s;
1753
1754   s = gst_message_get_structure (message);
1755
1756   /* We only care about state changed on the pipeline */
1757   if (s && GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (app->pipeline)) {
1758     GstState old, new, pending;
1759
1760     gst_message_parse_state_changed (message, &old, &new, &pending);
1761
1762     /* When state of the pipeline changes to paused or playing we start updating scale */
1763     if (new == GST_STATE_PLAYING) {
1764       set_update_scale (app, TRUE);
1765     } else {
1766       set_update_scale (app, FALSE);
1767     }
1768
1769     /* dump graph for (some) pipeline state changes */
1770     {
1771       gchar *dump_name;
1772
1773       dump_name = g_strdup_printf ("seek.%s_%s",
1774           gst_element_state_get_name (old), gst_element_state_get_name (new));
1775
1776       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (app->pipeline),
1777           GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
1778
1779       g_free (dump_name);
1780     }
1781   }
1782 }
1783
1784 static void
1785 msg_segment_done (GstBus * bus, GstMessage * message, SeekApp * app)
1786 {
1787   GstEvent *s_event;
1788   GstSeekFlags flags;
1789   gboolean res;
1790   GstFormat format;
1791
1792   GST_DEBUG ("position is %" GST_TIME_FORMAT, GST_TIME_ARGS (app->position));
1793   gst_message_parse_segment_done (message, &format, &app->position);
1794   GST_DEBUG ("end of segment at %" GST_TIME_FORMAT,
1795       GST_TIME_ARGS (app->position));
1796
1797   flags = 0;
1798   /* in the segment-done callback we never flush as this would not make sense
1799    * for seamless playback. */
1800   if (app->loop_seek)
1801     flags |= GST_SEEK_FLAG_SEGMENT;
1802   if (app->skip_seek)
1803     flags |= GST_SEEK_FLAG_SKIP;
1804
1805   s_event = gst_event_new_seek (app->rate,
1806       GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0),
1807       GST_SEEK_TYPE_SET, app->duration);
1808
1809   GST_DEBUG ("restart loop with rate %lf to 0 / %" GST_TIME_FORMAT,
1810       app->rate, GST_TIME_ARGS (app->duration));
1811
1812   res = send_event (app, s_event);
1813   if (!res)
1814     g_print ("segment seek failed\n");
1815 }
1816
1817 /* in stream buffering mode we PAUSE the pipeline until we receive a 100%
1818  * message */
1819 static void
1820 do_stream_buffering (SeekApp * app, gint percent)
1821 {
1822   gchar *bufstr;
1823
1824   gtk_statusbar_pop (GTK_STATUSBAR (app->statusbar), app->status_id);
1825   bufstr = g_strdup_printf ("Buffering...%d", percent);
1826   gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id, bufstr);
1827   g_free (bufstr);
1828
1829   if (percent == 100) {
1830     /* a 100% message means buffering is done */
1831     app->buffering = FALSE;
1832     /* if the desired state is playing, go back */
1833     if (app->state == GST_STATE_PLAYING) {
1834       /* no state management needed for live pipelines */
1835       if (!app->is_live) {
1836         fprintf (stderr, "Done buffering, setting pipeline to PLAYING ...\n");
1837         gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
1838       }
1839       gtk_statusbar_pop (GTK_STATUSBAR (app->statusbar), app->status_id);
1840       gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id,
1841           "Playing");
1842     }
1843   } else {
1844     /* buffering busy */
1845     if (app->buffering == FALSE && app->state == GST_STATE_PLAYING) {
1846       /* we were not buffering but PLAYING, PAUSE  the pipeline. */
1847       if (!app->is_live) {
1848         fprintf (stderr, "Buffering, setting pipeline to PAUSED ...\n");
1849         gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
1850       }
1851     }
1852     app->buffering = TRUE;
1853   }
1854 }
1855
1856 static void
1857 do_download_buffering (SeekApp * app, gint percent)
1858 {
1859   if (!app->buffering && percent < 100) {
1860     gchar *bufstr;
1861
1862     app->buffering = TRUE;
1863
1864     bufstr = g_strdup_printf ("Downloading...");
1865     gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id, bufstr);
1866     g_free (bufstr);
1867
1868     /* once we get a buffering message, we'll do the fill update */
1869     set_update_fill (app, TRUE);
1870
1871     if (app->state == GST_STATE_PLAYING && !app->is_live) {
1872       fprintf (stderr, "Downloading, setting pipeline to PAUSED ...\n");
1873       gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
1874       /* user has to manually start the playback */
1875       app->state = GST_STATE_PAUSED;
1876     }
1877   }
1878 }
1879
1880 static void
1881 msg_buffering (GstBus * bus, GstMessage * message, SeekApp * app)
1882 {
1883   gint percent;
1884
1885   gst_message_parse_buffering (message, &percent);
1886
1887   /* get more stats */
1888   gst_message_parse_buffering_stats (message, &app->mode, NULL, NULL,
1889       &app->buffering_left);
1890
1891   switch (app->mode) {
1892     case GST_BUFFERING_DOWNLOAD:
1893       do_download_buffering (app, percent);
1894       break;
1895     case GST_BUFFERING_LIVE:
1896       app->is_live = TRUE;
1897     case GST_BUFFERING_TIMESHIFT:
1898     case GST_BUFFERING_STREAM:
1899       do_stream_buffering (app, percent);
1900       break;
1901   }
1902 }
1903
1904 static void
1905 msg_clock_lost (GstBus * bus, GstMessage * message, SeekApp * app)
1906 {
1907   g_print ("clock lost! PAUSE and PLAY to select a new clock\n");
1908   if (app->state == GST_STATE_PLAYING) {
1909     gst_element_set_state (app->pipeline, GST_STATE_PAUSED);
1910     gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
1911   }
1912 }
1913
1914 static gboolean
1915 is_valid_color_balance_element (GstElement * element)
1916 {
1917   GstColorBalance *bal = GST_COLOR_BALANCE (element);
1918   gboolean have_brightness = FALSE;
1919   gboolean have_contrast = FALSE;
1920   gboolean have_hue = FALSE;
1921   gboolean have_saturation = FALSE;
1922   const GList *channels, *l;
1923
1924   channels = gst_color_balance_list_channels (bal);
1925   for (l = channels; l; l = l->next) {
1926     GstColorBalanceChannel *ch = l->data;
1927
1928     if (g_strrstr (ch->label, "BRIGHTNESS"))
1929       have_brightness = TRUE;
1930     else if (g_strrstr (ch->label, "CONTRAST"))
1931       have_contrast = TRUE;
1932     else if (g_strrstr (ch->label, "HUE"))
1933       have_hue = TRUE;
1934     else if (g_strrstr (ch->label, "SATURATION"))
1935       have_saturation = TRUE;
1936   }
1937
1938   return have_brightness && have_contrast && have_hue && have_saturation;
1939 }
1940
1941 static void
1942 find_interface_elements (SeekApp * app)
1943 {
1944   GstIterator *it;
1945   GValue item = { 0, };
1946   gboolean done = FALSE, hardware = FALSE;
1947
1948   if (app->pipeline_type == 0)
1949     return;
1950
1951   if (app->navigation_element)
1952     gst_object_unref (app->navigation_element);
1953   app->navigation_element = NULL;
1954
1955   if (app->colorbalance_element)
1956     gst_object_unref (app->colorbalance_element);
1957   app->colorbalance_element = NULL;
1958
1959   app->navigation_element =
1960       gst_bin_get_by_interface (GST_BIN (app->pipeline), GST_TYPE_NAVIGATION);
1961
1962   it = gst_bin_iterate_all_by_interface (GST_BIN (app->pipeline),
1963       GST_TYPE_COLOR_BALANCE);
1964   while (!done) {
1965     switch (gst_iterator_next (it, &item)) {
1966       case GST_ITERATOR_OK:{
1967         GstElement *element = GST_ELEMENT (g_value_get_object (&item));
1968
1969         if (is_valid_color_balance_element (element)) {
1970           if (!app->colorbalance_element) {
1971             app->colorbalance_element =
1972                 GST_ELEMENT_CAST (gst_object_ref (element));
1973             hardware =
1974                 (gst_color_balance_get_balance_type (GST_COLOR_BALANCE
1975                     (element)) == GST_COLOR_BALANCE_HARDWARE);
1976           } else if (!hardware) {
1977             gboolean tmp =
1978                 (gst_color_balance_get_balance_type (GST_COLOR_BALANCE
1979                     (element)) == GST_COLOR_BALANCE_HARDWARE);
1980
1981             if (tmp) {
1982               if (app->colorbalance_element)
1983                 gst_object_unref (app->colorbalance_element);
1984               app->colorbalance_element =
1985                   GST_ELEMENT_CAST (gst_object_ref (element));
1986               hardware = TRUE;
1987             }
1988           }
1989         }
1990
1991         g_value_reset (&item);
1992
1993         if (hardware && app->colorbalance_element)
1994           done = TRUE;
1995         break;
1996       }
1997       case GST_ITERATOR_RESYNC:
1998         gst_iterator_resync (it);
1999         done = FALSE;
2000         hardware = FALSE;
2001         if (app->colorbalance_element)
2002           gst_object_unref (app->colorbalance_element);
2003         app->colorbalance_element = NULL;
2004         break;
2005       case GST_ITERATOR_DONE:
2006       case GST_ITERATOR_ERROR:
2007       default:
2008         done = TRUE;
2009     }
2010   }
2011
2012   g_value_unset (&item);
2013   gst_iterator_free (it);
2014 }
2015
2016 /* called when Navigation command button is pressed */
2017 static void
2018 navigation_cmd_cb (GtkButton * button, SeekApp * app)
2019 {
2020   GstNavigationCommand cmd = GST_NAVIGATION_COMMAND_INVALID;
2021   gint i;
2022
2023   if (!app->navigation_element) {
2024     find_interface_elements (app);
2025     if (!app->navigation_element)
2026       return;
2027   }
2028
2029   for (i = 0; i < G_N_ELEMENTS (app->navigation_buttons); i++) {
2030     if (app->navigation_buttons[i].button == GTK_WIDGET (button)) {
2031       cmd = app->navigation_buttons[i].cmd;
2032       break;
2033     }
2034   }
2035
2036   if (cmd != GST_NAVIGATION_COMMAND_INVALID)
2037     gst_navigation_send_command (GST_NAVIGATION (app->navigation_element), cmd);
2038 }
2039
2040 #if defined (GDK_WINDOWING_X11) || defined (GDK_WINDOWING_WIN32) || defined (GDK_WINDOWING_QUARTZ)
2041 /* We set the xid here in response to the prepare-xwindow-id message via a
2042  * bus sync handler because we don't know the actual videosink used from the
2043  * start (as we don't know the pipeline, or bin elements such as autovideosink
2044  * or gconfvideosink may be used which create the actual videosink only once
2045  * the pipeline is started) */
2046 static GstBusSyncReply
2047 bus_sync_handler (GstBus * bus, GstMessage * message, SeekApp * app)
2048 {
2049   if (gst_is_video_overlay_prepare_window_handle_message (message)) {
2050     GstElement *element = GST_ELEMENT (GST_MESSAGE_SRC (message));
2051
2052     if (app->overlay_element)
2053       gst_object_unref (app->overlay_element);
2054     app->overlay_element = GST_ELEMENT (gst_object_ref (element));
2055
2056     g_print ("got prepare-xwindow-id, setting XID %" G_GUINTPTR_FORMAT "\n",
2057         app->embed_xid);
2058
2059     if (g_object_class_find_property (G_OBJECT_GET_CLASS (element),
2060             "force-aspect-ratio")) {
2061       g_object_set (element, "force-aspect-ratio", TRUE, NULL);
2062     }
2063
2064     /* Should have been initialised from main thread before (can't use
2065      * GDK_WINDOW_XID here with Gtk+ >= 2.18, because the sync handler will
2066      * be called from a streaming thread and GDK_WINDOW_XID maps to more than
2067      * a simple structure lookup with Gtk+ >= 2.18, where 'more' is stuff that
2068      * shouldn't be done from a non-GUI thread without explicit locking).  */
2069     g_assert (app->embed_xid != 0);
2070
2071     gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (element),
2072         app->embed_xid);
2073
2074     find_interface_elements (app);
2075   }
2076   return GST_BUS_PASS;
2077 }
2078 #endif
2079
2080 static gboolean
2081 draw_cb (GtkWidget * widget, cairo_t * cr, SeekApp * app)
2082 {
2083   if (app->state < GST_STATE_PAUSED) {
2084     int width, height;
2085
2086     width = gtk_widget_get_allocated_width (widget);
2087     height = gtk_widget_get_allocated_height (widget);
2088     cairo_set_source_rgb (cr, 0, 0, 0);
2089     cairo_rectangle (cr, 0, 0, width, height);
2090     cairo_fill (cr);
2091     return TRUE;
2092   }
2093
2094   if (app->overlay_element)
2095     gst_video_overlay_expose (GST_VIDEO_OVERLAY (app->overlay_element));
2096
2097   return FALSE;
2098 }
2099
2100 static void
2101 realize_cb (GtkWidget * widget, SeekApp * app)
2102 {
2103   GdkWindow *window = gtk_widget_get_window (widget);
2104
2105   /* This is here just for pedagogical purposes, GDK_WINDOW_XID will call it
2106    * as well */
2107   if (!gdk_window_ensure_native (window))
2108     g_error ("Couldn't create native window needed for GstXOverlay!");
2109
2110 #if defined (GDK_WINDOWING_WIN32)
2111   app->embed_xid = GDK_WINDOW_HWND (window);
2112   g_print ("Window realize: video window HWND = %lu\n", app->embed_xid);
2113 #elif defined (GDK_WINDOWING_QUARTZ)
2114   app->embed_xid = gdk_quartz_window_get_nsview (window);
2115   g_print ("Window realize: video window NSView = %p\n", app->embed_xid);
2116 #elif defined (GDK_WINDOWING_X11)
2117   app->embed_xid = GDK_WINDOW_XID (window);
2118   g_print ("Window realize: video window XID = %" G_GUINTPTR_FORMAT "\n",
2119       app->embed_xid);
2120 #endif
2121 }
2122
2123 static gboolean
2124 button_press_cb (GtkWidget * widget, GdkEventButton * event, SeekApp * app)
2125 {
2126   gtk_widget_grab_focus (widget);
2127
2128   if (app->navigation_element)
2129     gst_navigation_send_mouse_event (GST_NAVIGATION (app->navigation_element),
2130         "mouse-button-press", event->button, event->x, event->y);
2131
2132   return FALSE;
2133 }
2134
2135 static gboolean
2136 button_release_cb (GtkWidget * widget, GdkEventButton * event, SeekApp * app)
2137 {
2138   if (app->navigation_element)
2139     gst_navigation_send_mouse_event (GST_NAVIGATION (app->navigation_element),
2140         "mouse-button-release", event->button, event->x, event->y);
2141
2142   return FALSE;
2143 }
2144
2145 static gboolean
2146 key_press_cb (GtkWidget * widget, GdkEventKey * event, SeekApp * app)
2147 {
2148   if (app->navigation_element)
2149     gst_navigation_send_key_event (GST_NAVIGATION (app->navigation_element),
2150         "key-press", gdk_keyval_name (event->keyval));
2151
2152   return FALSE;
2153 }
2154
2155 static gboolean
2156 key_release_cb (GtkWidget * widget, GdkEventKey * event, SeekApp * app)
2157 {
2158   if (app->navigation_element)
2159     gst_navigation_send_key_event (GST_NAVIGATION (app->navigation_element),
2160         "key-release", gdk_keyval_name (event->keyval));
2161
2162   return FALSE;
2163 }
2164
2165 static gboolean
2166 motion_notify_cb (GtkWidget * widget, GdkEventMotion * event, SeekApp * app)
2167 {
2168   if (app->navigation_element)
2169     gst_navigation_send_mouse_event (GST_NAVIGATION (app->navigation_element),
2170         "mouse-move", 0, event->x, event->y);
2171
2172   return FALSE;
2173 }
2174
2175 static void
2176 msg_eos (GstBus * bus, GstMessage * message, SeekApp * app)
2177 {
2178   message_received (bus, message, app);
2179
2180   /* Set new uri for playerbins and continue playback */
2181   if (app->current_path && app->pipeline_type == 0) {
2182     stop_cb (NULL, app);
2183     app->current_path = g_list_next (app->current_path);
2184     app->current_sub_path = g_list_next (app->current_sub_path);
2185     if (app->current_path) {
2186       playbin_set_uri (app->pipeline, app->current_path->data,
2187           app->current_sub_path ? app->current_sub_path->data : NULL);
2188       play_cb (NULL, app);
2189     }
2190   }
2191 }
2192
2193 static void
2194 msg_step_done (GstBus * bus, GstMessage * message, SeekApp * app)
2195 {
2196   if (!app->shuttling)
2197     message_received (bus, message, app);
2198 }
2199
2200 static void
2201 msg (GstBus * bus, GstMessage * message, SeekApp * app)
2202 {
2203   GstNavigationMessageType nav_type;
2204
2205   nav_type = gst_navigation_message_get_type (message);
2206   switch (nav_type) {
2207     case GST_NAVIGATION_MESSAGE_COMMANDS_CHANGED:{
2208       GstQuery *query;
2209       gboolean res, j;
2210
2211       /* Heuristic to detect if we're dealing with a DVD menu */
2212       query = gst_navigation_query_new_commands ();
2213       res = gst_element_query (GST_ELEMENT (GST_MESSAGE_SRC (message)), query);
2214
2215       for (j = 0; j < G_N_ELEMENTS (app->navigation_buttons); j++)
2216         gtk_widget_set_sensitive (app->navigation_buttons[j].button, FALSE);
2217
2218       if (res) {
2219         gboolean is_menu = FALSE;
2220         guint i, n;
2221
2222         if (gst_navigation_query_parse_commands_length (query, &n)) {
2223           for (i = 0; i < n; i++) {
2224             GstNavigationCommand cmd;
2225
2226             if (!gst_navigation_query_parse_commands_nth (query, i, &cmd))
2227               break;
2228
2229             is_menu |= (cmd == GST_NAVIGATION_COMMAND_ACTIVATE);
2230             is_menu |= (cmd == GST_NAVIGATION_COMMAND_LEFT);
2231             is_menu |= (cmd == GST_NAVIGATION_COMMAND_RIGHT);
2232             is_menu |= (cmd == GST_NAVIGATION_COMMAND_UP);
2233             is_menu |= (cmd == GST_NAVIGATION_COMMAND_DOWN);
2234
2235             for (j = 0; j < G_N_ELEMENTS (app->navigation_buttons); j++) {
2236               if (app->navigation_buttons[j].cmd != cmd)
2237                 continue;
2238
2239               gtk_widget_set_sensitive (app->navigation_buttons[j].button,
2240                   TRUE);
2241             }
2242           }
2243         }
2244
2245         gtk_widget_set_sensitive (GTK_WIDGET (app->seek_scale), !is_menu);
2246       } else {
2247         g_assert_not_reached ();
2248       }
2249
2250       gst_query_unref (query);
2251       message_received (bus, message, app);
2252       break;
2253     }
2254     default:
2255       break;
2256   }
2257 }
2258
2259 static void
2260 connect_bus_signals (SeekApp * app)
2261 {
2262   GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
2263
2264 #if defined (GDK_WINDOWING_X11) || defined (GDK_WINDOWING_WIN32) || defined (GDK_WINDOWING_QUARTZ)
2265   if (app->pipeline_type != 0) {
2266     /* handle prepare-xwindow-id element message synchronously, but only for non-playbin */
2267     gst_bus_set_sync_handler (bus, (GstBusSyncHandler) bus_sync_handler, app);
2268   }
2269 #endif
2270
2271   gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
2272   gst_bus_enable_sync_message_emission (bus);
2273
2274   g_signal_connect (bus, "message::state-changed",
2275       G_CALLBACK (msg_state_changed), app);
2276   g_signal_connect (bus, "message::segment-done", G_CALLBACK (msg_segment_done),
2277       app);
2278   g_signal_connect (bus, "message::async-done", G_CALLBACK (msg_async_done),
2279       app);
2280
2281   g_signal_connect (bus, "message::new-clock", G_CALLBACK (message_received),
2282       app);
2283   g_signal_connect (bus, "message::clock-lost", G_CALLBACK (msg_clock_lost),
2284       app);
2285   g_signal_connect (bus, "message::error", G_CALLBACK (message_received), app);
2286   g_signal_connect (bus, "message::warning", G_CALLBACK (message_received),
2287       app);
2288   g_signal_connect (bus, "message::eos", G_CALLBACK (msg_eos), app);
2289   g_signal_connect (bus, "message::tag", G_CALLBACK (message_received), app);
2290   g_signal_connect (bus, "message::element", G_CALLBACK (message_received),
2291       app);
2292   g_signal_connect (bus, "message::segment-done", G_CALLBACK (message_received),
2293       app);
2294   g_signal_connect (bus, "message::buffering", G_CALLBACK (msg_buffering), app);
2295 //  g_signal_connect (bus, "message::step-done", G_CALLBACK (msg_step_done),
2296 //      app);
2297   g_signal_connect (bus, "message::step-start", G_CALLBACK (msg_step_done),
2298       app);
2299   g_signal_connect (bus, "sync-message::step-done",
2300       G_CALLBACK (msg_sync_step_done), app);
2301   g_signal_connect (bus, "message", G_CALLBACK (msg), app);
2302
2303   gst_object_unref (bus);
2304 }
2305
2306 /* Return GList of paths described in location string */
2307 static GList *
2308 handle_wildcards (const gchar * location)
2309 {
2310   GList *res = NULL;
2311   gchar *path = g_path_get_dirname (location);
2312   gchar *pattern = g_path_get_basename (location);
2313   GPatternSpec *pspec = g_pattern_spec_new (pattern);
2314   GDir *dir = g_dir_open (path, 0, NULL);
2315   const gchar *name;
2316
2317   g_print ("matching %s from %s\n", pattern, path);
2318
2319   if (!dir) {
2320     g_print ("opening directory %s failed\n", path);
2321     goto out;
2322   }
2323
2324   while ((name = g_dir_read_name (dir)) != NULL) {
2325     if (g_pattern_match_string (pspec, name)) {
2326       res = g_list_append (res, g_strjoin ("/", path, name, NULL));
2327       g_print ("  found clip %s\n", name);
2328     }
2329   }
2330
2331   g_dir_close (dir);
2332 out:
2333   g_pattern_spec_free (pspec);
2334   g_free (pattern);
2335   g_free (path);
2336
2337   return res;
2338 }
2339
2340 static void
2341 delete_event_cb (GtkWidget * widget, GdkEvent * event, SeekApp * app)
2342 {
2343   stop_cb (NULL, app);
2344   gtk_main_quit ();
2345 }
2346
2347 static void
2348 video_sink_activate_cb (GtkEntry * entry, SeekApp * app)
2349 {
2350   GstElement *sink = NULL;
2351   const gchar *text;
2352
2353   text = gtk_entry_get_text (entry);
2354   if (text != NULL && *text != '\0') {
2355     sink = gst_element_factory_make_or_warn (text, NULL);
2356   }
2357
2358   g_object_set (app->pipeline, "video-sink", sink, NULL);
2359 }
2360
2361 static void
2362 audio_sink_activate_cb (GtkEntry * entry, SeekApp * app)
2363 {
2364   GstElement *sink = NULL;
2365   const gchar *text;
2366
2367   text = gtk_entry_get_text (entry);
2368   if (text != NULL && *text != '\0') {
2369     sink = gst_element_factory_make_or_warn (text, NULL);
2370   }
2371
2372   g_object_set (app->pipeline, "audio-sink", sink, NULL);
2373 }
2374
2375 static void
2376 text_sink_activate_cb (GtkEntry * entry, SeekApp * app)
2377 {
2378   GstElement *sink = NULL;
2379   const gchar *text;
2380
2381   text = gtk_entry_get_text (entry);
2382   if (text != NULL && *text != '\0') {
2383     sink = gst_element_factory_make_or_warn (text, NULL);
2384   }
2385
2386   g_object_set (app->pipeline, "text-sink", sink, NULL);
2387 }
2388
2389 static void
2390 buffer_size_activate_cb (GtkEntry * entry, SeekApp * app)
2391 {
2392   const gchar *text;
2393
2394   text = gtk_entry_get_text (entry);
2395   if (text != NULL && *text != '\0') {
2396     gint v;
2397     gchar *endptr;
2398
2399     v = g_ascii_strtoll (text, &endptr, 10);
2400     if (endptr != text && v != G_MAXINT64 && v != G_MININT64) {
2401       g_object_set (app->pipeline, "buffer-size", v, NULL);
2402     }
2403   }
2404 }
2405
2406 static void
2407 buffer_duration_activate_cb (GtkEntry * entry, SeekApp * app)
2408 {
2409   const gchar *text;
2410
2411   text = gtk_entry_get_text (entry);
2412   if (text != NULL && *text != '\0') {
2413     gint64 v;
2414     gchar *endptr;
2415
2416     v = g_ascii_strtoll (text, &endptr, 10);
2417     if (endptr != text && v != G_MAXINT64 && v != G_MININT64) {
2418       g_object_set (app->pipeline, "buffer-duration", v, NULL);
2419     }
2420   }
2421 }
2422
2423 static void
2424 ringbuffer_maxsize_activate_cb (GtkEntry * entry, SeekApp * app)
2425 {
2426   const gchar *text;
2427
2428   text = gtk_entry_get_text (entry);
2429   if (text != NULL && *text != '\0') {
2430     guint64 v;
2431     gchar *endptr;
2432
2433     v = g_ascii_strtoull (text, &endptr, 10);
2434     if (endptr != text && v != G_MAXUINT64) {
2435       g_object_set (app->pipeline, "ring-buffer-max-size", v, NULL);
2436     }
2437   }
2438 }
2439
2440 static void
2441 connection_speed_activate_cb (GtkEntry * entry, SeekApp * app)
2442 {
2443   const gchar *text;
2444
2445   text = gtk_entry_get_text (entry);
2446   if (text != NULL && *text != '\0') {
2447     guint v;
2448     gchar *endptr;
2449
2450     v = g_ascii_strtoll (text, &endptr, 10);
2451     if (endptr != text && v != G_MAXINT64 && v != G_MININT64) {
2452       g_object_set (app->pipeline, "connection-speed", v, NULL);
2453     }
2454   }
2455 }
2456
2457 static void
2458 subtitle_encoding_activate_cb (GtkEntry * entry, SeekApp * app)
2459 {
2460   const gchar *text;
2461
2462   text = gtk_entry_get_text (entry);
2463   g_object_set (app->pipeline, "subtitle-encoding", text, NULL);
2464 }
2465
2466 static void
2467 subtitle_fontdesc_cb (GtkFontButton * button, SeekApp * app)
2468 {
2469   const gchar *text;
2470
2471   text = gtk_font_button_get_font_name (button);
2472   g_object_set (app->pipeline, "subtitle-font-desc", text, NULL);
2473 }
2474
2475 static void
2476 av_offset_activate_cb (GtkEntry * entry, SeekApp * app)
2477 {
2478   const gchar *text;
2479
2480   text = gtk_entry_get_text (entry);
2481   if (text != NULL && *text != '\0') {
2482     gint64 v;
2483     gchar *endptr;
2484
2485     v = g_ascii_strtoll (text, &endptr, 10);
2486     if (endptr != text && v != G_MAXINT64 && v != G_MININT64) {
2487       g_object_set (app->pipeline, "av-offset", v, NULL);
2488     }
2489   }
2490 }
2491
2492 static void
2493 print_usage (int argc, char **argv)
2494 {
2495   gint i;
2496
2497   g_print ("usage: %s <type> <filename>\n", argv[0]);
2498   g_print ("   possible types:\n");
2499
2500   for (i = 0; i < G_N_ELEMENTS (pipelines); i++) {
2501     g_print ("     %d = %s\n", i, pipelines[i].name);
2502   }
2503 }
2504
2505 static void
2506 create_ui (SeekApp * app)
2507 {
2508   GtkWidget *hbox, *vbox, *seek, *playbin, *step, *navigation, *colorbalance;
2509   GtkWidget *play_button, *pause_button, *stop_button;
2510   GtkAdjustment *adjustment;
2511
2512   /* initialize gui elements ... */
2513   app->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
2514   app->video_window = gtk_drawing_area_new ();
2515   g_signal_connect (app->video_window, "draw", G_CALLBACK (draw_cb), app);
2516   g_signal_connect (app->video_window, "realize", G_CALLBACK (realize_cb), app);
2517   g_signal_connect (app->video_window, "button-press-event",
2518       G_CALLBACK (button_press_cb), app);
2519   g_signal_connect (app->video_window, "button-release-event",
2520       G_CALLBACK (button_release_cb), app);
2521   g_signal_connect (app->video_window, "key-press-event",
2522       G_CALLBACK (key_press_cb), app);
2523   g_signal_connect (app->video_window, "key-release-event",
2524       G_CALLBACK (key_release_cb), app);
2525   g_signal_connect (app->video_window, "motion-notify-event",
2526       G_CALLBACK (motion_notify_cb), app);
2527   gtk_widget_set_can_focus (app->video_window, TRUE);
2528   gtk_widget_set_double_buffered (app->video_window, FALSE);
2529   gtk_widget_add_events (app->video_window,
2530       GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
2531       | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
2532
2533   app->statusbar = gtk_statusbar_new ();
2534   app->status_id =
2535       gtk_statusbar_get_context_id (GTK_STATUSBAR (app->statusbar), "seek");
2536   gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), app->status_id,
2537       "Stopped");
2538   hbox = gtk_hbox_new (FALSE, 0);
2539   vbox = gtk_vbox_new (FALSE, 0);
2540   gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
2541
2542   /* media controls */
2543   play_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
2544   pause_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PAUSE);
2545   stop_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_STOP);
2546
2547   /* seek expander */
2548   {
2549     GtkWidget *accurate_checkbox, *key_checkbox, *loop_checkbox,
2550         *flush_checkbox;
2551     GtkWidget *scrub_checkbox, *play_scrub_checkbox, *rate_label;
2552     GtkWidget *skip_checkbox, *rate_spinbutton;
2553     GtkWidget *flagtable, *advanced_seek, *advanced_seek_grid;
2554     GtkWidget *duration_label, *position_label, *seek_button;
2555
2556     seek = gtk_expander_new ("seek options");
2557     flagtable = gtk_grid_new ();
2558     gtk_grid_set_row_spacing (GTK_GRID (flagtable), 2);
2559     gtk_grid_set_row_homogeneous (GTK_GRID (flagtable), FALSE);
2560     gtk_grid_set_column_spacing (GTK_GRID (flagtable), 2);
2561     gtk_grid_set_column_homogeneous (GTK_GRID (flagtable), TRUE);
2562
2563     accurate_checkbox = gtk_check_button_new_with_label ("Accurate Seek");
2564     key_checkbox = gtk_check_button_new_with_label ("Key-unit Seek");
2565     loop_checkbox = gtk_check_button_new_with_label ("Loop");
2566     flush_checkbox = gtk_check_button_new_with_label ("Flush");
2567     scrub_checkbox = gtk_check_button_new_with_label ("Scrub");
2568     play_scrub_checkbox = gtk_check_button_new_with_label ("Play Scrub");
2569     skip_checkbox = gtk_check_button_new_with_label ("Play Skip");
2570     rate_spinbutton = gtk_spin_button_new_with_range (-100, 100, 0.1);
2571     gtk_spin_button_set_digits (GTK_SPIN_BUTTON (rate_spinbutton), 3);
2572     rate_label = gtk_label_new ("Rate");
2573
2574     gtk_widget_set_tooltip_text (accurate_checkbox,
2575         "accurate position is requested, this might be considerably slower for some formats");
2576     gtk_widget_set_tooltip_text (key_checkbox,
2577         "seek to the nearest keyframe. This might be faster but less accurate");
2578     gtk_widget_set_tooltip_text (loop_checkbox, "loop playback");
2579     gtk_widget_set_tooltip_text (flush_checkbox,
2580         "flush pipeline after seeking");
2581     gtk_widget_set_tooltip_text (rate_spinbutton,
2582         "define the playback rate, " "negative value trigger reverse playback");
2583     gtk_widget_set_tooltip_text (scrub_checkbox, "show images while seeking");
2584     gtk_widget_set_tooltip_text (play_scrub_checkbox,
2585         "play video while seeking");
2586     gtk_widget_set_tooltip_text (skip_checkbox,
2587         "Skip frames while playing at high frame rates");
2588
2589     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (flush_checkbox), TRUE);
2590     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scrub_checkbox), TRUE);
2591
2592     gtk_spin_button_set_value (GTK_SPIN_BUTTON (rate_spinbutton), app->rate);
2593
2594     g_signal_connect (G_OBJECT (accurate_checkbox), "toggled",
2595         G_CALLBACK (accurate_toggle_cb), app);
2596     g_signal_connect (G_OBJECT (key_checkbox), "toggled",
2597         G_CALLBACK (key_toggle_cb), app);
2598     g_signal_connect (G_OBJECT (loop_checkbox), "toggled",
2599         G_CALLBACK (loop_toggle_cb), app);
2600     g_signal_connect (G_OBJECT (flush_checkbox), "toggled",
2601         G_CALLBACK (flush_toggle_cb), app);
2602     g_signal_connect (G_OBJECT (scrub_checkbox), "toggled",
2603         G_CALLBACK (scrub_toggle_cb), app);
2604     g_signal_connect (G_OBJECT (play_scrub_checkbox), "toggled",
2605         G_CALLBACK (play_scrub_toggle_cb), app);
2606     g_signal_connect (G_OBJECT (skip_checkbox), "toggled",
2607         G_CALLBACK (skip_toggle_cb), app);
2608     g_signal_connect (G_OBJECT (rate_spinbutton), "value-changed",
2609         G_CALLBACK (rate_spinbutton_changed_cb), app);
2610
2611     gtk_grid_attach (GTK_GRID (flagtable), accurate_checkbox, 0, 0, 1, 1);
2612     gtk_grid_attach (GTK_GRID (flagtable), flush_checkbox, 1, 0, 1, 1);
2613     gtk_grid_attach (GTK_GRID (flagtable), loop_checkbox, 2, 0, 1, 1);
2614     gtk_grid_attach (GTK_GRID (flagtable), key_checkbox, 0, 1, 1, 1);
2615     gtk_grid_attach (GTK_GRID (flagtable), scrub_checkbox, 1, 1, 1, 1);
2616     gtk_grid_attach (GTK_GRID (flagtable), play_scrub_checkbox, 2, 1, 1, 1);
2617     gtk_grid_attach (GTK_GRID (flagtable), skip_checkbox, 3, 0, 1, 1);
2618     gtk_grid_attach (GTK_GRID (flagtable), rate_label, 4, 0, 1, 1);
2619     gtk_grid_attach (GTK_GRID (flagtable), rate_spinbutton, 4, 1, 1, 1);
2620
2621     advanced_seek = gtk_frame_new ("Advanced Seek");
2622     advanced_seek_grid = gtk_grid_new ();
2623     gtk_grid_set_row_spacing (GTK_GRID (advanced_seek_grid), 2);
2624     gtk_grid_set_row_homogeneous (GTK_GRID (advanced_seek_grid), FALSE);
2625     gtk_grid_set_column_spacing (GTK_GRID (advanced_seek_grid), 5);
2626     gtk_grid_set_column_homogeneous (GTK_GRID (advanced_seek_grid), FALSE);
2627
2628     app->seek_format_combo = gtk_combo_box_text_new ();
2629     g_signal_connect (app->seek_format_combo, "changed",
2630         G_CALLBACK (seek_format_changed_cb), app);
2631     gtk_grid_attach (GTK_GRID (advanced_seek_grid), app->seek_format_combo, 0,
2632         0, 1, 1);
2633
2634     app->seek_entry = gtk_entry_new ();
2635     gtk_entry_set_width_chars (GTK_ENTRY (app->seek_entry), 12);
2636     gtk_grid_attach (GTK_GRID (advanced_seek_grid), app->seek_entry, 0, 1, 1,
2637         1);
2638
2639     seek_button = gtk_button_new_with_label ("Seek");
2640     g_signal_connect (G_OBJECT (seek_button), "clicked",
2641         G_CALLBACK (advanced_seek_button_cb), app);
2642     gtk_grid_attach (GTK_GRID (advanced_seek_grid), seek_button, 1, 0, 1, 1);
2643
2644     position_label = gtk_label_new ("Position:");
2645     gtk_grid_attach (GTK_GRID (advanced_seek_grid), position_label, 2, 0, 1, 1);
2646     duration_label = gtk_label_new ("Duration:");
2647     gtk_grid_attach (GTK_GRID (advanced_seek_grid), duration_label, 2, 1, 1, 1);
2648
2649     app->seek_position_label = gtk_label_new ("-1");
2650     gtk_grid_attach (GTK_GRID (advanced_seek_grid), app->seek_position_label, 3,
2651         0, 1, 1);
2652     app->seek_duration_label = gtk_label_new ("-1");
2653     gtk_grid_attach (GTK_GRID (advanced_seek_grid), app->seek_duration_label, 3,
2654         1, 1, 1);
2655
2656     gtk_container_add (GTK_CONTAINER (advanced_seek), advanced_seek_grid);
2657     gtk_grid_attach (GTK_GRID (flagtable), advanced_seek, 0, 2, 3, 2);
2658     gtk_container_add (GTK_CONTAINER (seek), flagtable);
2659   }
2660
2661   /* step expander */
2662   {
2663     GtkWidget *hbox;
2664     GtkWidget *step_button, *shuttle_checkbox;
2665
2666     step = gtk_expander_new ("step options");
2667     hbox = gtk_hbox_new (FALSE, 0);
2668
2669     app->step_format_combo = gtk_combo_box_text_new ();
2670     gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (app->step_format_combo),
2671         "frames");
2672     gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (app->step_format_combo),
2673         "time (ms)");
2674     gtk_combo_box_set_active (GTK_COMBO_BOX (app->step_format_combo), 0);
2675     gtk_box_pack_start (GTK_BOX (hbox), app->step_format_combo, FALSE, FALSE,
2676         2);
2677
2678     app->step_amount_spinbutton = gtk_spin_button_new_with_range (1, 1000, 1);
2679     gtk_spin_button_set_digits (GTK_SPIN_BUTTON (app->step_amount_spinbutton),
2680         0);
2681     gtk_spin_button_set_value (GTK_SPIN_BUTTON (app->step_amount_spinbutton),
2682         1.0);
2683     gtk_box_pack_start (GTK_BOX (hbox), app->step_amount_spinbutton, FALSE,
2684         FALSE, 2);
2685
2686     app->step_rate_spinbutton = gtk_spin_button_new_with_range (0.0, 100, 0.1);
2687     gtk_spin_button_set_digits (GTK_SPIN_BUTTON (app->step_rate_spinbutton), 3);
2688     gtk_spin_button_set_value (GTK_SPIN_BUTTON (app->step_rate_spinbutton),
2689         1.0);
2690     gtk_box_pack_start (GTK_BOX (hbox), app->step_rate_spinbutton, FALSE, FALSE,
2691         2);
2692
2693     step_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_FORWARD);
2694     gtk_button_set_label (GTK_BUTTON (step_button), "Step");
2695     gtk_box_pack_start (GTK_BOX (hbox), step_button, FALSE, FALSE, 2);
2696
2697     g_signal_connect (G_OBJECT (step_button), "clicked", G_CALLBACK (step_cb),
2698         app);
2699
2700     /* shuttle scale */
2701     shuttle_checkbox = gtk_check_button_new_with_label ("Shuttle");
2702     gtk_box_pack_start (GTK_BOX (hbox), shuttle_checkbox, FALSE, FALSE, 2);
2703     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (shuttle_checkbox), FALSE);
2704     g_signal_connect (shuttle_checkbox, "toggled", G_CALLBACK (shuttle_toggled),
2705         app);
2706
2707     adjustment =
2708         GTK_ADJUSTMENT (gtk_adjustment_new (0.0, -3.00, 4.0, 0.1, 1.0, 1.0));
2709     app->shuttle_scale = gtk_hscale_new (adjustment);
2710     gtk_scale_set_digits (GTK_SCALE (app->shuttle_scale), 2);
2711     gtk_scale_set_value_pos (GTK_SCALE (app->shuttle_scale), GTK_POS_TOP);
2712     g_signal_connect (app->shuttle_scale, "value-changed",
2713         G_CALLBACK (shuttle_value_changed), app);
2714     g_signal_connect (app->shuttle_scale, "format_value",
2715         G_CALLBACK (shuttle_format_value), app);
2716
2717     gtk_box_pack_start (GTK_BOX (hbox), app->shuttle_scale, TRUE, TRUE, 2);
2718
2719     gtk_container_add (GTK_CONTAINER (step), hbox);
2720   }
2721
2722   /* navigation command expander */
2723   {
2724     GtkWidget *navigation_button;
2725     GtkWidget *grid;
2726     gint i = 0;
2727
2728     navigation = gtk_expander_new ("navigation commands");
2729     grid = gtk_grid_new ();
2730     gtk_grid_set_row_spacing (GTK_GRID (grid), 2);
2731     gtk_grid_set_row_homogeneous (GTK_GRID (grid), FALSE);
2732     gtk_grid_set_column_spacing (GTK_GRID (grid), 2);
2733     gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE);
2734
2735     navigation_button = gtk_button_new_with_label ("Menu 1");
2736     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2737         G_CALLBACK (navigation_cmd_cb), app);
2738     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2739     gtk_widget_set_sensitive (navigation_button, FALSE);
2740     gtk_widget_set_tooltip_text (navigation_button, "DVD Menu");
2741     app->navigation_buttons[i].button = navigation_button;
2742     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU1;
2743
2744     navigation_button = gtk_button_new_with_label ("Menu 2");
2745     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2746         G_CALLBACK (navigation_cmd_cb), app);
2747     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2748     gtk_widget_set_sensitive (navigation_button, FALSE);
2749     gtk_widget_set_tooltip_text (navigation_button, "DVD Title Menu");
2750     app->navigation_buttons[i].button = navigation_button;
2751     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU2;
2752
2753     navigation_button = gtk_button_new_with_label ("Menu 3");
2754     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2755         G_CALLBACK (navigation_cmd_cb), app);
2756     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2757     gtk_widget_set_sensitive (navigation_button, FALSE);
2758     gtk_widget_set_tooltip_text (navigation_button, "DVD Root Menu");
2759     app->navigation_buttons[i].button = navigation_button;
2760     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU3;
2761
2762     navigation_button = gtk_button_new_with_label ("Menu 4");
2763     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2764         G_CALLBACK (navigation_cmd_cb), app);
2765     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2766     gtk_widget_set_sensitive (navigation_button, FALSE);
2767     gtk_widget_set_tooltip_text (navigation_button, "DVD Subpicture Menu");
2768     app->navigation_buttons[i].button = navigation_button;
2769     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU4;
2770
2771     navigation_button = gtk_button_new_with_label ("Menu 5");
2772     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2773         G_CALLBACK (navigation_cmd_cb), app);
2774     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2775     gtk_widget_set_sensitive (navigation_button, FALSE);
2776     gtk_widget_set_tooltip_text (navigation_button, "DVD Audio Menu");
2777     app->navigation_buttons[i].button = navigation_button;
2778     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU5;
2779
2780     navigation_button = gtk_button_new_with_label ("Menu 6");
2781     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2782         G_CALLBACK (navigation_cmd_cb), app);
2783     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2784     gtk_widget_set_sensitive (navigation_button, FALSE);
2785     gtk_widget_set_tooltip_text (navigation_button, "DVD Angle Menu");
2786     app->navigation_buttons[i].button = navigation_button;
2787     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU6;
2788
2789     navigation_button = gtk_button_new_with_label ("Menu 7");
2790     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2791         G_CALLBACK (navigation_cmd_cb), app);
2792     gtk_grid_attach (GTK_GRID (grid), navigation_button, i, 0, 1, 1);
2793     gtk_widget_set_sensitive (navigation_button, FALSE);
2794     gtk_widget_set_tooltip_text (navigation_button, "DVD Chapter Menu");
2795     app->navigation_buttons[i].button = navigation_button;
2796     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_MENU7;
2797
2798     navigation_button = gtk_button_new_with_label ("Left");
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_LEFT;
2805
2806     navigation_button = gtk_button_new_with_label ("Right");
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_RIGHT;
2813
2814     navigation_button = gtk_button_new_with_label ("Up");
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_UP;
2821
2822     navigation_button = gtk_button_new_with_label ("Down");
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_DOWN;
2829
2830     navigation_button = gtk_button_new_with_label ("Activate");
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_ACTIVATE;
2837
2838     navigation_button = gtk_button_new_with_label ("Prev. 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_PREV_ANGLE;
2845
2846     navigation_button = gtk_button_new_with_label ("Next. Angle");
2847     g_signal_connect (G_OBJECT (navigation_button), "clicked",
2848         G_CALLBACK (navigation_cmd_cb), app);
2849     gtk_grid_attach (GTK_GRID (grid), navigation_button, i - 7, 1, 1, 1);
2850     gtk_widget_set_sensitive (navigation_button, FALSE);
2851     app->navigation_buttons[i].button = navigation_button;
2852     app->navigation_buttons[i++].cmd = GST_NAVIGATION_COMMAND_NEXT_ANGLE;
2853
2854     gtk_container_add (GTK_CONTAINER (navigation), grid);
2855   }
2856
2857   /* colorbalance expander */
2858   {
2859     GtkWidget *vbox, *frame;
2860
2861     colorbalance = gtk_expander_new ("color balance options");
2862     vbox = gtk_vbox_new (FALSE, 0);
2863
2864     /* contrast scale */
2865     frame = gtk_frame_new ("Contrast");
2866     adjustment =
2867         GTK_ADJUSTMENT (gtk_adjustment_new (N_GRAD / 2.0, 0.00, N_GRAD, 0.1,
2868             1.0, 1.0));
2869     app->contrast_scale = gtk_hscale_new (adjustment);
2870     gtk_scale_set_draw_value (GTK_SCALE (app->contrast_scale), FALSE);
2871     g_signal_connect (app->contrast_scale, "value-changed",
2872         G_CALLBACK (colorbalance_value_changed), app);
2873     gtk_container_add (GTK_CONTAINER (frame), app->contrast_scale);
2874     gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 2);
2875
2876     /* brightness scale */
2877     frame = gtk_frame_new ("Brightness");
2878     adjustment =
2879         GTK_ADJUSTMENT (gtk_adjustment_new (N_GRAD / 2.0, 0.00, N_GRAD, 0.1,
2880             1.0, 1.0));
2881     app->brightness_scale = gtk_hscale_new (adjustment);
2882     gtk_scale_set_draw_value (GTK_SCALE (app->brightness_scale), FALSE);
2883     g_signal_connect (app->brightness_scale, "value-changed",
2884         G_CALLBACK (colorbalance_value_changed), app);
2885     gtk_container_add (GTK_CONTAINER (frame), app->brightness_scale);
2886     gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 2);
2887
2888     /* hue scale */
2889     frame = gtk_frame_new ("Hue");
2890     adjustment =
2891         GTK_ADJUSTMENT (gtk_adjustment_new (N_GRAD / 2.0, 0.00, N_GRAD, 0.1,
2892             1.0, 1.0));
2893     app->hue_scale = gtk_hscale_new (adjustment);
2894     gtk_scale_set_draw_value (GTK_SCALE (app->hue_scale), FALSE);
2895     g_signal_connect (app->hue_scale, "value-changed",
2896         G_CALLBACK (colorbalance_value_changed), app);
2897     gtk_container_add (GTK_CONTAINER (frame), app->hue_scale);
2898     gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 2);
2899
2900     /* saturation scale */
2901     frame = gtk_frame_new ("Saturation");
2902     adjustment =
2903         GTK_ADJUSTMENT (gtk_adjustment_new (N_GRAD / 2.0, 0.00, N_GRAD, 0.1,
2904             1.0, 1.0));
2905     app->saturation_scale = gtk_hscale_new (adjustment);
2906     gtk_scale_set_draw_value (GTK_SCALE (app->saturation_scale), FALSE);
2907     g_signal_connect (app->saturation_scale, "value-changed",
2908         G_CALLBACK (colorbalance_value_changed), app);
2909     gtk_container_add (GTK_CONTAINER (frame), app->saturation_scale);
2910     gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 2);
2911
2912     gtk_container_add (GTK_CONTAINER (colorbalance), vbox);
2913   }
2914
2915   /* seek bar */
2916   adjustment =
2917       GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.00, N_GRAD, 0.1, 1.0, 1.0));
2918   app->seek_scale = gtk_hscale_new (adjustment);
2919   gtk_scale_set_digits (GTK_SCALE (app->seek_scale), 2);
2920   gtk_scale_set_value_pos (GTK_SCALE (app->seek_scale), GTK_POS_RIGHT);
2921   gtk_range_set_show_fill_level (GTK_RANGE (app->seek_scale), TRUE);
2922   gtk_range_set_fill_level (GTK_RANGE (app->seek_scale), N_GRAD);
2923
2924   g_signal_connect (app->seek_scale, "button_press_event",
2925       G_CALLBACK (start_seek), app);
2926   g_signal_connect (app->seek_scale, "button_release_event",
2927       G_CALLBACK (stop_seek), app);
2928   g_signal_connect (app->seek_scale, "format_value", G_CALLBACK (format_value),
2929       app);
2930
2931   if (app->pipeline_type == 0) {
2932     GtkWidget *pb2vbox, *boxes, *boxes2, *panel, *boxes3;
2933     GtkWidget *volume_label, *shot_button;
2934     GtkWidget *label;
2935
2936     playbin = gtk_expander_new ("playbin options");
2937     /* the playbin panel controls for the video/audio/subtitle tracks */
2938     panel = gtk_hbox_new (FALSE, 0);
2939     app->video_combo = gtk_combo_box_text_new ();
2940     app->audio_combo = gtk_combo_box_text_new ();
2941     app->text_combo = gtk_combo_box_text_new ();
2942     gtk_widget_set_sensitive (app->video_combo, FALSE);
2943     gtk_widget_set_sensitive (app->audio_combo, FALSE);
2944     gtk_widget_set_sensitive (app->text_combo, FALSE);
2945     gtk_box_pack_start (GTK_BOX (panel), app->video_combo, TRUE, TRUE, 2);
2946     gtk_box_pack_start (GTK_BOX (panel), app->audio_combo, TRUE, TRUE, 2);
2947     gtk_box_pack_start (GTK_BOX (panel), app->text_combo, TRUE, TRUE, 2);
2948     g_signal_connect (G_OBJECT (app->video_combo), "changed",
2949         G_CALLBACK (video_combo_cb), app);
2950     g_signal_connect (G_OBJECT (app->audio_combo), "changed",
2951         G_CALLBACK (audio_combo_cb), app);
2952     g_signal_connect (G_OBJECT (app->text_combo), "changed",
2953         G_CALLBACK (text_combo_cb), app);
2954     /* playbin panel for flag checkboxes and volume/mute */
2955     boxes = gtk_grid_new ();
2956     gtk_grid_set_row_spacing (GTK_GRID (boxes), 2);
2957     gtk_grid_set_row_homogeneous (GTK_GRID (boxes), FALSE);
2958     gtk_grid_set_column_spacing (GTK_GRID (boxes), 2);
2959     gtk_grid_set_column_homogeneous (GTK_GRID (boxes), TRUE);
2960
2961     app->video_checkbox = gtk_check_button_new_with_label ("Video");
2962     app->audio_checkbox = gtk_check_button_new_with_label ("Audio");
2963     app->text_checkbox = gtk_check_button_new_with_label ("Text");
2964     app->vis_checkbox = gtk_check_button_new_with_label ("Vis");
2965     app->soft_volume_checkbox = gtk_check_button_new_with_label ("Soft Volume");
2966     app->native_audio_checkbox =
2967         gtk_check_button_new_with_label ("Native Audio");
2968     app->native_video_checkbox =
2969         gtk_check_button_new_with_label ("Native Video");
2970     app->download_checkbox = gtk_check_button_new_with_label ("Download");
2971     app->buffering_checkbox = gtk_check_button_new_with_label ("Buffering");
2972     app->deinterlace_checkbox = gtk_check_button_new_with_label ("Deinterlace");
2973     app->soft_colorbalance_checkbox =
2974         gtk_check_button_new_with_label ("Soft Colorbalance");
2975     app->mute_checkbox = gtk_check_button_new_with_label ("Mute");
2976     volume_label = gtk_label_new ("Volume");
2977     app->volume_spinbutton = gtk_spin_button_new_with_range (0, 10.0, 0.1);
2978
2979     gtk_grid_attach (GTK_GRID (boxes), app->video_checkbox, 0, 0, 1, 1);
2980     gtk_grid_attach (GTK_GRID (boxes), app->audio_checkbox, 1, 0, 1, 1);
2981     gtk_grid_attach (GTK_GRID (boxes), app->text_checkbox, 2, 0, 1, 1);
2982     gtk_grid_attach (GTK_GRID (boxes), app->vis_checkbox, 3, 0, 1, 1);
2983     gtk_grid_attach (GTK_GRID (boxes), app->soft_volume_checkbox, 4, 0, 1, 1);
2984     gtk_grid_attach (GTK_GRID (boxes), app->native_audio_checkbox, 5, 0, 1, 1);
2985     gtk_grid_attach (GTK_GRID (boxes), app->native_video_checkbox, 0, 1, 1, 1);
2986     gtk_grid_attach (GTK_GRID (boxes), app->download_checkbox, 1, 1, 1, 1);
2987     gtk_grid_attach (GTK_GRID (boxes), app->buffering_checkbox, 2, 1, 1, 1);
2988     gtk_grid_attach (GTK_GRID (boxes), app->deinterlace_checkbox, 3, 1, 1, 1);
2989     gtk_grid_attach (GTK_GRID (boxes), app->soft_colorbalance_checkbox, 4, 1, 1,
2990         1);
2991
2992     gtk_grid_attach (GTK_GRID (boxes), app->mute_checkbox, 7, 0, 2, 1);
2993     gtk_grid_attach (GTK_GRID (boxes), volume_label, 6, 1, 1, 1);
2994     gtk_grid_attach (GTK_GRID (boxes), app->volume_spinbutton, 7, 1, 1, 1);
2995
2996     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->video_checkbox),
2997         TRUE);
2998     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->audio_checkbox),
2999         TRUE);
3000     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->text_checkbox), TRUE);
3001     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->vis_checkbox), FALSE);
3002     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->soft_volume_checkbox),
3003         TRUE);
3004     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
3005         (app->native_audio_checkbox), FALSE);
3006     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
3007         (app->native_video_checkbox), FALSE);
3008     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->download_checkbox),
3009         FALSE);
3010     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->buffering_checkbox),
3011         FALSE);
3012     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->deinterlace_checkbox),
3013         FALSE);
3014     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
3015         (app->soft_colorbalance_checkbox), TRUE);
3016     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->mute_checkbox),
3017         FALSE);
3018     gtk_spin_button_set_value (GTK_SPIN_BUTTON (app->volume_spinbutton), 1.0);
3019
3020     g_signal_connect (G_OBJECT (app->video_checkbox), "toggled",
3021         G_CALLBACK (video_toggle_cb), app);
3022     g_signal_connect (G_OBJECT (app->audio_checkbox), "toggled",
3023         G_CALLBACK (audio_toggle_cb), app);
3024     g_signal_connect (G_OBJECT (app->text_checkbox), "toggled",
3025         G_CALLBACK (text_toggle_cb), app);
3026     g_signal_connect (G_OBJECT (app->vis_checkbox), "toggled",
3027         G_CALLBACK (vis_toggle_cb), app);
3028     g_signal_connect (G_OBJECT (app->soft_volume_checkbox), "toggled",
3029         G_CALLBACK (soft_volume_toggle_cb), app);
3030     g_signal_connect (G_OBJECT (app->native_audio_checkbox), "toggled",
3031         G_CALLBACK (native_audio_toggle_cb), app);
3032     g_signal_connect (G_OBJECT (app->native_video_checkbox), "toggled",
3033         G_CALLBACK (native_video_toggle_cb), app);
3034     g_signal_connect (G_OBJECT (app->download_checkbox), "toggled",
3035         G_CALLBACK (download_toggle_cb), app);
3036     g_signal_connect (G_OBJECT (app->buffering_checkbox), "toggled",
3037         G_CALLBACK (buffering_toggle_cb), app);
3038     g_signal_connect (G_OBJECT (app->deinterlace_checkbox), "toggled",
3039         G_CALLBACK (deinterlace_toggle_cb), app);
3040     g_signal_connect (G_OBJECT (app->soft_colorbalance_checkbox), "toggled",
3041         G_CALLBACK (soft_colorbalance_toggle_cb), app);
3042     g_signal_connect (G_OBJECT (app->mute_checkbox), "toggled",
3043         G_CALLBACK (mute_toggle_cb), app);
3044     g_signal_connect (G_OBJECT (app->volume_spinbutton), "value-changed",
3045         G_CALLBACK (volume_spinbutton_changed_cb), app);
3046     /* playbin panel for snapshot */
3047     boxes2 = gtk_hbox_new (FALSE, 0);
3048     shot_button = gtk_button_new_from_stock (GTK_STOCK_SAVE);
3049     gtk_widget_set_tooltip_text (shot_button,
3050         "save a screenshot .png in the current directory");
3051     g_signal_connect (G_OBJECT (shot_button), "clicked", G_CALLBACK (shot_cb),
3052         app);
3053     app->vis_combo = gtk_combo_box_text_new ();
3054     g_signal_connect (G_OBJECT (app->vis_combo), "changed",
3055         G_CALLBACK (vis_combo_cb), app);
3056     gtk_widget_set_sensitive (app->vis_combo, FALSE);
3057     gtk_box_pack_start (GTK_BOX (boxes2), shot_button, TRUE, TRUE, 2);
3058     gtk_box_pack_start (GTK_BOX (boxes2), app->vis_combo, TRUE, TRUE, 2);
3059
3060     /* fill the vis combo box and the array of factories */
3061     init_visualization_features (app);
3062
3063     /* Grid with other properties */
3064     boxes3 = gtk_grid_new ();
3065     gtk_grid_set_row_spacing (GTK_GRID (boxes3), 2);
3066     gtk_grid_set_row_homogeneous (GTK_GRID (boxes3), FALSE);
3067     gtk_grid_set_column_spacing (GTK_GRID (boxes3), 2);
3068     gtk_grid_set_column_homogeneous (GTK_GRID (boxes3), TRUE);
3069
3070     label = gtk_label_new ("Video sink");
3071     gtk_grid_attach (GTK_GRID (boxes3), label, 0, 0, 1, 1);
3072     app->video_sink_entry = gtk_entry_new ();
3073     g_signal_connect (app->video_sink_entry, "activate",
3074         G_CALLBACK (video_sink_activate_cb), app);
3075     gtk_grid_attach (GTK_GRID (boxes3), app->video_sink_entry, 0, 1, 1, 1);
3076
3077     label = gtk_label_new ("Audio sink");
3078     gtk_grid_attach (GTK_GRID (boxes3), label, 1, 0, 1, 1);
3079     app->audio_sink_entry = gtk_entry_new ();
3080     g_signal_connect (app->audio_sink_entry, "activate",
3081         G_CALLBACK (audio_sink_activate_cb), app);
3082     gtk_grid_attach (GTK_GRID (boxes3), app->audio_sink_entry, 1, 1, 1, 1);
3083
3084     label = gtk_label_new ("Text sink");
3085     gtk_grid_attach (GTK_GRID (boxes3), label, 2, 0, 1, 1);
3086     app->text_sink_entry = gtk_entry_new ();
3087     g_signal_connect (app->text_sink_entry, "activate",
3088         G_CALLBACK (text_sink_activate_cb), app);
3089     gtk_grid_attach (GTK_GRID (boxes3), app->text_sink_entry, 2, 1, 1, 1);
3090
3091     label = gtk_label_new ("Buffer Size");
3092     gtk_grid_attach (GTK_GRID (boxes3), label, 0, 2, 1, 1);
3093     app->buffer_size_entry = gtk_entry_new ();
3094     gtk_entry_set_text (GTK_ENTRY (app->buffer_size_entry), "-1");
3095     g_signal_connect (app->buffer_size_entry, "activate",
3096         G_CALLBACK (buffer_size_activate_cb), app);
3097     gtk_grid_attach (GTK_GRID (boxes3), app->buffer_size_entry, 0, 3, 1, 1);
3098
3099     label = gtk_label_new ("Buffer Duration");
3100     gtk_grid_attach (GTK_GRID (boxes3), label, 1, 2, 1, 1);
3101     app->buffer_duration_entry = gtk_entry_new ();
3102     gtk_entry_set_text (GTK_ENTRY (app->buffer_duration_entry), "-1");
3103     g_signal_connect (app->buffer_duration_entry, "activate",
3104         G_CALLBACK (buffer_duration_activate_cb), app);
3105     gtk_grid_attach (GTK_GRID (boxes3), app->buffer_duration_entry, 1, 3, 1, 1);
3106
3107     label = gtk_label_new ("Ringbuffer Max Size");
3108     gtk_grid_attach (GTK_GRID (boxes3), label, 2, 2, 1, 1);
3109     app->ringbuffer_maxsize_entry = gtk_entry_new ();
3110     gtk_entry_set_text (GTK_ENTRY (app->ringbuffer_maxsize_entry), "0");
3111     g_signal_connect (app->ringbuffer_maxsize_entry, "activate",
3112         G_CALLBACK (ringbuffer_maxsize_activate_cb), app);
3113     gtk_grid_attach (GTK_GRID (boxes3), app->ringbuffer_maxsize_entry, 2, 3, 1,
3114         1);
3115
3116     label = gtk_label_new ("Connection Speed");
3117     gtk_grid_attach (GTK_GRID (boxes3), label, 3, 2, 1, 1);
3118     app->connection_speed_entry = gtk_entry_new ();
3119     gtk_entry_set_text (GTK_ENTRY (app->connection_speed_entry), "0");
3120     g_signal_connect (app->connection_speed_entry, "activate",
3121         G_CALLBACK (connection_speed_activate_cb), app);
3122     gtk_grid_attach (GTK_GRID (boxes3), app->connection_speed_entry, 3, 3, 1,
3123         1);
3124
3125     label = gtk_label_new ("A/V offset");
3126     gtk_grid_attach (GTK_GRID (boxes3), label, 4, 2, 1, 1);
3127     app->av_offset_entry = gtk_entry_new ();
3128     g_signal_connect (app->av_offset_entry, "activate",
3129         G_CALLBACK (av_offset_activate_cb), app);
3130     gtk_entry_set_text (GTK_ENTRY (app->av_offset_entry), "0");
3131     g_signal_connect (app->av_offset_entry, "activate",
3132         G_CALLBACK (av_offset_activate_cb), app);
3133     gtk_grid_attach (GTK_GRID (boxes3), app->av_offset_entry, 4, 3, 1, 1);
3134
3135     label = gtk_label_new ("Subtitle Encoding");
3136     gtk_grid_attach (GTK_GRID (boxes3), label, 0, 4, 1, 1);
3137     app->subtitle_encoding_entry = gtk_entry_new ();
3138     g_signal_connect (app->subtitle_encoding_entry, "activate",
3139         G_CALLBACK (subtitle_encoding_activate_cb), app);
3140     gtk_grid_attach (GTK_GRID (boxes3), app->subtitle_encoding_entry, 0, 5, 1,
3141         1);
3142
3143     label = gtk_label_new ("Subtitle Fontdesc");
3144     gtk_grid_attach (GTK_GRID (boxes3), label, 1, 4, 1, 1);
3145     app->subtitle_fontdesc_button = gtk_font_button_new ();
3146     g_signal_connect (app->subtitle_fontdesc_button, "font-set",
3147         G_CALLBACK (subtitle_fontdesc_cb), app);
3148     gtk_grid_attach (GTK_GRID (boxes3), app->subtitle_fontdesc_button, 1, 5, 1,
3149         1);
3150
3151     pb2vbox = gtk_vbox_new (FALSE, 0);
3152     gtk_box_pack_start (GTK_BOX (pb2vbox), panel, FALSE, FALSE, 2);
3153     gtk_box_pack_start (GTK_BOX (pb2vbox), boxes, FALSE, FALSE, 2);
3154     gtk_box_pack_start (GTK_BOX (pb2vbox), boxes2, FALSE, FALSE, 2);
3155     gtk_box_pack_start (GTK_BOX (pb2vbox), boxes3, FALSE, FALSE, 2);
3156     gtk_container_add (GTK_CONTAINER (playbin), pb2vbox);
3157   } else {
3158     playbin = NULL;
3159   }
3160
3161   /* do the packing stuff ... */
3162   gtk_window_set_default_size (GTK_WINDOW (app->window), 250, 96);
3163   /* FIXME: can we avoid this for audio only? */
3164   gtk_widget_set_size_request (GTK_WIDGET (app->video_window), -1,
3165       DEFAULT_VIDEO_HEIGHT);
3166   gtk_container_add (GTK_CONTAINER (app->window), vbox);
3167   gtk_box_pack_start (GTK_BOX (vbox), app->video_window, TRUE, TRUE, 2);
3168   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2);
3169   gtk_box_pack_start (GTK_BOX (hbox), play_button, FALSE, FALSE, 2);
3170   gtk_box_pack_start (GTK_BOX (hbox), pause_button, FALSE, FALSE, 2);
3171   gtk_box_pack_start (GTK_BOX (hbox), stop_button, FALSE, FALSE, 2);
3172
3173   gtk_box_pack_start (GTK_BOX (vbox), seek, FALSE, FALSE, 2);
3174   if (playbin)
3175     gtk_box_pack_start (GTK_BOX (vbox), playbin, FALSE, FALSE, 2);
3176   gtk_box_pack_start (GTK_BOX (vbox), step, FALSE, FALSE, 2);
3177   gtk_box_pack_start (GTK_BOX (vbox), navigation, FALSE, FALSE, 2);
3178   gtk_box_pack_start (GTK_BOX (vbox), colorbalance, FALSE, FALSE, 2);
3179   gtk_box_pack_start (GTK_BOX (vbox), gtk_hseparator_new (), FALSE, FALSE, 2);
3180   gtk_box_pack_start (GTK_BOX (vbox), app->seek_scale, FALSE, FALSE, 2);
3181   gtk_box_pack_start (GTK_BOX (vbox), app->statusbar, FALSE, FALSE, 2);
3182
3183   /* connect things ... */
3184   g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb),
3185       app);
3186   g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb),
3187       app);
3188   g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb),
3189       app);
3190
3191   g_signal_connect (G_OBJECT (app->window), "delete-event",
3192       G_CALLBACK (delete_event_cb), app);
3193 }
3194
3195 static void
3196 set_defaults (SeekApp * app)
3197 {
3198   memset (app, 0, sizeof (SeekApp));
3199
3200   app->flush_seek = TRUE;
3201   app->scrub = TRUE;
3202   app->rate = 1.0;
3203
3204   app->position = app->duration = -1;
3205   app->state = GST_STATE_NULL;
3206
3207   app->need_streams = TRUE;
3208
3209   g_mutex_init (&app->state_mutex);
3210
3211   app->play_rate = 1.0;
3212 }
3213
3214 static void
3215 reset_app (SeekApp * app)
3216 {
3217   g_free (app->audiosink_str);
3218   g_free (app->videosink_str);
3219
3220   g_list_free (app->formats);
3221
3222   g_mutex_clear (&app->state_mutex);
3223
3224   if (app->overlay_element)
3225     gst_object_unref (app->overlay_element);
3226   if (app->navigation_element)
3227     gst_object_unref (app->navigation_element);
3228
3229   g_list_foreach (app->paths, (GFunc) g_free, NULL);
3230   g_list_free (app->paths);
3231   g_list_foreach (app->sub_paths, (GFunc) g_free, NULL);
3232   g_list_free (app->sub_paths);
3233
3234   g_print ("free pipeline\n");
3235   gst_object_unref (app->pipeline);
3236 }
3237
3238 int
3239 main (int argc, char **argv)
3240 {
3241   SeekApp app;
3242   GOptionEntry options[] = {
3243     {"stats", 's', 0, G_OPTION_ARG_NONE, &app.stats,
3244         "Show pad stats", NULL},
3245     {"verbose", 'v', 0, G_OPTION_ARG_NONE, &app.verbose,
3246         "Verbose properties", NULL},
3247     {NULL}
3248   };
3249   GOptionContext *ctx;
3250   GError *err = NULL;
3251
3252   set_defaults (&app);
3253
3254   ctx = g_option_context_new ("- test seeking in gsteamer");
3255   g_option_context_add_main_entries (ctx, options, NULL);
3256   g_option_context_add_group (ctx, gst_init_get_option_group ());
3257   g_option_context_add_group (ctx, gtk_get_option_group (TRUE));
3258
3259   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
3260     g_print ("Error initializing: %s\n", err->message);
3261     exit (1);
3262   }
3263
3264   GST_DEBUG_CATEGORY_INIT (seek_debug, "seek", 0, "seek example");
3265
3266   if (argc < 3) {
3267     print_usage (argc, argv);
3268     exit (-1);
3269   }
3270
3271   app.pipeline_type = atoi (argv[1]);
3272
3273   if (app.pipeline_type < 0 || app.pipeline_type >= G_N_ELEMENTS (pipelines)) {
3274     print_usage (argc, argv);
3275     exit (-1);
3276   }
3277
3278   app.pipeline_spec = argv[2];
3279
3280   if (g_path_is_absolute (app.pipeline_spec) &&
3281       (g_strrstr (app.pipeline_spec, "*") != NULL ||
3282           g_strrstr (app.pipeline_spec, "?") != NULL)) {
3283     app.paths = handle_wildcards (app.pipeline_spec);
3284   } else {
3285     app.paths = g_list_prepend (app.paths, g_strdup (app.pipeline_spec));
3286   }
3287
3288   if (!app.paths) {
3289     g_print ("opening %s failed\n", app.pipeline_spec);
3290     exit (-1);
3291   }
3292
3293   app.current_path = app.paths;
3294
3295   if (argc > 3 && argv[3]) {
3296     if (g_path_is_absolute (argv[3]) &&
3297         (g_strrstr (argv[3], "*") != NULL ||
3298             g_strrstr (argv[3], "?") != NULL)) {
3299       app.sub_paths = handle_wildcards (argv[3]);
3300     } else {
3301       app.sub_paths = g_list_prepend (app.sub_paths, g_strdup (argv[3]));
3302     }
3303
3304     if (!app.sub_paths) {
3305       g_print ("opening %s failed\n", argv[3]);
3306       exit (-1);
3307     }
3308
3309     app.current_sub_path = app.sub_paths;
3310   }
3311
3312   pipelines[app.pipeline_type].func (&app, app.current_path->data);
3313   g_assert (app.pipeline);
3314
3315   create_ui (&app);
3316
3317   /* show the gui. */
3318   gtk_widget_show_all (app.window);
3319
3320   /* realize window now so that the video window gets created and we can
3321    * obtain its XID before the pipeline is started up and the videosink
3322    * asks for the XID of the window to render onto */
3323   gtk_widget_realize (app.window);
3324
3325 #if defined (GDK_WINDOWING_X11) || defined (GDK_WINDOWING_WIN32) || defined (GDK_WINDOWING_QUARTZ)
3326   /* we should have the XID now */
3327   g_assert (app.embed_xid != 0);
3328
3329   if (app.pipeline_type == 0) {
3330     gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (app.pipeline),
3331         app.embed_xid);
3332   }
3333 #endif
3334
3335   if (app.verbose) {
3336     g_signal_connect (app.pipeline, "deep_notify",
3337         G_CALLBACK (gst_object_default_deep_notify), NULL);
3338   }
3339
3340   connect_bus_signals (&app);
3341
3342   gtk_main ();
3343
3344   g_print ("NULL pipeline\n");
3345   gst_element_set_state (app.pipeline, GST_STATE_NULL);
3346
3347   reset_app (&app);
3348
3349   return 0;
3350 }