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., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
28 #include "gstfragmented.h"
31 #define GST_CAT_DEFAULT fragmented_debug
33 #if !GLIB_CHECK_VERSION (2, 33, 4)
34 #define g_list_copy_deep gst_g_list_copy_deep
36 gst_g_list_copy_deep (GList * list, GCopyFunc func, gpointer user_data)
38 list = g_list_copy (list);
43 for (l = list; l != NULL; l = l->next) {
44 l->data = func (l->data, user_data);
52 static GstM3U8 *gst_m3u8_new (void);
53 static void gst_m3u8_free (GstM3U8 * m3u8);
54 static gboolean gst_m3u8_update (GstM3U8 * m3u8, gchar * data,
56 static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
57 gchar * title, GstClockTime duration, guint sequence);
58 static void gst_m3u8_media_file_free (GstM3U8MediaFile * self);
59 gchar *uri_join (const gchar * uri, const gchar * path);
66 m3u8 = g_new0 (GstM3U8, 1);
72 gst_m3u8_set_uri (GstM3U8 * self, gchar * uri, gchar * base_uri, gchar * name)
74 g_return_if_fail (self != NULL);
79 g_free (self->base_uri);
80 self->base_uri = base_uri;
87 gst_m3u8_free (GstM3U8 * self)
89 g_return_if_fail (self != NULL);
92 g_free (self->base_uri);
94 g_free (self->codecs);
96 g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
97 g_list_free (self->files);
99 g_free (self->last_data);
100 g_list_foreach (self->lists, (GFunc) gst_m3u8_free, NULL);
101 g_list_free (self->lists);
102 g_list_foreach (self->iframe_lists, (GFunc) gst_m3u8_free, NULL);
103 g_list_free (self->iframe_lists);
108 static GstM3U8MediaFile *
109 gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration,
112 GstM3U8MediaFile *file;
114 file = g_new0 (GstM3U8MediaFile, 1);
117 file->duration = duration;
118 file->sequence = sequence;
124 gst_m3u8_media_file_free (GstM3U8MediaFile * self)
126 g_return_if_fail (self != NULL);
128 g_free (self->title);
134 static GstM3U8MediaFile *
135 gst_m3u8_media_file_copy (const GstM3U8MediaFile * self, gpointer user_data)
137 g_return_val_if_fail (self != NULL, NULL);
139 return gst_m3u8_media_file_new (g_strdup (self->uri), g_strdup (self->title),
140 self->duration, self->sequence);
144 _m3u8_copy (const GstM3U8 * self, GstM3U8 * parent)
148 g_return_val_if_fail (self != NULL, NULL);
150 dup = gst_m3u8_new ();
151 dup->uri = g_strdup (self->uri);
152 dup->base_uri = g_strdup (self->base_uri);
153 dup->name = g_strdup (self->name);
154 dup->endlist = self->endlist;
155 dup->version = self->version;
156 dup->targetduration = self->targetduration;
157 dup->allowcache = self->allowcache;
158 dup->bandwidth = self->bandwidth;
159 dup->program_id = self->program_id;
160 dup->codecs = g_strdup (self->codecs);
161 dup->width = self->width;
162 dup->height = self->height;
163 dup->iframe = self->iframe;
165 g_list_copy_deep (self->files, (GCopyFunc) gst_m3u8_media_file_copy,
169 dup->last_data = g_strdup (self->last_data);
170 dup->lists = g_list_copy_deep (self->lists, (GCopyFunc) _m3u8_copy, dup);
172 g_list_copy_deep (self->iframe_lists, (GCopyFunc) _m3u8_copy, dup);
173 /* NOTE: current_variant will get set in gst_m3u8_copy () */
174 dup->parent = parent;
175 dup->mediasequence = self->mediasequence;
180 gst_m3u8_copy (const GstM3U8 * self)
185 GstM3U8 *dup = _m3u8_copy (self, NULL);
187 if (self->current_variant != NULL) {
188 for (n = 0, entry = self->lists; entry; entry = entry->next, n++) {
189 if (entry == self->current_variant) {
190 dup->current_variant = g_list_nth (dup->lists, n);
195 if (!dup->current_variant) {
196 for (n = 0, entry = self->iframe_lists; entry; entry = entry->next, n++) {
197 if (entry == self->current_variant) {
198 dup->current_variant = g_list_nth (dup->iframe_lists, n);
203 if (!dup->current_variant) {
204 GST_ERROR ("Failed to determine current playlist");
213 int_from_string (gchar * ptr, gchar ** endptr, gint * val)
218 g_return_val_if_fail (ptr != NULL, FALSE);
219 g_return_val_if_fail (val != NULL, FALSE);
222 ret = g_ascii_strtoll (ptr, &end, 10);
223 if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
224 || (errno != 0 && ret == 0)) {
225 GST_WARNING ("%s", g_strerror (errno));
229 if (ret > G_MAXINT || ret < G_MININT) {
230 GST_WARNING ("%s", g_strerror (ERANGE));
243 int64_from_string (gchar * ptr, gchar ** endptr, gint64 * val)
248 g_return_val_if_fail (ptr != NULL, FALSE);
249 g_return_val_if_fail (val != NULL, FALSE);
252 ret = g_ascii_strtoll (ptr, &end, 10);
253 if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
254 || (errno != 0 && ret == 0)) {
255 GST_WARNING ("%s", g_strerror (errno));
268 double_from_string (gchar * ptr, gchar ** endptr, gdouble * val)
273 g_return_val_if_fail (ptr != NULL, FALSE);
274 g_return_val_if_fail (val != NULL, FALSE);
277 ret = g_ascii_strtod (ptr, &end);
278 if ((errno == ERANGE && (ret == HUGE_VAL || ret == -HUGE_VAL))
279 || (errno != 0 && ret == 0)) {
280 GST_WARNING ("%s", g_strerror (errno));
284 if (!isfinite (ret)) {
285 GST_WARNING ("%s", g_strerror (ERANGE));
292 *val = (gdouble) ret;
298 parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
300 gchar *end = NULL, *p;
302 g_return_val_if_fail (ptr != NULL, FALSE);
303 g_return_val_if_fail (*ptr != NULL, FALSE);
304 g_return_val_if_fail (a != NULL, FALSE);
305 g_return_val_if_fail (v != NULL, FALSE);
307 /* [attribute=value,]* */
310 end = p = g_utf8_strchr (*ptr, -1, ',');
312 gchar *q = g_utf8_strchr (*ptr, -1, '"');
314 /* special case, such as CODECS="avc1.77.30, mp4a.40.2" */
315 q = g_utf8_next_char (q);
317 q = g_utf8_strchr (q, -1, '"');
320 end = p = g_utf8_strchr (q, -1, ',');
326 end = g_utf8_next_char (end);
327 } while (end && *end == ' ');
331 *v = p = g_utf8_strchr (*ptr, -1, '=');
333 *v = g_utf8_next_char (*v);
336 GST_WARNING ("missing = after attribute");
345 unquote_string (gchar * string)
349 string_ret = strchr (string, '"');
350 if (string_ret != NULL) {
351 /* found initialization quotation mark of string */
352 string = string_ret + 1;
353 string_ret = strchr (string, '"');
354 if (string_ret != NULL) {
355 /* found finalizing quotation mark of string */
356 string_ret[0] = '\0';
359 ("wrong string unqouting - cannot find finalizing quotation mark");
367 _m3u8_compare_uri (GstM3U8 * a, gchar * uri)
369 g_return_val_if_fail (a != NULL, 0);
370 g_return_val_if_fail (uri != NULL, 0);
372 return g_strcmp0 (a->uri, uri);
376 gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b)
378 return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth;
382 * @data: a m3u8 playlist text data, taking ownership
385 gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
388 GstClockTime duration;
390 gboolean discontinuity = FALSE;
392 gchar *current_key = NULL;
393 gboolean have_iv = FALSE;
394 guint8 iv[16] = { 0, };
395 gint64 size = -1, offset = -1;
397 g_return_val_if_fail (self != NULL, FALSE);
398 g_return_val_if_fail (data != NULL, FALSE);
399 g_return_val_if_fail (updated != NULL, FALSE);
403 /* check if the data changed since last update */
404 if (self->last_data && g_str_equal (self->last_data, data)) {
405 GST_DEBUG ("Playlist is the same as previous one");
411 if (!g_str_has_prefix (data, "#EXTM3U")) {
412 GST_WARNING ("Data doesn't start with #EXTM3U");
418 g_free (self->last_data);
419 self->last_data = data;
422 g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
423 g_list_free (self->files);
427 /* By default, allow caching */
428 self->allowcache = TRUE;
437 end = g_utf8_strchr (data, -1, '\n');
441 r = g_utf8_strchr (data, -1, '\r');
445 if (data[0] != '#' && data[0] != '\0') {
447 if (duration <= 0 && list == NULL) {
448 GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data);
452 data = uri_join (self->base_uri ? self->base_uri : self->uri, data);
457 if (g_list_find_custom (self->lists, data,
458 (GCompareFunc) _m3u8_compare_uri)) {
459 GST_DEBUG ("Already have a list with this URI");
460 gst_m3u8_free (list);
463 gst_m3u8_set_uri (list, data, NULL, g_strdup (name));
464 self->lists = g_list_append (self->lists, list);
468 GstM3U8MediaFile *file;
470 gst_m3u8_media_file_new (data, title, duration,
471 self->mediasequence++);
473 /* set encryption params */
474 file->key = current_key ? g_strdup (current_key) : NULL;
477 memcpy (file->iv, iv, sizeof (iv));
479 guint8 *iv = file->iv + 12;
480 GST_WRITE_UINT32_BE (iv, file->sequence);
487 file->offset = offset;
489 GstM3U8MediaFile *prev =
490 self->files ? g_list_last (self->files)->data : NULL;
495 offset = prev->offset + prev->size;
497 file->offset = offset;
504 file->discont = discontinuity;
508 discontinuity = FALSE;
510 self->files = g_list_append (self->files, file);
513 } else if (g_str_has_prefix (data, "#EXT-X-ENDLIST")) {
514 self->endlist = TRUE;
515 } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
516 if (int_from_string (data + 15, &data, &val))
518 } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:") ||
519 g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:")) {
521 gboolean iframe = g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:");
524 new_list = gst_m3u8_new ();
525 new_list->parent = self;
526 new_list->iframe = iframe;
527 data = data + (iframe ? 26 : 18);
528 while (data && parse_attributes (&data, &a, &v)) {
529 if (g_str_equal (a, "BANDWIDTH")) {
530 if (!int_from_string (v, NULL, &new_list->bandwidth))
531 GST_WARNING ("Error while reading BANDWIDTH");
532 } else if (g_str_equal (a, "PROGRAM-ID")) {
533 if (!int_from_string (v, NULL, &new_list->program_id))
534 GST_WARNING ("Error while reading PROGRAM-ID");
535 } else if (g_str_equal (a, "CODECS")) {
536 g_free (new_list->codecs);
537 new_list->codecs = g_strdup (v);
538 } else if (g_str_equal (a, "RESOLUTION")) {
539 if (!int_from_string (v, &v, &new_list->width))
540 GST_WARNING ("Error while reading RESOLUTION width");
541 if (!v || *v != 'x') {
542 GST_WARNING ("Missing height");
544 v = g_utf8_next_char (v);
545 if (!int_from_string (v, NULL, &new_list->height))
546 GST_WARNING ("Error while reading RESOLUTION height");
548 } else if (iframe && g_str_equal (a, "URI")) {
550 gchar *uri = g_strdup (v);
553 uri = unquote_string (uri);
555 uri = uri_join (self->base_uri ? self->base_uri : self->uri, uri);
557 uri = uri_join (self->base_uri ? self->base_uri : self->uri, uri);
562 name = g_strdup (uri);
564 gst_m3u8_set_uri (new_list, uri, NULL, name);
567 ("Cannot remove quotation marks from i-frame-stream URI");
574 if (g_list_find_custom (self->iframe_lists, new_list->uri,
575 (GCompareFunc) _m3u8_compare_uri)) {
576 GST_DEBUG ("Already have a list with this URI");
577 gst_m3u8_free (new_list);
579 self->iframe_lists = g_list_append (self->iframe_lists, new_list);
581 } else if (list != NULL) {
582 GST_WARNING ("Found a list without a uri..., dropping");
583 gst_m3u8_free (list);
587 } else if (g_str_has_prefix (data, "#EXT-X-TARGETDURATION:")) {
588 if (int_from_string (data + 22, &data, &val))
589 self->targetduration = val * GST_SECOND;
590 } else if (g_str_has_prefix (data, "#EXT-X-MEDIA-SEQUENCE:")) {
591 if (int_from_string (data + 22, &data, &val))
592 self->mediasequence = val;
593 } else if (g_str_has_prefix (data, "#EXT-X-DISCONTINUITY")) {
594 discontinuity = TRUE;
595 } else if (g_str_has_prefix (data, "#EXT-X-PROGRAM-DATE-TIME:")) {
596 /* <YYYY-MM-DDThh:mm:ssZ> */
597 GST_DEBUG ("FIXME parse date");
598 } else if (g_str_has_prefix (data, "#EXT-X-ALLOW-CACHE:")) {
599 self->allowcache = g_ascii_strcasecmp (data + 19, "YES") == 0;
600 } else if (g_str_has_prefix (data, "#EXT-X-KEY:")) {
605 /* IV and KEY are only valid until the next #EXT-X-KEY */
607 g_free (current_key);
609 while (data && parse_attributes (&data, &a, &v)) {
610 if (g_str_equal (a, "URI")) {
611 gchar *key = g_strdup (v);
614 key = unquote_string (key);
617 uri_join (self->base_uri ? self->base_uri : self->uri, key);
620 ("Cannot remove quotation marks from decryption key URI");
623 } else if (g_str_equal (a, "IV")) {
627 if (strlen (ivp) < 32 + 2 || (!g_str_has_prefix (ivp, "0x")
628 && !g_str_has_prefix (ivp, "0X"))) {
629 GST_WARNING ("Can't read IV");
634 for (i = 0; i < 16; i++) {
637 h = g_ascii_xdigit_value (*ivp);
639 l = g_ascii_xdigit_value (*ivp);
641 if (h == -1 || l == -1) {
645 iv[i] = (h << 4) | l;
649 GST_WARNING ("Can't read IV");
653 } else if (g_str_equal (a, "METHOD")) {
654 if (!g_str_equal (v, "AES-128")) {
655 GST_WARNING ("Encryption method %s not supported", v);
660 } else if (g_str_has_prefix (data, "#EXTINF:")) {
662 if (!double_from_string (data + 8, &data, &fval)) {
663 GST_WARNING ("Can't read EXTINF duration");
666 duration = fval * (gdouble) GST_SECOND;
667 if (duration > self->targetduration)
668 GST_WARNING ("EXTINF duration > TARGETDURATION");
669 if (!data || *data != ',')
671 data = g_utf8_next_char (data);
674 title = g_strdup (data);
676 } else if (g_str_has_prefix (data, "#EXT-X-BYTERANGE:")) {
677 gchar *v = data + 17;
679 if (int64_from_string (v, &v, &size)) {
680 if (*v == '@' && !int64_from_string (v + 1, &v, &offset))
686 GST_LOG ("Ignored line: %s", data);
692 data = g_utf8_next_char (end); /* skip \n */
695 g_free (current_key);
698 /* reorder playlists by bitrate */
700 gchar *top_variant_uri = NULL;
701 gboolean iframe = FALSE;
703 if (!self->current_variant) {
704 top_variant_uri = GST_M3U8 (self->lists->data)->uri;
706 top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
707 iframe = GST_M3U8 (self->current_variant->data)->iframe;
711 g_list_sort (self->lists,
712 (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
715 g_list_sort (self->iframe_lists,
716 (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
719 self->current_variant =
720 g_list_find_custom (self->iframe_lists, top_variant_uri,
721 (GCompareFunc) _m3u8_compare_uri);
723 self->current_variant = g_list_find_custom (self->lists, top_variant_uri,
724 (GCompareFunc) _m3u8_compare_uri);
731 gst_m3u8_client_new (const gchar * uri, const gchar * base_uri)
733 GstM3U8Client *client;
735 g_return_val_if_fail (uri != NULL, NULL);
737 client = g_new0 (GstM3U8Client, 1);
738 client->main = gst_m3u8_new ();
739 client->current = NULL;
740 client->sequence = -1;
741 client->sequence_position = 0;
742 client->update_failed_count = 0;
743 g_mutex_init (&client->lock);
744 gst_m3u8_set_uri (client->main, g_strdup (uri), g_strdup (base_uri), NULL);
750 gst_m3u8_client_free (GstM3U8Client * self)
752 g_return_if_fail (self != NULL);
754 gst_m3u8_free (self->main);
755 g_mutex_clear (&self->lock);
760 gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8)
762 g_return_if_fail (self != NULL);
764 GST_M3U8_CLIENT_LOCK (self);
765 if (m3u8 != self->current) {
766 self->current = m3u8;
767 self->update_failed_count = 0;
769 GST_M3U8_CLIENT_UNLOCK (self);
773 gst_m3u8_client_update (GstM3U8Client * self, gchar * data)
776 gboolean updated = FALSE;
777 gboolean ret = FALSE;
779 g_return_val_if_fail (self != NULL, FALSE);
781 GST_M3U8_CLIENT_LOCK (self);
782 m3u8 = self->current ? self->current : self->main;
784 if (!gst_m3u8_update (m3u8, data, &updated))
788 self->update_failed_count++;
792 if (self->current && !self->current->files) {
793 GST_ERROR ("Invalid media playlist, it does not contain any media files");
797 /* select the first playlist, for now */
798 if (!self->current) {
799 if (self->main->lists) {
800 self->current = self->main->current_variant->data;
802 self->current = self->main;
806 if (m3u8->files && self->sequence == -1) {
808 GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence;
809 self->sequence_position = 0;
810 GST_DEBUG ("Setting first sequence at %u", (guint) self->sequence);
815 GST_M3U8_CLIENT_UNLOCK (self);
820 _find_m3u8_list_match (const GstM3U8 * a, const GstM3U8 * b)
822 if (g_strcmp0 (a->name, b->name) == 0 &&
823 a->bandwidth == b->bandwidth &&
824 a->program_id == b->program_id &&
825 g_strcmp0 (a->codecs, b->codecs) == 0 &&
826 a->width == b->width &&
827 a->height == b->height && a->iframe == b->iframe) {
835 gst_m3u8_client_update_variant_playlist (GstM3U8Client * self, gchar * data,
836 const gchar * uri, const gchar * base_uri)
838 gboolean ret = FALSE;
839 GList *list_entry, *unmatched_lists;
840 GstM3U8Client *new_client;
843 g_return_val_if_fail (self != NULL, FALSE);
845 new_client = gst_m3u8_client_new (uri, base_uri);
846 if (gst_m3u8_client_update (new_client, data)) {
847 if (!new_client->main->lists) {
849 ("Cannot update variant playlist: New playlist is not a variant playlist");
850 gst_m3u8_client_free (new_client);
854 GST_M3U8_CLIENT_LOCK (self);
856 if (!self->main->lists) {
858 ("Cannot update variant playlist: Current playlist is not a variant playlist");
862 /* Now see if the variant playlist still has the same lists */
863 unmatched_lists = g_list_copy (self->main->lists);
864 for (list_entry = new_client->main->lists; list_entry;
865 list_entry = list_entry->next) {
866 GList *match = g_list_find_custom (unmatched_lists, list_entry->data,
867 (GCompareFunc) _find_m3u8_list_match);
869 unmatched_lists = g_list_remove_link (unmatched_lists, match);
872 if (unmatched_lists != NULL) {
873 g_list_free (unmatched_lists);
875 /* We should attempt to handle the case where playlists are dropped/replaced,
876 * and possibly switch over to a comparable (not neccessarily identical)
880 ("Cannot update variant playlist, unable to match all playlists");
884 /* Switch out the variant playlist */
887 self->main = gst_m3u8_copy (new_client->main);
888 if (self->main->lists)
889 self->current = self->main->current_variant->data;
891 self->current = self->main;
898 GST_M3U8_CLIENT_UNLOCK (self);
901 gst_m3u8_client_free (new_client);
906 _find_current (GstM3U8MediaFile * file, GstM3U8Client * client)
908 return file->sequence != client->sequence;
912 find_next_fragment (GstM3U8Client * client, GList * l, gboolean forward)
914 GstM3U8MediaFile *file;
922 if (forward && file->sequence >= client->sequence)
924 else if (!forward && file->sequence <= client->sequence)
927 l = (forward ? l->next : l->prev);
934 gst_m3u8_client_get_next_fragment (GstM3U8Client * client,
935 gboolean * discontinuity, gchar ** uri, GstClockTime * duration,
936 GstClockTime * timestamp, gint64 * range_start, gint64 * range_end,
937 gchar ** key, guint8 ** iv, gboolean forward)
940 GstM3U8MediaFile *file;
942 g_return_val_if_fail (client != NULL, FALSE);
943 g_return_val_if_fail (client->current != NULL, FALSE);
945 GST_M3U8_CLIENT_LOCK (client);
946 GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, client->sequence);
947 if (client->sequence < 0) {
948 GST_M3U8_CLIENT_UNLOCK (client);
951 l = find_next_fragment (client, client->current->files, forward);
953 GST_M3U8_CLIENT_UNLOCK (client);
957 file = GST_M3U8_MEDIA_FILE (l->data);
958 GST_DEBUG ("Got fragment with sequence %u (client sequence %u)",
959 (guint) file->sequence, (guint) client->sequence);
962 *timestamp = client->sequence_position;
965 *discontinuity = client->sequence != file->sequence || file->discont;
967 *uri = g_strdup (file->uri);
969 *duration = file->duration;
971 *range_start = file->offset;
973 *range_end = file->size != -1 ? file->offset + file->size - 1 : -1;
975 *key = g_strdup (file->key);
977 *iv = g_new (guint8, sizeof (file->iv));
978 memcpy (*iv, file->iv, sizeof (file->iv));
981 client->sequence = file->sequence;
983 GST_M3U8_CLIENT_UNLOCK (client);
988 gst_m3u8_client_advance_fragment (GstM3U8Client * client, gboolean forward)
991 GstM3U8MediaFile *file;
993 g_return_if_fail (client != NULL);
994 g_return_if_fail (client->current != NULL);
996 GST_M3U8_CLIENT_LOCK (client);
997 GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, client->sequence);
998 l = g_list_find_custom (client->current->files, client,
999 (GCompareFunc) _find_current);
1001 GST_ERROR ("Could not find current fragment");
1002 GST_M3U8_CLIENT_UNLOCK (client);
1006 file = GST_M3U8_MEDIA_FILE (l->data);
1007 GST_DEBUG ("Advancing from sequence %u", (guint) file->sequence);
1009 client->sequence = file->sequence + 1;
1010 client->sequence_position += file->duration;
1012 client->sequence = file->sequence - 1;
1013 if (client->sequence_position > file->duration)
1014 client->sequence_position -= file->duration;
1016 client->sequence_position = 0;
1018 GST_M3U8_CLIENT_UNLOCK (client);
1022 _sum_duration (GstM3U8MediaFile * self, GstClockTime * duration)
1024 *duration += self->duration;
1028 gst_m3u8_client_get_duration (GstM3U8Client * client)
1030 GstClockTime duration = 0;
1032 g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
1034 GST_M3U8_CLIENT_LOCK (client);
1035 /* We can only get the duration for on-demand streams */
1036 if (!client->current || !client->current->endlist) {
1037 GST_M3U8_CLIENT_UNLOCK (client);
1038 return GST_CLOCK_TIME_NONE;
1040 if (client->current->files)
1041 g_list_foreach (client->current->files, (GFunc) _sum_duration, &duration);
1042 GST_M3U8_CLIENT_UNLOCK (client);
1047 gst_m3u8_client_get_target_duration (GstM3U8Client * client)
1049 GstClockTime duration = 0;
1051 g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
1053 GST_M3U8_CLIENT_LOCK (client);
1054 duration = client->current->targetduration;
1055 GST_M3U8_CLIENT_UNLOCK (client);
1060 gst_m3u8_client_get_uri (GstM3U8Client * client)
1064 g_return_val_if_fail (client != NULL, NULL);
1066 GST_M3U8_CLIENT_LOCK (client);
1067 uri = client->main ? g_strdup (client->main->uri) : NULL;
1068 GST_M3U8_CLIENT_UNLOCK (client);
1073 gst_m3u8_client_get_current_uri (GstM3U8Client * client)
1077 g_return_val_if_fail (client != NULL, NULL);
1079 GST_M3U8_CLIENT_LOCK (client);
1080 uri = g_strdup (client->current->uri);
1081 GST_M3U8_CLIENT_UNLOCK (client);
1086 gst_m3u8_client_has_main(GstM3U8Client * client)
1090 g_return_val_if_fail (client != NULL, FALSE);
1092 GST_M3U8_CLIENT_LOCK (client);
1097 GST_M3U8_CLIENT_UNLOCK (client);
1102 gst_m3u8_client_has_variant_playlist (GstM3U8Client * client)
1106 g_return_val_if_fail (client != NULL, FALSE);
1108 GST_M3U8_CLIENT_LOCK (client);
1109 ret = (client->main->lists != NULL);
1110 GST_M3U8_CLIENT_UNLOCK (client);
1115 gst_m3u8_client_is_live (GstM3U8Client * client)
1119 g_return_val_if_fail (client != NULL, FALSE);
1121 GST_M3U8_CLIENT_LOCK (client);
1122 ret = GST_M3U8_CLIENT_IS_LIVE (client);
1123 GST_M3U8_CLIENT_UNLOCK (client);
1128 gst_m3u8_client_get_playlist_for_bitrate (GstM3U8Client * client, guint bitrate)
1130 GList *list, *current_variant;
1132 GST_M3U8_CLIENT_LOCK (client);
1133 current_variant = client->main->current_variant;
1135 /* Go to the highest possible bandwidth allowed */
1136 while (GST_M3U8 (current_variant->data)->bandwidth <= bitrate) {
1137 list = g_list_next (current_variant);
1140 current_variant = list;
1143 while (GST_M3U8 (current_variant->data)->bandwidth > bitrate) {
1144 list = g_list_previous (current_variant);
1147 current_variant = list;
1149 GST_M3U8_CLIENT_UNLOCK (client);
1151 return current_variant;
1155 uri_join (const gchar * uri1, const gchar * uri2)
1157 gchar *uri_copy, *tmp, *ret = NULL;
1159 if (gst_uri_is_valid (uri2))
1160 return g_strdup (uri2);
1162 uri_copy = g_strdup (uri1);
1163 if (uri2[0] != '/') {
1164 /* uri2 is a relative uri2 */
1165 /* look for query params */
1166 tmp = g_utf8_strchr (uri_copy, -1, '?');
1168 /* find last / char, ignoring query params */
1169 tmp = g_utf8_strrchr (uri_copy, tmp - uri_copy, '/');
1171 /* find last / char in URL */
1172 tmp = g_utf8_strrchr (uri_copy, -1, '/');
1175 GST_WARNING ("Can't build a valid uri_copy");
1180 ret = g_strdup_printf ("%s/%s", uri_copy, uri2);
1182 /* uri2 is an absolute uri2 */
1183 char *scheme, *hostname;
1186 /* find the : in <scheme>:// */
1187 tmp = g_utf8_strchr (uri_copy, -1, ':');
1189 GST_WARNING ("Can't build a valid uri_copy");
1198 tmp = g_utf8_strchr (hostname, -1, '/');
1202 ret = g_strdup_printf ("%s://%s%s", scheme, hostname, uri2);
1211 gst_m3u8_client_get_current_fragment_duration (GstM3U8Client * client)
1216 g_return_val_if_fail (client != NULL, 0);
1218 GST_M3U8_CLIENT_LOCK (client);
1220 list = g_list_find_custom (client->current->files, client,
1221 (GCompareFunc) _find_current);
1225 dur = GST_M3U8_MEDIA_FILE (list->data)->duration;
1228 GST_M3U8_CLIENT_UNLOCK (client);