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