2 * Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
26 #include "gstfragmented.h"
29 #define GST_CAT_DEFAULT fragmented_debug
31 static GstM3U8 *gst_m3u8_new (void);
32 static void gst_m3u8_free (GstM3U8 * m3u8);
33 static gboolean gst_m3u8_update (GstM3U8 * m3u8, gchar * data,
35 static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
36 gchar * title, gint duration, guint sequence);
37 static void gst_m3u8_media_file_free (GstM3U8MediaFile * self);
44 m3u8 = g_new0 (GstM3U8, 1);
50 gst_m3u8_set_uri (GstM3U8 * self, gchar * uri)
52 g_return_if_fail (self != NULL);
60 gst_m3u8_free (GstM3U8 * self)
62 g_return_if_fail (self != NULL);
65 g_free (self->allowcache);
66 g_free (self->codecs);
68 g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
69 g_list_free (self->files);
71 g_free (self->last_data);
72 g_list_foreach (self->lists, (GFunc) gst_m3u8_free, NULL);
73 g_list_free (self->lists);
78 static GstM3U8MediaFile *
79 gst_m3u8_media_file_new (gchar * uri, gchar * title, gint duration,
82 GstM3U8MediaFile *file;
84 file = g_new0 (GstM3U8MediaFile, 1);
87 file->duration = duration;
88 file->sequence = sequence;
94 gst_m3u8_media_file_free (GstM3U8MediaFile * self)
96 g_return_if_fail (self != NULL);
104 int_from_string (gchar * ptr, gchar ** endptr, gint * val)
109 g_return_val_if_fail (ptr != NULL, FALSE);
110 g_return_val_if_fail (val != NULL, FALSE);
113 ret = strtol (ptr, &end, 10);
114 if ((errno == ERANGE && (ret == LONG_MAX || ret == LONG_MIN))
115 || (errno != 0 && ret == 0)) {
116 GST_WARNING ("%s", g_strerror (errno));
120 if (ret > G_MAXINT) {
121 GST_WARNING ("%s", g_strerror (ERANGE));
134 parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
138 g_return_val_if_fail (ptr != NULL, FALSE);
139 g_return_val_if_fail (*ptr != NULL, FALSE);
140 g_return_val_if_fail (a != NULL, FALSE);
141 g_return_val_if_fail (v != NULL, FALSE);
143 /* [attribute=value,]* */
146 end = p = g_utf8_strchr (*ptr, -1, ',');
149 end = g_utf8_next_char (end);
150 } while (end && *end == ' ');
154 *v = p = g_utf8_strchr (*ptr, -1, '=');
156 *v = g_utf8_next_char (*v);
159 GST_WARNING ("missing = after attribute");
168 _m3u8_compare_uri (GstM3U8 * a, gchar * uri)
170 g_return_val_if_fail (a != NULL, 0);
171 g_return_val_if_fail (uri != NULL, 0);
173 return g_strcmp0 (a->uri, uri);
177 gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b)
179 return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth;
183 * @data: a m3u8 playlist text data, taking ownership
186 gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
190 // gboolean discontinuity;
193 g_return_val_if_fail (self != NULL, FALSE);
194 g_return_val_if_fail (data != NULL, FALSE);
195 g_return_val_if_fail (updated != NULL, FALSE);
199 /* check if the data changed since last update */
200 if (self->last_data && g_str_equal (self->last_data, data)) {
201 GST_DEBUG ("Playlist is the same as previous one");
207 if (!g_str_has_prefix (data, "#EXTM3U")) {
208 GST_WARNING ("Data doesn't start with #EXTM3U");
214 g_free (self->last_data);
215 self->last_data = data;
218 g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
219 g_list_free (self->files);
228 end = g_utf8_strchr (data, -1, '\n');
232 if (data[0] != '#') {
235 if (duration < 0 && list == NULL) {
236 GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data);
240 if (!gst_uri_is_valid (data)) {
243 GST_WARNING ("uri not set, can't build a valid uri");
246 slash = g_utf8_strrchr (self->uri, -1, '/');
248 GST_WARNING ("Can't build a valid uri");
253 data = g_strdup_printf ("%s/%s", self->uri, data);
256 data = g_strdup (data);
259 r = g_utf8_strchr (data, -1, '\r');
264 if (g_list_find_custom (self->lists, data,
265 (GCompareFunc) _m3u8_compare_uri)) {
266 GST_DEBUG ("Already have a list with this URI");
267 gst_m3u8_free (list);
270 gst_m3u8_set_uri (list, data);
271 self->lists = g_list_append (self->lists, list);
275 GstM3U8MediaFile *file;
277 gst_m3u8_media_file_new (data, title, duration,
278 self->mediasequence++);
281 self->files = g_list_append (self->files, file);
284 } else if (g_str_has_prefix (data, "#EXT-X-ENDLIST")) {
285 self->endlist = TRUE;
286 } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
287 if (int_from_string (data + 15, &data, &val))
289 } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:")) {
293 GST_WARNING ("Found a list without a uri..., dropping");
294 gst_m3u8_free (list);
297 list = gst_m3u8_new ();
299 while (data && parse_attributes (&data, &a, &v)) {
300 if (g_str_equal (a, "BANDWIDTH")) {
301 if (!int_from_string (v, NULL, &list->bandwidth))
302 GST_WARNING ("Error while reading BANDWIDTH");
303 } else if (g_str_equal (a, "PROGRAM-ID")) {
304 if (!int_from_string (v, NULL, &list->program_id))
305 GST_WARNING ("Error while reading PROGRAM-ID");
306 } else if (g_str_equal (a, "CODECS")) {
307 g_free (list->codecs);
308 list->codecs = g_strdup (v);
309 } else if (g_str_equal (a, "RESOLUTION")) {
310 if (!int_from_string (v, &v, &list->width))
311 GST_WARNING ("Error while reading RESOLUTION width");
312 if (!v || *v != '=') {
313 GST_WARNING ("Missing height");
315 v = g_utf8_next_char (v);
316 if (!int_from_string (v, NULL, &list->height))
317 GST_WARNING ("Error while reading RESOLUTION height");
321 } else if (g_str_has_prefix (data, "#EXT-X-TARGETDURATION:")) {
322 if (int_from_string (data + 22, &data, &val))
323 self->targetduration = val;
324 } else if (g_str_has_prefix (data, "#EXT-X-MEDIA-SEQUENCE:")) {
325 if (int_from_string (data + 22, &data, &val))
326 self->mediasequence = val;
327 } else if (g_str_has_prefix (data, "#EXT-X-DISCONTINUITY")) {
328 /* discontinuity = TRUE; */
329 } else if (g_str_has_prefix (data, "#EXT-X-PROGRAM-DATE-TIME:")) {
330 /* <YYYY-MM-DDThh:mm:ssZ> */
331 GST_DEBUG ("FIXME parse date");
332 } else if (g_str_has_prefix (data, "#EXT-X-ALLOW-CACHE:")) {
333 g_free (self->allowcache);
334 self->allowcache = g_strdup (data + 19);
335 } else if (g_str_has_prefix (data, "#EXTINF:")) {
336 if (!int_from_string (data + 8, &data, &val)) {
337 GST_WARNING ("Can't read EXTINF duration");
341 if (duration > self->targetduration)
342 GST_WARNING ("EXTINF duration > TARGETDURATION");
343 if (!data || *data != ',')
345 data = g_utf8_next_char (data);
348 title = g_strdup (data);
351 GST_LOG ("Ignored line: %s", data);
357 data = g_utf8_next_char (end); /* skip \n */
360 /* redorder playlists by bitrate */
362 gchar *top_variant_uri = NULL;
364 if (!self->current_variant)
365 top_variant_uri = GST_M3U8 (self->lists->data)->uri;
367 top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
370 g_list_sort (self->lists,
371 (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
373 self->current_variant = g_list_find_custom (self->lists, top_variant_uri,
374 (GCompareFunc) _m3u8_compare_uri);
381 gst_m3u8_client_new (const gchar * uri)
383 GstM3U8Client *client;
385 g_return_val_if_fail (uri != NULL, NULL);
387 client = g_new0 (GstM3U8Client, 1);
388 client->main = gst_m3u8_new ();
389 client->current = NULL;
390 client->sequence = -1;
391 client->update_failed_count = 0;
392 client->lock = g_mutex_new ();
393 gst_m3u8_set_uri (client->main, g_strdup (uri));
399 gst_m3u8_client_free (GstM3U8Client * self)
401 g_return_if_fail (self != NULL);
403 gst_m3u8_free (self->main);
404 g_mutex_free (self->lock);
409 gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8)
411 g_return_if_fail (self != NULL);
413 GST_M3U8_CLIENT_LOCK (self);
414 if (m3u8 != self->current) {
415 self->current = m3u8;
416 self->update_failed_count = 0;
418 GST_M3U8_CLIENT_UNLOCK (self);
422 gst_m3u8_client_update (GstM3U8Client * self, gchar * data)
425 gboolean updated = FALSE;
426 gboolean ret = FALSE;
428 g_return_val_if_fail (self != NULL, FALSE);
430 GST_M3U8_CLIENT_LOCK (self);
431 m3u8 = self->current ? self->current : self->main;
433 if (!gst_m3u8_update (m3u8, data, &updated))
437 self->update_failed_count++;
441 /* select the first playlist, for now */
442 if (!self->current) {
443 if (self->main->lists) {
444 self->current = self->main->current_variant->data;
446 self->current = self->main;
450 if (m3u8->files && self->sequence == -1) {
452 GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence;
453 GST_DEBUG ("Setting first sequence at %d", self->sequence);
458 GST_M3U8_CLIENT_UNLOCK (self);
463 _find_next (GstM3U8MediaFile * file, GstM3U8Client * client)
465 GST_DEBUG ("Found fragment %d", file->sequence);
466 if (file->sequence >= client->sequence)
472 gst_m3u8_client_get_current_position (GstM3U8Client * client,
473 GstClockTime * timestamp)
478 l = g_list_find_custom (client->current->files, client,
479 (GCompareFunc) _find_next);
482 for (walk = client->current->files; walk; walk = walk->next) {
485 *timestamp += GST_M3U8_MEDIA_FILE (walk->data)->duration;
487 *timestamp *= GST_SECOND;
491 gst_m3u8_client_get_next_fragment (GstM3U8Client * client,
492 gboolean * discontinuity, const gchar ** uri, GstClockTime * duration,
493 GstClockTime * timestamp)
496 GstM3U8MediaFile *file;
498 g_return_val_if_fail (client != NULL, FALSE);
499 g_return_val_if_fail (client->current != NULL, FALSE);
500 g_return_val_if_fail (discontinuity != NULL, FALSE);
502 GST_M3U8_CLIENT_LOCK (client);
503 GST_DEBUG ("Looking for fragment %d", client->sequence);
504 l = g_list_find_custom (client->current->files, client,
505 (GCompareFunc) _find_next);
507 GST_M3U8_CLIENT_UNLOCK (client);
511 gst_m3u8_client_get_current_position (client, timestamp);
513 file = GST_M3U8_MEDIA_FILE (l->data);
515 *discontinuity = client->sequence != file->sequence;
516 client->sequence = file->sequence + 1;
519 *duration = file->duration * GST_SECOND;
521 GST_M3U8_CLIENT_UNLOCK (client);
526 _sum_duration (GstM3U8MediaFile * self, GstClockTime * duration)
528 *duration += self->duration;
532 gst_m3u8_client_get_duration (GstM3U8Client * client)
534 GstClockTime duration = 0;
536 g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
538 GST_M3U8_CLIENT_LOCK (client);
539 /* We can only get the duration for on-demand streams */
540 if (!client->current->endlist) {
541 GST_M3U8_CLIENT_UNLOCK (client);
542 return GST_CLOCK_TIME_NONE;
545 g_list_foreach (client->current->files, (GFunc) _sum_duration, &duration);
546 GST_M3U8_CLIENT_UNLOCK (client);
547 return duration * GST_SECOND;
551 gst_m3u8_client_get_target_duration (GstM3U8Client * client)
553 GstClockTime duration = 0;
555 g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
557 GST_M3U8_CLIENT_LOCK (client);
558 duration = client->current->targetduration;
559 GST_M3U8_CLIENT_UNLOCK (client);
560 return duration * GST_SECOND;
564 gst_m3u8_client_get_uri (GstM3U8Client * client)
568 g_return_val_if_fail (client != NULL, NULL);
570 GST_M3U8_CLIENT_LOCK (client);
571 uri = client->main->uri;
572 GST_M3U8_CLIENT_UNLOCK (client);
577 gst_m3u8_client_get_current_uri (GstM3U8Client * client)
581 g_return_val_if_fail (client != NULL, NULL);
583 GST_M3U8_CLIENT_LOCK (client);
584 uri = client->current->uri;
585 GST_M3U8_CLIENT_UNLOCK (client);
590 gst_m3u8_client_has_variant_playlist (GstM3U8Client * client)
594 g_return_val_if_fail (client != NULL, FALSE);
596 GST_M3U8_CLIENT_LOCK (client);
597 ret = (client->main->lists != NULL);
598 GST_M3U8_CLIENT_UNLOCK (client);
603 gst_m3u8_client_is_live (GstM3U8Client * client)
607 g_return_val_if_fail (client != NULL, FALSE);
609 GST_M3U8_CLIENT_LOCK (client);
610 if (!client->current || client->current->endlist)
614 GST_M3U8_CLIENT_UNLOCK (client);