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