tools: gst-play: remove unnecessary variable
[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   gchar *loc;
500
501   gst_element_set_state (play->playbin, GST_STATE_READY);
502   play_reset (play);
503
504   loc = play_uri_get_display_name (play, next_uri);
505   g_print (_("Now playing %s\n"), loc);
506   g_free (loc);
507
508   g_object_set (play->playbin, "uri", next_uri, NULL);
509
510   switch (gst_element_set_state (play->playbin, GST_STATE_PAUSED)) {
511     case GST_STATE_CHANGE_FAILURE:
512       /* ignore, we should get an error message posted on the bus */
513       break;
514     case GST_STATE_CHANGE_NO_PREROLL:
515       g_print ("Pipeline is live.\n");
516       play->is_live = TRUE;
517       break;
518     case GST_STATE_CHANGE_ASYNC:
519       g_print ("Prerolling...\r");
520       break;
521     default:
522       break;
523   }
524
525   if (play->desired_state != GST_STATE_PAUSED)
526     gst_element_set_state (play->playbin, play->desired_state);
527 }
528
529 /* returns FALSE if we have reached the end of the playlist */
530 static gboolean
531 play_next (GstPlay * play)
532 {
533   if ((play->cur_idx + 1) >= play->num_uris)
534     return FALSE;
535
536   play_uri (play, play->uris[++play->cur_idx]);
537   return TRUE;
538 }
539
540 /* returns FALSE if we have reached the beginning of the playlist */
541 static gboolean
542 play_prev (GstPlay * play)
543 {
544   if (play->cur_idx == 0 || play->num_uris <= 1)
545     return FALSE;
546
547   play_uri (play, play->uris[--play->cur_idx]);
548   return TRUE;
549 }
550
551 static void
552 play_about_to_finish (GstElement * playbin, gpointer user_data)
553 {
554   GstPlay *play = user_data;
555   const gchar *next_uri;
556   gchar *loc;
557   guint next_idx;
558
559   if (!play->gapless)
560     return;
561
562   next_idx = play->cur_idx + 1;
563   if (next_idx >= play->num_uris)
564     return;
565
566   next_uri = play->uris[next_idx];
567   loc = play_uri_get_display_name (play, next_uri);
568   g_print (_("About to finish, preparing next title: %s"), loc);
569   g_print ("\n");
570   g_free (loc);
571
572   g_object_set (play->playbin, "uri", next_uri, NULL);
573   play->cur_idx = next_idx;
574 }
575
576 static void
577 do_play (GstPlay * play)
578 {
579   gint i;
580
581   /* dump playlist */
582   for (i = 0; i < play->num_uris; ++i)
583     GST_INFO ("%4u : %s", i, play->uris[i]);
584
585   if (!play_next (play))
586     return;
587
588   g_main_loop_run (play->loop);
589 }
590
591 static void
592 add_to_playlist (GPtrArray * playlist, const gchar * filename)
593 {
594   GDir *dir;
595   gchar *uri;
596
597   if (gst_uri_is_valid (filename)) {
598     g_ptr_array_add (playlist, g_strdup (filename));
599     return;
600   }
601
602   if ((dir = g_dir_open (filename, 0, NULL))) {
603     const gchar *entry;
604
605     /* FIXME: sort entries for each directory? */
606     while ((entry = g_dir_read_name (dir))) {
607       gchar *path;
608
609       path = g_strconcat (filename, G_DIR_SEPARATOR_S, entry, NULL);
610       add_to_playlist (playlist, path);
611       g_free (path);
612     }
613
614     g_dir_close (dir);
615     return;
616   }
617
618   uri = gst_filename_to_uri (filename, NULL);
619   if (uri != NULL)
620     g_ptr_array_add (playlist, uri);
621   else
622     g_warning ("Could not make URI out of filename '%s'", filename);
623 }
624
625 static void
626 shuffle_uris (gchar ** uris, guint num)
627 {
628   gchar *tmp;
629   guint i, j;
630
631   if (num < 2)
632     return;
633
634   for (i = 0; i < num; i++) {
635     /* gets equally distributed random number in 0..num-1 [0;num[ */
636     j = g_random_int_range (0, num);
637     tmp = uris[j];
638     uris[j] = uris[i];
639     uris[i] = tmp;
640   }
641 }
642
643 static void
644 restore_terminal (void)
645 {
646   gst_play_kb_set_key_handler (NULL, NULL);
647 }
648
649 static void
650 toggle_paused (GstPlay * play)
651 {
652   if (play->desired_state == GST_STATE_PLAYING)
653     play->desired_state = GST_STATE_PAUSED;
654   else
655     play->desired_state = GST_STATE_PLAYING;
656
657   if (!play->buffering) {
658     gst_element_set_state (play->playbin, play->desired_state);
659   } else if (play->desired_state == GST_STATE_PLAYING) {
660     g_print ("\nWill play as soon as buffering finishes)\n");
661   }
662 }
663
664 static void
665 relative_seek (GstPlay * play, gdouble percent)
666 {
667   GstQuery *query;
668   gboolean seekable = FALSE;
669   gint64 dur = -1, pos = -1;
670
671   g_return_if_fail (percent >= -1.0 && percent <= 1.0);
672
673   if (!gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos))
674     goto seek_failed;
675
676   query = gst_query_new_seeking (GST_FORMAT_TIME);
677   if (!gst_element_query (play->playbin, query)) {
678     gst_query_unref (query);
679     goto seek_failed;
680   }
681
682   gst_query_parse_seeking (query, NULL, &seekable, NULL, &dur);
683   gst_query_unref (query);
684
685   if (!seekable || dur <= 0)
686     goto seek_failed;
687
688   pos = pos + dur * percent;
689   if (pos > dur) {
690     if (!play_next (play)) {
691       g_print ("\n%s\n", _("Reached end of play list."));
692       g_main_loop_quit (play->loop);
693     }
694   } else {
695     if (pos < 0)
696       pos = 0;
697
698     play_do_seek (play, pos, play->rate, play->trick_mode);
699   }
700
701   return;
702
703 seek_failed:
704   {
705     g_print ("\nCould not seek.\n");
706   }
707 }
708
709 static gboolean
710 play_set_rate_and_trick_mode (GstPlay * play, gdouble rate,
711     GstPlayTrickMode mode)
712 {
713   gint64 pos = -1;
714
715   g_return_val_if_fail (rate != 0, FALSE);
716
717   if (!gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos))
718     return FALSE;
719
720   return play_do_seek (play, pos, rate, mode);
721 }
722
723 static gboolean
724 play_do_seek (GstPlay * play, gint64 pos, gdouble rate, GstPlayTrickMode mode)
725 {
726   GstSeekFlags seek_flags;
727   GstQuery *query;
728   GstEvent *seek;
729   gboolean seekable = FALSE;
730
731   query = gst_query_new_seeking (GST_FORMAT_TIME);
732   if (!gst_element_query (play->playbin, query)) {
733     gst_query_unref (query);
734     return FALSE;
735   }
736
737   gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
738   gst_query_unref (query);
739
740   if (!seekable)
741     return FALSE;
742
743   seek_flags = GST_SEEK_FLAG_FLUSH;
744
745   switch (mode) {
746     case GST_PLAY_TRICK_MODE_DEFAULT:
747       seek_flags |= GST_SEEK_FLAG_TRICKMODE;
748       break;
749     case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
750       seek_flags |= GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
751       break;
752     case GST_PLAY_TRICK_MODE_KEY_UNITS:
753       seek_flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS;
754       break;
755     case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
756       seek_flags |=
757           GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
758       break;
759     case GST_PLAY_TRICK_MODE_NONE:
760     default:
761       break;
762   }
763
764   if (rate > 0)
765     seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
766         seek_flags | GST_SEEK_FLAG_KEY_UNIT,
767         /* start */ GST_SEEK_TYPE_SET, pos,
768         /* stop */ GST_SEEK_TYPE_NONE, 0);
769   else
770     seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
771         seek_flags | GST_SEEK_FLAG_ACCURATE,
772         /* start */ GST_SEEK_TYPE_SET, 0,
773         /* stop */ GST_SEEK_TYPE_SET, pos);
774
775   if (!gst_element_send_event (play->playbin, seek))
776     return FALSE;
777
778   play->rate = rate;
779   play->trick_mode = mode;
780   return TRUE;
781 }
782
783 static void
784 play_set_playback_rate (GstPlay * play, gdouble rate)
785 {
786   if (play_set_rate_and_trick_mode (play, rate, play->trick_mode)) {
787     g_print (_("Playback rate: %.2f"), rate);
788     g_print ("                               \n");
789   } else {
790     g_print ("\n");
791     g_print (_("Could not change playback rate to %.2f"), rate);
792     g_print (".\n");
793   }
794 }
795
796 static void
797 play_set_relative_playback_rate (GstPlay * play, gdouble rate_step,
798     gboolean reverse_direction)
799 {
800   gdouble new_rate = play->rate + rate_step;
801
802   if (reverse_direction)
803     new_rate *= -1.0;
804
805   play_set_playback_rate (play, new_rate);
806 }
807
808 static const gchar *
809 trick_mode_get_description (GstPlayTrickMode mode)
810 {
811   switch (mode) {
812     case GST_PLAY_TRICK_MODE_NONE:
813       return "normal playback, trick modes disabled";
814     case GST_PLAY_TRICK_MODE_DEFAULT:
815       return "trick mode: default";
816     case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
817       return "trick mode: default, no audio";
818     case GST_PLAY_TRICK_MODE_KEY_UNITS:
819       return "trick mode: key frames only";
820     case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
821       return "trick mode: key frames only, no audio";
822     default:
823       break;
824   }
825   return "unknown trick mode";
826 }
827
828 static void
829 play_switch_trick_mode (GstPlay * play)
830 {
831   GstPlayTrickMode new_mode = ++play->trick_mode;
832   const gchar *mode_desc;
833
834   if (new_mode == GST_PLAY_TRICK_MODE_LAST)
835     new_mode = GST_PLAY_TRICK_MODE_NONE;
836
837   mode_desc = trick_mode_get_description (new_mode);
838
839   if (play_set_rate_and_trick_mode (play, play->rate, new_mode)) {
840     g_print ("Rate: %.2f (%s)                      \n", play->rate, mode_desc);
841   } else {
842     g_print ("\nCould not change trick mode to %s.\n", mode_desc);
843   }
844 }
845
846 static void
847 print_keyboard_help (void)
848 {
849   static struct
850   {
851     const gchar *key_desc;
852     const gchar *key_help;
853   } key_controls[] = {
854     {
855     N_("space"), N_("pause/unpause")}, {
856     N_("q or ESC"), N_("quit")}, {
857     ">", N_("play next")}, {
858     "<", N_("play previous")}, {
859     "\342\206\222", N_("seek forward")}, {
860     "\342\206\220", N_("seek backward")}, {
861     "\342\206\221", N_("volume up")}, {
862     "\342\206\223", N_("volume down")}, {
863     "+", N_("increase playback rate")}, {
864     "-", N_("decrease playback rate")}, {
865     "d", N_("change playback direction")}, {
866     "t", N_("enable/disable trick modes")}, {
867   "k", N_("show keyboard shortcuts")},};
868   guint i, chars_to_pad, desc_len, max_desc_len = 0;
869
870   g_print ("\n\n%s\n\n", _("Interactive mode - keyboard controls:"));
871
872   for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
873     desc_len = g_utf8_strlen (key_controls[i].key_desc, -1);
874     max_desc_len = MAX (max_desc_len, desc_len);
875   }
876   ++max_desc_len;
877
878   for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
879     chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1);
880     g_print ("\t%s", key_controls[i].key_desc);
881     g_print ("%-*s: ", chars_to_pad, "");
882     g_print ("%s\n", key_controls[i].key_help);
883   }
884   g_print ("\n");
885 }
886
887 static void
888 keyboard_cb (const gchar * key_input, gpointer user_data)
889 {
890   GstPlay *play = (GstPlay *) user_data;
891
892   switch (g_ascii_tolower (key_input[0])) {
893     case 'k':
894       print_keyboard_help ();
895       break;
896     case ' ':
897       toggle_paused (play);
898       break;
899     case 'q':
900     case 'Q':
901       g_main_loop_quit (play->loop);
902       break;
903     case '>':
904       if (!play_next (play)) {
905         g_print ("\n%s\n", _("Reached end of play list."));
906         g_main_loop_quit (play->loop);
907       }
908       break;
909     case '<':
910       play_prev (play);
911       break;
912     case '+':
913       if (play->rate > -0.2 && play->rate < 0.0)
914         play_set_relative_playback_rate (play, 0.0, TRUE);
915       else if (ABS (play->rate) < 2.0)
916         play_set_relative_playback_rate (play, 0.1, FALSE);
917       else if (ABS (play->rate) < 4.0)
918         play_set_relative_playback_rate (play, 0.5, FALSE);
919       else
920         play_set_relative_playback_rate (play, 1.0, FALSE);
921       break;
922     case '-':
923       if (play->rate > 0.0 && play->rate < 0.20)
924         play_set_relative_playback_rate (play, 0.0, TRUE);
925       else if (ABS (play->rate) <= 2.0)
926         play_set_relative_playback_rate (play, -0.1, FALSE);
927       else if (ABS (play->rate) <= 4.0)
928         play_set_relative_playback_rate (play, -0.5, FALSE);
929       else
930         play_set_relative_playback_rate (play, -1.0, FALSE);
931       break;
932     case 'd':
933       play_set_relative_playback_rate (play, 0.0, TRUE);
934       break;
935     case 't':
936       play_switch_trick_mode (play);
937       break;
938     case 27:                   /* ESC */
939       if (key_input[1] == '\0') {
940         g_main_loop_quit (play->loop);
941         break;
942       }
943       /* fall through */
944     default:
945       if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) {
946         relative_seek (play, +0.08);
947       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) {
948         relative_seek (play, -0.01);
949       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_UP) == 0) {
950         play_set_relative_volume (play, +1.0 / VOLUME_STEPS);
951       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_DOWN) == 0) {
952         play_set_relative_volume (play, -1.0 / VOLUME_STEPS);
953       } else {
954         GST_INFO ("keyboard input:");
955         for (; *key_input != '\0'; ++key_input)
956           GST_INFO ("  code %3d", *key_input);
957       }
958       break;
959   }
960 }
961
962 int
963 main (int argc, char **argv)
964 {
965   GstPlay *play;
966   GPtrArray *playlist;
967   gboolean print_version = FALSE;
968   gboolean interactive = TRUE;
969   gboolean gapless = FALSE;
970   gboolean shuffle = FALSE;
971   gdouble volume = -1;
972   gchar **filenames = NULL;
973   gchar *audio_sink = NULL;
974   gchar *video_sink = NULL;
975   gchar **uris;
976   guint num, i;
977   GError *err = NULL;
978   GOptionContext *ctx;
979   gchar *playlist_file = NULL;
980   GOptionEntry options[] = {
981     {"version", 0, 0, G_OPTION_ARG_NONE, &print_version,
982         N_("Print version information and exit"), NULL},
983     {"videosink", 0, 0, G_OPTION_ARG_STRING, &video_sink,
984         N_("Video sink to use (default is autovideosink)"), NULL},
985     {"audiosink", 0, 0, G_OPTION_ARG_STRING, &audio_sink,
986         N_("Audio sink to use (default is autoaudiosink)"), NULL},
987     {"gapless", 0, 0, G_OPTION_ARG_NONE, &gapless,
988         N_("Enable gapless playback"), NULL},
989     {"shuffle", 0, 0, G_OPTION_ARG_NONE, &shuffle,
990         N_("Shuffle playlist"), NULL},
991     {"no-interactive", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,
992           &interactive,
993         N_("Disable interactive control via the keyboard"), NULL},
994     {"volume", 0, 0, G_OPTION_ARG_DOUBLE, &volume,
995         N_("Volume"), NULL},
996     {"playlist", 0, 0, G_OPTION_ARG_FILENAME, &playlist_file,
997         N_("Playlist file containing input media files"), NULL},
998     {"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet,
999         N_("Do not print any output (apart from errors)"), NULL},
1000     {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL},
1001     {NULL}
1002   };
1003
1004   setlocale (LC_ALL, "");
1005
1006 #ifdef ENABLE_NLS
1007   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1008   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1009   textdomain (GETTEXT_PACKAGE);
1010 #endif
1011
1012   g_set_prgname ("gst-play-" GST_API_VERSION);
1013
1014   ctx = g_option_context_new ("FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ...");
1015   g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
1016   g_option_context_add_group (ctx, gst_init_get_option_group ());
1017   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
1018     g_print ("Error initializing: %s\n", GST_STR_NULL (err->message));
1019     return 1;
1020   }
1021   g_option_context_free (ctx);
1022
1023   GST_DEBUG_CATEGORY_INIT (play_debug, "play", 0, "gst-play");
1024
1025   if (print_version) {
1026     gchar *version_str;
1027
1028     version_str = gst_version_string ();
1029     g_print ("%s version %s\n", g_get_prgname (), PACKAGE_VERSION);
1030     g_print ("%s\n", version_str);
1031     g_print ("%s\n", GST_PACKAGE_ORIGIN);
1032     g_free (version_str);
1033
1034     g_free (audio_sink);
1035     g_free (video_sink);
1036     g_free (playlist_file);
1037
1038     return 0;
1039   }
1040
1041   playlist = g_ptr_array_new ();
1042
1043   if (playlist_file != NULL) {
1044     gchar *playlist_contents = NULL;
1045     gchar **lines = NULL;
1046
1047     if (g_file_get_contents (playlist_file, &playlist_contents, NULL, &err)) {
1048       lines = g_strsplit (playlist_contents, "\n", 0);
1049       num = g_strv_length (lines);
1050
1051       for (i = 0; i < num; i++) {
1052         if (lines[i][0] != '\0') {
1053           GST_LOG ("Playlist[%d]: %s", i + 1, lines[i]);
1054           add_to_playlist (playlist, lines[i]);
1055         }
1056       }
1057       g_strfreev (lines);
1058       g_free (playlist_contents);
1059     } else {
1060       g_printerr ("Could not read playlist: %s\n", err->message);
1061       g_clear_error (&err);
1062     }
1063     g_free (playlist_file);
1064     playlist_file = NULL;
1065   }
1066
1067   if (playlist->len == 0 && (filenames == NULL || *filenames == NULL)) {
1068     g_printerr (_("Usage: %s FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ..."),
1069         "gst-play-" GST_API_VERSION);
1070     g_printerr ("\n\n"),
1071         g_printerr ("%s\n\n",
1072         _("You must provide at least one filename or URI to play."));
1073     /* No input provided. Free array */
1074     g_ptr_array_free (playlist, TRUE);
1075
1076     g_free (audio_sink);
1077     g_free (video_sink);
1078
1079     return 1;
1080   }
1081
1082   /* fill playlist */
1083   if (filenames != NULL && *filenames != NULL) {
1084     num = g_strv_length (filenames);
1085     for (i = 0; i < num; ++i) {
1086       GST_LOG ("command line argument: %s", filenames[i]);
1087       add_to_playlist (playlist, filenames[i]);
1088     }
1089     g_strfreev (filenames);
1090   }
1091
1092   num = playlist->len;
1093   g_ptr_array_add (playlist, NULL);
1094
1095   uris = (gchar **) g_ptr_array_free (playlist, FALSE);
1096
1097   if (shuffle)
1098     shuffle_uris (uris, num);
1099
1100   /* prepare */
1101   play = play_new (uris, audio_sink, video_sink, gapless, volume);
1102
1103   if (interactive) {
1104     if (gst_play_kb_set_key_handler (keyboard_cb, play)) {
1105       g_print (_("Press 'k' to see a list of keyboard shortcuts.\n"));
1106       atexit (restore_terminal);
1107     } else {
1108       g_print ("Interactive keyboard handling in terminal not available.\n");
1109     }
1110   }
1111
1112   /* play */
1113   do_play (play);
1114
1115   /* clean up */
1116   play_free (play);
1117
1118   g_free (audio_sink);
1119   g_free (video_sink);
1120
1121   g_print ("\n");
1122   return 0;
1123 }