tutorials/playback: Fix more occurences of GST_TIME_FORMAT / GST_FORMAT_TIME mixups
[platform/upstream/gstreamer.git] / markdown / tutorials / playback / progressive-streaming.md
1 # Playback tutorial 4: Progressive streaming
2
3 ## Goal
4
5 [](tutorials/basic/streaming.md) showed how to
6 enhance the user experience in poor network conditions, by taking
7 buffering into account. This tutorial further expands
8 [](tutorials/basic/streaming.md) by enabling
9 the local storage of the streamed media, and describes the advantages of
10 this technique. In particular, it shows:
11
12   - How to enable progressive downloading
13   - How to know what has been downloaded
14   - How to know where it has been downloaded
15   - How to limit the amount of downloaded data that is kept
16
17 ## Introduction
18
19 When streaming, data is fetched from the network and a small buffer of
20 future-data is kept to ensure smooth playback (see
21 [](tutorials/basic/streaming.md)). However, data
22 is discarded as soon as it is displayed or rendered (there is no
23 past-data buffer). This means, that if a user wants to jump back and
24 continue playback from a point in the past, data needs to be
25 re-downloaded.
26
27 Media players tailored for streaming, like YouTube, usually keep all
28 downloaded data stored locally for this contingency. A graphical widget
29 is also normally used to show how much of the file has already been
30 downloaded.
31
32 `playbin` offers similar functionalities through the `DOWNLOAD` flag
33 which stores the media in a local temporary file for faster playback of
34 already-downloaded chunks.
35
36 This code also shows how to use the Buffering Query, which allows
37 knowing what parts of the file are available.
38
39 ## A network-resilient example with local storage
40
41 Copy this code into a text file named `playback-tutorial-4.c`.
42
43 **playback-tutorial-4.c**
44
45 ``` c
46 #include <gst/gst.h>
47 #include <string.h>
48
49 #define GRAPH_LENGTH 78
50
51 /* playbin flags */
52 typedef enum {
53   GST_PLAY_FLAG_DOWNLOAD      = (1 << 7) /* Enable progressive download (on selected formats) */
54 } GstPlayFlags;
55
56 typedef struct _CustomData {
57   gboolean is_live;
58   GstElement *pipeline;
59   GMainLoop *loop;
60   gint buffering_level;
61 } CustomData;
62
63 static void got_location (GstObject *gstobject, GstObject *prop_object, GParamSpec *prop, gpointer data) {
64   gchar *location;
65   g_object_get (G_OBJECT (prop_object), "temp-location", &location, NULL);
66   g_print ("Temporary file: %s\n", location);
67   g_free (location);
68   /* Uncomment this line to keep the temporary file after the program exits */
69   /* g_object_set (G_OBJECT (prop_object), "temp-remove", FALSE, NULL); */
70 }
71
72 static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) {
73
74   switch (GST_MESSAGE_TYPE (msg)) {
75     case GST_MESSAGE_ERROR: {
76       GError *err;
77       gchar *debug;
78
79       gst_message_parse_error (msg, &err, &debug);
80       g_print ("Error: %s\n", err->message);
81       g_error_free (err);
82       g_free (debug);
83
84       gst_element_set_state (data->pipeline, GST_STATE_READY);
85       g_main_loop_quit (data->loop);
86       break;
87     }
88     case GST_MESSAGE_EOS:
89       /* end-of-stream */
90       gst_element_set_state (data->pipeline, GST_STATE_READY);
91       g_main_loop_quit (data->loop);
92       break;
93     case GST_MESSAGE_BUFFERING:
94       /* If the stream is live, we do not care about buffering. */
95       if (data->is_live) break;
96
97       gst_message_parse_buffering (msg, &data->buffering_level);
98
99       /* Wait until buffering is complete before start/resume playing */
100       if (data->buffering_level < 100)
101         gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
102       else
103         gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
104       break;
105     case GST_MESSAGE_CLOCK_LOST:
106       /* Get a new clock */
107       gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
108       gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
109       break;
110     default:
111       /* Unhandled message */
112       break;
113     }
114 }
115
116 static gboolean refresh_ui (CustomData *data) {
117   GstQuery *query;
118   gboolean result;
119
120   query = gst_query_new_buffering (GST_FORMAT_PERCENT);
121   result = gst_element_query (data->pipeline, query);
122   if (result) {
123     gint n_ranges, range, i;
124     gchar graph[GRAPH_LENGTH + 1];
125     gint64 position = 0, duration = 0;
126
127     memset (graph, ' ', GRAPH_LENGTH);
128     graph[GRAPH_LENGTH] = '\0';
129
130     n_ranges = gst_query_get_n_buffering_ranges (query);
131     for (range = 0; range < n_ranges; range++) {
132       gint64 start, stop;
133       gst_query_parse_nth_buffering_range (query, range, &start, &stop);
134       start = start * GRAPH_LENGTH / (stop - start);
135       stop = stop * GRAPH_LENGTH / (stop - start);
136       for (i = (gint)start; i < stop; i++)
137         graph [i] = '-';
138     }
139     if (gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position) &&
140         GST_CLOCK_TIME_IS_VALID (position) &&
141         gst_element_query_duration (data->pipeline, GST_FORMAT_TIME, &duration) &&
142         GST_CLOCK_TIME_IS_VALID (duration)) {
143       i = (gint)(GRAPH_LENGTH * (double)position / (double)(duration + 1));
144       graph [i] = data->buffering_level < 100 ? 'X' : '>';
145     }
146     g_print ("[%s]", graph);
147     if (data->buffering_level < 100) {
148       g_print (" Buffering: %3d%%", data->buffering_level);
149     } else {
150       g_print ("                ");
151     }
152     g_print ("\r");
153   }
154
155   return TRUE;
156
157 }
158
159 int main(int argc, char *argv[]) {
160   GstElement *pipeline;
161   GstBus *bus;
162   GstStateChangeReturn ret;
163   GMainLoop *main_loop;
164   CustomData data;
165   guint flags;
166
167   /* Initialize GStreamer */
168   gst_init (&argc, &argv);
169
170   /* Initialize our data structure */
171   memset (&data, 0, sizeof (data));
172   data.buffering_level = 100;
173
174   /* Build the pipeline */
175   pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
176   bus = gst_element_get_bus (pipeline);
177
178   /* Set the download flag */
179   g_object_get (pipeline, "flags", &flags, NULL);
180   flags |= GST_PLAY_FLAG_DOWNLOAD;
181   g_object_set (pipeline, "flags", flags, NULL);
182
183   /* Uncomment this line to limit the amount of downloaded data */
184   /* g_object_set (pipeline, "ring-buffer-max-size", (guint64)4000000, NULL); */
185
186   /* Start playing */
187   ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
188   if (ret == GST_STATE_CHANGE_FAILURE) {
189     g_printerr ("Unable to set the pipeline to the playing state.\n");
190     gst_object_unref (pipeline);
191     return -1;
192   } else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
193     data.is_live = TRUE;
194   }
195
196   main_loop = g_main_loop_new (NULL, FALSE);
197   data.loop = main_loop;
198   data.pipeline = pipeline;
199
200   gst_bus_add_signal_watch (bus);
201   g_signal_connect (bus, "message", G_CALLBACK (cb_message), &data);
202   g_signal_connect (pipeline, "deep-notify::temp-location", G_CALLBACK (got_location), NULL);
203
204   /* Register a function that GLib will call every second */
205   g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
206
207   g_main_loop_run (main_loop);
208
209   /* Free resources */
210   g_main_loop_unref (main_loop);
211   gst_object_unref (bus);
212   gst_element_set_state (pipeline, GST_STATE_NULL);
213   gst_object_unref (pipeline);
214   g_print ("\n");
215   return 0;
216 }
217 ```
218
219 > ![information] If you need help to compile this code, refer to the
220 > **Building the tutorials** section for your platform: [Mac] or
221 > [Windows] or use this specific command on Linux:
222 >
223 > `` gcc playback-tutorial-4.c -o playback-tutorial-4 `pkg-config --cflags --libs gstreamer-1.0` ``
224 >
225 > If you need help to run this code, refer to the **Running the
226 > tutorials** section for your platform: [Mac OS X], [Windows][1], for
227 > [iOS] or for [android].
228 >
229 > This tutorial opens a window and displays a movie, with accompanying
230 > audio. The media is fetched from the Internet, so the window might
231 > take a few seconds to appear, depending on your connection
232 > speed. In the console window, you should see a message indicating
233 > where the media is being stored, and a text graph representing the
234 > downloaded portions and the current position. A buffering message
235 > appears whenever buffering is required, which might never happen is
236 > your network connection is fast enough
237 >
238 > Required libraries: `gstreamer-1.0`
239
240
241 ## Walkthrough
242
243 This code is based on that of [](tutorials/basic/streaming.md). Let’s review
244 only the differences.
245
246 ### Setup
247
248 ``` c
249 /* Set the download flag */
250 g_object_get (pipeline, "flags", &flags, NULL);
251 flags |= GST_PLAY_FLAG_DOWNLOAD;
252 g_object_set (pipeline, "flags", flags, NULL);
253 ```
254
255 By setting this flag, `playbin` instructs its internal queue (a
256 `queue2` element, actually) to store all downloaded
257 data.
258
259 ``` c
260 g_signal_connect (pipeline, "deep-notify::temp-location", G_CALLBACK (got_location), NULL);
261 ```
262
263 `deep-notify` signals are emitted by `GstObject` elements (like
264 `playbin`) when the properties of any of their children elements
265 change. In this case we want to know when the `temp-location` property
266 changes, indicating that the `queue2` has decided where to store the
267 downloaded
268 data.
269
270 ``` c
271 static void got_location (GstObject *gstobject, GstObject *prop_object, GParamSpec *prop, gpointer data) {
272   gchar *location;
273   g_object_get (G_OBJECT (prop_object), "temp-location", &location, NULL);
274   g_print ("Temporary file: %s\n", location);
275   g_free (location);
276   /* Uncomment this line to keep the temporary file after the program exits */
277   /* g_object_set (G_OBJECT (prop_object), "temp-remove", FALSE, NULL); */
278 }
279 ```
280
281 The `temp-location` property is read from the element that triggered the
282 signal (the `queue2`) and printed on screen.
283
284 When the pipeline state changes from `PAUSED` to `READY`, this file is
285 removed. As the comment reads, you can keep it by setting the
286 `temp-remove` property of the `queue2` to `FALSE`.
287
288 > ![warning]
289 > On Windows this file is usually created inside the `Temporary Internet Files` folder, which might hide it from Windows Explorer. If you cannot find the downloaded files, try to use the console.
290
291 ### User Interface
292
293 In `main` we also install a timer which we use to refresh the UI every
294 second.
295
296 ``` c
297 /* Register a function that GLib will call every second */
298 g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
299 ```
300
301 The `refresh_ui` method queries the pipeline to find out which parts of
302 the file have been downloaded and what the currently playing position
303 is. It builds a graph to display this information (sort of a text-mode
304 user interface) and prints it on screen, overwriting the previous one so
305 it looks like it is animated:
306
307     [---->-------                ]
308
309 The dashes ‘`-`’ indicate the downloaded parts, and the greater-than
310 sign ‘`>`’ shows the current position (turning into an ‘`X`’ when the
311 pipeline is paused). Keep in mind that if your network is fast enough,
312 you will not see the download bar (the dashes) advance at all; it will
313 be completely full from the beginning.
314
315 ``` c
316 static gboolean refresh_ui (CustomData *data) {
317   GstQuery *query;
318   gboolean result;
319   query = gst_query_new_buffering (GST_FORMAT_PERCENT);
320   result = gst_element_query (data->pipeline, query);
321 ```
322
323 The first thing we do in `refresh_ui` is construct a new Buffering
324 `GstQuery` with `gst_query_new_buffering()` and pass it to the pipeline
325 (`playbin`) with `gst_element_query()`. In [](tutorials/basic/time-management.md) we have
326 already seen how to perform simple queries like Position and Duration
327 using specific methods. More complex queries, like Buffering, need to
328 use the more general `gst_element_query()`.
329
330 The Buffering query can be made in different `GstFormat` (TIME, BYTES,
331 PERCENTAGE and a few more). Not all elements can answer the query in all
332 the formats, so you need to check which ones are supported in your
333 particular pipeline. If `gst_element_query()` returns `TRUE`, the query
334 succeeded. The answer to the query is contained in the same
335 `GstQuery` structure we created, and can be retrieved using multiple
336 parse methods:
337
338 ``` c
339 n_ranges = gst_query_get_n_buffering_ranges (query);
340 for (range = 0; range < n_ranges; range++) {
341   gint64 start, stop;
342   gst_query_parse_nth_buffering_range (query, range, &start, &stop);
343   start = start * GRAPH_LENGTH / (stop - start);
344   stop = stop * GRAPH_LENGTH / (stop - start);
345   for (i = (gint)start; i < stop; i++)
346     graph [i] = '-';
347 }
348 ```
349
350 Data does not need to be downloaded in consecutive pieces from the
351 beginning of the file: Seeking, for example, might force to start
352 downloading from a new position and leave a downloaded chunk behind.
353 Therefore, `gst_query_get_n_buffering_ranges()` returns the number of
354 chunks, or *ranges* of downloaded data, and then, the position and size
355 of each range is retrieved with `gst_query_parse_nth_buffering_range()`.
356
357 The format of the returned values (start and stop position for each
358 range) depends on what we requested in the
359 `gst_query_new_buffering()` call. In this case, PERCENTAGE. These
360 values are used to generate the graph.
361
362 ``` c
363 if (gst_element_query_position (data->pipeline, &format, &position) &&
364     GST_CLOCK_TIME_IS_VALID (position) &&
365     gst_element_query_duration (data->pipeline, &format, &duration) &&
366     GST_CLOCK_TIME_IS_VALID (duration)) {
367   i = (gint)(GRAPH_LENGTH * (double)position / (double)(duration + 1));
368   graph [i] = data->buffering_level < 100 ? 'X' : '>';
369 }
370 ```
371
372 Next, the current position is queried. It could be queried in the
373 PERCENT format, so code similar to the one used for the ranges is used,
374 but currently this format is not well supported for position queries.
375 Instead, we use the TIME format and also query the duration to obtain a
376 percentage.
377
378 The current position is indicated with either a ‘`>`’ or an ‘`X`’
379 depending on the buffering level. If it is below 100%, the code in the
380 `cb_message` method will have set the pipeline to `PAUSED`, so we print
381 an ‘`X`’. If the buffering level is 100% the pipeline is in the
382 `PLAYING` state and we print a ‘`>`’.
383
384 ``` c
385 if (data->buffering_level < 100) {
386   g_print (" Buffering: %3d%%", data->buffering_level);
387 } else {
388   g_print ("                ");
389 }
390 ```
391
392 Finally, if the buffering level is below 100%, we report this
393 information (and delete it otherwise).
394
395 ### Limiting the size of the downloaded file
396
397 ``` c
398 /* Uncomment this line to limit the amount of downloaded data */
399 /* g_object_set (pipeline, "ring-buffer-max-size", (guint64)4000000, NULL); */
400 ```
401
402 Uncomment line 139 to see how this can be achieved. This reduces the
403 size of the temporary file, by overwriting already played regions.
404 Observe the download bar to see which regions are kept available in the
405 file.
406
407 ## Conclusion
408
409 This tutorial has shown:
410
411   - How to enable progressive downloading with the
412     `GST_PLAY_FLAG_DOWNLOAD` `playbin` flag
413   - How to know what has been downloaded using a Buffering `GstQuery`
414   - How to know where it has been downloaded with the
415     `deep-notify::temp-location` signal
416   - How to limit the size of the temporary file with
417     the `ring-buffer-max-size` property of `playbin`.
418
419 It has been a pleasure having you here, and see you soon!
420
421   [information]: images/icons/emoticons/information.png
422   [Mac]: installing/on-mac-osx.md
423   [Windows]: installing/on-windows.md
424   [Mac OS X]: installing/on-mac-osx.md#building-the-tutorials
425   [1]: installing/on-windows.md#running-the-tutorials
426   [iOS]: installing/for-ios-development.md#building-the-tutorials
427   [android]: installing/for-android-development.md#building-the-tutorials
428   [warning]: images/icons/emoticons/warning.png