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