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