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