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