35dad5f1cb212a4b1f1726ae409019296940ed2a
[platform/upstream/gstreamer.git] / subprojects / gst-docs / markdown / tutorials / playback / playbin-usage.md
1 # Playback tutorial 1: Playbin usage
2
3
4 {{ ALERT_PY.md }}
5
6 {{ ALERT_JS.md }}
7
8 ## Goal
9
10 We have already worked with the `playbin` element, which is capable of
11 building a complete playback pipeline without much work on our side.
12 This tutorial shows how to further customize `playbin` in case its
13 default values do not suit our particular needs.
14
15 We will learn:
16
17 -   How to find out how many streams a file contains, and how to switch
18     among them.
19
20 -   How to gather information regarding each stream.
21
22 ## Introduction
23
24 More often than not, multiple audio, video and subtitle streams can be
25 found embedded in a single file. The most common case are regular
26 movies, which contain one video and one audio stream (Stereo or 5.1
27 audio tracks are considered a single stream). It is also increasingly
28 common to find movies with one video and multiple audio streams, to
29 account for different languages. In this case, the user selects one
30 audio stream, and the application will only play that one, ignoring the
31 others.
32
33 To be able to select the appropriate stream, the user needs to know
34 certain information about them, for example, their language. This
35 information is embedded in the streams in the form of “metadata”
36 (annexed data), and this tutorial shows how to retrieve it.
37
38 Subtitles can also be embedded in a file, along with audio and video,
39 but they are dealt with in more detail in [Playback tutorial 2: Subtitle
40 management]. Finally, multiple video streams can also be found in a
41 single file, for example, in DVD with multiple angles of the same scene,
42 but they are somewhat rare.
43
44 > ![information] Embedding multiple streams inside a single file is
45 > called “multiplexing” or “muxing”, and such file is then known as a
46 > “container”. Common container formats are Matroska (.mkv), Quicktime
47 > (.qt, .mov, .mp4), Ogg (.ogg) or Webm (.webm).
48 >
49 > Retrieving the individual streams from within the container is called
50 > “demultiplexing” or “demuxing”.
51
52 The following code recovers the amount of streams in the file, their
53 associated metadata, and allows switching the audio stream while the
54 media is playing.
55
56 ## The multilingual player
57
58 Copy this code into a text file named `playback-tutorial-1.c` (or find
59 it in the GStreamer installation).
60
61 **playback-tutorial-1.c**
62
63 ``` c
64 #include <gst/gst.h>
65
66 /* Structure to contain all our information, so we can pass it around */
67 typedef struct _CustomData {
68   GstElement *playbin;  /* Our one and only element */
69
70   gint n_video;          /* Number of embedded video streams */
71   gint n_audio;          /* Number of embedded audio streams */
72   gint n_text;           /* Number of embedded subtitle streams */
73
74   gint current_video;    /* Currently playing video stream */
75   gint current_audio;    /* Currently playing audio stream */
76   gint current_text;     /* Currently playing subtitle stream */
77
78   GMainLoop *main_loop;  /* GLib's Main Loop */
79 } CustomData;
80
81 /* playbin flags */
82 typedef enum {
83   GST_PLAY_FLAG_VIDEO         = (1 << 0), /* We want video output */
84   GST_PLAY_FLAG_AUDIO         = (1 << 1), /* We want audio output */
85   GST_PLAY_FLAG_TEXT          = (1 << 2)  /* We want subtitle output */
86 } GstPlayFlags;
87
88 /* Forward definition for the message and keyboard processing functions */
89 static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data);
90 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data);
91
92 int main(int argc, char *argv[]) {
93   CustomData data;
94   GstBus *bus;
95   GstStateChangeReturn ret;
96   gint flags;
97   GIOChannel *io_stdin;
98
99   /* Initialize GStreamer */
100   gst_init (&argc, &argv);
101
102   /* Create the elements */
103   data.playbin = gst_element_factory_make ("playbin", "playbin");
104
105   if (!data.playbin) {
106     g_printerr ("Not all elements could be created.\n");
107     return -1;
108   }
109
110   /* Set the URI to play */
111   g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_cropped_multilingual.webm", NULL);
112
113   /* Set flags to show Audio and Video but ignore Subtitles */
114   g_object_get (data.playbin, "flags", &flags, NULL);
115   flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO;
116   flags &= ~GST_PLAY_FLAG_TEXT;
117   g_object_set (data.playbin, "flags", flags, NULL);
118
119   /* Set connection speed. This will affect some internal decisions of playbin */
120   g_object_set (data.playbin, "connection-speed", 56, NULL);
121
122   /* Add a bus watch, so we get notified when a message arrives */
123   bus = gst_element_get_bus (data.playbin);
124   gst_bus_add_watch (bus, (GstBusFunc)handle_message, &data);
125
126   /* Add a keyboard watch so we get notified of keystrokes */
127 #ifdef G_OS_WIN32
128   io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
129 #else
130   io_stdin = g_io_channel_unix_new (fileno (stdin));
131 #endif
132   g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);
133
134   /* Start playing */
135   ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
136   if (ret == GST_STATE_CHANGE_FAILURE) {
137     g_printerr ("Unable to set the pipeline to the playing state.\n");
138     gst_object_unref (data.playbin);
139     return -1;
140   }
141
142   /* Create a GLib Main Loop and set it to run */
143   data.main_loop = g_main_loop_new (NULL, FALSE);
144   g_main_loop_run (data.main_loop);
145
146   /* Free resources */
147   g_main_loop_unref (data.main_loop);
148   g_io_channel_unref (io_stdin);
149   gst_object_unref (bus);
150   gst_element_set_state (data.playbin, GST_STATE_NULL);
151   gst_object_unref (data.playbin);
152   return 0;
153 }
154
155 /* Extract some metadata from the streams and print it on the screen */
156 static void analyze_streams (CustomData *data) {
157   gint i;
158   GstTagList *tags;
159   gchar *str;
160   guint rate;
161
162   /* Read some properties */
163   g_object_get (data->playbin, "n-video", &data->n_video, NULL);
164   g_object_get (data->playbin, "n-audio", &data->n_audio, NULL);
165   g_object_get (data->playbin, "n-text", &data->n_text, NULL);
166
167   g_print ("%d video stream(s), %d audio stream(s), %d text stream(s)\n",
168     data->n_video, data->n_audio, data->n_text);
169
170   g_print ("\n");
171   for (i = 0; i < data->n_video; i++) {
172     tags = NULL;
173     /* Retrieve the stream's video tags */
174     g_signal_emit_by_name (data->playbin, "get-video-tags", i, &tags);
175     if (tags) {
176       g_print ("video stream %d:\n", i);
177       gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
178       g_print ("  codec: %s\n", str ? str : "unknown");
179       g_free (str);
180       gst_tag_list_free (tags);
181     }
182   }
183
184   g_print ("\n");
185   for (i = 0; i < data->n_audio; i++) {
186     tags = NULL;
187     /* Retrieve the stream's audio tags */
188     g_signal_emit_by_name (data->playbin, "get-audio-tags", i, &tags);
189     if (tags) {
190       g_print ("audio stream %d:\n", i);
191       if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str)) {
192         g_print ("  codec: %s\n", str);
193         g_free (str);
194       }
195       if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
196         g_print ("  language: %s\n", str);
197         g_free (str);
198       }
199       if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate)) {
200         g_print ("  bitrate: %d\n", rate);
201       }
202       gst_tag_list_free (tags);
203     }
204   }
205
206   g_print ("\n");
207   for (i = 0; i < data->n_text; i++) {
208     tags = NULL;
209     /* Retrieve the stream's subtitle tags */
210     g_signal_emit_by_name (data->playbin, "get-text-tags", i, &tags);
211     if (tags) {
212       g_print ("subtitle stream %d:\n", i);
213       if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
214         g_print ("  language: %s\n", str);
215         g_free (str);
216       }
217       gst_tag_list_free (tags);
218     }
219   }
220
221   g_object_get (data->playbin, "current-video", &data->current_video, NULL);
222   g_object_get (data->playbin, "current-audio", &data->current_audio, NULL);
223   g_object_get (data->playbin, "current-text", &data->current_text, NULL);
224
225   g_print ("\n");
226   g_print ("Currently playing video stream %d, audio stream %d and text stream %d\n",
227     data->current_video, data->current_audio, data->current_text);
228   g_print ("Type any number and hit ENTER to select a different audio stream\n");
229 }
230
231 /* Process messages from GStreamer */
232 static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data) {
233   GError *err;
234   gchar *debug_info;
235
236   switch (GST_MESSAGE_TYPE (msg)) {
237     case GST_MESSAGE_ERROR:
238       gst_message_parse_error (msg, &err, &debug_info);
239       g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
240       g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
241       g_clear_error (&err);
242       g_free (debug_info);
243       g_main_loop_quit (data->main_loop);
244       break;
245     case GST_MESSAGE_EOS:
246       g_print ("End-Of-Stream reached.\n");
247       g_main_loop_quit (data->main_loop);
248       break;
249     case GST_MESSAGE_STATE_CHANGED: {
250       GstState old_state, new_state, pending_state;
251       gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
252       if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
253         if (new_state == GST_STATE_PLAYING) {
254           /* Once we are in the playing state, analyze the streams */
255           analyze_streams (data);
256         }
257       }
258     } break;
259   }
260
261   /* We want to keep receiving messages */
262   return TRUE;
263 }
264
265 /* Process keyboard input */
266 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
267   gchar *str = NULL;
268
269   if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) == G_IO_STATUS_NORMAL) {
270     int index = g_ascii_strtoull (str, NULL, 0);
271     if (index < 0 || index >= data->n_audio) {
272       g_printerr ("Index out of bounds\n");
273     } else {
274       /* If the input was a valid audio stream index, set the current audio stream */
275       g_print ("Setting current audio stream to %d\n", index);
276       g_object_set (data->playbin, "current-audio", index, NULL);
277     }
278   }
279   g_free (str);
280   return TRUE;
281 }
282 ```
283
284 > ![information] If you need help to compile this code, refer to the
285 > **Building the tutorials** section for your platform: [Mac] or
286 > [Windows] or use this specific command on Linux:
287 >
288 > `` gcc playback-tutorial-1.c -o playback-tutorial-1 `pkg-config --cflags --libs gstreamer-1.0` ``
289 >
290 > If you need help to run this code, refer to the **Running the
291 > tutorials** section for your platform: [Mac OS X], [Windows][1], for
292 > [iOS] or for [android].
293 >
294 > This tutorial opens a window and displays a movie, with accompanying
295 > audio. The media is fetched from the Internet, so the window might take
296 > a few seconds to appear, depending on your connection speed. The number
297 > of audio streams is shown in the terminal, and the user can switch from
298 > one to another by entering a number and pressing enter. A small delay is
299 > to be expected.
300 >
301 > Bear in mind that there is no latency management (buffering), so on slow
302 > connections, the movie might stop after a few seconds. See how [Tutorial
303 > 12: Live streaming] solves this issue.
304 >
305 > Required libraries: `gstreamer-1.0`
306
307 ## Walkthrough
308
309 ``` c
310 /* Structure to contain all our information, so we can pass it around */
311 typedef struct _CustomData {
312   GstElement *playbin;  /* Our one and only element */
313
314   gint n_video;          /* Number of embedded video streams */
315   gint n_audio;          /* Number of embedded audio streams */
316   gint n_text;           /* Number of embedded subtitle streams */
317
318   gint current_video;    /* Currently playing video stream */
319   gint current_audio;    /* Currently playing audio stream */
320   gint current_text;     /* Currently playing subtitle stream */
321
322   GMainLoop *main_loop;  /* GLib's Main Loop */
323 } CustomData;
324 ```
325
326 We start, as usual, putting all our variables in a structure, so we can
327 pass it around to functions. For this tutorial, we need the amount of
328 streams of each type, and the currently playing one. Also, we are going
329 to use a different mechanism to wait for messages that allows
330 interactivity, so we need a GLib's main loop object.
331
332 ``` c
333 /* playbin flags */
334 typedef enum {
335   GST_PLAY_FLAG_VIDEO         = (1 << 0), /* We want video output */
336   GST_PLAY_FLAG_AUDIO         = (1 << 1), /* We want audio output */
337   GST_PLAY_FLAG_TEXT          = (1 << 2)  /* We want subtitle output */
338 } GstPlayFlags;
339 ```
340
341 Later we are going to set some of `playbin`'s flags. We would like to
342 have a handy enum that allows manipulating these flags easily, but since
343 `playbin` is a plug-in and not a part of the GStreamer core, this enum
344 is not available to us. The “trick” is simply to declare this enum in
345 our code, as it appears in the `playbin` documentation: `GstPlayFlags`.
346 GObject allows introspection, so the possible values for these flags can
347 be retrieved at runtime without using this trick, but in a far more
348 cumbersome way.
349
350 ``` c
351 /* Forward definition for the message and keyboard processing functions */
352 static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data);
353 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data);
354 ```
355
356 Forward declarations for the two callbacks we will be using.
357 `handle_message` for the GStreamer messages, as we have already seen,
358 and `handle_keyboard` for key strokes, since this tutorial is
359 introducing a limited amount of interactivity.
360
361 We skip over the creation of the pipeline, the instantiation of
362 `playbin` and pointing it to our test media through the `uri`
363 property. `playbin` is in itself a pipeline, and in this case it is the
364 only element in the pipeline, so we skip completely the creation of the
365 pipeline, and use directly the  `playbin` element.
366
367 We focus on some of the other properties of `playbin`, though:
368
369 ``` c
370 /* Set flags to show Audio and Video but ignore Subtitles */
371 g_object_get (data.playbin, "flags", &flags, NULL);
372 flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO;
373 flags &= ~GST_PLAY_FLAG_TEXT;
374 g_object_set (data.playbin, "flags", flags, NULL);
375 ```
376
377 `playbin`'s behavior can be changed through its `flags` property, which
378 can have any combination of `GstPlayFlags`. The most interesting values
379 are:
380
381 | Flag                      | Description                                                                                                                        |
382 |---------------------------|------------------------------------------------------------------------------------------------------------------------------------|
383 | GST_PLAY_FLAG_VIDEO       | Enable video rendering. If this flag is not set, there will be no video output.                                                    |
384 | GST_PLAY_FLAG_AUDIO       | Enable audio rendering. If this flag is not set, there will be no audio output.                                                    |
385 | GST_PLAY_FLAG_TEXT        | Enable subtitle rendering. If this flag is not set, subtitles will not be shown in the video output.                               |
386 | GST_PLAY_FLAG_VIS         | Enable rendering of visualisations when there is no video stream. Playback tutorial 6: Audio visualization goes into more details. |
387 | GST_PLAY_FLAG_DOWNLOAD    | See Basic tutorial 12: Streaming  and Playback tutorial 4: Progressive streaming.                                                  |
388 | GST_PLAY_FLAG_BUFFERING   | See Basic tutorial 12: Streaming  and Playback tutorial 4: Progressive streaming.                                                  |
389 | GST_PLAY_FLAG_DEINTERLACE | If the video content was interlaced, this flag instructs playbin to deinterlace it before displaying it.                           |
390
391 In our case, for demonstration purposes, we are enabling audio and video
392 and disabling subtitles, leaving the rest of flags to their default
393 values (this is why we read the current value of the flags with
394 `g_object_get()` before overwriting it with `g_object_set()`).
395
396 ``` c
397 /* Set connection speed. This will affect some internal decisions of playbin */
398 g_object_set (data.playbin, "connection-speed", 56, NULL);
399 ```
400
401 This property is not really useful in this example.
402 `connection-speed` informs `playbin` of the maximum speed of our network
403 connection, so, in case multiple versions of the requested media are
404 available in the server, `playbin` chooses the most appropriate. This is
405 mostly used in combination with streaming protocols like `mms` or
406 `rtsp`.
407
408 We have set all these properties one by one, but we could have all of
409 them with a single call to `g_object_set()`:
410
411 ``` c
412 g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_cropped_multilingual.webm", "flags", flags, "connection-speed", 56, NULL);
413 ```
414
415 This is why `g_object_set()` requires a NULL as the last parameter.
416
417 ``` c
418   /* Add a keyboard watch so we get notified of keystrokes */
419 #ifdef _WIN32
420   io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
421 #else
422   io_stdin = g_io_channel_unix_new (fileno (stdin));
423 #endif
424   g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);
425 ```
426
427 These lines connect a callback function to the standard input (the
428 keyboard). The mechanism shown here is specific to GLib, and not really
429 related to GStreamer, so there is no point in going into much depth.
430 Applications normally have their own way of handling user input, and
431 GStreamer has little to do with it besides the Navigation interface
432 discussed briefly in [Tutorial 17: DVD playback].
433
434 ``` c
435 /* Create a GLib Main Loop and set it to run */
436 data.main_loop = g_main_loop_new (NULL, FALSE);
437 g_main_loop_run (data.main_loop);
438 ```
439
440 To allow interactivity, we will no longer poll the GStreamer bus
441 manually. Instead, we create a `GMainLoop`(GLib main loop) and set it
442 running with `g_main_loop_run()`. This function blocks and will not
443 return until `g_main_loop_quit()` is issued. In the meantime, it will
444 call the callbacks we have registered at the appropriate
445 times: `handle_message` when a message appears on the bus, and
446 `handle_keyboard` when the user presses any key.
447
448 There is nothing new in handle\_message, except that when the pipeline
449 moves to the PLAYING state, it will call the `analyze_streams` function:
450
451 ``` c
452 /* Extract some metadata from the streams and print it on the screen */
453 static void analyze_streams (CustomData *data) {
454   gint i;
455   GstTagList *tags;
456   gchar *str;
457   guint rate;
458
459   /* Read some properties */
460   g_object_get (data->playbin, "n-video", &data->n_video, NULL);
461   g_object_get (data->playbin, "n-audio", &data->n_audio, NULL);
462   g_object_get (data->playbin, "n-text", &data->n_text, NULL);
463 ```
464
465 As the comment says, this function just gathers information from the
466 media and prints it on the screen. The number of video, audio and
467 subtitle streams is directly available through the `n-video`,
468 `n-audio` and `n-text` properties.
469
470 ``` c
471 for (i = 0; i < data->n_video; i++) {
472   tags = NULL;
473   /* Retrieve the stream's video tags */
474   g_signal_emit_by_name (data->playbin, "get-video-tags", i, &tags);
475   if (tags) {
476     g_print ("video stream %d:\n", i);
477     gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
478     g_print ("  codec: %s\n", str ? str : "unknown");
479     g_free (str);
480     gst_tag_list_free (tags);
481   }
482 }
483 ```
484
485 Now, for each stream, we want to retrieve its metadata. Metadata is
486 stored as tags in a `GstTagList` structure, which is a list of data
487 pieces identified by a name. The `GstTagList` associated with a stream
488 can be recovered with `g_signal_emit_by_name()`, and then individual
489 tags are extracted with the `gst_tag_list_get_*` functions
490 like `gst_tag_list_get_string()` for example.
491
492 > ![information]
493 > This rather unintuitive way of retrieving the tag list
494 > is called an Action Signal. Action signals are emitted by the
495 > application to a specific element, which then performs an action and
496 > returns a result. They behave like a dynamic function call, in which
497 > methods of a class are identified by their name (the signal's name)
498 > instead of their memory address. These signals are listed In the
499 > documentation along with the regular signals, and are tagged “Action”.
500 > See `playbin`, for example.
501
502 `playbin` defines 3 action signals to retrieve metadata:
503 `get-video-tags`, `get-audio-tags` and `get-text-tags`. The name if the
504 tags is standardized, and the list can be found in the `GstTagList`
505 documentation. In this example we are interested in the
506 `GST_TAG_LANGUAGE_CODE` of the streams and their `GST_TAG_*_CODEC`
507 (audio, video or text).
508
509 ``` c
510 g_object_get (data->playbin, "current-video", &data->current_video, NULL);
511 g_object_get (data->playbin, "current-audio", &data->current_audio, NULL);
512 g_object_get (data->playbin, "current-text", &data->current_text, NULL);
513 ```
514
515 Once we have extracted all the metadata we want, we get the streams that
516 are currently selected through 3 more properties of `playbin`:
517 `current-video`, `current-audio` and `current-text`.
518
519 It is interesting to always check the currently selected streams and
520 never make any assumption. Multiple internal conditions can make
521 `playbin` behave differently in different executions. Also, the order in
522 which the streams are listed can change from one run to another, so
523 checking the metadata to identify one particular stream becomes crucial.
524
525 ``` c
526 /* Process keyboard input */
527 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
528   gchar *str = NULL;
529
530   if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) == G_IO_STATUS_NORMAL) {
531     int index = g_ascii_strtoull (str, NULL, 0);
532     if (index < 0 || index >= data->n_audio) {
533       g_printerr ("Index out of bounds\n");
534     } else {
535       /* If the input was a valid audio stream index, set the current audio stream */
536       g_print ("Setting current audio stream to %d\n", index);
537       g_object_set (data->playbin, "current-audio", index, NULL);
538     }
539   }
540   g_free (str);
541   return TRUE;
542 }
543 ```
544
545 Finally, we allow the user to switch the running audio stream. This very
546 basic function just reads a string from the standard input (the
547 keyboard), interprets it as a number, and tries to set the
548 `current-audio` property of `playbin` (which previously we have only
549 read).
550
551 Bear in mind that the switch is not immediate. Some of the previously
552 decoded audio will still be flowing through the pipeline, while the new
553 stream becomes active and is decoded. The delay depends on the
554 particular multiplexing of the streams in the container, and the length
555 `playbin` has selected for its internal queues (which depends on the
556 network conditions).
557
558 If you execute the tutorial, you will be able to switch from one
559 language to another while the movie is running by pressing 0, 1 or 2
560 (and ENTER). This concludes this tutorial.
561
562 ## Conclusion
563
564 This tutorial has shown:
565
566 -   A few more of `playbin`'s properties: `flags`, `connection-speed`,
567     `n-video`, `n-audio`, `n-text`, `current-video`, `current-audio` and
568     `current-text`.
569
570 -   How to retrieve the list of tags associated with a stream
571     with `g_signal_emit_by_name()`.
572
573 -   How to retrieve a particular tag from the list with
574     `gst_tag_list_get_string()`or `gst_tag_list_get_uint()`
575
576 -   How to switch the current audio simply by writing to the
577     `current-audio` property.
578
579 The next playback tutorial shows how to handle subtitles, either
580 embedded in the container or in an external file.
581
582 Remember that attached to this page you should find the complete source
583 code of the tutorial and any accessory files needed to build it.
584
585 It has been a pleasure having you here, and see you soon!
586
587   [Playback tutorial 2: Subtitle management]: tutorials/playback/subtitle-management.md
588   [information]: images/icons/emoticons/information.svg
589   [Mac]: installing/on-mac-osx.md
590   [Windows]: installing/on-windows.md
591   [Mac OS X]: installing/on-mac-osx.md#building-the-tutorials
592   [1]: installing/on-windows.md#running-the-tutorials
593   [iOS]: installing/for-ios-development.md#building-the-tutorials
594   [android]: installing/for-android-development.md#building-the-tutorials