hlsdemux: Fix accessing invalidated memory
[platform/upstream/gstreamer.git] / ext / hls / m3u8.c
1 /* GStreamer
2  * Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
3  *
4  * m3u8.c:
5  *
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.
10  *
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.
15  *
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.
20  */
21
22 #include <stdlib.h>
23 #include <math.h>
24 #include <errno.h>
25 #include <glib.h>
26 #include <string.h>
27
28 #include "gstfragmented.h"
29 #include "m3u8.h"
30
31 #define GST_CAT_DEFAULT fragmented_debug
32
33 #if !GLIB_CHECK_VERSION (2, 33, 4)
34 #define g_list_copy_deep gst_g_list_copy_deep
35 static GList *
36 gst_g_list_copy_deep (GList * list, GCopyFunc func, gpointer user_data)
37 {
38   list = g_list_copy (list);
39
40   if (func != NULL) {
41     GList *l;
42
43     for (l = list; l != NULL; l = l->next) {
44       l->data = func (l->data, user_data);
45     }
46   }
47
48   return list;
49 }
50 #endif
51
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,
55     gboolean * updated);
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);
60
61 static GstM3U8 *
62 gst_m3u8_new (void)
63 {
64   GstM3U8 *m3u8;
65
66   m3u8 = g_new0 (GstM3U8, 1);
67
68   return m3u8;
69 }
70
71 static void
72 gst_m3u8_set_uri (GstM3U8 * self, gchar * uri, gchar * base_uri, gchar * name)
73 {
74   g_return_if_fail (self != NULL);
75
76   g_free (self->uri);
77   self->uri = uri;
78
79   g_free (self->base_uri);
80   self->base_uri = base_uri;
81
82   g_free (self->name);
83   self->name = name;
84 }
85
86 static void
87 gst_m3u8_free (GstM3U8 * self)
88 {
89   g_return_if_fail (self != NULL);
90
91   g_free (self->uri);
92   g_free (self->base_uri);
93   g_free (self->name);
94   g_free (self->codecs);
95
96   g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
97   g_list_free (self->files);
98
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);
104
105   g_free (self);
106 }
107
108 static GstM3U8MediaFile *
109 gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration,
110     guint sequence)
111 {
112   GstM3U8MediaFile *file;
113
114   file = g_new0 (GstM3U8MediaFile, 1);
115   file->uri = uri;
116   file->title = title;
117   file->duration = duration;
118   file->sequence = sequence;
119
120   return file;
121 }
122
123 static void
124 gst_m3u8_media_file_free (GstM3U8MediaFile * self)
125 {
126   g_return_if_fail (self != NULL);
127
128   g_free (self->title);
129   g_free (self->uri);
130   g_free (self->key);
131   g_free (self);
132 }
133
134 static GstM3U8MediaFile *
135 gst_m3u8_media_file_copy (const GstM3U8MediaFile * self, gpointer user_data)
136 {
137   g_return_val_if_fail (self != NULL, NULL);
138
139   return gst_m3u8_media_file_new (g_strdup (self->uri), g_strdup (self->title),
140       self->duration, self->sequence);
141 }
142
143 static GstM3U8 *
144 _m3u8_copy (const GstM3U8 * self, GstM3U8 * parent)
145 {
146   GstM3U8 *dup;
147
148   g_return_val_if_fail (self != NULL, NULL);
149
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;
164   dup->files =
165       g_list_copy_deep (self->files, (GCopyFunc) gst_m3u8_media_file_copy,
166       NULL);
167
168   /* private */
169   dup->last_data = g_strdup (self->last_data);
170   dup->lists = g_list_copy_deep (self->lists, (GCopyFunc) _m3u8_copy, dup);
171   dup->iframe_lists =
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;
176   return dup;
177 }
178
179 static GstM3U8 *
180 gst_m3u8_copy (const GstM3U8 * self)
181 {
182   GList *entry;
183   guint n;
184
185   GstM3U8 *dup = _m3u8_copy (self, NULL);
186
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);
191         break;
192       }
193     }
194
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);
199           break;
200         }
201       }
202
203       if (!dup->current_variant) {
204         GST_ERROR ("Failed to determine current playlist");
205       }
206     }
207   }
208
209   return dup;
210 }
211
212 static gboolean
213 int_from_string (gchar * ptr, gchar ** endptr, gint * val)
214 {
215   gchar *end;
216   gint64 ret;
217
218   g_return_val_if_fail (ptr != NULL, FALSE);
219   g_return_val_if_fail (val != NULL, FALSE);
220
221   errno = 0;
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));
226     return FALSE;
227   }
228
229   if (ret > G_MAXINT || ret < G_MININT) {
230     GST_WARNING ("%s", g_strerror (ERANGE));
231     return FALSE;
232   }
233
234   if (endptr)
235     *endptr = end;
236
237   *val = (gint) ret;
238
239   return end != ptr;
240 }
241
242 static gboolean
243 int64_from_string (gchar * ptr, gchar ** endptr, gint64 * val)
244 {
245   gchar *end;
246   gint64 ret;
247
248   g_return_val_if_fail (ptr != NULL, FALSE);
249   g_return_val_if_fail (val != NULL, FALSE);
250
251   errno = 0;
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));
256     return FALSE;
257   }
258
259   if (endptr)
260     *endptr = end;
261
262   *val = ret;
263
264   return end != ptr;
265 }
266
267 static gboolean
268 double_from_string (gchar * ptr, gchar ** endptr, gdouble * val)
269 {
270   gchar *end;
271   gdouble ret;
272
273   g_return_val_if_fail (ptr != NULL, FALSE);
274   g_return_val_if_fail (val != NULL, FALSE);
275
276   errno = 0;
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));
281     return FALSE;
282   }
283
284   if (!isfinite (ret)) {
285     GST_WARNING ("%s", g_strerror (ERANGE));
286     return FALSE;
287   }
288
289   if (endptr)
290     *endptr = end;
291
292   *val = (gdouble) ret;
293
294   return end != ptr;
295 }
296
297 static gboolean
298 parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
299 {
300   gchar *end = NULL, *p;
301
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);
306
307   /* [attribute=value,]* */
308
309   *a = *ptr;
310   end = p = g_utf8_strchr (*ptr, -1, ',');
311   if (end) {
312     gchar *q = g_utf8_strchr (*ptr, -1, '"');
313     if (q && q < end) {
314       /* special case, such as CODECS="avc1.77.30, mp4a.40.2" */
315       q = g_utf8_next_char (q);
316       if (q) {
317         q = g_utf8_strchr (q, -1, '"');
318       }
319       if (q) {
320         end = p = g_utf8_strchr (q, -1, ',');
321       }
322     }
323   }
324   if (end) {
325     do {
326       end = g_utf8_next_char (end);
327     } while (end && *end == ' ');
328     *p = '\0';
329   }
330
331   *v = p = g_utf8_strchr (*ptr, -1, '=');
332   if (*v) {
333     *v = g_utf8_next_char (*v);
334     *p = '\0';
335   } else {
336     GST_WARNING ("missing = after attribute");
337     return FALSE;
338   }
339
340   *ptr = end;
341   return TRUE;
342 }
343
344 static gchar *
345 unquote_string (gchar * string)
346 {
347   gchar *string_ret;
348
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';
357     } else {
358       GST_WARNING
359           ("wrong string unqouting - cannot find finalizing quotation mark");
360       return NULL;
361     }
362   }
363   return string;
364 }
365
366 static gint
367 _m3u8_compare_uri (GstM3U8 * a, gchar * uri)
368 {
369   g_return_val_if_fail (a != NULL, 0);
370   g_return_val_if_fail (uri != NULL, 0);
371
372   return g_strcmp0 (a->uri, uri);
373 }
374
375 static gint
376 gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b)
377 {
378   return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth;
379 }
380
381 /*
382  * @data: a m3u8 playlist text data, taking ownership
383  */
384 static gboolean
385 gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
386 {
387   gint val;
388   GstClockTime duration;
389   gchar *title, *end;
390   gboolean discontinuity = FALSE;
391   GstM3U8 *list;
392   gchar *current_key = NULL;
393   gboolean have_iv = FALSE;
394   guint8 iv[16] = { 0, };
395   gint64 size = -1, offset = -1;
396
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);
400
401   *updated = TRUE;
402
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");
406     *updated = FALSE;
407     g_free (data);
408     return TRUE;
409   }
410
411   if (!g_str_has_prefix (data, "#EXTM3U")) {
412     GST_WARNING ("Data doesn't start with #EXTM3U");
413     *updated = FALSE;
414     g_free (data);
415     return FALSE;
416   }
417
418   g_free (self->last_data);
419   self->last_data = data;
420
421   if (self->files) {
422     g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
423     g_list_free (self->files);
424     self->files = NULL;
425   }
426
427   /* By default, allow caching */
428   self->allowcache = TRUE;
429
430   list = NULL;
431   duration = 0;
432   title = NULL;
433   data += 7;
434   while (TRUE) {
435     gchar *r;
436
437     end = g_utf8_strchr (data, -1, '\n');
438     if (end)
439       *end = '\0';
440
441     r = g_utf8_strchr (data, -1, '\r');
442     if (r)
443       *r = '\0';
444
445     if (data[0] != '#' && data[0] != '\0') {
446       gchar *name = data;
447       if (duration <= 0 && list == NULL) {
448         GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data);
449         goto next_line;
450       }
451
452       data = uri_join (self->base_uri ? self->base_uri : self->uri, data);
453       if (data == NULL)
454         goto next_line;
455
456       if (list != NULL) {
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);
461           g_free (data);
462         } else {
463           gst_m3u8_set_uri (list, data, NULL, g_strdup (name));
464           self->lists = g_list_append (self->lists, list);
465         }
466         list = NULL;
467       } else {
468         GstM3U8MediaFile *file;
469         file =
470             gst_m3u8_media_file_new (data, title, duration,
471             self->mediasequence++);
472
473         /* set encryption params */
474         file->key = current_key ? g_strdup (current_key) : NULL;
475         if (file->key) {
476           if (have_iv) {
477             memcpy (file->iv, iv, sizeof (iv));
478           } else {
479             guint8 *iv = file->iv + 12;
480             GST_WRITE_UINT32_BE (iv, file->sequence);
481           }
482         }
483
484         if (size != -1) {
485           file->size = size;
486           if (offset != -1) {
487             file->offset = offset;
488           } else {
489             GstM3U8MediaFile *prev =
490                 self->files ? g_list_last (self->files)->data : NULL;
491
492             if (!prev) {
493               offset = 0;
494             } else {
495               offset = prev->offset + prev->size;
496             }
497             file->offset = offset;
498           }
499         } else {
500           file->size = -1;
501           file->offset = 0;
502         }
503
504         file->discont = discontinuity;
505
506         duration = 0;
507         title = NULL;
508         discontinuity = FALSE;
509         size = offset = -1;
510         self->files = g_list_append (self->files, file);
511       }
512
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))
517         self->version = 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:")) {
520       gchar *v, *a;
521       gboolean iframe = g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:");
522       GstM3U8 *new_list;
523
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");
543           } else {
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");
547           }
548         } else if (iframe && g_str_equal (a, "URI")) {
549           gchar *name;
550           gchar *uri = g_strdup (v);
551           gchar *urip = uri;
552
553           uri = unquote_string (uri);
554           if (uri) {
555             uri = uri_join (self->base_uri ? self->base_uri : self->uri, uri);
556
557             uri = uri_join (self->base_uri ? self->base_uri : self->uri, uri);
558             if (uri == NULL) {
559               g_free (urip);
560               continue;
561             }
562             name = g_strdup (uri);
563
564             gst_m3u8_set_uri (new_list, uri, NULL, name);
565           } else {
566             GST_WARNING
567                 ("Cannot remove quotation marks from i-frame-stream URI");
568           }
569           g_free (urip);
570         }
571       }
572
573       if (iframe) {
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);
578         } else {
579           self->iframe_lists = g_list_append (self->iframe_lists, new_list);
580         }
581       } else if (list != NULL) {
582         GST_WARNING ("Found a list without a uri..., dropping");
583         gst_m3u8_free (list);
584       } else {
585         list = new_list;
586       }
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:")) {
601       gchar *v, *a;
602
603       data = data + 11;
604
605       /* IV and KEY are only valid until the next #EXT-X-KEY */
606       have_iv = FALSE;
607       g_free (current_key);
608       current_key = NULL;
609       while (data && parse_attributes (&data, &a, &v)) {
610         if (g_str_equal (a, "URI")) {
611           gchar *key = g_strdup (v);
612           gchar *keyp = key;
613
614           key = unquote_string (key);
615           if (key) {
616             current_key =
617                 uri_join (self->base_uri ? self->base_uri : self->uri, key);
618           } else {
619             GST_WARNING
620                 ("Cannot remove quotation marks from decryption key URI");
621           }
622           g_free (keyp);
623         } else if (g_str_equal (a, "IV")) {
624           gchar *ivp = v;
625           gint i;
626
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");
630             continue;
631           }
632
633           ivp += 2;
634           for (i = 0; i < 16; i++) {
635             gint h, l;
636
637             h = g_ascii_xdigit_value (*ivp);
638             ivp++;
639             l = g_ascii_xdigit_value (*ivp);
640             ivp++;
641             if (h == -1 || l == -1) {
642               i = -1;
643               break;
644             }
645             iv[i] = (h << 4) | l;
646           }
647
648           if (i == -1) {
649             GST_WARNING ("Can't read IV");
650             continue;
651           }
652           have_iv = TRUE;
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);
656             continue;
657           }
658         }
659       }
660     } else if (g_str_has_prefix (data, "#EXTINF:")) {
661       gdouble fval;
662       if (!double_from_string (data + 8, &data, &fval)) {
663         GST_WARNING ("Can't read EXTINF duration");
664         goto next_line;
665       }
666       duration = fval * (gdouble) GST_SECOND;
667       if (duration > self->targetduration)
668         GST_WARNING ("EXTINF duration > TARGETDURATION");
669       if (!data || *data != ',')
670         goto next_line;
671       data = g_utf8_next_char (data);
672       if (data != end) {
673         g_free (title);
674         title = g_strdup (data);
675       }
676     } else if (g_str_has_prefix (data, "#EXT-X-BYTERANGE:")) {
677       gchar *v = data + 17;
678
679       if (int64_from_string (v, &v, &size)) {
680         if (*v == '@' && !int64_from_string (v + 1, &v, &offset))
681           goto next_line;
682       } else {
683         goto next_line;
684       }
685     } else {
686       GST_LOG ("Ignored line: %s", data);
687     }
688
689   next_line:
690     if (!end)
691       break;
692     data = g_utf8_next_char (end);      /* skip \n */
693   }
694
695   g_free (current_key);
696   current_key = NULL;
697
698   /* reorder playlists by bitrate */
699   if (self->lists) {
700     gchar *top_variant_uri = NULL;
701     gboolean iframe = FALSE;
702
703     if (!self->current_variant) {
704       top_variant_uri = GST_M3U8 (self->lists->data)->uri;
705     } else {
706       top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
707       iframe = GST_M3U8 (self->current_variant->data)->iframe;
708     }
709
710     self->lists =
711         g_list_sort (self->lists,
712         (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
713
714     self->iframe_lists =
715         g_list_sort (self->iframe_lists,
716         (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
717
718     if (iframe)
719       self->current_variant =
720           g_list_find_custom (self->iframe_lists, top_variant_uri,
721           (GCompareFunc) _m3u8_compare_uri);
722     else
723       self->current_variant = g_list_find_custom (self->lists, top_variant_uri,
724           (GCompareFunc) _m3u8_compare_uri);
725   }
726
727   return TRUE;
728 }
729
730 GstM3U8Client *
731 gst_m3u8_client_new (const gchar * uri, const gchar * base_uri)
732 {
733   GstM3U8Client *client;
734
735   g_return_val_if_fail (uri != NULL, NULL);
736
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);
745
746   return client;
747 }
748
749 void
750 gst_m3u8_client_free (GstM3U8Client * self)
751 {
752   g_return_if_fail (self != NULL);
753
754   gst_m3u8_free (self->main);
755   g_mutex_clear (&self->lock);
756   g_free (self);
757 }
758
759 void
760 gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8)
761 {
762   g_return_if_fail (self != NULL);
763
764   GST_M3U8_CLIENT_LOCK (self);
765   if (m3u8 != self->current) {
766     self->current = m3u8;
767     self->update_failed_count = 0;
768   }
769   GST_M3U8_CLIENT_UNLOCK (self);
770 }
771
772 gboolean
773 gst_m3u8_client_update (GstM3U8Client * self, gchar * data)
774 {
775   GstM3U8 *m3u8;
776   gboolean updated = FALSE;
777   gboolean ret = FALSE;
778
779   g_return_val_if_fail (self != NULL, FALSE);
780
781   GST_M3U8_CLIENT_LOCK (self);
782   m3u8 = self->current ? self->current : self->main;
783
784   if (!gst_m3u8_update (m3u8, data, &updated))
785     goto out;
786
787   if (!updated) {
788     self->update_failed_count++;
789     goto out;
790   }
791
792   if (self->current && !self->current->files) {
793     GST_ERROR ("Invalid media playlist, it does not contain any media files");
794     goto out;
795   }
796
797   /* select the first playlist, for now */
798   if (!self->current) {
799     if (self->main->lists) {
800       self->current = self->main->current_variant->data;
801     } else {
802       self->current = self->main;
803     }
804   }
805
806   if (m3u8->files && self->sequence == -1) {
807     self->sequence =
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);
811   }
812
813   ret = TRUE;
814 out:
815   GST_M3U8_CLIENT_UNLOCK (self);
816   return ret;
817 }
818
819 static gint
820 _find_m3u8_list_match (const GstM3U8 * a, const GstM3U8 * b)
821 {
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) {
828     return 0;
829   }
830
831   return 1;
832 }
833
834 gboolean
835 gst_m3u8_client_update_variant_playlist (GstM3U8Client * self, gchar * data,
836     const gchar * uri, const gchar * base_uri)
837 {
838   gboolean ret = FALSE;
839   GList *list_entry, *unmatched_lists;
840   GstM3U8Client *new_client;
841   GstM3U8 *old;
842
843   g_return_val_if_fail (self != NULL, FALSE);
844
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) {
848       GST_ERROR
849           ("Cannot update variant playlist: New playlist is not a variant playlist");
850       gst_m3u8_client_free (new_client);
851       return FALSE;
852     }
853
854     GST_M3U8_CLIENT_LOCK (self);
855
856     if (!self->main->lists) {
857       GST_ERROR
858           ("Cannot update variant playlist: Current playlist is not a variant playlist");
859       goto out;
860     }
861
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);
868       if (match)
869         unmatched_lists = g_list_remove_link (unmatched_lists, match);
870     }
871
872     if (unmatched_lists != NULL) {
873       g_list_free (unmatched_lists);
874
875       /* We should attempt to handle the case where playlists are dropped/replaced,
876        * and possibly switch over to a comparable (not neccessarily identical)
877        * playlist.
878        */
879       GST_FIXME
880           ("Cannot update variant playlist, unable to match all playlists");
881       goto out;
882     }
883
884     /* Switch out the variant playlist */
885     old = self->main;
886
887     self->main = gst_m3u8_copy (new_client->main);
888     if (self->main->lists)
889       self->current = self->main->current_variant->data;
890     else
891       self->current = self->main;
892
893     gst_m3u8_free (old);
894
895     ret = TRUE;
896
897   out:
898     GST_M3U8_CLIENT_UNLOCK (self);
899   }
900
901   gst_m3u8_client_free (new_client);
902   return ret;
903 }
904
905 static gboolean
906 _find_current (GstM3U8MediaFile * file, GstM3U8Client * client)
907 {
908   return file->sequence != client->sequence;
909 }
910
911 static GList *
912 find_next_fragment (GstM3U8Client * client, GList * l, gboolean forward)
913 {
914   GstM3U8MediaFile *file;
915
916   if (!forward)
917     l = g_list_last (l);
918
919   while (l) {
920     file = l->data;
921
922     if (forward && file->sequence >= client->sequence)
923       break;
924     else if (!forward && file->sequence <= client->sequence)
925       break;
926
927     l = (forward ? l->next : l->prev);
928   }
929
930   return l;
931 }
932
933 gboolean
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)
938 {
939   GList *l;
940   GstM3U8MediaFile *file;
941
942   g_return_val_if_fail (client != NULL, FALSE);
943   g_return_val_if_fail (client->current != NULL, FALSE);
944
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);
949     return FALSE;
950   }
951   l = find_next_fragment (client, client->current->files, forward);
952   if (!l) {
953     GST_M3U8_CLIENT_UNLOCK (client);
954     return FALSE;
955   }
956
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);
960
961   if (timestamp)
962     *timestamp = client->sequence_position;
963
964   if (discontinuity)
965     *discontinuity = client->sequence != file->sequence || file->discont;
966   if (uri)
967     *uri = g_strdup (file->uri);
968   if (duration)
969     *duration = file->duration;
970   if (range_start)
971     *range_start = file->offset;
972   if (range_end)
973     *range_end = file->size != -1 ? file->offset + file->size - 1 : -1;
974   if (key)
975     *key = g_strdup (file->key);
976   if (iv) {
977     *iv = g_new (guint8, sizeof (file->iv));
978     memcpy (*iv, file->iv, sizeof (file->iv));
979   }
980
981   client->sequence = file->sequence;
982
983   GST_M3U8_CLIENT_UNLOCK (client);
984   return TRUE;
985 }
986
987 void
988 gst_m3u8_client_advance_fragment (GstM3U8Client * client, gboolean forward)
989 {
990   GList *l;
991   GstM3U8MediaFile *file;
992
993   g_return_if_fail (client != NULL);
994   g_return_if_fail (client->current != NULL);
995
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);
1000   if (l == NULL) {
1001     GST_ERROR ("Could not find current fragment");
1002     GST_M3U8_CLIENT_UNLOCK (client);
1003     return;
1004   }
1005
1006   file = GST_M3U8_MEDIA_FILE (l->data);
1007   GST_DEBUG ("Advancing from sequence %u", (guint) file->sequence);
1008   if (forward) {
1009     client->sequence = file->sequence + 1;
1010     client->sequence_position += file->duration;
1011   } else {
1012     client->sequence = file->sequence - 1;
1013     if (client->sequence_position > file->duration)
1014       client->sequence_position -= file->duration;
1015     else
1016       client->sequence_position = 0;
1017   }
1018   GST_M3U8_CLIENT_UNLOCK (client);
1019 }
1020
1021 static void
1022 _sum_duration (GstM3U8MediaFile * self, GstClockTime * duration)
1023 {
1024   *duration += self->duration;
1025 }
1026
1027 GstClockTime
1028 gst_m3u8_client_get_duration (GstM3U8Client * client)
1029 {
1030   GstClockTime duration = 0;
1031
1032   g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
1033
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;
1039   }
1040   if (client->current->files)
1041     g_list_foreach (client->current->files, (GFunc) _sum_duration, &duration);
1042   GST_M3U8_CLIENT_UNLOCK (client);
1043   return duration;
1044 }
1045
1046 GstClockTime
1047 gst_m3u8_client_get_target_duration (GstM3U8Client * client)
1048 {
1049   GstClockTime duration = 0;
1050
1051   g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
1052
1053   GST_M3U8_CLIENT_LOCK (client);
1054   duration = client->current->targetduration;
1055   GST_M3U8_CLIENT_UNLOCK (client);
1056   return duration;
1057 }
1058
1059 gchar *
1060 gst_m3u8_client_get_uri (GstM3U8Client * client)
1061 {
1062   gchar *uri;
1063
1064   g_return_val_if_fail (client != NULL, NULL);
1065
1066   GST_M3U8_CLIENT_LOCK (client);
1067   uri = client->main ? g_strdup (client->main->uri) : NULL;
1068   GST_M3U8_CLIENT_UNLOCK (client);
1069   return uri;
1070 }
1071
1072 gchar *
1073 gst_m3u8_client_get_current_uri (GstM3U8Client * client)
1074 {
1075   gchar *uri;
1076
1077   g_return_val_if_fail (client != NULL, NULL);
1078
1079   GST_M3U8_CLIENT_LOCK (client);
1080   uri = g_strdup (client->current->uri);
1081   GST_M3U8_CLIENT_UNLOCK (client);
1082   return uri;
1083 }
1084
1085 gboolean
1086 gst_m3u8_client_has_main(GstM3U8Client * client)
1087 {
1088   gboolean ret;
1089
1090   g_return_val_if_fail (client != NULL, FALSE);
1091
1092   GST_M3U8_CLIENT_LOCK (client);
1093   if (client->main)
1094     ret = TRUE;
1095   else
1096     ret = FALSE;
1097   GST_M3U8_CLIENT_UNLOCK (client);
1098   return ret;
1099 }
1100
1101 gboolean
1102 gst_m3u8_client_has_variant_playlist (GstM3U8Client * client)
1103 {
1104   gboolean ret;
1105
1106   g_return_val_if_fail (client != NULL, FALSE);
1107
1108   GST_M3U8_CLIENT_LOCK (client);
1109   ret = (client->main->lists != NULL);
1110   GST_M3U8_CLIENT_UNLOCK (client);
1111   return ret;
1112 }
1113
1114 gboolean
1115 gst_m3u8_client_is_live (GstM3U8Client * client)
1116 {
1117   gboolean ret;
1118
1119   g_return_val_if_fail (client != NULL, FALSE);
1120
1121   GST_M3U8_CLIENT_LOCK (client);
1122   ret = GST_M3U8_CLIENT_IS_LIVE (client);
1123   GST_M3U8_CLIENT_UNLOCK (client);
1124   return ret;
1125 }
1126
1127 GList *
1128 gst_m3u8_client_get_playlist_for_bitrate (GstM3U8Client * client, guint bitrate)
1129 {
1130   GList *list, *current_variant;
1131
1132   GST_M3U8_CLIENT_LOCK (client);
1133   current_variant = client->main->current_variant;
1134
1135   /*  Go to the highest possible bandwidth allowed */
1136   while (GST_M3U8 (current_variant->data)->bandwidth <= bitrate) {
1137     list = g_list_next (current_variant);
1138     if (!list)
1139       break;
1140     current_variant = list;
1141   }
1142
1143   while (GST_M3U8 (current_variant->data)->bandwidth > bitrate) {
1144     list = g_list_previous (current_variant);
1145     if (!list)
1146       break;
1147     current_variant = list;
1148   }
1149   GST_M3U8_CLIENT_UNLOCK (client);
1150
1151   return current_variant;
1152 }
1153
1154 gchar *
1155 uri_join (const gchar * uri1, const gchar * uri2)
1156 {
1157   gchar *uri_copy, *tmp, *ret = NULL;
1158
1159   if (gst_uri_is_valid (uri2))
1160     return g_strdup (uri2);
1161
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, '?');
1167     if (tmp) {
1168       /* find last / char, ignoring query params */
1169       tmp = g_utf8_strrchr (uri_copy, tmp - uri_copy, '/');
1170     } else {
1171       /* find last / char in URL */
1172       tmp = g_utf8_strrchr (uri_copy, -1, '/');
1173     }
1174     if (!tmp) {
1175       GST_WARNING ("Can't build a valid uri_copy");
1176       goto out;
1177     }
1178
1179     *tmp = '\0';
1180     ret = g_strdup_printf ("%s/%s", uri_copy, uri2);
1181   } else {
1182     /* uri2 is an absolute uri2 */
1183     char *scheme, *hostname;
1184
1185     scheme = uri_copy;
1186     /* find the : in <scheme>:// */
1187     tmp = g_utf8_strchr (uri_copy, -1, ':');
1188     if (!tmp) {
1189       GST_WARNING ("Can't build a valid uri_copy");
1190       goto out;
1191     }
1192
1193     *tmp = '\0';
1194
1195     /* skip :// */
1196     hostname = tmp + 3;
1197
1198     tmp = g_utf8_strchr (hostname, -1, '/');
1199     if (tmp)
1200       *tmp = '\0';
1201
1202     ret = g_strdup_printf ("%s://%s%s", scheme, hostname, uri2);
1203   }
1204
1205 out:
1206   g_free (uri_copy);
1207   return ret;
1208 }
1209
1210 guint64
1211 gst_m3u8_client_get_current_fragment_duration (GstM3U8Client * client)
1212 {
1213   guint64 dur;
1214   GList *list;
1215
1216   g_return_val_if_fail (client != NULL, 0);
1217
1218   GST_M3U8_CLIENT_LOCK (client);
1219
1220   list = g_list_find_custom (client->current->files, client,
1221       (GCompareFunc) _find_current);
1222   if (list == NULL) {
1223     dur = -1;
1224   } else {
1225     dur = GST_M3U8_MEDIA_FILE (list->data)->duration;
1226   }
1227
1228   GST_M3U8_CLIENT_UNLOCK (client);
1229   return dur;
1230 }