tools: gst-play: add volume control
[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  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <locale.h>
27
28 #include <gst/gst.h>
29 #include <gst/gst-i18n-app.h>
30 #include <gst/audio/audio.h>
31 #include <gst/pbutils/pbutils.h>
32 #include <gst/math-compat.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <string.h>
36
37 #include "gst-play-kb.h"
38
39 #define VOLUME_STEPS 20
40
41 GST_DEBUG_CATEGORY (play_debug);
42 #define GST_CAT_DEFAULT play_debug
43
44 typedef struct
45 {
46   gchar **uris;
47   guint num_uris;
48   gint cur_idx;
49
50   GstElement *playbin;
51
52   GMainLoop *loop;
53   guint bus_watch;
54   guint timeout;
55
56   /* missing plugin messages */
57   GList *missing;
58
59   gboolean buffering;
60   gboolean is_live;
61
62   GstState desired_state;       /* as per user interaction, PAUSED or PLAYING */
63
64   /* configuration */
65   gboolean gapless;
66 } GstPlay;
67
68 static gboolean play_bus_msg (GstBus * bus, GstMessage * msg, gpointer data);
69 static gboolean play_next (GstPlay * play);
70 static gboolean play_prev (GstPlay * play);
71 static gboolean play_timeout (gpointer user_data);
72 static void play_about_to_finish (GstElement * playbin, gpointer user_data);
73 static void play_reset (GstPlay * play);
74 static void play_set_relative_volume (GstPlay * play, gdouble volume_step);
75
76 static GstPlay *
77 play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink,
78     gboolean gapless, gdouble initial_volume)
79 {
80   GstElement *sink;
81   GstPlay *play;
82
83   play = g_new0 (GstPlay, 1);
84
85   play->uris = uris;
86   play->num_uris = g_strv_length (uris);
87   play->cur_idx = -1;
88
89   play->playbin = gst_element_factory_make ("playbin", "playbin");
90
91   if (audio_sink != NULL) {
92     if (strchr (audio_sink, ' ') != NULL)
93       sink = gst_parse_bin_from_description (audio_sink, TRUE, NULL);
94     else
95       sink = gst_element_factory_make (audio_sink, NULL);
96
97     if (sink != NULL)
98       g_object_set (play->playbin, "audio-sink", sink, NULL);
99     else
100       g_warning ("Couldn't create specified audio sink '%s'", audio_sink);
101   }
102   if (video_sink != NULL) {
103     if (strchr (video_sink, ' ') != NULL)
104       sink = gst_parse_bin_from_description (video_sink, TRUE, NULL);
105     else
106       sink = gst_element_factory_make (video_sink, NULL);
107
108     if (sink != NULL)
109       g_object_set (play->playbin, "video-sink", sink, NULL);
110     else
111       g_warning ("Couldn't create specified video sink '%s'", video_sink);
112   }
113
114   play->loop = g_main_loop_new (NULL, FALSE);
115
116   play->bus_watch = gst_bus_add_watch (GST_ELEMENT_BUS (play->playbin),
117       play_bus_msg, play);
118
119   /* FIXME: make configurable incl. 0 for disable */
120   play->timeout = g_timeout_add (100, play_timeout, play);
121
122   play->missing = NULL;
123   play->buffering = FALSE;
124   play->is_live = FALSE;
125
126   play->desired_state = GST_STATE_PLAYING;
127
128   play->gapless = gapless;
129   if (gapless) {
130     g_signal_connect (play->playbin, "about-to-finish",
131         G_CALLBACK (play_about_to_finish), play);
132   }
133
134   play_set_relative_volume (play, initial_volume - 1.0);
135
136   return play;
137 }
138
139 static void
140 play_free (GstPlay * play)
141 {
142   play_reset (play);
143
144   gst_element_set_state (play->playbin, GST_STATE_NULL);
145   gst_object_unref (play->playbin);
146
147   g_source_remove (play->bus_watch);
148   g_source_remove (play->timeout);
149   g_main_loop_unref (play->loop);
150
151   g_strfreev (play->uris);
152   g_free (play);
153 }
154
155 /* reset for new file/stream */
156 static void
157 play_reset (GstPlay * play)
158 {
159   g_list_foreach (play->missing, (GFunc) gst_message_unref, NULL);
160   play->missing = NULL;
161
162   play->buffering = FALSE;
163   play->is_live = FALSE;
164 }
165
166 static void
167 play_set_relative_volume (GstPlay * play, gdouble volume_step)
168 {
169   gdouble volume;
170
171   volume = gst_stream_volume_get_volume (GST_STREAM_VOLUME (play->playbin),
172       GST_STREAM_VOLUME_FORMAT_LINEAR);
173
174   volume = round ((volume + volume_step) * VOLUME_STEPS) / VOLUME_STEPS;
175   volume = CLAMP (volume, 0.0, 10.0);
176
177   gst_stream_volume_set_volume (GST_STREAM_VOLUME (play->playbin),
178       GST_STREAM_VOLUME_FORMAT_LINEAR, volume);
179
180   g_print ("Volume: %.0f%%                  \n", volume * 100);
181 }
182
183 /* returns TRUE if something was installed and we should restart playback */
184 static gboolean
185 play_install_missing_plugins (GstPlay * play)
186 {
187   /* FIXME: implement: try to install any missing plugins we haven't
188    * tried to install before */
189   return FALSE;
190 }
191
192 static gboolean
193 play_bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
194 {
195   GstPlay *play = user_data;
196
197   switch (GST_MESSAGE_TYPE (msg)) {
198     case GST_MESSAGE_ASYNC_DONE:
199
200       /* dump graph on preroll */
201       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (play->playbin),
202           GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.async-done");
203
204       g_print ("Prerolled.\r");
205       if (play->missing != NULL && play_install_missing_plugins (play)) {
206         g_print ("New plugins installed, trying again...\n");
207         --play->cur_idx;
208         play_next (play);
209       }
210       break;
211     case GST_MESSAGE_BUFFERING:{
212       gint percent;
213
214       if (!play->buffering)
215         g_print ("\n");
216
217       gst_message_parse_buffering (msg, &percent);
218       g_print ("%s %d%%  \r", _("Buffering..."), percent);
219
220       /* no state management needed for live pipelines */
221       if (play->is_live)
222         break;
223
224       if (percent == 100) {
225         /* a 100% message means buffering is done */
226         if (play->buffering) {
227           play->buffering = FALSE;
228           gst_element_set_state (play->playbin, play->desired_state);
229         }
230       } else {
231         /* buffering... */
232         if (!play->buffering) {
233           gst_element_set_state (play->playbin, GST_STATE_PAUSED);
234           play->buffering = TRUE;
235         }
236       }
237       break;
238     }
239     case GST_MESSAGE_CLOCK_LOST:{
240       g_print (_("Clock lost, selecting a new one\n"));
241       gst_element_set_state (play->playbin, GST_STATE_PAUSED);
242       gst_element_set_state (play->playbin, GST_STATE_PLAYING);
243       break;
244     }
245     case GST_MESSAGE_LATENCY:
246       g_print ("Redistribute latency...\n");
247       gst_bin_recalculate_latency (GST_BIN (play->playbin));
248       break;
249     case GST_MESSAGE_REQUEST_STATE:{
250       GstState state;
251       gchar *name;
252
253       name = gst_object_get_path_string (GST_MESSAGE_SRC (msg));
254
255       gst_message_parse_request_state (msg, &state);
256
257       g_print ("Setting state to %s as requested by %s...\n",
258           gst_element_state_get_name (state), name);
259
260       gst_element_set_state (play->playbin, state);
261       g_free (name);
262       break;
263     }
264     case GST_MESSAGE_EOS:
265       /* print final position at end */
266       play_timeout (play);
267       g_print ("\n");
268       /* and switch to next item in list */
269       if (!play_next (play)) {
270         g_print ("Reached end of play list.\n");
271         g_main_loop_quit (play->loop);
272       }
273       break;
274     case GST_MESSAGE_WARNING:{
275       GError *err;
276       gchar *dbg = NULL;
277
278       /* dump graph on warning */
279       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (play->playbin),
280           GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.warning");
281
282       gst_message_parse_warning (msg, &err, &dbg);
283       g_printerr ("WARNING %s\n", err->message);
284       if (dbg != NULL)
285         g_printerr ("WARNING debug information: %s\n", dbg);
286       g_error_free (err);
287       g_free (dbg);
288       break;
289     }
290     case GST_MESSAGE_ERROR:{
291       GError *err;
292       gchar *dbg;
293
294       /* dump graph on error */
295       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (play->playbin),
296           GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.error");
297
298       gst_message_parse_error (msg, &err, &dbg);
299       g_printerr ("ERROR %s for %s\n", err->message, play->uris[play->cur_idx]);
300       if (dbg != NULL)
301         g_printerr ("ERROR debug information: %s\n", dbg);
302       g_error_free (err);
303       g_free (dbg);
304
305       /* flush any other error messages from the bus and clean up */
306       gst_element_set_state (play->playbin, GST_STATE_NULL);
307
308       if (play->missing != NULL && play_install_missing_plugins (play)) {
309         g_print ("New plugins installed, trying again...\n");
310         --play->cur_idx;
311         play_next (play);
312         break;
313       }
314       /* try next item in list then */
315       if (!play_next (play)) {
316         g_print ("Reached end of play list.\n");
317         g_main_loop_quit (play->loop);
318       }
319       break;
320     }
321     default:
322       if (gst_is_missing_plugin_message (msg)) {
323         gchar *desc;
324
325         desc = gst_missing_plugin_message_get_description (msg);
326         g_print ("Missing plugin: %s\n", desc);
327         g_free (desc);
328         play->missing = g_list_append (play->missing, gst_message_ref (msg));
329       }
330       break;
331   }
332
333   return TRUE;
334 }
335
336 static gboolean
337 play_timeout (gpointer user_data)
338 {
339   GstPlay *play = user_data;
340   gint64 pos = -1, dur = -1;
341   gchar status[64] = { 0, };
342
343   if (play->buffering)
344     return TRUE;
345
346   gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos);
347   gst_element_query_duration (play->playbin, GST_FORMAT_TIME, &dur);
348
349   if (play->desired_state == GST_STATE_PAUSED)
350     g_snprintf (status, sizeof (status), "Paused");
351   else
352     memset (status, ' ', sizeof (status) - 1);
353
354   if (pos >= 0 && dur > 0) {
355     gchar dstr[32], pstr[32];
356
357     /* FIXME: pretty print in nicer format */
358     g_snprintf (pstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (pos));
359     pstr[9] = '\0';
360     g_snprintf (dstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (dur));
361     dstr[9] = '\0';
362     g_print ("%s / %s %s\r", pstr, dstr, status);
363   }
364
365   return TRUE;
366 }
367
368 static gchar *
369 play_uri_get_display_name (GstPlay * play, const gchar * uri)
370 {
371   gchar *loc;
372
373   if (gst_uri_has_protocol (uri, "file")) {
374     loc = g_filename_from_uri (uri, NULL, NULL);
375   } else if (gst_uri_has_protocol (uri, "pushfile")) {
376     loc = g_filename_from_uri (uri + 4, NULL, NULL);
377   } else {
378     loc = g_strdup (uri);
379   }
380
381   /* Maybe additionally use glib's filename to display name function */
382   return loc;
383 }
384
385 static void
386 play_uri (GstPlay * play, const gchar * next_uri)
387 {
388   GstStateChangeReturn sret;
389   gchar *loc;
390
391   gst_element_set_state (play->playbin, GST_STATE_READY);
392   play_reset (play);
393
394   loc = play_uri_get_display_name (play, next_uri);
395   g_print ("Now playing %s\n", loc);
396   g_free (loc);
397
398   g_object_set (play->playbin, "uri", next_uri, NULL);
399
400   sret = gst_element_set_state (play->playbin, play->desired_state);
401   switch (sret) {
402     case GST_STATE_CHANGE_FAILURE:
403       /* ignore, we should get an error message posted on the bus */
404       break;
405     case GST_STATE_CHANGE_NO_PREROLL:
406       g_print ("Pipeline is live.\n");
407       play->is_live = TRUE;
408       break;
409     case GST_STATE_CHANGE_ASYNC:
410       g_print ("Prerolling...\r");
411       break;
412     default:
413       break;
414   }
415 }
416
417 /* returns FALSE if we have reached the end of the playlist */
418 static gboolean
419 play_next (GstPlay * play)
420 {
421   if ((play->cur_idx + 1) >= play->num_uris)
422     return FALSE;
423
424   play_uri (play, play->uris[++play->cur_idx]);
425   return TRUE;
426 }
427
428 /* returns FALSE if we have reached the beginning of the playlist */
429 static gboolean
430 play_prev (GstPlay * play)
431 {
432   if (play->cur_idx == 0 || play->num_uris <= 1)
433     return FALSE;
434
435   play_uri (play, play->uris[--play->cur_idx]);
436   return TRUE;
437 }
438
439 static void
440 play_about_to_finish (GstElement * playbin, gpointer user_data)
441 {
442   GstPlay *play = user_data;
443   const gchar *next_uri;
444   gchar *loc;
445   guint next_idx;
446
447   if (!play->gapless)
448     return;
449
450   next_idx = play->cur_idx + 1;
451   if (next_idx >= play->num_uris)
452     return;
453
454   next_uri = play->uris[next_idx];
455   loc = play_uri_get_display_name (play, next_uri);
456   g_print ("About to finish, preparing next title: %s\n", loc);
457   g_free (loc);
458
459   g_object_set (play->playbin, "uri", next_uri, NULL);
460   play->cur_idx = next_idx;
461 }
462
463 static void
464 do_play (GstPlay * play)
465 {
466   gint i;
467
468   /* dump playlist */
469   for (i = 0; i < play->num_uris; ++i)
470     GST_INFO ("%4u : %s", i, play->uris[i]);
471
472   if (!play_next (play))
473     return;
474
475   g_main_loop_run (play->loop);
476 }
477
478 static void
479 add_to_playlist (GPtrArray * playlist, const gchar * filename)
480 {
481   GDir *dir;
482   gchar *uri;
483
484   if (gst_uri_is_valid (filename)) {
485     g_ptr_array_add (playlist, g_strdup (filename));
486     return;
487   }
488
489   if ((dir = g_dir_open (filename, 0, NULL))) {
490     const gchar *entry;
491
492     /* FIXME: sort entries for each directory? */
493     while ((entry = g_dir_read_name (dir))) {
494       gchar *path;
495
496       path = g_strconcat (filename, G_DIR_SEPARATOR_S, entry, NULL);
497       add_to_playlist (playlist, path);
498       g_free (path);
499     }
500
501     g_dir_close (dir);
502     return;
503   }
504
505   uri = gst_filename_to_uri (filename, NULL);
506   if (uri != NULL)
507     g_ptr_array_add (playlist, uri);
508   else
509     g_warning ("Could not make URI out of filename '%s'", filename);
510 }
511
512 static void
513 shuffle_uris (gchar ** uris, guint num)
514 {
515   gchar *tmp;
516   guint i, j;
517
518   if (num < 2)
519     return;
520
521   for (i = 0; i < num; i++) {
522     /* gets equally distributed random number in 0..num-1 [0;num[ */
523     j = g_random_int_range (0, num);
524     tmp = uris[j];
525     uris[j] = uris[i];
526     uris[i] = tmp;
527   }
528 }
529
530 static void
531 restore_terminal (void)
532 {
533   gst_play_kb_set_key_handler (NULL, NULL);
534 }
535
536 static void
537 toggle_paused (GstPlay * play)
538 {
539   if (play->desired_state == GST_STATE_PLAYING)
540     play->desired_state = GST_STATE_PAUSED;
541   else
542     play->desired_state = GST_STATE_PLAYING;
543
544   if (!play->buffering) {
545     gst_element_set_state (play->playbin, play->desired_state);
546   } else if (play->desired_state == GST_STATE_PLAYING) {
547     g_print ("\nWill play as soon as buffering finishes)\n");
548   }
549 }
550
551 static void
552 relative_seek (GstPlay * play, gdouble percent)
553 {
554   GstQuery *query;
555   gboolean seekable = FALSE;
556   gint64 dur = -1, pos = -1;
557
558   g_return_if_fail (percent >= -1.0 && percent <= 1.0);
559
560   if (!gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos))
561     goto seek_failed;
562
563   query = gst_query_new_seeking (GST_FORMAT_TIME);
564   if (!gst_element_query (play->playbin, query)) {
565     gst_query_unref (query);
566     goto seek_failed;
567   }
568
569   gst_query_parse_seeking (query, NULL, &seekable, NULL, &dur);
570   gst_query_unref (query);
571
572   if (!seekable || dur <= 0)
573     goto seek_failed;
574
575   pos = pos + dur * percent;
576   if (pos > dur) {
577     if (!play_next (play)) {
578       g_print ("\nReached end of play list.\n");
579       g_main_loop_quit (play->loop);
580     }
581   } else {
582     if (pos < 0)
583       pos = 0;
584     if (!gst_element_seek_simple (play->playbin, GST_FORMAT_TIME,
585             GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, pos))
586       goto seek_failed;
587   }
588
589   return;
590
591 seek_failed:
592   {
593     g_print ("\nCould not seek.\n");
594   }
595 }
596
597 static void
598 keyboard_cb (const gchar * key_input, gpointer user_data)
599 {
600   GstPlay *play = (GstPlay *) user_data;
601
602   switch (g_ascii_tolower (key_input[0])) {
603     case ' ':
604       toggle_paused (play);
605       break;
606     case 'q':
607     case 'Q':
608       g_main_loop_quit (play->loop);
609       break;
610     case '>':
611       if (!play_next (play)) {
612         g_print ("\nReached end of play list.\n");
613         g_main_loop_quit (play->loop);
614       }
615       break;
616     case '<':
617       play_prev (play);
618       break;
619     case 27:                   /* ESC */
620       if (key_input[1] == '\0') {
621         g_main_loop_quit (play->loop);
622         break;
623       }
624       /* fall through */
625     default:
626       if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) {
627         relative_seek (play, +0.08);
628       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) {
629         relative_seek (play, -0.01);
630       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_UP) == 0) {
631         play_set_relative_volume (play, +1.0 / VOLUME_STEPS);
632       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_DOWN) == 0) {
633         play_set_relative_volume (play, -1.0 / VOLUME_STEPS);
634       } else {
635         GST_INFO ("keyboard input:");
636         for (; *key_input != '\0'; ++key_input)
637           GST_INFO ("  code %3d", *key_input);
638       }
639       break;
640   }
641 }
642
643 int
644 main (int argc, char **argv)
645 {
646   GstPlay *play;
647   GPtrArray *playlist;
648   gboolean print_version = FALSE;
649   gboolean interactive = FALSE; /* FIXME: maybe enable by default? */
650   gboolean gapless = FALSE;
651   gboolean shuffle = FALSE;
652   gdouble volume = 1.0;
653   gchar **filenames = NULL;
654   gchar *audio_sink = NULL;
655   gchar *video_sink = NULL;
656   gchar **uris;
657   guint num, i;
658   GError *err = NULL;
659   GOptionContext *ctx;
660   GOptionEntry options[] = {
661     {"version", 0, 0, G_OPTION_ARG_NONE, &print_version,
662         N_("Print version information and exit"), NULL},
663     {"videosink", 0, 0, G_OPTION_ARG_STRING, &video_sink,
664         N_("Video sink to use (default is autovideosink)"), NULL},
665     {"audiosink", 0, 0, G_OPTION_ARG_STRING, &audio_sink,
666         N_("Audio sink to use (default is autoaudiosink)"), NULL},
667     {"gapless", 0, 0, G_OPTION_ARG_NONE, &gapless,
668         N_("Enable gapless playback"), NULL},
669     {"shuffle", 0, 0, G_OPTION_ARG_NONE, &shuffle,
670         N_("Shuffle playlist"), NULL},
671     {"interactive", 0, 0, G_OPTION_ARG_NONE, &interactive,
672         N_("Interactive control via keyboard"), NULL},
673     {"volume", 0, 0, G_OPTION_ARG_DOUBLE, &volume,
674         N_("Volume"), NULL},
675     {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL},
676     {NULL}
677   };
678
679   setlocale (LC_ALL, "");
680
681 #ifdef ENABLE_NLS
682   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
683   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
684   textdomain (GETTEXT_PACKAGE);
685 #endif
686
687   g_set_prgname ("gst-play-" GST_API_VERSION);
688
689   ctx = g_option_context_new ("FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ...");
690   g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
691   g_option_context_add_group (ctx, gst_init_get_option_group ());
692   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
693     g_print ("Error initializing: %s\n", GST_STR_NULL (err->message));
694     return 1;
695   }
696   g_option_context_free (ctx);
697
698   GST_DEBUG_CATEGORY_INIT (play_debug, "play", 0, "gst-play");
699
700   if (print_version) {
701     gchar *version_str;
702
703     version_str = gst_version_string ();
704     g_print ("%s version %s\n", g_get_prgname (), PACKAGE_VERSION);
705     g_print ("%s\n", version_str);
706     g_print ("%s\n", GST_PACKAGE_ORIGIN);
707     g_free (version_str);
708     return 0;
709   }
710
711   if (filenames == NULL || *filenames == NULL) {
712     g_printerr (_("Usage: %s FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ..."),
713         "gst-play-" GST_API_VERSION);
714     g_printerr ("\n\n"),
715         g_printerr ("%s\n\n",
716         _("You must provide at least one filename or URI to play."));
717     return 1;
718   }
719
720   playlist = g_ptr_array_new ();
721
722   /* fill playlist */
723   num = g_strv_length (filenames);
724   for (i = 0; i < num; ++i) {
725     GST_LOG ("command line argument: %s", filenames[i]);
726     add_to_playlist (playlist, filenames[i]);
727   }
728   g_strfreev (filenames);
729
730   num = playlist->len;
731   g_ptr_array_add (playlist, NULL);
732
733   uris = (gchar **) g_ptr_array_free (playlist, FALSE);
734
735   if (shuffle)
736     shuffle_uris (uris, num);
737
738   /* prepare */
739   play = play_new (uris, audio_sink, video_sink, gapless, volume);
740
741   if (interactive) {
742     if (gst_play_kb_set_key_handler (keyboard_cb, play)) {
743       atexit (restore_terminal);
744     } else {
745       g_print ("Interactive keyboard handling in terminal not available.\n");
746     }
747   }
748
749   /* play */
750   do_play (play);
751
752   /* clean up */
753   play_free (play);
754
755   g_print ("\n");
756   return 0;
757 }