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