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