f2b8ad6158940364e45031fd4dfb9846688fd984
[platform/upstream/gst-plugins-base.git] / tools / gst-play.c
1 /* GStreamer command line playback testing utility
2  *
3  * Copyright (C) 2013-2014 Tim-Philipp Müller <tim centricular net>
4  * Copyright (C) 2013 Collabora Ltd.
5  * Copyright (C) 2015 Centricular Ltd
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <locale.h>
28
29 #include <gst/gst.h>
30 #include <gst/gst-i18n-app.h>
31 #include <gst/audio/audio.h>
32 #include <gst/video/video.h>
33 #include <gst/pbutils/pbutils.h>
34 #include <gst/math-compat.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
38
39 #include <glib/gprintf.h>
40
41 #include "gst-play-kb.h"
42
43 #define VOLUME_STEPS 20
44
45 GST_DEBUG_CATEGORY (play_debug);
46 #define GST_CAT_DEFAULT play_debug
47
48 typedef enum
49 {
50   GST_PLAY_TRICK_MODE_NONE = 0,
51   GST_PLAY_TRICK_MODE_DEFAULT,
52   GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO,
53   GST_PLAY_TRICK_MODE_KEY_UNITS,
54   GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO,
55   GST_PLAY_TRICK_MODE_LAST
56 } GstPlayTrickMode;
57
58 typedef struct
59 {
60   gchar **uris;
61   guint num_uris;
62   gint cur_idx;
63
64   GstElement *playbin;
65
66   GMainLoop *loop;
67   guint bus_watch;
68   guint timeout;
69
70   /* missing plugin messages */
71   GList *missing;
72
73   gboolean buffering;
74   gboolean is_live;
75
76   GstState desired_state;       /* as per user interaction, PAUSED or PLAYING */
77
78   /* configuration */
79   gboolean gapless;
80
81   GstPlayTrickMode trick_mode;
82   gdouble rate;
83 } GstPlay;
84
85 static gboolean quiet = FALSE;
86
87 static gboolean play_bus_msg (GstBus * bus, GstMessage * msg, gpointer data);
88 static gboolean play_next (GstPlay * play);
89 static gboolean play_prev (GstPlay * play);
90 static gboolean play_timeout (gpointer user_data);
91 static void play_about_to_finish (GstElement * playbin, gpointer user_data);
92 static void play_reset (GstPlay * play);
93 static void play_set_relative_volume (GstPlay * play, gdouble volume_step);
94 static gboolean play_do_seek (GstPlay * play, gint64 pos, gdouble rate,
95     GstPlayTrickMode mode);
96
97 /* *INDENT-OFF* */
98 static void gst_play_printf (const gchar * format, ...) G_GNUC_PRINTF (1, 2);
99 /* *INDENT-ON* */
100
101 static void keyboard_cb (const gchar * key_input, gpointer user_data);
102 static void relative_seek (GstPlay * play, gdouble percent);
103
104 static void
105 gst_play_printf (const gchar * format, ...)
106 {
107   gchar *str = NULL;
108   va_list args;
109   int len;
110
111   if (quiet)
112     return;
113
114   va_start (args, format);
115
116   len = g_vasprintf (&str, format, args);
117
118   va_end (args);
119
120   if (len > 0 && str != NULL)
121     g_print ("%s", str);
122
123   g_free (str);
124 }
125
126 #define g_print gst_play_printf
127
128 static GstPlay *
129 play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink,
130     gboolean gapless, gdouble initial_volume)
131 {
132   GstElement *sink;
133   GstPlay *play;
134
135   play = g_new0 (GstPlay, 1);
136
137   play->uris = uris;
138   play->num_uris = g_strv_length (uris);
139   play->cur_idx = -1;
140
141   play->playbin = gst_element_factory_make ("playbin", "playbin");
142
143   if (audio_sink != NULL) {
144     if (strchr (audio_sink, ' ') != NULL)
145       sink = gst_parse_bin_from_description (audio_sink, TRUE, NULL);
146     else
147       sink = gst_element_factory_make (audio_sink, NULL);
148
149     if (sink != NULL)
150       g_object_set (play->playbin, "audio-sink", sink, NULL);
151     else
152       g_warning ("Couldn't create specified audio sink '%s'", audio_sink);
153   }
154   if (video_sink != NULL) {
155     if (strchr (video_sink, ' ') != NULL)
156       sink = gst_parse_bin_from_description (video_sink, TRUE, NULL);
157     else
158       sink = gst_element_factory_make (video_sink, NULL);
159
160     if (sink != NULL)
161       g_object_set (play->playbin, "video-sink", sink, NULL);
162     else
163       g_warning ("Couldn't create specified video sink '%s'", video_sink);
164   }
165
166   play->loop = g_main_loop_new (NULL, FALSE);
167
168   play->bus_watch = gst_bus_add_watch (GST_ELEMENT_BUS (play->playbin),
169       play_bus_msg, play);
170
171   /* FIXME: make configurable incl. 0 for disable */
172   play->timeout = g_timeout_add (100, play_timeout, play);
173
174   play->missing = NULL;
175   play->buffering = FALSE;
176   play->is_live = FALSE;
177
178   play->desired_state = GST_STATE_PLAYING;
179
180   play->gapless = gapless;
181   if (gapless) {
182     g_signal_connect (play->playbin, "about-to-finish",
183         G_CALLBACK (play_about_to_finish), play);
184   }
185
186   if (initial_volume != -1)
187     play_set_relative_volume (play, initial_volume - 1.0);
188
189   play->rate = 1.0;
190   play->trick_mode = GST_PLAY_TRICK_MODE_NONE;
191
192   return play;
193 }
194
195 static void
196 play_free (GstPlay * play)
197 {
198   play_reset (play);
199
200   gst_element_set_state (play->playbin, GST_STATE_NULL);
201   gst_object_unref (play->playbin);
202
203   g_source_remove (play->bus_watch);
204   g_source_remove (play->timeout);
205   g_main_loop_unref (play->loop);
206
207   g_strfreev (play->uris);
208   g_free (play);
209 }
210
211 /* reset for new file/stream */
212 static void
213 play_reset (GstPlay * play)
214 {
215   g_list_foreach (play->missing, (GFunc) gst_message_unref, NULL);
216   play->missing = NULL;
217
218   play->buffering = FALSE;
219   play->is_live = FALSE;
220 }
221
222 static void
223 play_set_relative_volume (GstPlay * play, gdouble volume_step)
224 {
225   gdouble volume;
226
227   volume = gst_stream_volume_get_volume (GST_STREAM_VOLUME (play->playbin),
228       GST_STREAM_VOLUME_FORMAT_CUBIC);
229
230   volume = round ((volume + volume_step) * VOLUME_STEPS) / VOLUME_STEPS;
231   volume = CLAMP (volume, 0.0, 10.0);
232
233   gst_stream_volume_set_volume (GST_STREAM_VOLUME (play->playbin),
234       GST_STREAM_VOLUME_FORMAT_CUBIC, volume);
235
236   g_print (_("Volume: %.0f%%"), volume * 100);
237   g_print ("                  \n");
238 }
239
240 /* returns TRUE if something was installed and we should restart playback */
241 static gboolean
242 play_install_missing_plugins (GstPlay * play)
243 {
244   /* FIXME: implement: try to install any missing plugins we haven't
245    * tried to install before */
246   return FALSE;
247 }
248
249 static gboolean
250 play_bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
251 {
252   GstPlay *play = user_data;
253
254   switch (GST_MESSAGE_TYPE (msg)) {
255     case GST_MESSAGE_ASYNC_DONE:
256
257       /* dump graph on preroll */
258       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (play->playbin),
259           GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.async-done");
260
261       g_print ("Prerolled.\r");
262       if (play->missing != NULL && play_install_missing_plugins (play)) {
263         g_print ("New plugins installed, trying again...\n");
264         --play->cur_idx;
265         play_next (play);
266       }
267       break;
268     case GST_MESSAGE_BUFFERING:{
269       gint percent;
270
271       if (!play->buffering)
272         g_print ("\n");
273
274       gst_message_parse_buffering (msg, &percent);
275       g_print ("%s %d%%  \r", _("Buffering..."), percent);
276
277       if (percent == 100) {
278         /* a 100% message means buffering is done */
279         if (play->buffering) {
280           play->buffering = FALSE;
281           /* no state management needed for live pipelines */
282           if (!play->is_live)
283             gst_element_set_state (play->playbin, play->desired_state);
284         }
285       } else {
286         /* buffering... */
287         if (!play->buffering) {
288           if (!play->is_live)
289             gst_element_set_state (play->playbin, GST_STATE_PAUSED);
290           play->buffering = TRUE;
291         }
292       }
293       break;
294     }
295     case GST_MESSAGE_CLOCK_LOST:{
296       g_print (_("Clock lost, selecting a new one\n"));
297       gst_element_set_state (play->playbin, GST_STATE_PAUSED);
298       gst_element_set_state (play->playbin, GST_STATE_PLAYING);
299       break;
300     }
301     case GST_MESSAGE_LATENCY:
302       g_print ("Redistribute latency...\n");
303       gst_bin_recalculate_latency (GST_BIN (play->playbin));
304       break;
305     case GST_MESSAGE_REQUEST_STATE:{
306       GstState state;
307       gchar *name;
308
309       name = gst_object_get_path_string (GST_MESSAGE_SRC (msg));
310
311       gst_message_parse_request_state (msg, &state);
312
313       g_print ("Setting state to %s as requested by %s...\n",
314           gst_element_state_get_name (state), name);
315
316       gst_element_set_state (play->playbin, state);
317       g_free (name);
318       break;
319     }
320     case GST_MESSAGE_EOS:
321       /* print final position at end */
322       play_timeout (play);
323       g_print ("\n");
324       /* and switch to next item in list */
325       if (!play_next (play)) {
326         g_print ("%s\n", _("Reached end of play list."));
327         g_main_loop_quit (play->loop);
328       }
329       break;
330     case GST_MESSAGE_WARNING:{
331       GError *err;
332       gchar *dbg = NULL;
333
334       /* dump graph on warning */
335       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (play->playbin),
336           GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.warning");
337
338       gst_message_parse_warning (msg, &err, &dbg);
339       g_printerr ("WARNING %s\n", err->message);
340       if (dbg != NULL)
341         g_printerr ("WARNING debug information: %s\n", dbg);
342       g_error_free (err);
343       g_free (dbg);
344       break;
345     }
346     case GST_MESSAGE_ERROR:{
347       GError *err;
348       gchar *dbg;
349
350       /* dump graph on error */
351       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (play->playbin),
352           GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.error");
353
354       gst_message_parse_error (msg, &err, &dbg);
355       g_printerr ("ERROR %s for %s\n", err->message, play->uris[play->cur_idx]);
356       if (dbg != NULL)
357         g_printerr ("ERROR debug information: %s\n", dbg);
358       g_error_free (err);
359       g_free (dbg);
360
361       /* flush any other error messages from the bus and clean up */
362       gst_element_set_state (play->playbin, GST_STATE_NULL);
363
364       if (play->missing != NULL && play_install_missing_plugins (play)) {
365         g_print ("New plugins installed, trying again...\n");
366         --play->cur_idx;
367         play_next (play);
368         break;
369       }
370       /* try next item in list then */
371       if (!play_next (play)) {
372         g_print ("%s\n", _("Reached end of play list."));
373         g_main_loop_quit (play->loop);
374       }
375       break;
376     }
377     case GST_MESSAGE_ELEMENT:
378     {
379       GstNavigationMessageType mtype = gst_navigation_message_get_type (msg);
380       if (mtype == GST_NAVIGATION_MESSAGE_EVENT) {
381         GstEvent *ev;
382
383         if (gst_navigation_message_parse_event (msg, &ev)) {
384           GstNavigationEventType e_type = gst_navigation_event_get_type (ev);
385           switch (e_type) {
386             case GST_NAVIGATION_EVENT_KEY_PRESS:
387             {
388               const gchar *key;
389
390               if (gst_navigation_event_parse_key_event (ev, &key)) {
391                 GST_INFO ("Key press: %s", key);
392
393                 if (strcmp (key, "Left") == 0)
394                   key = GST_PLAY_KB_ARROW_LEFT;
395                 else if (strcmp (key, "Right") == 0)
396                   key = GST_PLAY_KB_ARROW_RIGHT;
397                 else if (strcmp (key, "Up") == 0)
398                   key = GST_PLAY_KB_ARROW_UP;
399                 else if (strcmp (key, "Down") == 0)
400                   key = GST_PLAY_KB_ARROW_DOWN;
401                 else if (strcmp (key, "space") == 0)
402                   key = " ";
403                 else if (strlen (key) > 1)
404                   break;
405
406                 keyboard_cb (key, user_data);
407               }
408               break;
409             }
410             case GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS:
411             {
412               gint button;
413               if (gst_navigation_event_parse_mouse_button_event (ev, &button,
414                       NULL, NULL)) {
415                 if (button == 4) {
416                   /* wheel up */
417                   relative_seek (play, +0.08);
418                 } else if (button == 5) {
419                   /* wheel down */
420                   relative_seek (play, -0.01);
421                 }
422               }
423               break;
424             }
425             default:
426               break;
427           }
428         }
429       }
430       break;
431     }
432     default:
433       if (gst_is_missing_plugin_message (msg)) {
434         gchar *desc;
435
436         desc = gst_missing_plugin_message_get_description (msg);
437         g_print ("Missing plugin: %s\n", desc);
438         g_free (desc);
439         play->missing = g_list_append (play->missing, gst_message_ref (msg));
440       }
441       break;
442   }
443
444   return TRUE;
445 }
446
447 static gboolean
448 play_timeout (gpointer user_data)
449 {
450   GstPlay *play = user_data;
451   gint64 pos = -1, dur = -1;
452   gchar status[64] = { 0, };
453
454   if (play->buffering)
455     return TRUE;
456
457   gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos);
458   gst_element_query_duration (play->playbin, GST_FORMAT_TIME, &dur);
459
460   if (play->desired_state == GST_STATE_PAUSED)
461     g_snprintf (status, sizeof (status), "Paused");
462   else
463     memset (status, ' ', sizeof (status) - 1);
464
465   if (pos >= 0 && dur > 0) {
466     gchar dstr[32], pstr[32];
467
468     /* FIXME: pretty print in nicer format */
469     g_snprintf (pstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (pos));
470     pstr[9] = '\0';
471     g_snprintf (dstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (dur));
472     dstr[9] = '\0';
473     g_print ("%s / %s %s\r", pstr, dstr, status);
474   }
475
476   return TRUE;
477 }
478
479 static gchar *
480 play_uri_get_display_name (GstPlay * play, const gchar * uri)
481 {
482   gchar *loc;
483
484   if (gst_uri_has_protocol (uri, "file")) {
485     loc = g_filename_from_uri (uri, NULL, NULL);
486   } else if (gst_uri_has_protocol (uri, "pushfile")) {
487     loc = g_filename_from_uri (uri + 4, NULL, NULL);
488   } else {
489     loc = g_strdup (uri);
490   }
491
492   /* Maybe additionally use glib's filename to display name function */
493   return loc;
494 }
495
496 static void
497 play_uri (GstPlay * play, const gchar * next_uri)
498 {
499   GstStateChangeReturn sret;
500   gchar *loc;
501
502   gst_element_set_state (play->playbin, GST_STATE_READY);
503   play_reset (play);
504
505   loc = play_uri_get_display_name (play, next_uri);
506   g_print (_("Now playing %s"), loc);
507   g_print ("\n");
508   g_free (loc);
509
510   g_object_set (play->playbin, "uri", next_uri, NULL);
511
512   sret = gst_element_set_state (play->playbin, GST_STATE_PAUSED);
513   switch (sret) {
514     case GST_STATE_CHANGE_FAILURE:
515       /* ignore, we should get an error message posted on the bus */
516       break;
517     case GST_STATE_CHANGE_NO_PREROLL:
518       g_print ("Pipeline is live.\n");
519       play->is_live = TRUE;
520       break;
521     case GST_STATE_CHANGE_ASYNC:
522       g_print ("Prerolling...\r");
523       break;
524     default:
525       break;
526   }
527   if (play->desired_state != GST_STATE_PAUSED)
528     sret = gst_element_set_state (play->playbin, play->desired_state);
529 }
530
531 /* returns FALSE if we have reached the end of the playlist */
532 static gboolean
533 play_next (GstPlay * play)
534 {
535   if ((play->cur_idx + 1) >= play->num_uris)
536     return FALSE;
537
538   play_uri (play, play->uris[++play->cur_idx]);
539   return TRUE;
540 }
541
542 /* returns FALSE if we have reached the beginning of the playlist */
543 static gboolean
544 play_prev (GstPlay * play)
545 {
546   if (play->cur_idx == 0 || play->num_uris <= 1)
547     return FALSE;
548
549   play_uri (play, play->uris[--play->cur_idx]);
550   return TRUE;
551 }
552
553 static void
554 play_about_to_finish (GstElement * playbin, gpointer user_data)
555 {
556   GstPlay *play = user_data;
557   const gchar *next_uri;
558   gchar *loc;
559   guint next_idx;
560
561   if (!play->gapless)
562     return;
563
564   next_idx = play->cur_idx + 1;
565   if (next_idx >= play->num_uris)
566     return;
567
568   next_uri = play->uris[next_idx];
569   loc = play_uri_get_display_name (play, next_uri);
570   g_print (_("About to finish, preparing next title: %s"), loc);
571   g_print ("\n");
572   g_free (loc);
573
574   g_object_set (play->playbin, "uri", next_uri, NULL);
575   play->cur_idx = next_idx;
576 }
577
578 static void
579 do_play (GstPlay * play)
580 {
581   gint i;
582
583   /* dump playlist */
584   for (i = 0; i < play->num_uris; ++i)
585     GST_INFO ("%4u : %s", i, play->uris[i]);
586
587   if (!play_next (play))
588     return;
589
590   g_main_loop_run (play->loop);
591 }
592
593 static void
594 add_to_playlist (GPtrArray * playlist, const gchar * filename)
595 {
596   GDir *dir;
597   gchar *uri;
598
599   if (gst_uri_is_valid (filename)) {
600     g_ptr_array_add (playlist, g_strdup (filename));
601     return;
602   }
603
604   if ((dir = g_dir_open (filename, 0, NULL))) {
605     const gchar *entry;
606
607     /* FIXME: sort entries for each directory? */
608     while ((entry = g_dir_read_name (dir))) {
609       gchar *path;
610
611       path = g_strconcat (filename, G_DIR_SEPARATOR_S, entry, NULL);
612       add_to_playlist (playlist, path);
613       g_free (path);
614     }
615
616     g_dir_close (dir);
617     return;
618   }
619
620   uri = gst_filename_to_uri (filename, NULL);
621   if (uri != NULL)
622     g_ptr_array_add (playlist, uri);
623   else
624     g_warning ("Could not make URI out of filename '%s'", filename);
625 }
626
627 static void
628 shuffle_uris (gchar ** uris, guint num)
629 {
630   gchar *tmp;
631   guint i, j;
632
633   if (num < 2)
634     return;
635
636   for (i = 0; i < num; i++) {
637     /* gets equally distributed random number in 0..num-1 [0;num[ */
638     j = g_random_int_range (0, num);
639     tmp = uris[j];
640     uris[j] = uris[i];
641     uris[i] = tmp;
642   }
643 }
644
645 static void
646 restore_terminal (void)
647 {
648   gst_play_kb_set_key_handler (NULL, NULL);
649 }
650
651 static void
652 toggle_paused (GstPlay * play)
653 {
654   if (play->desired_state == GST_STATE_PLAYING)
655     play->desired_state = GST_STATE_PAUSED;
656   else
657     play->desired_state = GST_STATE_PLAYING;
658
659   if (!play->buffering) {
660     gst_element_set_state (play->playbin, play->desired_state);
661   } else if (play->desired_state == GST_STATE_PLAYING) {
662     g_print ("\nWill play as soon as buffering finishes)\n");
663   }
664 }
665
666 static void
667 relative_seek (GstPlay * play, gdouble percent)
668 {
669   GstQuery *query;
670   gboolean seekable = FALSE;
671   gint64 dur = -1, pos = -1;
672
673   g_return_if_fail (percent >= -1.0 && percent <= 1.0);
674
675   if (!gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos))
676     goto seek_failed;
677
678   query = gst_query_new_seeking (GST_FORMAT_TIME);
679   if (!gst_element_query (play->playbin, query)) {
680     gst_query_unref (query);
681     goto seek_failed;
682   }
683
684   gst_query_parse_seeking (query, NULL, &seekable, NULL, &dur);
685   gst_query_unref (query);
686
687   if (!seekable || dur <= 0)
688     goto seek_failed;
689
690   pos = pos + dur * percent;
691   if (pos > dur) {
692     if (!play_next (play)) {
693       g_print ("\n%s\n", _("Reached end of play list."));
694       g_main_loop_quit (play->loop);
695     }
696   } else {
697     if (pos < 0)
698       pos = 0;
699
700     play_do_seek (play, pos, play->rate, play->trick_mode);
701   }
702
703   return;
704
705 seek_failed:
706   {
707     g_print ("\nCould not seek.\n");
708   }
709 }
710
711 static gboolean
712 play_set_rate_and_trick_mode (GstPlay * play, gdouble rate,
713     GstPlayTrickMode mode)
714 {
715   gint64 pos = -1;
716
717   g_return_val_if_fail (rate != 0, FALSE);
718
719   if (!gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos))
720     return FALSE;
721
722   return play_do_seek (play, pos, rate, mode);
723 }
724
725 static gboolean
726 play_do_seek (GstPlay * play, gint64 pos, gdouble rate, GstPlayTrickMode mode)
727 {
728   GstSeekFlags seek_flags;
729   GstQuery *query;
730   GstEvent *seek;
731   gboolean seekable = FALSE;
732
733   query = gst_query_new_seeking (GST_FORMAT_TIME);
734   if (!gst_element_query (play->playbin, query)) {
735     gst_query_unref (query);
736     return FALSE;
737   }
738
739   gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
740   gst_query_unref (query);
741
742   if (!seekable)
743     return FALSE;
744
745   seek_flags = GST_SEEK_FLAG_FLUSH;
746
747   switch (mode) {
748     case GST_PLAY_TRICK_MODE_DEFAULT:
749       seek_flags |= GST_SEEK_FLAG_TRICKMODE;
750       break;
751     case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
752       seek_flags |= GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
753       break;
754     case GST_PLAY_TRICK_MODE_KEY_UNITS:
755       seek_flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS;
756       break;
757     case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
758       seek_flags |=
759           GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
760       break;
761     case GST_PLAY_TRICK_MODE_NONE:
762     default:
763       break;
764   }
765
766   if (rate > 0)
767     seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
768         seek_flags | GST_SEEK_FLAG_KEY_UNIT,
769         /* start */ GST_SEEK_TYPE_SET, pos,
770         /* stop */ GST_SEEK_TYPE_NONE, 0);
771   else
772     seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
773         seek_flags | GST_SEEK_FLAG_ACCURATE,
774         /* start */ GST_SEEK_TYPE_SET, 0,
775         /* stop */ GST_SEEK_TYPE_SET, pos);
776
777   if (!gst_element_send_event (play->playbin, seek))
778     return FALSE;
779
780   play->rate = rate;
781   play->trick_mode = mode;
782   return TRUE;
783 }
784
785 static void
786 play_set_playback_rate (GstPlay * play, gdouble rate)
787 {
788   if (play_set_rate_and_trick_mode (play, rate, play->trick_mode)) {
789     g_print (_("Playback rate: %.2f"), rate);
790     g_print ("                               \n");
791   } else {
792     g_print ("\n");
793     g_print (_("Could not change playback rate to %.2f"), rate);
794     g_print (".\n");
795   }
796 }
797
798 static void
799 play_set_relative_playback_rate (GstPlay * play, gdouble rate_step,
800     gboolean reverse_direction)
801 {
802   gdouble new_rate = play->rate + rate_step;
803
804   if (reverse_direction)
805     new_rate *= -1.0;
806
807   play_set_playback_rate (play, new_rate);
808 }
809
810 static const gchar *
811 trick_mode_get_description (GstPlayTrickMode mode)
812 {
813   switch (mode) {
814     case GST_PLAY_TRICK_MODE_NONE:
815       return "normal playback, trick modes disabled";
816     case GST_PLAY_TRICK_MODE_DEFAULT:
817       return "trick mode: default";
818     case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
819       return "trick mode: default, no audio";
820     case GST_PLAY_TRICK_MODE_KEY_UNITS:
821       return "trick mode: key frames only";
822     case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
823       return "trick mode: key frames only, no audio";
824     default:
825       break;
826   }
827   return "unknown trick mode";
828 }
829
830 static void
831 play_switch_trick_mode (GstPlay * play)
832 {
833   GstPlayTrickMode new_mode = ++play->trick_mode;
834   const gchar *mode_desc;
835
836   if (new_mode == GST_PLAY_TRICK_MODE_LAST)
837     new_mode = GST_PLAY_TRICK_MODE_NONE;
838
839   mode_desc = trick_mode_get_description (new_mode);
840
841   if (play_set_rate_and_trick_mode (play, play->rate, new_mode)) {
842     g_print ("Rate: %.2f (%s)                      \n", play->rate, mode_desc);
843   } else {
844     g_print ("\nCould not change trick mode to %s.\n", mode_desc);
845   }
846 }
847
848 static void
849 print_keyboard_help (void)
850 {
851   static struct
852   {
853     const gchar *key_desc;
854     const gchar *key_help;
855   } key_controls[] = {
856     {
857     N_("space"), N_("pause/unpause")}, {
858     N_("q or ESC"), N_("quit")}, {
859     ">", N_("play next")}, {
860     "<", N_("play previous")}, {
861     "\342\206\222", N_("seek forward")}, {
862     "\342\206\220", N_("seek backward")}, {
863     "\342\206\221", N_("volume up")}, {
864     "\342\206\223", N_("volume down")}, {
865     "+", N_("increase playback rate")}, {
866     "-", N_("decrease playback rate")}, {
867     "d", N_("change playback direction")}, {
868     "t", N_("enable/disable trick modes")}, {
869   "k", N_("show keyboard shortcuts")},};
870   guint i, chars_to_pad, desc_len, max_desc_len = 0;
871
872   g_print ("\n\n%s\n\n", _("Interactive mode - keyboard controls:"));
873
874   for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
875     desc_len = g_utf8_strlen (key_controls[i].key_desc, -1);
876     max_desc_len = MAX (max_desc_len, desc_len);
877   }
878   ++max_desc_len;
879
880   for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
881     chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1);
882     g_print ("\t%s", key_controls[i].key_desc);
883     g_print ("%-*s: ", chars_to_pad, "");
884     g_print ("%s\n", key_controls[i].key_help);
885   }
886   g_print ("\n");
887 }
888
889 static void
890 keyboard_cb (const gchar * key_input, gpointer user_data)
891 {
892   GstPlay *play = (GstPlay *) user_data;
893
894   switch (g_ascii_tolower (key_input[0])) {
895     case 'k':
896       print_keyboard_help ();
897       break;
898     case ' ':
899       toggle_paused (play);
900       break;
901     case 'q':
902     case 'Q':
903       g_main_loop_quit (play->loop);
904       break;
905     case '>':
906       if (!play_next (play)) {
907         g_print ("\n%s\n", _("Reached end of play list."));
908         g_main_loop_quit (play->loop);
909       }
910       break;
911     case '<':
912       play_prev (play);
913       break;
914     case '+':
915       if (play->rate > -0.2 && play->rate < 0.0)
916         play_set_relative_playback_rate (play, 0.0, TRUE);
917       else if (ABS (play->rate) < 2.0)
918         play_set_relative_playback_rate (play, 0.1, FALSE);
919       else if (ABS (play->rate) < 4.0)
920         play_set_relative_playback_rate (play, 0.5, FALSE);
921       else
922         play_set_relative_playback_rate (play, 1.0, FALSE);
923       break;
924     case '-':
925       if (play->rate > 0.0 && play->rate < 0.20)
926         play_set_relative_playback_rate (play, 0.0, TRUE);
927       else if (ABS (play->rate) <= 2.0)
928         play_set_relative_playback_rate (play, -0.1, FALSE);
929       else if (ABS (play->rate) <= 4.0)
930         play_set_relative_playback_rate (play, -0.5, FALSE);
931       else
932         play_set_relative_playback_rate (play, -1.0, FALSE);
933       break;
934     case 'd':
935       play_set_relative_playback_rate (play, 0.0, TRUE);
936       break;
937     case 't':
938       play_switch_trick_mode (play);
939       break;
940     case 27:                   /* ESC */
941       if (key_input[1] == '\0') {
942         g_main_loop_quit (play->loop);
943         break;
944       }
945       /* fall through */
946     default:
947       if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) {
948         relative_seek (play, +0.08);
949       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) {
950         relative_seek (play, -0.01);
951       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_UP) == 0) {
952         play_set_relative_volume (play, +1.0 / VOLUME_STEPS);
953       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_DOWN) == 0) {
954         play_set_relative_volume (play, -1.0 / VOLUME_STEPS);
955       } else {
956         GST_INFO ("keyboard input:");
957         for (; *key_input != '\0'; ++key_input)
958           GST_INFO ("  code %3d", *key_input);
959       }
960       break;
961   }
962 }
963
964 int
965 main (int argc, char **argv)
966 {
967   GstPlay *play;
968   GPtrArray *playlist;
969   gboolean print_version = FALSE;
970   gboolean interactive = TRUE;
971   gboolean gapless = FALSE;
972   gboolean shuffle = FALSE;
973   gdouble volume = -1;
974   gchar **filenames = NULL;
975   gchar *audio_sink = NULL;
976   gchar *video_sink = NULL;
977   gchar **uris;
978   guint num, i;
979   GError *err = NULL;
980   GOptionContext *ctx;
981   gchar *playlist_file = NULL;
982   GOptionEntry options[] = {
983     {"version", 0, 0, G_OPTION_ARG_NONE, &print_version,
984         N_("Print version information and exit"), NULL},
985     {"videosink", 0, 0, G_OPTION_ARG_STRING, &video_sink,
986         N_("Video sink to use (default is autovideosink)"), NULL},
987     {"audiosink", 0, 0, G_OPTION_ARG_STRING, &audio_sink,
988         N_("Audio sink to use (default is autoaudiosink)"), NULL},
989     {"gapless", 0, 0, G_OPTION_ARG_NONE, &gapless,
990         N_("Enable gapless playback"), NULL},
991     {"shuffle", 0, 0, G_OPTION_ARG_NONE, &shuffle,
992         N_("Shuffle playlist"), NULL},
993     {"no-interactive", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,
994           &interactive,
995         N_("Disable interactive control via the keyboard"), NULL},
996     {"volume", 0, 0, G_OPTION_ARG_DOUBLE, &volume,
997         N_("Volume"), NULL},
998     {"playlist", 0, 0, G_OPTION_ARG_FILENAME, &playlist_file,
999         N_("Playlist file containing input media files"), NULL},
1000     {"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet,
1001         N_("Do not print any output (apart from errors)"), NULL},
1002     {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL},
1003     {NULL}
1004   };
1005
1006   setlocale (LC_ALL, "");
1007
1008 #ifdef ENABLE_NLS
1009   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1010   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1011   textdomain (GETTEXT_PACKAGE);
1012 #endif
1013
1014   g_set_prgname ("gst-play-" GST_API_VERSION);
1015
1016   ctx = g_option_context_new ("FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ...");
1017   g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
1018   g_option_context_add_group (ctx, gst_init_get_option_group ());
1019   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
1020     g_print ("Error initializing: %s\n", GST_STR_NULL (err->message));
1021     return 1;
1022   }
1023   g_option_context_free (ctx);
1024
1025   GST_DEBUG_CATEGORY_INIT (play_debug, "play", 0, "gst-play");
1026
1027   if (print_version) {
1028     gchar *version_str;
1029
1030     version_str = gst_version_string ();
1031     g_print ("%s version %s\n", g_get_prgname (), PACKAGE_VERSION);
1032     g_print ("%s\n", version_str);
1033     g_print ("%s\n", GST_PACKAGE_ORIGIN);
1034     g_free (version_str);
1035
1036     g_free (audio_sink);
1037     g_free (video_sink);
1038     g_free (playlist_file);
1039
1040     return 0;
1041   }
1042
1043   playlist = g_ptr_array_new ();
1044
1045   if (playlist_file != NULL) {
1046     gchar *playlist_contents = NULL;
1047     gchar **lines = NULL;
1048
1049     if (g_file_get_contents (playlist_file, &playlist_contents, NULL, &err)) {
1050       lines = g_strsplit (playlist_contents, "\n", 0);
1051       num = g_strv_length (lines);
1052
1053       for (i = 0; i < num; i++) {
1054         if (lines[i][0] != '\0') {
1055           GST_LOG ("Playlist[%d]: %s", i + 1, lines[i]);
1056           add_to_playlist (playlist, lines[i]);
1057         }
1058       }
1059       g_strfreev (lines);
1060       g_free (playlist_contents);
1061     } else {
1062       g_printerr ("Could not read playlist: %s\n", err->message);
1063       g_clear_error (&err);
1064     }
1065     g_free (playlist_file);
1066     playlist_file = NULL;
1067   }
1068
1069   if (playlist->len == 0 && (filenames == NULL || *filenames == NULL)) {
1070     g_printerr (_("Usage: %s FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ..."),
1071         "gst-play-" GST_API_VERSION);
1072     g_printerr ("\n\n"),
1073         g_printerr ("%s\n\n",
1074         _("You must provide at least one filename or URI to play."));
1075     /* No input provided. Free array */
1076     g_ptr_array_free (playlist, TRUE);
1077
1078     g_free (audio_sink);
1079     g_free (video_sink);
1080
1081     return 1;
1082   }
1083
1084   /* fill playlist */
1085   if (filenames != NULL && *filenames != NULL) {
1086     num = g_strv_length (filenames);
1087     for (i = 0; i < num; ++i) {
1088       GST_LOG ("command line argument: %s", filenames[i]);
1089       add_to_playlist (playlist, filenames[i]);
1090     }
1091     g_strfreev (filenames);
1092   }
1093
1094   num = playlist->len;
1095   g_ptr_array_add (playlist, NULL);
1096
1097   uris = (gchar **) g_ptr_array_free (playlist, FALSE);
1098
1099   if (shuffle)
1100     shuffle_uris (uris, num);
1101
1102   /* prepare */
1103   play = play_new (uris, audio_sink, video_sink, gapless, volume);
1104
1105   if (interactive) {
1106     if (gst_play_kb_set_key_handler (keyboard_cb, play)) {
1107       g_print (_("Press 'k' to see a list of keyboard shortcuts.\n"));
1108       atexit (restore_terminal);
1109     } else {
1110       g_print ("Interactive keyboard handling in terminal not available.\n");
1111     }
1112   }
1113
1114   /* play */
1115   do_play (play);
1116
1117   /* clean up */
1118   play_free (play);
1119
1120   g_free (audio_sink);
1121   g_free (video_sink);
1122
1123   g_print ("\n");
1124   return 0;
1125 }