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