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