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