video-blend: fix blending 8-bit and 16-bit frames together
[platform/upstream/gstreamer.git] / tools / gst-play.c
1 /* GStreamer command line playback testing utility
2  *
3  * Copyright (C) 2013-2014 Tim-Philipp Müller <tim centricular net>
4  * Copyright (C) 2013 Collabora Ltd.
5  * Copyright (C) 2015 Centricular Ltd
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <locale.h>
28
29 #include <gst/gst.h>
30 #include <gst/gst-i18n-app.h>
31 #include <gst/audio/audio.h>
32 #include <gst/video/video.h>
33 #include <gst/pbutils/pbutils.h>
34 #include <gst/tag/tag.h>
35 #include <gst/math-compat.h>
36 #include <stdlib.h>
37 #include <stdio.h>
38 #include <string.h>
39
40 #include <glib/gprintf.h>
41
42 #include "gst-play-kb.h"
43
44 #define VOLUME_STEPS 20
45
46 static gboolean wait_on_eos = FALSE;
47
48 GST_DEBUG_CATEGORY (play_debug);
49 #define GST_CAT_DEFAULT play_debug
50
51 typedef enum
52 {
53   GST_PLAY_TRICK_MODE_NONE = 0,
54   GST_PLAY_TRICK_MODE_DEFAULT,
55   GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO,
56   GST_PLAY_TRICK_MODE_KEY_UNITS,
57   GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO,
58   GST_PLAY_TRICK_MODE_INSTANT_RATE,
59   GST_PLAY_TRICK_MODE_LAST
60 } GstPlayTrickMode;
61
62 typedef enum
63 {
64   GST_PLAY_TRACK_TYPE_INVALID = 0,
65   GST_PLAY_TRACK_TYPE_AUDIO,
66   GST_PLAY_TRACK_TYPE_VIDEO,
67   GST_PLAY_TRACK_TYPE_SUBTITLE
68 } GstPlayTrackType;
69
70 typedef struct
71 {
72   gchar **uris;
73   guint num_uris;
74   gint cur_idx;
75
76   GstElement *playbin;
77
78   /* playbin3 variables */
79   gboolean is_playbin3;
80   GstStreamCollection *collection;
81   gchar *cur_audio_sid;
82   gchar *cur_video_sid;
83   gchar *cur_text_sid;
84   GMutex selection_lock;
85
86   GMainLoop *loop;
87   guint bus_watch;
88   guint timeout;
89
90   /* missing plugin messages */
91   GList *missing;
92
93   gboolean buffering;
94   gboolean is_live;
95
96   GstState desired_state;       /* as per user interaction, PAUSED or PLAYING */
97
98   gulong deep_notify_id;
99
100   /* configuration */
101   gboolean gapless;
102
103   GstPlayTrickMode trick_mode;
104   gdouble rate;
105 } GstPlay;
106
107 static gboolean quiet = FALSE;
108 static gboolean instant_rate_changes = FALSE;
109
110 static gboolean play_bus_msg (GstBus * bus, GstMessage * msg, gpointer data);
111 static gboolean play_next (GstPlay * play);
112 static gboolean play_prev (GstPlay * play);
113 static gboolean play_timeout (gpointer user_data);
114 static void play_about_to_finish (GstElement * playbin, gpointer user_data);
115 static void play_reset (GstPlay * play);
116 static void play_set_relative_volume (GstPlay * play, gdouble volume_step);
117 static gboolean play_do_seek (GstPlay * play, gint64 pos, gdouble rate,
118     GstPlayTrickMode mode);
119
120 /* *INDENT-OFF* */
121 static void gst_play_printf (const gchar * format, ...) G_GNUC_PRINTF (1, 2);
122 /* *INDENT-ON* */
123
124 static void keyboard_cb (const gchar * key_input, gpointer user_data);
125 static void relative_seek (GstPlay * play, gdouble percent);
126
127 static void
128 gst_play_printf (const gchar * format, ...)
129 {
130   gchar *str = NULL;
131   va_list args;
132   int len;
133
134   if (quiet)
135     return;
136
137   va_start (args, format);
138
139   len = g_vasprintf (&str, format, args);
140
141   va_end (args);
142
143   if (len > 0 && str != NULL)
144     gst_print ("%s", str);
145
146   g_free (str);
147 }
148
149 #define gst_print gst_play_printf
150
151 static GstPlay *
152 play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink,
153     gboolean gapless, gdouble initial_volume, gboolean verbose,
154     const gchar * flags_string, gboolean use_playbin3)
155 {
156   GstElement *sink, *playbin;
157   GstPlay *play;
158
159
160   if (use_playbin3) {
161     playbin = gst_element_factory_make ("playbin3", "playbin");
162   } else {
163     playbin = gst_element_factory_make ("playbin", "playbin");
164   }
165
166   if (playbin == NULL)
167     return NULL;
168
169   play = g_new0 (GstPlay, 1);
170
171   play->uris = uris;
172   play->num_uris = g_strv_length (uris);
173   play->cur_idx = -1;
174
175   play->playbin = playbin;
176
177   if (use_playbin3) {
178     play->is_playbin3 = TRUE;
179   } else {
180     const gchar *env = g_getenv ("USE_PLAYBIN3");
181     if (env && g_str_has_prefix (env, "1"))
182       play->is_playbin3 = TRUE;
183   }
184
185   g_mutex_init (&play->selection_lock);
186
187   if (audio_sink != NULL) {
188     if (strchr (audio_sink, ' ') != NULL)
189       sink = gst_parse_bin_from_description (audio_sink, TRUE, NULL);
190     else
191       sink = gst_element_factory_make (audio_sink, NULL);
192
193     if (sink != NULL)
194       g_object_set (play->playbin, "audio-sink", sink, NULL);
195     else
196       g_warning ("Couldn't create specified audio sink '%s'", audio_sink);
197   }
198   if (video_sink != NULL) {
199     if (strchr (video_sink, ' ') != NULL)
200       sink = gst_parse_bin_from_description (video_sink, TRUE, NULL);
201     else
202       sink = gst_element_factory_make (video_sink, NULL);
203
204     if (sink != NULL)
205       g_object_set (play->playbin, "video-sink", sink, NULL);
206     else
207       g_warning ("Couldn't create specified video sink '%s'", video_sink);
208   }
209
210   if (flags_string != NULL) {
211     GParamSpec *pspec;
212     GValue val = { 0, };
213
214     pspec =
215         g_object_class_find_property (G_OBJECT_GET_CLASS (playbin), "flags");
216     g_value_init (&val, pspec->value_type);
217     if (gst_value_deserialize (&val, flags_string))
218       g_object_set_property (G_OBJECT (play->playbin), "flags", &val);
219     else
220       gst_printerr ("Couldn't convert '%s' to playbin flags!\n", flags_string);
221     g_value_unset (&val);
222   }
223
224   if (verbose) {
225     play->deep_notify_id =
226         gst_element_add_property_deep_notify_watch (play->playbin, NULL, TRUE);
227   }
228
229   play->loop = g_main_loop_new (NULL, FALSE);
230
231   play->bus_watch = gst_bus_add_watch (GST_ELEMENT_BUS (play->playbin),
232       play_bus_msg, play);
233
234   /* FIXME: make configurable incl. 0 for disable */
235   play->timeout = g_timeout_add (100, play_timeout, play);
236
237   play->missing = NULL;
238   play->buffering = FALSE;
239   play->is_live = FALSE;
240
241   play->desired_state = GST_STATE_PLAYING;
242
243   play->gapless = gapless;
244   if (gapless) {
245     g_signal_connect (play->playbin, "about-to-finish",
246         G_CALLBACK (play_about_to_finish), play);
247   }
248
249   if (initial_volume != -1)
250     play_set_relative_volume (play, initial_volume - 1.0);
251
252   play->rate = 1.0;
253   play->trick_mode = GST_PLAY_TRICK_MODE_NONE;
254
255   return play;
256 }
257
258 static void
259 play_free (GstPlay * play)
260 {
261   /* No need to see all those pad caps going to NULL etc., it's just noise */
262   if (play->deep_notify_id != 0)
263     g_signal_handler_disconnect (play->playbin, play->deep_notify_id);
264
265   play_reset (play);
266
267   gst_element_set_state (play->playbin, GST_STATE_NULL);
268   gst_object_unref (play->playbin);
269
270   g_source_remove (play->bus_watch);
271   g_source_remove (play->timeout);
272   g_main_loop_unref (play->loop);
273
274   g_strfreev (play->uris);
275
276   if (play->collection)
277     gst_object_unref (play->collection);
278   g_free (play->cur_audio_sid);
279   g_free (play->cur_video_sid);
280   g_free (play->cur_text_sid);
281
282   g_mutex_clear (&play->selection_lock);
283
284   g_free (play);
285 }
286
287 /* reset for new file/stream */
288 static void
289 play_reset (GstPlay * play)
290 {
291   g_list_foreach (play->missing, (GFunc) gst_message_unref, NULL);
292   play->missing = NULL;
293
294   play->buffering = FALSE;
295   play->is_live = FALSE;
296 }
297
298 static void
299 play_set_relative_volume (GstPlay * play, gdouble volume_step)
300 {
301   gdouble volume;
302
303   volume = gst_stream_volume_get_volume (GST_STREAM_VOLUME (play->playbin),
304       GST_STREAM_VOLUME_FORMAT_CUBIC);
305
306   volume = round ((volume + volume_step) * VOLUME_STEPS) / VOLUME_STEPS;
307   volume = CLAMP (volume, 0.0, 10.0);
308
309   gst_stream_volume_set_volume (GST_STREAM_VOLUME (play->playbin),
310       GST_STREAM_VOLUME_FORMAT_CUBIC, volume);
311
312   gst_print (_("Volume: %.0f%%"), volume * 100);
313   gst_print ("                  \n");
314 }
315
316 static void
317 play_toggle_audio_mute (GstPlay * play)
318 {
319   gboolean mute;
320
321   mute = gst_stream_volume_get_mute (GST_STREAM_VOLUME (play->playbin));
322
323   mute = !mute;
324   gst_stream_volume_set_mute (GST_STREAM_VOLUME (play->playbin), mute);
325
326   if (mute)
327     gst_print (_("Mute: on"));
328   else
329     gst_print (_("Mute: off"));
330   gst_print ("                  \n");
331 }
332
333 /* returns TRUE if something was installed and we should restart playback */
334 static gboolean
335 play_install_missing_plugins (GstPlay * play)
336 {
337   /* FIXME: implement: try to install any missing plugins we haven't
338    * tried to install before */
339   return FALSE;
340 }
341
342 static gboolean
343 play_bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
344 {
345   GstPlay *play = user_data;
346
347   switch (GST_MESSAGE_TYPE (msg)) {
348     case GST_MESSAGE_ASYNC_DONE:
349
350       /* dump graph on preroll */
351       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (play->playbin),
352           GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.async-done");
353
354       gst_print ("Prerolled.\r");
355       if (play->missing != NULL && play_install_missing_plugins (play)) {
356         gst_print ("New plugins installed, trying again...\n");
357         --play->cur_idx;
358         play_next (play);
359       }
360       break;
361     case GST_MESSAGE_BUFFERING:{
362       gint percent;
363
364       if (!play->buffering)
365         gst_print ("\n");
366
367       gst_message_parse_buffering (msg, &percent);
368       gst_print ("%s %d%%  \r", _("Buffering..."), percent);
369
370       if (percent == 100) {
371         /* a 100% message means buffering is done */
372         if (play->buffering) {
373           play->buffering = FALSE;
374           /* no state management needed for live pipelines */
375           if (!play->is_live)
376             gst_element_set_state (play->playbin, play->desired_state);
377         }
378       } else {
379         /* buffering... */
380         if (!play->buffering) {
381           if (!play->is_live)
382             gst_element_set_state (play->playbin, GST_STATE_PAUSED);
383           play->buffering = TRUE;
384         }
385       }
386       break;
387     }
388     case GST_MESSAGE_CLOCK_LOST:{
389       gst_print (_("Clock lost, selecting a new one\n"));
390       gst_element_set_state (play->playbin, GST_STATE_PAUSED);
391       gst_element_set_state (play->playbin, GST_STATE_PLAYING);
392       break;
393     }
394     case GST_MESSAGE_LATENCY:
395       gst_print ("Redistribute latency...\n");
396       gst_bin_recalculate_latency (GST_BIN (play->playbin));
397       break;
398     case GST_MESSAGE_REQUEST_STATE:{
399       GstState state;
400       gchar *name;
401
402       name = gst_object_get_path_string (GST_MESSAGE_SRC (msg));
403
404       gst_message_parse_request_state (msg, &state);
405
406       gst_print ("Setting state to %s as requested by %s...\n",
407           gst_element_state_get_name (state), name);
408
409       gst_element_set_state (play->playbin, state);
410       g_free (name);
411       break;
412     }
413     case GST_MESSAGE_EOS:
414       /* print final position at end */
415       play_timeout (play);
416       gst_print ("\n");
417       /* and switch to next item in list */
418       if (!wait_on_eos && !play_next (play)) {
419         gst_print ("%s\n", _("Reached end of play list."));
420         g_main_loop_quit (play->loop);
421       }
422       break;
423     case GST_MESSAGE_WARNING:{
424       GError *err;
425       gchar *dbg = NULL;
426
427       /* dump graph on warning */
428       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (play->playbin),
429           GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.warning");
430
431       gst_message_parse_warning (msg, &err, &dbg);
432       gst_printerr ("WARNING %s\n", err->message);
433       if (dbg != NULL)
434         gst_printerr ("WARNING debug information: %s\n", dbg);
435       g_clear_error (&err);
436       g_free (dbg);
437       break;
438     }
439     case GST_MESSAGE_ERROR:{
440       GError *err;
441       gchar *dbg;
442
443       /* dump graph on error */
444       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (play->playbin),
445           GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.error");
446
447       gst_message_parse_error (msg, &err, &dbg);
448       gst_printerr ("ERROR %s for %s\n", err->message,
449           play->uris[play->cur_idx]);
450       if (dbg != NULL)
451         gst_printerr ("ERROR debug information: %s\n", dbg);
452       g_clear_error (&err);
453       g_free (dbg);
454
455       /* flush any other error messages from the bus and clean up */
456       gst_element_set_state (play->playbin, GST_STATE_NULL);
457
458       if (play->missing != NULL && play_install_missing_plugins (play)) {
459         gst_print ("New plugins installed, trying again...\n");
460         --play->cur_idx;
461         play_next (play);
462         break;
463       }
464       /* try next item in list then */
465       if (!play_next (play)) {
466         gst_print ("%s\n", _("Reached end of play list."));
467         g_main_loop_quit (play->loop);
468       }
469       break;
470     }
471     case GST_MESSAGE_ELEMENT:
472     {
473       GstNavigationMessageType mtype = gst_navigation_message_get_type (msg);
474       if (mtype == GST_NAVIGATION_MESSAGE_EVENT) {
475         GstEvent *ev = NULL;
476
477         if (gst_navigation_message_parse_event (msg, &ev)) {
478           GstNavigationEventType e_type = gst_navigation_event_get_type (ev);
479           switch (e_type) {
480             case GST_NAVIGATION_EVENT_KEY_PRESS:
481             {
482               const gchar *key;
483
484               if (gst_navigation_event_parse_key_event (ev, &key)) {
485                 GST_INFO ("Key press: %s", key);
486
487                 if (strcmp (key, "Left") == 0)
488                   key = GST_PLAY_KB_ARROW_LEFT;
489                 else if (strcmp (key, "Right") == 0)
490                   key = GST_PLAY_KB_ARROW_RIGHT;
491                 else if (strcmp (key, "Up") == 0)
492                   key = GST_PLAY_KB_ARROW_UP;
493                 else if (strcmp (key, "Down") == 0)
494                   key = GST_PLAY_KB_ARROW_DOWN;
495                 else if (strcmp (key, "space") == 0 ||
496                     strcmp (key, "Space") == 0)
497                   key = " ";
498                 else if (strlen (key) > 1)
499                   break;
500
501                 keyboard_cb (key, user_data);
502               }
503               break;
504             }
505             case GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS:
506             {
507               gint button;
508               if (gst_navigation_event_parse_mouse_button_event (ev, &button,
509                       NULL, NULL)) {
510                 if (button == 4) {
511                   /* wheel up */
512                   relative_seek (play, +0.08);
513                 } else if (button == 5) {
514                   /* wheel down */
515                   relative_seek (play, -0.01);
516                 }
517               }
518               break;
519             }
520             default:
521               break;
522           }
523         }
524         if (ev)
525           gst_event_unref (ev);
526       }
527       break;
528     }
529     case GST_MESSAGE_PROPERTY_NOTIFY:{
530       const GValue *val;
531       const gchar *name;
532       GstObject *obj;
533       gchar *val_str = NULL;
534       gchar *obj_name;
535
536       gst_message_parse_property_notify (msg, &obj, &name, &val);
537
538       obj_name = gst_object_get_path_string (GST_OBJECT (obj));
539       if (val != NULL) {
540         if (G_VALUE_HOLDS_STRING (val))
541           val_str = g_value_dup_string (val);
542         else if (G_VALUE_TYPE (val) == GST_TYPE_CAPS)
543           val_str = gst_caps_to_string (g_value_get_boxed (val));
544         else if (G_VALUE_TYPE (val) == GST_TYPE_TAG_LIST)
545           val_str = gst_tag_list_to_string (g_value_get_boxed (val));
546         else
547           val_str = gst_value_serialize (val);
548       } else {
549         val_str = g_strdup ("(no value)");
550       }
551
552       gst_play_printf ("%s: %s = %s\n", obj_name, name, val_str);
553       g_free (obj_name);
554       g_free (val_str);
555       break;
556     }
557     case GST_MESSAGE_STREAM_COLLECTION:
558     {
559       GstStreamCollection *collection = NULL;
560       gst_message_parse_stream_collection (msg, &collection);
561
562       if (collection) {
563         g_mutex_lock (&play->selection_lock);
564         gst_object_replace ((GstObject **) & play->collection,
565             (GstObject *) collection);
566         g_mutex_unlock (&play->selection_lock);
567       }
568       break;
569     }
570     case GST_MESSAGE_STREAMS_SELECTED:
571     {
572       GstStreamCollection *collection = NULL;
573       guint i, len;
574
575       gst_message_parse_streams_selected (msg, &collection);
576       if (collection) {
577         g_mutex_lock (&play->selection_lock);
578         gst_object_replace ((GstObject **) & play->collection,
579             (GstObject *) collection);
580
581         /* Free all last stream-ids */
582         g_free (play->cur_audio_sid);
583         g_free (play->cur_video_sid);
584         g_free (play->cur_text_sid);
585         play->cur_audio_sid = NULL;
586         play->cur_video_sid = NULL;
587         play->cur_text_sid = NULL;
588
589         len = gst_message_streams_selected_get_size (msg);
590         for (i = 0; i < len; i++) {
591           GstStream *stream = gst_message_streams_selected_get_stream (msg, i);
592           if (stream) {
593             GstStreamType type = gst_stream_get_stream_type (stream);
594             const gchar *stream_id = gst_stream_get_stream_id (stream);
595
596             if (type & GST_STREAM_TYPE_AUDIO) {
597               play->cur_audio_sid = g_strdup (stream_id);
598             } else if (type & GST_STREAM_TYPE_VIDEO) {
599               play->cur_video_sid = g_strdup (stream_id);
600             } else if (type & GST_STREAM_TYPE_TEXT) {
601               play->cur_text_sid = g_strdup (stream_id);
602             } else {
603               gst_print ("Unknown stream type with stream-id %s", stream_id);
604             }
605             gst_object_unref (stream);
606           }
607         }
608
609         gst_object_unref (collection);
610         g_mutex_unlock (&play->selection_lock);
611       }
612       break;
613     }
614     default:
615       if (gst_is_missing_plugin_message (msg)) {
616         gchar *desc;
617
618         desc = gst_missing_plugin_message_get_description (msg);
619         gst_print ("Missing plugin: %s\n", desc);
620         g_free (desc);
621         play->missing = g_list_append (play->missing, gst_message_ref (msg));
622       }
623       break;
624   }
625
626   return TRUE;
627 }
628
629 static gboolean
630 play_timeout (gpointer user_data)
631 {
632   GstPlay *play = user_data;
633   gint64 pos = -1, dur = -1;
634   const gchar *paused = _("Paused");
635   gchar *status;
636
637   if (play->buffering)
638     return TRUE;
639
640   gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos);
641   gst_element_query_duration (play->playbin, GST_FORMAT_TIME, &dur);
642
643   if (play->desired_state == GST_STATE_PAUSED) {
644     status = (gchar *) paused;
645   } else {
646     gint len = g_utf8_strlen (paused, -1);
647     status = g_newa (gchar, len + 1);
648     memset (status, ' ', len);
649     status[len] = '\0';
650   }
651
652   if (pos >= 0 && dur > 0) {
653     gchar dstr[32], pstr[32];
654
655     /* FIXME: pretty print in nicer format */
656     g_snprintf (pstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (pos));
657     pstr[9] = '\0';
658     g_snprintf (dstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (dur));
659     dstr[9] = '\0';
660     gst_print ("%s / %s %s\r", pstr, dstr, status);
661   }
662
663   return TRUE;
664 }
665
666 static gchar *
667 play_uri_get_display_name (GstPlay * play, const gchar * uri)
668 {
669   gchar *loc;
670
671   if (gst_uri_has_protocol (uri, "file")) {
672     loc = g_filename_from_uri (uri, NULL, NULL);
673   } else if (gst_uri_has_protocol (uri, "pushfile")) {
674     loc = g_filename_from_uri (uri + 4, NULL, NULL);
675   } else {
676     loc = g_strdup (uri);
677   }
678
679   /* Maybe additionally use glib's filename to display name function */
680   return loc;
681 }
682
683 static void
684 play_uri (GstPlay * play, const gchar * next_uri)
685 {
686   gchar *loc;
687
688   gst_element_set_state (play->playbin, GST_STATE_READY);
689   play_reset (play);
690
691   loc = play_uri_get_display_name (play, next_uri);
692   gst_print (_("Now playing %s\n"), loc);
693   g_free (loc);
694
695   g_object_set (play->playbin, "uri", next_uri, NULL);
696
697   switch (gst_element_set_state (play->playbin, GST_STATE_PAUSED)) {
698     case GST_STATE_CHANGE_FAILURE:
699       /* ignore, we should get an error message posted on the bus */
700       break;
701     case GST_STATE_CHANGE_NO_PREROLL:
702       gst_print ("Pipeline is live.\n");
703       play->is_live = TRUE;
704       break;
705     case GST_STATE_CHANGE_ASYNC:
706       gst_print ("Prerolling...\r");
707       break;
708     default:
709       break;
710   }
711
712   if (play->desired_state != GST_STATE_PAUSED)
713     gst_element_set_state (play->playbin, play->desired_state);
714 }
715
716 /* returns FALSE if we have reached the end of the playlist */
717 static gboolean
718 play_next (GstPlay * play)
719 {
720   if ((play->cur_idx + 1) >= play->num_uris)
721     return FALSE;
722
723   play_uri (play, play->uris[++play->cur_idx]);
724   return TRUE;
725 }
726
727 /* returns FALSE if we have reached the beginning of the playlist */
728 static gboolean
729 play_prev (GstPlay * play)
730 {
731   if (play->cur_idx == 0 || play->num_uris <= 1)
732     return FALSE;
733
734   play_uri (play, play->uris[--play->cur_idx]);
735   return TRUE;
736 }
737
738 static void
739 play_about_to_finish (GstElement * playbin, gpointer user_data)
740 {
741   GstPlay *play = user_data;
742   const gchar *next_uri;
743   gchar *loc;
744   guint next_idx;
745
746   if (!play->gapless)
747     return;
748
749   next_idx = play->cur_idx + 1;
750   if (next_idx >= play->num_uris)
751     return;
752
753   next_uri = play->uris[next_idx];
754   loc = play_uri_get_display_name (play, next_uri);
755   gst_print (_("About to finish, preparing next title: %s"), loc);
756   gst_print ("\n");
757   g_free (loc);
758
759   g_object_set (play->playbin, "uri", next_uri, NULL);
760   play->cur_idx = next_idx;
761 }
762
763 static void
764 do_play (GstPlay * play)
765 {
766   gint i;
767
768   /* dump playlist */
769   for (i = 0; i < play->num_uris; ++i)
770     GST_INFO ("%4u : %s", i, play->uris[i]);
771
772   if (!play_next (play))
773     return;
774
775   g_main_loop_run (play->loop);
776 }
777
778 static gint
779 compare (gconstpointer a, gconstpointer b)
780 {
781   gchar *a1, *b1;
782   gint ret;
783
784   a1 = g_utf8_collate_key_for_filename ((gchar *) a, -1);
785   b1 = g_utf8_collate_key_for_filename ((gchar *) b, -1);
786   ret = strcmp (a1, b1);
787   g_free (a1);
788   g_free (b1);
789
790   return ret;
791 }
792
793 static void
794 add_to_playlist (GPtrArray * playlist, const gchar * filename)
795 {
796   GDir *dir;
797   gchar *uri;
798
799   if (gst_uri_is_valid (filename)) {
800     g_ptr_array_add (playlist, g_strdup (filename));
801     return;
802   }
803
804   if ((dir = g_dir_open (filename, 0, NULL))) {
805     const gchar *entry;
806     GList *l, *files = NULL;
807
808     while ((entry = g_dir_read_name (dir))) {
809       gchar *path;
810
811       path = g_build_filename (filename, entry, NULL);
812       files = g_list_insert_sorted (files, path, compare);
813     }
814
815     g_dir_close (dir);
816
817     for (l = files; l != NULL; l = l->next) {
818       gchar *path = (gchar *) l->data;
819
820       add_to_playlist (playlist, path);
821       g_free (path);
822     }
823     g_list_free (files);
824     return;
825   }
826
827   uri = gst_filename_to_uri (filename, NULL);
828   if (uri != NULL)
829     g_ptr_array_add (playlist, uri);
830   else
831     g_warning ("Could not make URI out of filename '%s'", filename);
832 }
833
834 static void
835 shuffle_uris (gchar ** uris, guint num)
836 {
837   gchar *tmp;
838   guint i, j;
839
840   if (num < 2)
841     return;
842
843   for (i = num - 1; i >= 1; i--) {
844     /* +1 because number returned will be in range [a;b[ so excl. stop */
845     j = g_random_int_range (0, i + 1);
846     tmp = uris[j];
847     uris[j] = uris[i];
848     uris[i] = tmp;
849   }
850 }
851
852 static void
853 restore_terminal (void)
854 {
855   gst_play_kb_set_key_handler (NULL, NULL);
856 }
857
858 static void
859 toggle_paused (GstPlay * play)
860 {
861   if (play->desired_state == GST_STATE_PLAYING)
862     play->desired_state = GST_STATE_PAUSED;
863   else
864     play->desired_state = GST_STATE_PLAYING;
865
866   if (!play->buffering) {
867     gst_element_set_state (play->playbin, play->desired_state);
868   } else if (play->desired_state == GST_STATE_PLAYING) {
869     gst_print ("\nWill play as soon as buffering finishes)\n");
870   }
871 }
872
873 static void
874 relative_seek (GstPlay * play, gdouble percent)
875 {
876   GstQuery *query;
877   gboolean seekable = FALSE;
878   gint64 dur = -1, pos = -1, step;
879
880   g_return_if_fail (percent >= -1.0 && percent <= 1.0);
881
882   if (!gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos))
883     goto seek_failed;
884
885   query = gst_query_new_seeking (GST_FORMAT_TIME);
886   if (!gst_element_query (play->playbin, query)) {
887     gst_query_unref (query);
888     goto seek_failed;
889   }
890
891   gst_query_parse_seeking (query, NULL, &seekable, NULL, &dur);
892   gst_query_unref (query);
893
894   if (!seekable || dur <= 0)
895     goto seek_failed;
896
897   step = dur * percent;
898   if (ABS (step) < GST_SECOND)
899     step = (percent < 0) ? -GST_SECOND : GST_SECOND;
900
901   pos = pos + step;
902   if (pos > dur) {
903     if (!play_next (play)) {
904       gst_print ("\n%s\n", _("Reached end of play list."));
905       g_main_loop_quit (play->loop);
906     }
907   } else {
908     if (pos < 0)
909       pos = 0;
910
911     play_do_seek (play, pos, play->rate, play->trick_mode);
912   }
913
914   return;
915
916 seek_failed:
917   {
918     gst_print ("\nCould not seek.\n");
919   }
920 }
921
922 static gboolean
923 play_set_rate_and_trick_mode (GstPlay * play, gdouble rate,
924     GstPlayTrickMode mode)
925 {
926   gint64 pos = -1;
927
928   g_return_val_if_fail (rate != 0, FALSE);
929
930   if (!gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos))
931     return FALSE;
932
933   return play_do_seek (play, pos, rate, mode);
934 }
935
936 static gboolean
937 play_do_seek (GstPlay * play, gint64 pos, gdouble rate, GstPlayTrickMode mode)
938 {
939   GstSeekFlags seek_flags;
940   GstQuery *query;
941   GstEvent *seek;
942   gboolean seekable = FALSE;
943
944   query = gst_query_new_seeking (GST_FORMAT_TIME);
945   if (!gst_element_query (play->playbin, query)) {
946     gst_query_unref (query);
947     return FALSE;
948   }
949
950   gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
951   gst_query_unref (query);
952
953   if (!seekable)
954     return FALSE;
955
956   seek_flags = 0;
957
958   switch (mode) {
959     case GST_PLAY_TRICK_MODE_DEFAULT:
960       seek_flags |= GST_SEEK_FLAG_TRICKMODE;
961       break;
962     case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
963       seek_flags |= GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
964       break;
965     case GST_PLAY_TRICK_MODE_KEY_UNITS:
966       seek_flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS;
967       break;
968     case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
969       seek_flags |=
970           GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
971       break;
972     case GST_PLAY_TRICK_MODE_NONE:
973     default:
974       break;
975   }
976
977   /* See if we can do an instant rate change (not changing dir) */
978   if (mode & GST_PLAY_TRICK_MODE_INSTANT_RATE && rate * play->rate > 0) {
979     seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
980         seek_flags | GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
981         GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE,
982         GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
983     if (gst_element_send_event (play->playbin, seek)) {
984       goto done;
985     }
986   }
987
988   /* No instant rate change, need to do a flushing seek */
989   seek_flags |= GST_SEEK_FLAG_FLUSH;
990   if (rate >= 0)
991     seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
992         seek_flags | GST_SEEK_FLAG_ACCURATE,
993         /* start */ GST_SEEK_TYPE_SET, pos,
994         /* stop */ GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
995   else
996     seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
997         seek_flags | GST_SEEK_FLAG_ACCURATE,
998         /* start */ GST_SEEK_TYPE_SET, 0,
999         /* stop */ GST_SEEK_TYPE_SET, pos);
1000
1001   if (!gst_element_send_event (play->playbin, seek))
1002     return FALSE;
1003
1004 done:
1005   play->rate = rate;
1006   play->trick_mode = mode & ~GST_PLAY_TRICK_MODE_INSTANT_RATE;
1007   return TRUE;
1008 }
1009
1010 static void
1011 play_set_playback_rate (GstPlay * play, gdouble rate)
1012 {
1013   GstPlayTrickMode mode = play->trick_mode;
1014
1015   if (instant_rate_changes)
1016     mode |= GST_PLAY_TRICK_MODE_INSTANT_RATE;
1017
1018   if (play_set_rate_and_trick_mode (play, rate, mode)) {
1019     gst_print (_("Playback rate: %.2f"), rate);
1020     gst_print ("                               \n");
1021   } else {
1022     gst_print ("\n");
1023     gst_print (_("Could not change playback rate to %.2f"), rate);
1024     gst_print (".\n");
1025   }
1026 }
1027
1028 static void
1029 play_set_relative_playback_rate (GstPlay * play, gdouble rate_step,
1030     gboolean reverse_direction)
1031 {
1032   gdouble new_rate = play->rate + rate_step;
1033
1034   if (reverse_direction)
1035     new_rate *= -1.0;
1036
1037   play_set_playback_rate (play, new_rate);
1038 }
1039
1040 static const gchar *
1041 trick_mode_get_description (GstPlayTrickMode mode)
1042 {
1043   switch (mode) {
1044     case GST_PLAY_TRICK_MODE_NONE:
1045       return "normal playback, trick modes disabled";
1046     case GST_PLAY_TRICK_MODE_DEFAULT:
1047       return "trick mode: default";
1048     case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
1049       return "trick mode: default, no audio";
1050     case GST_PLAY_TRICK_MODE_KEY_UNITS:
1051       return "trick mode: key frames only";
1052     case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
1053       return "trick mode: key frames only, no audio";
1054     default:
1055       break;
1056   }
1057   return "unknown trick mode";
1058 }
1059
1060 static void
1061 play_switch_trick_mode (GstPlay * play)
1062 {
1063   GstPlayTrickMode new_mode = ++play->trick_mode;
1064   const gchar *mode_desc;
1065
1066   if (new_mode == GST_PLAY_TRICK_MODE_LAST)
1067     new_mode = GST_PLAY_TRICK_MODE_NONE;
1068
1069   mode_desc = trick_mode_get_description (new_mode);
1070
1071   if (play_set_rate_and_trick_mode (play, play->rate, new_mode)) {
1072     gst_print ("Rate: %.2f (%s)                      \n", play->rate,
1073         mode_desc);
1074   } else {
1075     gst_print ("\nCould not change trick mode to %s.\n", mode_desc);
1076   }
1077 }
1078
1079 static GstStream *
1080 play_get_nth_stream_in_collection (GstPlay * play, guint index,
1081     GstPlayTrackType track_type)
1082 {
1083   guint len, i, n_streams = 0;
1084   GstStreamType target_type;
1085
1086   switch (track_type) {
1087     case GST_PLAY_TRACK_TYPE_AUDIO:
1088       target_type = GST_STREAM_TYPE_AUDIO;
1089       break;
1090     case GST_PLAY_TRACK_TYPE_VIDEO:
1091       target_type = GST_STREAM_TYPE_VIDEO;
1092       break;
1093     case GST_PLAY_TRACK_TYPE_SUBTITLE:
1094       target_type = GST_STREAM_TYPE_TEXT;
1095       break;
1096     default:
1097       return NULL;
1098   }
1099
1100   len = gst_stream_collection_get_size (play->collection);
1101
1102   for (i = 0; i < len; i++) {
1103     GstStream *stream = gst_stream_collection_get_stream (play->collection, i);
1104     GstStreamType type = gst_stream_get_stream_type (stream);
1105
1106     if (type & target_type) {
1107       if (index == n_streams)
1108         return stream;
1109
1110       n_streams++;
1111     }
1112   }
1113
1114   return NULL;
1115 }
1116
1117 static void
1118 play_cycle_track_selection (GstPlay * play, GstPlayTrackType track_type)
1119 {
1120   const gchar *prop_cur, *prop_n, *prop_get, *name;
1121   gint cur = -1, n = -1;
1122   guint flag, cur_flags;
1123
1124   /* playbin3 variables */
1125   GList *selected_streams = NULL;
1126   gint cur_audio_idx = -1, cur_video_idx = -1, cur_text_idx = -1;
1127   gint nb_audio = 0, nb_video = 0, nb_text = 0;
1128   guint len, i;
1129
1130   g_mutex_lock (&play->selection_lock);
1131   if (play->is_playbin3) {
1132     if (!play->collection) {
1133       gst_print ("No stream-collection\n");
1134       g_mutex_unlock (&play->selection_lock);
1135       return;
1136     }
1137
1138     /* Check the total number of streams of each type */
1139     len = gst_stream_collection_get_size (play->collection);
1140     for (i = 0; i < len; i++) {
1141       GstStream *stream =
1142           gst_stream_collection_get_stream (play->collection, i);
1143       if (stream) {
1144         GstStreamType type = gst_stream_get_stream_type (stream);
1145         const gchar *sid = gst_stream_get_stream_id (stream);
1146
1147         if (type & GST_STREAM_TYPE_AUDIO) {
1148           if (play->cur_audio_sid && !g_strcmp0 (play->cur_audio_sid, sid))
1149             cur_audio_idx = nb_audio;
1150           nb_audio++;
1151         } else if (type & GST_STREAM_TYPE_VIDEO) {
1152           if (play->cur_video_sid && !g_strcmp0 (play->cur_video_sid, sid))
1153             cur_video_idx = nb_video;
1154           nb_video++;
1155         } else if (type & GST_STREAM_TYPE_TEXT) {
1156           if (play->cur_text_sid && !g_strcmp0 (play->cur_text_sid, sid))
1157             cur_text_idx = nb_text;
1158           nb_text++;
1159         } else {
1160           gst_print ("Unknown stream type with stream-id %s", sid);
1161         }
1162       }
1163     }
1164   }
1165
1166   switch (track_type) {
1167     case GST_PLAY_TRACK_TYPE_AUDIO:
1168       prop_get = "get-audio-tags";
1169       prop_cur = "current-audio";
1170       prop_n = "n-audio";
1171       name = "audio";
1172       flag = 0x2;
1173       if (play->is_playbin3) {
1174         n = nb_audio;
1175         cur = cur_audio_idx;
1176         if (play->cur_video_sid) {
1177           selected_streams =
1178               g_list_append (selected_streams, play->cur_video_sid);
1179         }
1180         if (play->cur_text_sid) {
1181           selected_streams =
1182               g_list_append (selected_streams, play->cur_text_sid);
1183         }
1184       }
1185       break;
1186     case GST_PLAY_TRACK_TYPE_VIDEO:
1187       prop_get = "get-video-tags";
1188       prop_cur = "current-video";
1189       prop_n = "n-video";
1190       name = "video";
1191       flag = 0x1;
1192       if (play->is_playbin3) {
1193         n = nb_video;
1194         cur = cur_video_idx;
1195         if (play->cur_audio_sid) {
1196           selected_streams =
1197               g_list_append (selected_streams, play->cur_audio_sid);
1198         }
1199         if (play->cur_text_sid) {
1200           selected_streams =
1201               g_list_append (selected_streams, play->cur_text_sid);
1202         }
1203       }
1204       break;
1205     case GST_PLAY_TRACK_TYPE_SUBTITLE:
1206       prop_get = "get-text-tags";
1207       prop_cur = "current-text";
1208       prop_n = "n-text";
1209       name = "subtitle";
1210       flag = 0x4;
1211       if (play->is_playbin3) {
1212         n = nb_text;
1213         cur = cur_text_idx;
1214         if (play->cur_audio_sid) {
1215           selected_streams =
1216               g_list_append (selected_streams, play->cur_audio_sid);
1217         }
1218         if (play->cur_video_sid) {
1219           selected_streams =
1220               g_list_append (selected_streams, play->cur_video_sid);
1221         }
1222       }
1223       break;
1224     default:
1225       return;
1226   }
1227
1228   if (play->is_playbin3) {
1229     if (n > 0) {
1230       if (cur < 0)
1231         cur = 0;
1232       else
1233         cur = (cur + 1) % (n + 1);
1234     }
1235   } else {
1236     g_object_get (play->playbin, prop_cur, &cur, prop_n, &n, "flags",
1237         &cur_flags, NULL);
1238
1239     if (!(cur_flags & flag))
1240       cur = 0;
1241     else
1242       cur = (cur + 1) % (n + 1);
1243   }
1244
1245   if (n < 1) {
1246     gst_print ("No %s tracks.\n", name);
1247     g_mutex_unlock (&play->selection_lock);
1248   } else {
1249     gchar *lcode = NULL, *lname = NULL;
1250     const gchar *lang = NULL;
1251     GstTagList *tags = NULL;
1252
1253     if (cur >= n && track_type != GST_PLAY_TRACK_TYPE_VIDEO) {
1254       cur = -1;
1255       gst_print ("Disabling %s.           \n", name);
1256       if (play->is_playbin3) {
1257         /* Just make it empty for the track type */
1258       } else if (cur_flags & flag) {
1259         cur_flags &= ~flag;
1260         g_object_set (play->playbin, "flags", cur_flags, NULL);
1261       }
1262     } else {
1263       /* For video we only want to switch between streams, not disable it altogether */
1264       if (cur >= n)
1265         cur = 0;
1266
1267       if (play->is_playbin3) {
1268         GstStream *stream;
1269
1270         stream = play_get_nth_stream_in_collection (play, cur, track_type);
1271         if (stream) {
1272           selected_streams = g_list_append (selected_streams,
1273               (gchar *) gst_stream_get_stream_id (stream));
1274           tags = gst_stream_get_tags (stream);
1275         } else {
1276           gst_print ("Collection has no stream for track %d of %d.\n",
1277               cur + 1, n);
1278         }
1279       } else {
1280         if (!(cur_flags & flag) && track_type != GST_PLAY_TRACK_TYPE_VIDEO) {
1281           cur_flags |= flag;
1282           g_object_set (play->playbin, "flags", cur_flags, NULL);
1283         }
1284         g_signal_emit_by_name (play->playbin, prop_get, cur, &tags);
1285       }
1286
1287       if (tags != NULL) {
1288         if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &lcode))
1289           lang = gst_tag_get_language_name (lcode);
1290         else if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_NAME, &lname))
1291           lang = lname;
1292         gst_tag_list_unref (tags);
1293       }
1294       if (lang != NULL)
1295         gst_print ("Switching to %s track %d of %d (%s).\n", name, cur + 1, n,
1296             lang);
1297       else
1298         gst_print ("Switching to %s track %d of %d.\n", name, cur + 1, n);
1299     }
1300     g_free (lcode);
1301     g_free (lname);
1302     g_mutex_unlock (&play->selection_lock);
1303
1304     if (play->is_playbin3) {
1305       if (selected_streams)
1306         gst_element_send_event (play->playbin,
1307             gst_event_new_select_streams (selected_streams));
1308       else
1309         gst_print ("Can't disable all streams !\n");
1310     } else {
1311       g_object_set (play->playbin, prop_cur, cur, NULL);
1312     }
1313   }
1314
1315   if (selected_streams)
1316     g_list_free (selected_streams);
1317 }
1318
1319 static void
1320 print_keyboard_help (void)
1321 {
1322   static struct
1323   {
1324     const gchar *key_desc;
1325     const gchar *key_help;
1326   } key_controls[] = {
1327     {
1328     N_("space"), N_("pause/unpause")}, {
1329     N_("q or ESC"), N_("quit")}, {
1330     N_("> or n"), N_("play next")}, {
1331     N_("< or b"), N_("play previous")}, {
1332     "\342\206\222", N_("seek forward")}, {
1333     "\342\206\220", N_("seek backward")}, {
1334     "\342\206\221", N_("volume up")}, {
1335     "\342\206\223", N_("volume down")}, {
1336     "m", N_("toggle audio mute on/off")}, {
1337     "+", N_("increase playback rate")}, {
1338     "-", N_("decrease playback rate")}, {
1339     "d", N_("change playback direction")}, {
1340     "t", N_("enable/disable trick modes")}, {
1341     "a", N_("change audio track")}, {
1342     "v", N_("change video track")}, {
1343     "s", N_("change subtitle track")}, {
1344     "0", N_("seek to beginning")}, {
1345   "k", N_("show keyboard shortcuts")},};
1346   guint i, chars_to_pad, desc_len, max_desc_len = 0;
1347
1348   gst_print ("\n\n%s\n\n", _("Interactive mode - keyboard controls:"));
1349
1350   for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
1351     desc_len = g_utf8_strlen (key_controls[i].key_desc, -1);
1352     max_desc_len = MAX (max_desc_len, desc_len);
1353   }
1354   ++max_desc_len;
1355
1356   for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
1357     chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1);
1358     gst_print ("\t%s", key_controls[i].key_desc);
1359     gst_print ("%-*s: ", chars_to_pad, "");
1360     gst_print ("%s\n", key_controls[i].key_help);
1361   }
1362   gst_print ("\n");
1363 }
1364
1365 static void
1366 keyboard_cb (const gchar * key_input, gpointer user_data)
1367 {
1368   GstPlay *play = (GstPlay *) user_data;
1369   gchar key = '\0';
1370
1371   /* only want to switch/case on single char, not first char of string */
1372   if (key_input[0] != '\0' && key_input[1] == '\0')
1373     key = g_ascii_tolower (key_input[0]);
1374
1375   switch (key) {
1376     case 'k':
1377       print_keyboard_help ();
1378       break;
1379     case ' ':
1380       toggle_paused (play);
1381       break;
1382     case 'q':
1383     case 'Q':
1384       g_main_loop_quit (play->loop);
1385       break;
1386     case 'n':
1387     case '>':
1388       if (!play_next (play)) {
1389         gst_print ("\n%s\n", _("Reached end of play list."));
1390         g_main_loop_quit (play->loop);
1391       }
1392       break;
1393     case 'b':
1394     case '<':
1395       play_prev (play);
1396       break;
1397     case '+':
1398       if (play->rate > -0.2 && play->rate < 0.0)
1399         play_set_relative_playback_rate (play, 0.0, TRUE);
1400       else if (ABS (play->rate) < 2.0)
1401         play_set_relative_playback_rate (play, 0.1, FALSE);
1402       else if (ABS (play->rate) < 4.0)
1403         play_set_relative_playback_rate (play, 0.5, FALSE);
1404       else
1405         play_set_relative_playback_rate (play, 1.0, FALSE);
1406       break;
1407     case '-':
1408       if (play->rate > 0.0 && play->rate < 0.20)
1409         play_set_relative_playback_rate (play, 0.0, TRUE);
1410       else if (ABS (play->rate) <= 2.0)
1411         play_set_relative_playback_rate (play, -0.1, FALSE);
1412       else if (ABS (play->rate) <= 4.0)
1413         play_set_relative_playback_rate (play, -0.5, FALSE);
1414       else
1415         play_set_relative_playback_rate (play, -1.0, FALSE);
1416       break;
1417     case 'd':
1418       play_set_relative_playback_rate (play, 0.0, TRUE);
1419       break;
1420     case 't':
1421       play_switch_trick_mode (play);
1422       break;
1423     case 27:                   /* ESC */
1424       if (key_input[1] == '\0') {
1425         g_main_loop_quit (play->loop);
1426         break;
1427       }
1428     case 'a':
1429       play_cycle_track_selection (play, GST_PLAY_TRACK_TYPE_AUDIO);
1430       break;
1431     case 'v':
1432       play_cycle_track_selection (play, GST_PLAY_TRACK_TYPE_VIDEO);
1433       break;
1434     case 's':
1435       play_cycle_track_selection (play, GST_PLAY_TRACK_TYPE_SUBTITLE);
1436       break;
1437     case '0':
1438       play_do_seek (play, 0, play->rate, play->trick_mode);
1439       break;
1440     case 'm':
1441       play_toggle_audio_mute (play);
1442       break;
1443     default:
1444       if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) {
1445         relative_seek (play, +0.08);
1446       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) {
1447         relative_seek (play, -0.01);
1448       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_UP) == 0) {
1449         play_set_relative_volume (play, +1.0 / VOLUME_STEPS);
1450       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_DOWN) == 0) {
1451         play_set_relative_volume (play, -1.0 / VOLUME_STEPS);
1452       } else {
1453         GST_INFO ("keyboard input:");
1454         for (; *key_input != '\0'; ++key_input)
1455           GST_INFO ("  code %3d", *key_input);
1456       }
1457       break;
1458   }
1459 }
1460
1461 int
1462 main (int argc, char **argv)
1463 {
1464   GstPlay *play;
1465   GPtrArray *playlist;
1466   gboolean verbose = FALSE;
1467   gboolean print_version = FALSE;
1468   gboolean interactive = TRUE;
1469   gboolean gapless = FALSE;
1470   gboolean shuffle = FALSE;
1471   gdouble volume = -1;
1472   gchar **filenames = NULL;
1473   gchar *audio_sink = NULL;
1474   gchar *video_sink = NULL;
1475   gchar **uris;
1476   gchar *flags = NULL;
1477   guint num, i;
1478   GError *err = NULL;
1479   GOptionContext *ctx;
1480   gchar *playlist_file = NULL;
1481   gboolean use_playbin3 = FALSE;
1482   GOptionEntry options[] = {
1483     {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
1484         N_("Output status information and property notifications"), NULL},
1485     {"flags", 0, 0, G_OPTION_ARG_STRING, &flags,
1486           N_("Control playback behaviour setting playbin 'flags' property"),
1487         NULL},
1488     {"version", 0, 0, G_OPTION_ARG_NONE, &print_version,
1489         N_("Print version information and exit"), NULL},
1490     {"videosink", 0, 0, G_OPTION_ARG_STRING, &video_sink,
1491         N_("Video sink to use (default is autovideosink)"), NULL},
1492     {"audiosink", 0, 0, G_OPTION_ARG_STRING, &audio_sink,
1493         N_("Audio sink to use (default is autoaudiosink)"), NULL},
1494     {"gapless", 0, 0, G_OPTION_ARG_NONE, &gapless,
1495         N_("Enable gapless playback"), NULL},
1496     {"shuffle", 0, 0, G_OPTION_ARG_NONE, &shuffle,
1497         N_("Shuffle playlist"), NULL},
1498     {"no-interactive", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,
1499           &interactive,
1500         N_("Disable interactive control via the keyboard"), NULL},
1501     {"volume", 0, 0, G_OPTION_ARG_DOUBLE, &volume,
1502         N_("Volume"), NULL},
1503     {"playlist", 0, 0, G_OPTION_ARG_FILENAME, &playlist_file,
1504         N_("Playlist file containing input media files"), NULL},
1505     {"instant-rate-changes", 'i', 0, G_OPTION_ARG_NONE, &instant_rate_changes,
1506           N_
1507           ("Use the experimental instant-rate-change flag when changing rate"),
1508         NULL},
1509     {"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet,
1510         N_("Do not print any output (apart from errors)"), NULL},
1511     {"use-playbin3", 0, 0, G_OPTION_ARG_NONE, &use_playbin3,
1512           N_("Use playbin3 pipeline")
1513           N_("(default varies depending on 'USE_PLAYBIN' env variable)"),
1514         NULL},
1515     {"wait-on-eos", 0, 0, G_OPTION_ARG_NONE, &wait_on_eos,
1516           N_
1517           ("Keep showing the last frame on EOS until quit or playlist change command "
1518               "(gapless is ignored)"),
1519         NULL},
1520     {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL},
1521     {NULL}
1522   };
1523
1524   setlocale (LC_ALL, "");
1525
1526 #ifdef ENABLE_NLS
1527   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1528   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1529   textdomain (GETTEXT_PACKAGE);
1530 #endif
1531
1532   g_set_prgname ("gst-play-" GST_API_VERSION);
1533   /* Ensure XInitThreads() is called if/when needed */
1534   g_setenv ("GST_GL_XINITTHREADS", "1", TRUE);
1535
1536   ctx = g_option_context_new ("FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ...");
1537   g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
1538   g_option_context_add_group (ctx, gst_init_get_option_group ());
1539   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
1540     gst_print ("Error initializing: %s\n", GST_STR_NULL (err->message));
1541     g_option_context_free (ctx);
1542     g_clear_error (&err);
1543     return 1;
1544   }
1545   g_option_context_free (ctx);
1546
1547   GST_DEBUG_CATEGORY_INIT (play_debug, "play", 0, "gst-play");
1548
1549   if (print_version) {
1550     gchar *version_str;
1551
1552     version_str = gst_version_string ();
1553     gst_print ("%s version %s\n", g_get_prgname (), PACKAGE_VERSION);
1554     gst_print ("%s\n", version_str);
1555     gst_print ("%s\n", GST_PACKAGE_ORIGIN);
1556     g_free (version_str);
1557
1558     g_free (audio_sink);
1559     g_free (video_sink);
1560     g_free (playlist_file);
1561
1562     return 0;
1563   }
1564
1565   if (wait_on_eos)
1566     gapless = FALSE;
1567
1568   playlist = g_ptr_array_new ();
1569
1570   if (playlist_file != NULL) {
1571     gchar *playlist_contents = NULL;
1572     gchar **lines = NULL;
1573
1574     if (g_file_get_contents (playlist_file, &playlist_contents, NULL, &err)) {
1575       lines = g_strsplit (playlist_contents, "\n", 0);
1576       num = g_strv_length (lines);
1577
1578       for (i = 0; i < num; i++) {
1579         if (lines[i][0] != '\0') {
1580           GST_LOG ("Playlist[%d]: %s", i + 1, lines[i]);
1581           add_to_playlist (playlist, lines[i]);
1582         }
1583       }
1584       g_strfreev (lines);
1585       g_free (playlist_contents);
1586     } else {
1587       gst_printerr ("Could not read playlist: %s\n", err->message);
1588       g_clear_error (&err);
1589     }
1590     g_free (playlist_file);
1591     playlist_file = NULL;
1592   }
1593
1594   if (playlist->len == 0 && (filenames == NULL || *filenames == NULL)) {
1595     gst_printerr (_("Usage: %s FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ..."),
1596         "gst-play-" GST_API_VERSION);
1597     gst_printerr ("\n\n"),
1598         gst_printerr ("%s\n\n",
1599         _("You must provide at least one filename or URI to play."));
1600     /* No input provided. Free array */
1601     g_ptr_array_free (playlist, TRUE);
1602
1603     g_free (audio_sink);
1604     g_free (video_sink);
1605
1606     return 1;
1607   }
1608
1609   /* fill playlist */
1610   if (filenames != NULL && *filenames != NULL) {
1611     num = g_strv_length (filenames);
1612     for (i = 0; i < num; ++i) {
1613       GST_LOG ("command line argument: %s", filenames[i]);
1614       add_to_playlist (playlist, filenames[i]);
1615     }
1616     g_strfreev (filenames);
1617   }
1618
1619   num = playlist->len;
1620   g_ptr_array_add (playlist, NULL);
1621
1622   uris = (gchar **) g_ptr_array_free (playlist, FALSE);
1623
1624   if (shuffle)
1625     shuffle_uris (uris, num);
1626
1627   /* prepare */
1628   play = play_new (uris, audio_sink, video_sink, gapless, volume, verbose,
1629       flags, use_playbin3);
1630
1631   if (play == NULL) {
1632     gst_printerr
1633         ("Failed to create 'playbin' element. Check your GStreamer installation.\n");
1634     return EXIT_FAILURE;
1635   }
1636
1637   if (interactive) {
1638     if (gst_play_kb_set_key_handler (keyboard_cb, play)) {
1639       gst_print (_("Press 'k' to see a list of keyboard shortcuts.\n"));
1640       atexit (restore_terminal);
1641     } else {
1642       gst_print ("Interactive keyboard handling in terminal not available.\n");
1643     }
1644   }
1645
1646   /* play */
1647   do_play (play);
1648
1649   /* clean up */
1650   play_free (play);
1651
1652   g_free (audio_sink);
1653   g_free (video_sink);
1654
1655   gst_print ("\n");
1656   gst_deinit ();
1657   return 0;
1658 }