hlsdemux: Fix NULL pointer dereference when checking if there is a next fragment
[platform/upstream/gstreamer.git] / ext / hls / m3u8.c
1 /* GStreamer
2  * Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
3  * Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
4  *
5  * m3u8.c:
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #include <stdlib.h>
24 #include <math.h>
25 #include <errno.h>
26 #include <glib.h>
27 #include <string.h>
28
29 #include "gsthls.h"
30 #include "m3u8.h"
31
32 #define GST_CAT_DEFAULT hls_debug
33
34 static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
35     gchar * title, GstClockTime duration, guint sequence);
36 gchar *uri_join (const gchar * uri, const gchar * path);
37
38 GstM3U8 *
39 gst_m3u8_new (void)
40 {
41   GstM3U8 *m3u8;
42
43   m3u8 = g_new0 (GstM3U8, 1);
44
45   m3u8->current_file = NULL;
46   m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
47   m3u8->sequence = -1;
48   m3u8->sequence_position = 0;
49   m3u8->highest_sequence_number = -1;
50   m3u8->duration = GST_CLOCK_TIME_NONE;
51
52   g_mutex_init (&m3u8->lock);
53   m3u8->ref_count = 1;
54
55   return m3u8;
56 }
57
58 /* call with M3U8_LOCK held */
59 static void
60 gst_m3u8_take_uri (GstM3U8 * self, gchar * uri, gchar * base_uri, gchar * name)
61 {
62   g_return_if_fail (self != NULL);
63
64   if (self->uri != uri) {
65     g_free (self->uri);
66     self->uri = uri;
67   }
68   if (self->base_uri != base_uri) {
69     g_free (self->base_uri);
70     self->base_uri = base_uri;
71   }
72   if (self->name != name) {
73     g_free (self->name);
74     self->name = name;
75   }
76 }
77
78 void
79 gst_m3u8_set_uri (GstM3U8 * m3u8, const gchar * uri, const gchar * base_uri,
80     const gchar * name)
81 {
82   GST_M3U8_LOCK (m3u8);
83   gst_m3u8_take_uri (m3u8, g_strdup (uri), g_strdup (base_uri),
84       g_strdup (name));
85   GST_M3U8_UNLOCK (m3u8);
86 }
87
88 GstM3U8 *
89 gst_m3u8_ref (GstM3U8 * m3u8)
90 {
91   g_assert (m3u8 != NULL && m3u8->ref_count > 0);
92
93   g_atomic_int_add (&m3u8->ref_count, 1);
94   return m3u8;
95 }
96
97 void
98 gst_m3u8_unref (GstM3U8 * self)
99 {
100   g_return_if_fail (self != NULL && self->ref_count > 0);
101
102   if (g_atomic_int_dec_and_test (&self->ref_count)) {
103     g_free (self->uri);
104     g_free (self->base_uri);
105     g_free (self->name);
106
107     g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_unref, NULL);
108     g_list_free (self->files);
109
110     g_free (self->last_data);
111     g_free (self);
112   }
113 }
114
115 static GstM3U8MediaFile *
116 gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration,
117     guint sequence)
118 {
119   GstM3U8MediaFile *file;
120
121   file = g_new0 (GstM3U8MediaFile, 1);
122   file->uri = uri;
123   file->title = title;
124   file->duration = duration;
125   file->sequence = sequence;
126   file->ref_count = 1;
127
128   return file;
129 }
130
131 GstM3U8MediaFile *
132 gst_m3u8_media_file_ref (GstM3U8MediaFile * mfile)
133 {
134   g_assert (mfile != NULL && mfile->ref_count > 0);
135
136   g_atomic_int_add (&mfile->ref_count, 1);
137   return mfile;
138 }
139
140 void
141 gst_m3u8_media_file_unref (GstM3U8MediaFile * self)
142 {
143   g_return_if_fail (self != NULL && self->ref_count > 0);
144
145   if (g_atomic_int_dec_and_test (&self->ref_count)) {
146     g_free (self->title);
147     g_free (self->uri);
148     g_free (self->key);
149     g_free (self);
150   }
151 }
152
153 static gboolean
154 int_from_string (gchar * ptr, gchar ** endptr, gint * val)
155 {
156   gchar *end;
157   gint64 ret;
158
159   g_return_val_if_fail (ptr != NULL, FALSE);
160   g_return_val_if_fail (val != NULL, FALSE);
161
162   errno = 0;
163   ret = g_ascii_strtoll (ptr, &end, 10);
164   if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
165       || (errno != 0 && ret == 0)) {
166     GST_WARNING ("%s", g_strerror (errno));
167     return FALSE;
168   }
169
170   if (ret > G_MAXINT || ret < G_MININT) {
171     GST_WARNING ("%s", g_strerror (ERANGE));
172     return FALSE;
173   }
174
175   if (endptr)
176     *endptr = end;
177
178   *val = (gint) ret;
179
180   return end != ptr;
181 }
182
183 static gboolean
184 int64_from_string (gchar * ptr, gchar ** endptr, gint64 * val)
185 {
186   gchar *end;
187   gint64 ret;
188
189   g_return_val_if_fail (ptr != NULL, FALSE);
190   g_return_val_if_fail (val != NULL, FALSE);
191
192   errno = 0;
193   ret = g_ascii_strtoll (ptr, &end, 10);
194   if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
195       || (errno != 0 && ret == 0)) {
196     GST_WARNING ("%s", g_strerror (errno));
197     return FALSE;
198   }
199
200   if (endptr)
201     *endptr = end;
202
203   *val = ret;
204
205   return end != ptr;
206 }
207
208 static gboolean
209 double_from_string (gchar * ptr, gchar ** endptr, gdouble * val)
210 {
211   gchar *end;
212   gdouble ret;
213
214   g_return_val_if_fail (ptr != NULL, FALSE);
215   g_return_val_if_fail (val != NULL, FALSE);
216
217   errno = 0;
218   ret = g_ascii_strtod (ptr, &end);
219   if ((errno == ERANGE && (ret == HUGE_VAL || ret == -HUGE_VAL))
220       || (errno != 0 && ret == 0)) {
221     GST_WARNING ("%s", g_strerror (errno));
222     return FALSE;
223   }
224
225   if (!isfinite (ret)) {
226     GST_WARNING ("%s", g_strerror (ERANGE));
227     return FALSE;
228   }
229
230   if (endptr)
231     *endptr = end;
232
233   *val = (gdouble) ret;
234
235   return end != ptr;
236 }
237
238 static gboolean
239 parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
240 {
241   gchar *end = NULL, *p, *ve;
242
243   g_return_val_if_fail (ptr != NULL, FALSE);
244   g_return_val_if_fail (*ptr != NULL, FALSE);
245   g_return_val_if_fail (a != NULL, FALSE);
246   g_return_val_if_fail (v != NULL, FALSE);
247
248   /* [attribute=value,]* */
249
250   *a = *ptr;
251   end = p = g_utf8_strchr (*ptr, -1, ',');
252   if (end) {
253     gchar *q = g_utf8_strchr (*ptr, -1, '"');
254     if (q && q < end) {
255       /* special case, such as CODECS="avc1.77.30, mp4a.40.2" */
256       q = g_utf8_next_char (q);
257       if (q) {
258         q = g_utf8_strchr (q, -1, '"');
259       }
260       if (q) {
261         end = p = g_utf8_strchr (q, -1, ',');
262       }
263     }
264   }
265   if (end) {
266     do {
267       end = g_utf8_next_char (end);
268     } while (end && *end == ' ');
269     *p = '\0';
270   }
271
272   *v = p = g_utf8_strchr (*ptr, -1, '=');
273   if (*v) {
274     *p = '\0';
275     *v = g_utf8_next_char (*v);
276     if (**v == '"') {
277       ve = g_utf8_next_char (*v);
278       if (ve) {
279         ve = g_utf8_strchr (ve, -1, '"');
280       }
281       if (ve) {
282         *v = g_utf8_next_char (*v);
283         *ve = '\0';
284       } else {
285         GST_WARNING ("Cannot remove quotation marks from %s", *a);
286       }
287     }
288   } else {
289     GST_WARNING ("missing = after attribute");
290     return FALSE;
291   }
292
293   *ptr = end;
294   return TRUE;
295 }
296
297 static gint
298 gst_hls_variant_stream_compare_by_bitrate (gconstpointer a, gconstpointer b)
299 {
300   const GstHLSVariantStream *vs_a = (const GstHLSVariantStream *) a;
301   const GstHLSVariantStream *vs_b = (const GstHLSVariantStream *) b;
302
303   if (vs_a->bandwidth == vs_b->bandwidth)
304     return g_strcmp0 (vs_a->name, vs_b->name);
305
306   return vs_a->bandwidth - vs_b->bandwidth;
307 }
308
309 /*
310  * @data: a m3u8 playlist text data, taking ownership
311  */
312 gboolean
313 gst_m3u8_update (GstM3U8 * self, gchar * data)
314 {
315   gint val;
316   GstClockTime duration;
317   gchar *title, *end;
318   gboolean discontinuity = FALSE;
319   gchar *current_key = NULL;
320   gboolean have_iv = FALSE;
321   guint8 iv[16] = { 0, };
322   gint64 size = -1, offset = -1;
323   gint64 mediasequence;
324
325   g_return_val_if_fail (self != NULL, FALSE);
326   g_return_val_if_fail (data != NULL, FALSE);
327
328   GST_M3U8_LOCK (self);
329
330   /* check if the data changed since last update */
331   if (self->last_data && g_str_equal (self->last_data, data)) {
332     GST_DEBUG ("Playlist is the same as previous one");
333     g_free (data);
334     GST_M3U8_UNLOCK (self);
335     return TRUE;
336   }
337
338   if (!g_str_has_prefix (data, "#EXTM3U")) {
339     GST_WARNING ("Data doesn't start with #EXTM3U");
340     g_free (data);
341     GST_M3U8_UNLOCK (self);
342     return FALSE;
343   }
344
345   if (g_strrstr (data, "\n#EXT-X-STREAM-INF:") != NULL) {
346     GST_WARNING ("Not a media playlist, but a master playlist!");
347     GST_M3U8_UNLOCK (self);
348     return FALSE;
349   }
350
351   GST_TRACE ("data:\n%s", data);
352
353   g_free (self->last_data);
354   self->last_data = data;
355
356   self->current_file = NULL;
357   if (self->files) {
358     g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_unref, NULL);
359     g_list_free (self->files);
360     self->files = NULL;
361   }
362   self->duration = GST_CLOCK_TIME_NONE;
363   mediasequence = 0;
364
365   /* By default, allow caching */
366   self->allowcache = TRUE;
367
368   duration = 0;
369   title = NULL;
370   data += 7;
371   while (TRUE) {
372     gchar *r;
373
374     end = g_utf8_strchr (data, -1, '\n');
375     if (end)
376       *end = '\0';
377
378     r = g_utf8_strchr (data, -1, '\r');
379     if (r)
380       *r = '\0';
381
382     if (data[0] != '#' && data[0] != '\0') {
383       if (duration <= 0) {
384         GST_LOG ("%s: got line without EXTINF, dropping", data);
385         goto next_line;
386       }
387
388       data = uri_join (self->base_uri ? self->base_uri : self->uri, data);
389       if (data != NULL) {
390         GstM3U8MediaFile *file;
391         file = gst_m3u8_media_file_new (data, title, duration, mediasequence++);
392
393         /* set encryption params */
394         file->key = current_key ? g_strdup (current_key) : NULL;
395         if (file->key) {
396           if (have_iv) {
397             memcpy (file->iv, iv, sizeof (iv));
398           } else {
399             guint8 *iv = file->iv + 12;
400             GST_WRITE_UINT32_BE (iv, file->sequence);
401           }
402         }
403
404         if (size != -1) {
405           file->size = size;
406           if (offset != -1) {
407             file->offset = offset;
408           } else {
409             GstM3U8MediaFile *prev = self->files ? self->files->data : NULL;
410
411             if (!prev) {
412               offset = 0;
413             } else {
414               offset = prev->offset + prev->size;
415             }
416             file->offset = offset;
417           }
418         } else {
419           file->size = -1;
420           file->offset = 0;
421         }
422
423         file->discont = discontinuity;
424
425         duration = 0;
426         title = NULL;
427         discontinuity = FALSE;
428         size = offset = -1;
429         self->files = g_list_prepend (self->files, file);
430       }
431
432     } else if (g_str_has_prefix (data, "#EXTINF:")) {
433       gdouble fval;
434       if (!double_from_string (data + 8, &data, &fval)) {
435         GST_WARNING ("Can't read EXTINF duration");
436         goto next_line;
437       }
438       duration = fval * (gdouble) GST_SECOND;
439       if (self->targetduration > 0 && duration > self->targetduration) {
440         GST_WARNING ("EXTINF duration (%" GST_TIME_FORMAT
441             ") > TARGETDURATION (%" GST_TIME_FORMAT ")",
442             GST_TIME_ARGS (duration), GST_TIME_ARGS (self->targetduration));
443       }
444       if (!data || *data != ',')
445         goto next_line;
446       data = g_utf8_next_char (data);
447       if (data != end) {
448         g_free (title);
449         title = g_strdup (data);
450       }
451     } else if (g_str_has_prefix (data, "#EXT-X-")) {
452       gchar *data_ext_x = data + 7;
453
454       /* All these entries start with #EXT-X- */
455       if (g_str_has_prefix (data_ext_x, "ENDLIST")) {
456         self->endlist = TRUE;
457       } else if (g_str_has_prefix (data_ext_x, "VERSION:")) {
458         if (int_from_string (data + 15, &data, &val))
459           self->version = val;
460       } else if (g_str_has_prefix (data_ext_x, "TARGETDURATION:")) {
461         if (int_from_string (data + 22, &data, &val))
462           self->targetduration = val * GST_SECOND;
463       } else if (g_str_has_prefix (data_ext_x, "MEDIA-SEQUENCE:")) {
464         if (int_from_string (data + 22, &data, &val))
465           mediasequence = val;
466       } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY")) {
467         discontinuity = TRUE;
468       } else if (g_str_has_prefix (data_ext_x, "PROGRAM-DATE-TIME:")) {
469         /* <YYYY-MM-DDThh:mm:ssZ> */
470         GST_DEBUG ("FIXME parse date");
471       } else if (g_str_has_prefix (data_ext_x, "ALLOW-CACHE:")) {
472         self->allowcache = g_ascii_strcasecmp (data + 19, "YES") == 0;
473       } else if (g_str_has_prefix (data_ext_x, "KEY:")) {
474         gchar *v, *a;
475
476         data = data + 11;
477
478         /* IV and KEY are only valid until the next #EXT-X-KEY */
479         have_iv = FALSE;
480         g_free (current_key);
481         current_key = NULL;
482         while (data && parse_attributes (&data, &a, &v)) {
483           if (g_str_equal (a, "URI")) {
484             current_key =
485                 uri_join (self->base_uri ? self->base_uri : self->uri, v);
486           } else if (g_str_equal (a, "IV")) {
487             gchar *ivp = v;
488             gint i;
489
490             if (strlen (ivp) < 32 + 2 || (!g_str_has_prefix (ivp, "0x")
491                     && !g_str_has_prefix (ivp, "0X"))) {
492               GST_WARNING ("Can't read IV");
493               continue;
494             }
495
496             ivp += 2;
497             for (i = 0; i < 16; i++) {
498               gint h, l;
499
500               h = g_ascii_xdigit_value (*ivp);
501               ivp++;
502               l = g_ascii_xdigit_value (*ivp);
503               ivp++;
504               if (h == -1 || l == -1) {
505                 i = -1;
506                 break;
507               }
508               iv[i] = (h << 4) | l;
509             }
510
511             if (i == -1) {
512               GST_WARNING ("Can't read IV");
513               continue;
514             }
515             have_iv = TRUE;
516           } else if (g_str_equal (a, "METHOD")) {
517             if (!g_str_equal (v, "AES-128")) {
518               GST_WARNING ("Encryption method %s not supported", v);
519               continue;
520             }
521           }
522         }
523       } else if (g_str_has_prefix (data_ext_x, "BYTERANGE:")) {
524         gchar *v = data + 17;
525
526         if (int64_from_string (v, &v, &size)) {
527           if (*v == '@' && !int64_from_string (v + 1, &v, &offset))
528             goto next_line;
529         } else {
530           goto next_line;
531         }
532       } else {
533         GST_LOG ("Ignored line: %s", data);
534       }
535     } else {
536       GST_LOG ("Ignored line: %s", data);
537     }
538
539   next_line:
540     if (!end)
541       break;
542     data = g_utf8_next_char (end);      /* skip \n */
543   }
544
545   g_free (current_key);
546   current_key = NULL;
547
548   if (self->files == NULL) {
549     GST_ERROR ("Invalid media playlist, it does not contain any media files");
550     GST_M3U8_UNLOCK (self);
551     return FALSE;
552   }
553
554   self->files = g_list_reverse (self->files);
555
556   /* calculate the start and end times of this media playlist. */
557   {
558     GList *walk;
559     GstM3U8MediaFile *file;
560     GstClockTime duration = 0;
561
562     for (walk = self->files; walk; walk = walk->next) {
563       file = walk->data;
564       duration += file->duration;
565       if (file->sequence > self->highest_sequence_number) {
566         if (self->highest_sequence_number >= 0) {
567           /* if an update of the media playlist has been missed, there
568              will be a gap between self->highest_sequence_number and the
569              first sequence number in this media playlist. In this situation
570              assume that the missing fragments had a duration of
571              targetduration each */
572           self->last_file_end +=
573               (file->sequence - self->highest_sequence_number -
574               1) * self->targetduration;
575         }
576         self->last_file_end += file->duration;
577         self->highest_sequence_number = file->sequence;
578       }
579     }
580     if (GST_M3U8_IS_LIVE (self)) {
581       self->first_file_start = self->last_file_end - duration;
582       GST_DEBUG ("Live playlist range %" GST_TIME_FORMAT " -> %"
583           GST_TIME_FORMAT, GST_TIME_ARGS (self->first_file_start),
584           GST_TIME_ARGS (self->last_file_end));
585     }
586     self->duration = duration;
587   }
588
589   /* first-time setup */
590   if (self->files && self->sequence == -1) {
591     GList *file;
592
593     if (GST_M3U8_IS_LIVE (self)) {
594       gint i;
595
596       file = g_list_last (self->files);
597
598       /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
599        * the end of the playlist. See section 6.3.3 of HLS draft. Note
600        * the -1, because GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE = 1 means
601        * start 1 target-duration from the end */
602       for (i = 0; i < GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE - 1 && file->prev;
603           ++i)
604         file = file->prev;
605     } else {
606       file = g_list_first (self->files);
607     }
608     self->current_file = file;
609     self->sequence = GST_M3U8_MEDIA_FILE (file->data)->sequence;
610     self->sequence_position = 0;
611     GST_DEBUG ("first sequence: %u", (guint) self->sequence);
612   }
613
614   GST_LOG ("processed media playlist %s, %u fragments", self->name,
615       g_list_length (self->files));
616
617   GST_M3U8_UNLOCK (self);
618
619   return TRUE;
620 }
621
622 /* call with M3U8_LOCK held */
623 static GList *
624 m3u8_find_next_fragment (GstM3U8 * m3u8, gboolean forward)
625 {
626   GstM3U8MediaFile *file;
627   GList *l = m3u8->files;
628
629   if (forward) {
630     while (l) {
631       file = l->data;
632
633       if (file->sequence >= m3u8->sequence)
634         break;
635
636       l = l->next;
637     }
638   } else {
639     l = g_list_last (l);
640
641     while (l) {
642       file = l->data;
643
644       if (file->sequence <= m3u8->sequence)
645         break;
646
647       l = l->prev;
648     }
649   }
650
651   return l;
652 }
653
654 GstM3U8MediaFile *
655 gst_m3u8_get_next_fragment (GstM3U8 * m3u8, gboolean forward,
656     GstClockTime * sequence_position, gboolean * discont)
657 {
658   GstM3U8MediaFile *file = NULL;
659
660   g_return_val_if_fail (m3u8 != NULL, NULL);
661
662   GST_M3U8_LOCK (m3u8);
663
664   GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
665
666   if (m3u8->sequence < 0)       /* can't happen really */
667     goto out;
668
669   if (m3u8->current_file == NULL)
670     m3u8->current_file = m3u8_find_next_fragment (m3u8, forward);
671
672   if (m3u8->current_file == NULL)
673     goto out;
674
675   file = gst_m3u8_media_file_ref (m3u8->current_file->data);
676
677   GST_DEBUG ("Got fragment with sequence %u (current sequence %u)",
678       (guint) file->sequence, (guint) m3u8->sequence);
679
680   if (sequence_position)
681     *sequence_position = m3u8->sequence_position;
682   if (discont)
683     *discont = file->discont || (m3u8->sequence != file->sequence);
684
685   m3u8->current_file_duration = file->duration;
686   m3u8->sequence = file->sequence;
687
688 out:
689
690   GST_M3U8_UNLOCK (m3u8);
691
692   return file;
693 }
694
695 gboolean
696 gst_m3u8_has_next_fragment (GstM3U8 * m3u8, gboolean forward)
697 {
698   gboolean have_next;
699   GList *cur;
700
701   g_return_val_if_fail (m3u8 != NULL, FALSE);
702
703   GST_M3U8_LOCK (m3u8);
704
705   GST_DEBUG ("Checking next fragment %" G_GINT64_FORMAT,
706       m3u8->sequence + (forward ? 1 : -1));
707
708   if (m3u8->current_file) {
709     cur = m3u8->current_file;
710   } else {
711     cur = m3u8_find_next_fragment (m3u8, forward);
712   }
713
714   have_next = cur && ((forward && cur->next) || (!forward && cur->prev));
715
716   GST_M3U8_UNLOCK (m3u8);
717
718   return have_next;
719 }
720
721 /* call with M3U8_LOCK held */
722 static void
723 m3u8_alternate_advance (GstM3U8 * m3u8, gboolean forward)
724 {
725   gint targetnum = m3u8->sequence;
726   GList *tmp;
727   GstM3U8MediaFile *mf;
728
729   /* figure out the target seqnum */
730   if (forward)
731     targetnum += 1;
732   else
733     targetnum -= 1;
734
735   for (tmp = m3u8->files; tmp; tmp = tmp->next) {
736     mf = (GstM3U8MediaFile *) tmp->data;
737     if (mf->sequence == targetnum)
738       break;
739   }
740   if (tmp == NULL) {
741     GST_WARNING ("Can't find next fragment");
742     return;
743   }
744   m3u8->current_file = tmp;
745   m3u8->sequence = targetnum;
746   m3u8->current_file_duration = GST_M3U8_MEDIA_FILE (tmp->data)->duration;
747 }
748
749 void
750 gst_m3u8_advance_fragment (GstM3U8 * m3u8, gboolean forward)
751 {
752   GstM3U8MediaFile *file;
753
754   g_return_if_fail (m3u8 != NULL);
755
756   GST_M3U8_LOCK (m3u8);
757
758   GST_DEBUG ("Sequence position was %" GST_TIME_FORMAT,
759       GST_TIME_ARGS (m3u8->sequence_position));
760   if (GST_CLOCK_TIME_IS_VALID (m3u8->current_file_duration)) {
761     /* Advance our position based on the previous fragment we played */
762     if (forward)
763       m3u8->sequence_position += m3u8->current_file_duration;
764     else if (m3u8->current_file_duration < m3u8->sequence_position)
765       m3u8->sequence_position -= m3u8->current_file_duration;
766     else
767       m3u8->sequence_position = 0;
768     m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
769     GST_DEBUG ("Sequence position now %" GST_TIME_FORMAT,
770         GST_TIME_ARGS (m3u8->sequence_position));
771   }
772   if (!m3u8->current_file) {
773     GList *l;
774
775     GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
776     for (l = m3u8->files; l != NULL; l = l->next) {
777       if (GST_M3U8_MEDIA_FILE (l->data)->sequence == m3u8->sequence) {
778         m3u8->current_file = l;
779         break;
780       }
781     }
782     if (m3u8->current_file == NULL) {
783       GST_DEBUG
784           ("Could not find current fragment, trying next fragment directly");
785       m3u8_alternate_advance (m3u8, forward);
786
787       /* Resync sequence number if the above has failed for live streams */
788       if (m3u8->current_file == NULL && GST_M3U8_IS_LIVE (m3u8)) {
789         /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
790            the end of the playlist. See section 6.3.3 of HLS draft */
791         gint pos =
792             g_list_length (m3u8->files) - GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
793         m3u8->current_file = g_list_nth (m3u8->files, pos >= 0 ? pos : 0);
794         m3u8->current_file_duration =
795             GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
796
797         GST_WARNING ("Resyncing live playlist");
798       }
799       goto out;
800     }
801   }
802
803   file = GST_M3U8_MEDIA_FILE (m3u8->current_file->data);
804   GST_DEBUG ("Advancing from sequence %u", (guint) file->sequence);
805   if (forward) {
806     m3u8->current_file = m3u8->current_file->next;
807     if (m3u8->current_file) {
808       m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
809     } else {
810       m3u8->sequence = file->sequence + 1;
811     }
812   } else {
813     m3u8->current_file = m3u8->current_file->prev;
814     if (m3u8->current_file) {
815       m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
816     } else {
817       m3u8->sequence = file->sequence - 1;
818     }
819   }
820   if (m3u8->current_file) {
821     /* Store duration of the fragment we're using to update the position 
822      * the next time we advance */
823     m3u8->current_file_duration =
824         GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
825   }
826
827 out:
828
829   GST_M3U8_UNLOCK (m3u8);
830 }
831
832 GstClockTime
833 gst_m3u8_get_duration (GstM3U8 * m3u8)
834 {
835   GstClockTime duration = GST_CLOCK_TIME_NONE;
836
837   g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
838
839   GST_M3U8_LOCK (m3u8);
840
841   /* We can only get the duration for on-demand streams */
842   if (!m3u8->endlist)
843     goto out;
844
845   if (!GST_CLOCK_TIME_IS_VALID (m3u8->duration) && m3u8->files != NULL) {
846     GList *f;
847
848     m3u8->duration = 0;
849     for (f = m3u8->files; f != NULL; f = f->next)
850       m3u8->duration += GST_M3U8_MEDIA_FILE (f)->duration;
851   }
852   duration = m3u8->duration;
853
854 out:
855
856   GST_M3U8_UNLOCK (m3u8);
857
858   return duration;
859 }
860
861 GstClockTime
862 gst_m3u8_get_target_duration (GstM3U8 * m3u8)
863 {
864   GstClockTime target_duration;
865
866   g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
867
868   GST_M3U8_LOCK (m3u8);
869   target_duration = m3u8->targetduration;
870   GST_M3U8_UNLOCK (m3u8);
871
872   return target_duration;
873 }
874
875 gchar *
876 gst_m3u8_get_uri (GstM3U8 * m3u8)
877 {
878   gchar *uri;
879
880   GST_M3U8_LOCK (m3u8);
881   uri = g_strdup (m3u8->uri);
882   GST_M3U8_UNLOCK (m3u8);
883
884   return uri;
885 }
886
887 gboolean
888 gst_m3u8_is_live (GstM3U8 * m3u8)
889 {
890   gboolean is_live;
891
892   g_return_val_if_fail (m3u8 != NULL, FALSE);
893
894   GST_M3U8_LOCK (m3u8);
895   is_live = GST_M3U8_IS_LIVE (m3u8);
896   GST_M3U8_UNLOCK (m3u8);
897
898   return is_live;
899 }
900
901 gchar *
902 uri_join (const gchar * uri1, const gchar * uri2)
903 {
904   gchar *uri_copy, *tmp, *ret = NULL;
905
906   if (gst_uri_is_valid (uri2))
907     return g_strdup (uri2);
908
909   uri_copy = g_strdup (uri1);
910   if (uri2[0] != '/') {
911     /* uri2 is a relative uri2 */
912     /* look for query params */
913     tmp = g_utf8_strchr (uri_copy, -1, '?');
914     if (tmp) {
915       /* find last / char, ignoring query params */
916       tmp = g_utf8_strrchr (uri_copy, tmp - uri_copy, '/');
917     } else {
918       /* find last / char in URL */
919       tmp = g_utf8_strrchr (uri_copy, -1, '/');
920     }
921     if (!tmp) {
922       GST_WARNING ("Can't build a valid uri_copy");
923       goto out;
924     }
925
926     *tmp = '\0';
927     ret = g_strdup_printf ("%s/%s", uri_copy, uri2);
928   } else {
929     /* uri2 is an absolute uri2 */
930     char *scheme, *hostname;
931
932     scheme = uri_copy;
933     /* find the : in <scheme>:// */
934     tmp = g_utf8_strchr (uri_copy, -1, ':');
935     if (!tmp) {
936       GST_WARNING ("Can't build a valid uri_copy");
937       goto out;
938     }
939
940     *tmp = '\0';
941
942     /* skip :// */
943     hostname = tmp + 3;
944
945     tmp = g_utf8_strchr (hostname, -1, '/');
946     if (tmp)
947       *tmp = '\0';
948
949     ret = g_strdup_printf ("%s://%s%s", scheme, hostname, uri2);
950   }
951
952 out:
953   g_free (uri_copy);
954   return ret;
955 }
956
957 gboolean
958 gst_m3u8_get_seek_range (GstM3U8 * m3u8, gint64 * start, gint64 * stop)
959 {
960   GstClockTime duration = 0;
961   GList *walk;
962   GstM3U8MediaFile *file;
963   guint count;
964   guint min_distance = 0;
965
966   g_return_val_if_fail (m3u8 != NULL, FALSE);
967
968   GST_M3U8_LOCK (m3u8);
969
970   if (m3u8->files == NULL)
971     goto out;
972
973   if (GST_M3U8_IS_LIVE (m3u8)) {
974     /* min_distance is used to make sure the seek range is never closer than
975        GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE fragments from the end of a live
976        playlist - see 6.3.3. "Playing the Playlist file" of the HLS draft */
977     min_distance = GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
978   }
979   count = g_list_length (m3u8->files);
980
981   for (walk = m3u8->files; walk && count >= min_distance; walk = walk->next) {
982     file = walk->data;
983     --count;
984     duration += file->duration;
985   }
986
987   if (duration <= 0)
988     goto out;
989
990   *start = m3u8->first_file_start;
991   *stop = *start + duration;
992
993 out:
994
995   GST_M3U8_UNLOCK (m3u8);
996   return (duration > 0);
997 }
998
999 GstHLSMedia *
1000 gst_hls_media_ref (GstHLSMedia * media)
1001 {
1002   g_assert (media != NULL && media->ref_count > 0);
1003   g_atomic_int_add (&media->ref_count, 1);
1004   return media;
1005 }
1006
1007 void
1008 gst_hls_media_unref (GstHLSMedia * media)
1009 {
1010   g_assert (media != NULL && media->ref_count > 0);
1011   if (g_atomic_int_dec_and_test (&media->ref_count)) {
1012     g_free (media->group_id);
1013     g_free (media->name);
1014     g_free (media->uri);
1015     g_free (media);
1016   }
1017 }
1018
1019 static GstHLSMediaType
1020 gst_m3u8_get_hls_media_type_from_string (const gchar * type_name)
1021 {
1022   if (strcmp (type_name, "AUDIO") == 0)
1023     return GST_HLS_MEDIA_TYPE_AUDIO;
1024   if (strcmp (type_name, "VIDEO") == 0)
1025     return GST_HLS_MEDIA_TYPE_VIDEO;
1026   if (strcmp (type_name, "SUBTITLES") == 0)
1027     return GST_HLS_MEDIA_TYPE_SUBTITLES;
1028   if (strcmp (type_name, "CLOSED_CAPTIONS") == 0)
1029     return GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS;
1030
1031   return GST_HLS_MEDIA_TYPE_INVALID;
1032 }
1033
1034 #define GST_HLS_MEDIA_TYPE_NAME(mtype) gst_m3u8_hls_media_type_get_nick(mtype)
1035 static inline const gchar *
1036 gst_m3u8_hls_media_type_get_nick (GstHLSMediaType mtype)
1037 {
1038   static const gchar *nicks[GST_HLS_N_MEDIA_TYPES] = { "audio", "video",
1039     "subtitle", "closed-captions"
1040   };
1041
1042   if (mtype < 0 || mtype >= GST_HLS_N_MEDIA_TYPES)
1043     return "invalid";
1044
1045   return nicks[mtype];
1046 }
1047
1048 /* returns unquoted copy of string */
1049 static gchar *
1050 gst_m3u8_unquote (const gchar * str)
1051 {
1052   const gchar *start, *end;
1053
1054   start = strchr (str, '"');
1055   if (start == NULL)
1056     return g_strdup (str);
1057   end = strchr (start + 1, '"');
1058   if (end == NULL) {
1059     GST_WARNING ("Broken quoted string [%s] - can't find end quote", str);
1060     return g_strdup (start + 1);
1061   }
1062   return g_strndup (start + 1, (gsize) (end - (start + 1)));
1063 }
1064
1065 static GstHLSMedia *
1066 gst_m3u8_parse_media (gchar * desc, const gchar * base_uri)
1067 {
1068   GstHLSMediaType mtype = GST_HLS_MEDIA_TYPE_INVALID;
1069   GstHLSMedia *media;
1070   gchar *a, *v;
1071
1072   media = g_new0 (GstHLSMedia, 1);
1073   media->ref_count = 1;
1074   media->playlist = gst_m3u8_new ();
1075
1076   GST_LOG ("parsing %s", desc);
1077   while (desc != NULL && parse_attributes (&desc, &a, &v)) {
1078     if (strcmp (a, "TYPE") == 0) {
1079       media->mtype = gst_m3u8_get_hls_media_type_from_string (v);
1080     } else if (strcmp (a, "GROUP-ID") == 0) {
1081       g_free (media->group_id);
1082       media->group_id = gst_m3u8_unquote (v);
1083     } else if (strcmp (a, "NAME") == 0) {
1084       g_free (media->name);
1085       media->name = gst_m3u8_unquote (v);
1086     } else if (strcmp (a, "URI") == 0) {
1087       gchar *uri;
1088
1089       g_free (media->uri);
1090       uri = gst_m3u8_unquote (v);
1091       media->uri = uri_join (base_uri, uri);
1092       g_free (uri);
1093     } else if (strcmp (a, "LANGUAGE") == 0) {
1094       g_free (media->lang);
1095       media->lang = gst_m3u8_unquote (v);
1096     } else if (strcmp (a, "DEFAULT") == 0) {
1097       media->is_default = g_ascii_strcasecmp (v, "yes") == 0;
1098     } else if (strcmp (a, "FORCED") == 0) {
1099       media->forced = g_ascii_strcasecmp (v, "yes") == 0;
1100     } else if (strcmp (a, "AUTOSELECT") == 0) {
1101       media->autoselect = g_ascii_strcasecmp (v, "yes") == 0;
1102     } else {
1103       /* unhandled: ASSOC-LANGUAGE, INSTREAM-ID, CHARACTERISTICS */
1104       GST_FIXME ("EXT-X-MEDIA: unhandled attribute: %s = %s", a, v);
1105     }
1106   }
1107
1108   if (media->mtype == GST_HLS_MEDIA_TYPE_INVALID)
1109     goto required_attributes_missing;
1110
1111   if (media->uri == NULL)
1112     goto existing_stream;
1113
1114   if (media->group_id == NULL || media->name == NULL)
1115     goto required_attributes_missing;
1116
1117   if (mtype == GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS)
1118     goto uri_with_cc;
1119
1120   GST_DEBUG ("media: %s, group '%s', name '%s', uri '%s', %s %s %s, lang=%s",
1121       GST_HLS_MEDIA_TYPE_NAME (media->mtype), media->group_id, media->name,
1122       media->uri, media->is_default ? "default" : "-",
1123       media->autoselect ? "autoselect" : "-",
1124       media->forced ? "forced" : "-", media->lang ? media->lang : "??");
1125
1126   return media;
1127
1128 uri_with_cc:
1129   {
1130     GST_WARNING ("closed captions EXT-X-MEDIA should not have URI specified");
1131     goto out_error;
1132   }
1133 required_attributes_missing:
1134   {
1135     GST_WARNING ("EXT-X-MEDIA description is missing required attributes");
1136     goto out_error;
1137     /* fall through */
1138   }
1139 existing_stream:
1140   {
1141     GST_DEBUG ("EXT-X-MEDIA without URI, describes embedded stream, skipping");
1142     /* fall through */
1143   }
1144
1145 out_error:
1146   {
1147     gst_hls_media_unref (media);
1148     return NULL;
1149   }
1150 }
1151
1152 static GstHLSVariantStream *
1153 gst_hls_variant_stream_new (void)
1154 {
1155   GstHLSVariantStream *stream;
1156
1157   stream = g_new0 (GstHLSVariantStream, 1);
1158   stream->m3u8 = gst_m3u8_new ();
1159   stream->refcount = 1;
1160   return stream;
1161 }
1162
1163 GstHLSVariantStream *
1164 gst_hls_variant_stream_ref (GstHLSVariantStream * stream)
1165 {
1166   g_atomic_int_inc (&stream->refcount);
1167   return stream;
1168 }
1169
1170 void
1171 gst_hls_variant_stream_unref (GstHLSVariantStream * stream)
1172 {
1173   if (g_atomic_int_dec_and_test (&stream->refcount)) {
1174     gint i;
1175
1176     g_free (stream->name);
1177     g_free (stream->uri);
1178     g_free (stream->codecs);
1179     gst_m3u8_unref (stream->m3u8);
1180     for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1181       g_free (stream->media_groups[i]);
1182       g_list_free_full (stream->media[i], (GDestroyNotify) gst_hls_media_unref);
1183     }
1184     g_free (stream);
1185   }
1186 }
1187
1188 static GstHLSVariantStream *
1189 find_variant_stream_by_name (GList * list, const gchar * name)
1190 {
1191   for (; list != NULL; list = list->next) {
1192     GstHLSVariantStream *variant_stream = list->data;
1193
1194     if (variant_stream->name != NULL && !strcmp (variant_stream->name, name))
1195       return variant_stream;
1196   }
1197   return NULL;
1198 }
1199
1200 static GstHLSVariantStream *
1201 find_variant_stream_by_uri (GList * list, const gchar * uri)
1202 {
1203   for (; list != NULL; list = list->next) {
1204     GstHLSVariantStream *variant_stream = list->data;
1205
1206     if (variant_stream->uri != NULL && !strcmp (variant_stream->uri, uri))
1207       return variant_stream;
1208   }
1209   return NULL;
1210 }
1211
1212 static GstHLSMasterPlaylist *
1213 gst_hls_master_playlist_new (void)
1214 {
1215   GstHLSMasterPlaylist *playlist;
1216
1217   playlist = g_new0 (GstHLSMasterPlaylist, 1);
1218   playlist->refcount = 1;
1219   playlist->is_simple = FALSE;
1220
1221   return playlist;
1222 }
1223
1224 void
1225 gst_hls_master_playlist_unref (GstHLSMasterPlaylist * playlist)
1226 {
1227   if (g_atomic_int_dec_and_test (&playlist->refcount)) {
1228     g_list_free_full (playlist->variants,
1229         (GDestroyNotify) gst_hls_variant_stream_unref);
1230     g_list_free_full (playlist->iframe_variants,
1231         (GDestroyNotify) gst_hls_variant_stream_unref);
1232     g_free (playlist->last_data);
1233     g_free (playlist);
1234   }
1235 }
1236
1237 static gint
1238 hls_media_name_compare_func (gconstpointer media, gconstpointer name)
1239 {
1240   return strcmp (((GstHLSMedia *) media)->name, (const gchar *) name);
1241 }
1242
1243 /* Takes ownership of @data */
1244 GstHLSMasterPlaylist *
1245 gst_hls_master_playlist_new_from_data (gchar * data, const gchar * base_uri)
1246 {
1247   GHashTable *media_groups[GST_HLS_N_MEDIA_TYPES] = { NULL, };
1248   GstHLSMasterPlaylist *playlist;
1249   GstHLSVariantStream *pending_stream;
1250   gchar *end, *free_data = data;
1251   gint val, i;
1252   GList *l;
1253
1254   if (!g_str_has_prefix (data, "#EXTM3U")) {
1255     GST_WARNING ("Data doesn't start with #EXTM3U");
1256     g_free (free_data);
1257     return NULL;
1258   }
1259
1260   playlist = gst_hls_master_playlist_new ();
1261
1262   /* store data before we modify it for parsing */
1263   playlist->last_data = g_strdup (data);
1264
1265   GST_TRACE ("data:\n%s", data);
1266
1267   if (strstr (data, "\n#EXTINF:") != NULL) {
1268     GST_INFO ("This is a simple media playlist, not a master playlist");
1269
1270     pending_stream = gst_hls_variant_stream_new ();
1271     pending_stream->name = g_strdup (base_uri);
1272     pending_stream->uri = g_strdup (base_uri);
1273     gst_m3u8_set_uri (pending_stream->m3u8, base_uri, NULL, base_uri);
1274     playlist->variants = g_list_append (playlist->variants, pending_stream);
1275     playlist->default_variant = gst_hls_variant_stream_ref (pending_stream);
1276     playlist->is_simple = TRUE;
1277
1278     if (!gst_m3u8_update (pending_stream->m3u8, data)) {
1279       GST_WARNING ("Failed to parse media playlist");
1280       gst_hls_master_playlist_unref (playlist);
1281       playlist = NULL;
1282     }
1283     return playlist;
1284   }
1285
1286   pending_stream = NULL;
1287   data += 7;
1288   while (TRUE) {
1289     gchar *r;
1290
1291     end = g_utf8_strchr (data, -1, '\n');
1292     if (end)
1293       *end = '\0';
1294
1295     r = g_utf8_strchr (data, -1, '\r');
1296     if (r)
1297       *r = '\0';
1298
1299     if (data[0] != '#' && data[0] != '\0') {
1300       gchar *name, *uri;
1301
1302       if (pending_stream == NULL) {
1303         GST_LOG ("%s: got line without EXT-STREAM-INF, dropping", data);
1304         goto next_line;
1305       }
1306
1307       name = data;
1308       uri = uri_join (base_uri, name);
1309       if (uri == NULL)
1310         goto next_line;
1311
1312       pending_stream->name = g_strdup (name);
1313       pending_stream->uri = uri;
1314
1315       if (find_variant_stream_by_name (playlist->variants, name)
1316           || find_variant_stream_by_uri (playlist->variants, uri)) {
1317         GST_DEBUG ("Already have a list with this name or URI: %s", name);
1318         gst_hls_variant_stream_unref (pending_stream);
1319       } else {
1320         GST_INFO ("stream %s @ %u: %s", name, pending_stream->bandwidth, uri);
1321         gst_m3u8_set_uri (pending_stream->m3u8, uri, NULL, name);
1322         playlist->variants = g_list_append (playlist->variants, pending_stream);
1323         /* use first stream in the playlist as default */
1324         if (playlist->default_variant == NULL) {
1325           playlist->default_variant =
1326               gst_hls_variant_stream_ref (pending_stream);
1327         }
1328       }
1329       pending_stream = NULL;
1330     } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
1331       if (int_from_string (data + 15, &data, &val))
1332         playlist->version = val;
1333     } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:") ||
1334         g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:")) {
1335       GstHLSVariantStream *stream;
1336       gchar *v, *a;
1337
1338       stream = gst_hls_variant_stream_new ();
1339       stream->iframe = g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:");
1340       data += stream->iframe ? 26 : 18;
1341       while (data && parse_attributes (&data, &a, &v)) {
1342         if (g_str_equal (a, "BANDWIDTH")) {
1343           if (!int_from_string (v, NULL, &stream->bandwidth))
1344             GST_WARNING ("Error while reading BANDWIDTH");
1345         } else if (g_str_equal (a, "PROGRAM-ID")) {
1346           if (!int_from_string (v, NULL, &stream->program_id))
1347             GST_WARNING ("Error while reading PROGRAM-ID");
1348         } else if (g_str_equal (a, "CODECS")) {
1349           g_free (stream->codecs);
1350           stream->codecs = g_strdup (v);
1351         } else if (g_str_equal (a, "RESOLUTION")) {
1352           if (!int_from_string (v, &v, &stream->width))
1353             GST_WARNING ("Error while reading RESOLUTION width");
1354           if (!v || *v != 'x') {
1355             GST_WARNING ("Missing height");
1356           } else {
1357             v = g_utf8_next_char (v);
1358             if (!int_from_string (v, NULL, &stream->height))
1359               GST_WARNING ("Error while reading RESOLUTION height");
1360           }
1361         } else if (stream->iframe && g_str_equal (a, "URI")) {
1362           stream->uri = uri_join (base_uri, v);
1363           if (stream->uri != NULL) {
1364             stream->name = g_strdup (stream->uri);
1365             gst_m3u8_set_uri (stream->m3u8, stream->uri, NULL, stream->name);
1366           } else {
1367             gst_hls_variant_stream_unref (stream);
1368           }
1369         } else if (g_str_equal (a, "AUDIO")) {
1370           g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO]);
1371           stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO] = gst_m3u8_unquote (v);
1372         } else if (g_str_equal (a, "SUBTITLES")) {
1373           g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES]);
1374           stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES] =
1375               gst_m3u8_unquote (v);
1376         } else if (g_str_equal (a, "VIDEO")) {
1377           g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO]);
1378           stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO] = gst_m3u8_unquote (v);
1379         } else if (g_str_equal (a, "CLOSED-CAPTIONS")) {
1380           /* closed captions will be embedded inside the video stream, ignore */
1381         }
1382       }
1383
1384       if (stream->iframe) {
1385         if (find_variant_stream_by_uri (playlist->iframe_variants, stream->uri)) {
1386           GST_DEBUG ("Already have a list with this URI");
1387           gst_hls_variant_stream_unref (stream);
1388         } else {
1389           playlist->iframe_variants =
1390               g_list_append (playlist->iframe_variants, stream);
1391         }
1392       } else {
1393         if (pending_stream != NULL) {
1394           GST_WARNING ("variant stream without uri, dropping");
1395           gst_hls_variant_stream_unref (pending_stream);
1396         }
1397         pending_stream = stream;
1398       }
1399     } else if (g_str_has_prefix (data, "#EXT-X-MEDIA:")) {
1400       GstHLSMedia *media;
1401       GList *list;
1402
1403       media = gst_m3u8_parse_media (data + strlen ("#EXT-X-MEDIA:"), base_uri);
1404
1405       if (media == NULL)
1406         goto next_line;
1407
1408       if (media_groups[media->mtype] == NULL) {
1409         media_groups[media->mtype] =
1410             g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1411       }
1412
1413       list = g_hash_table_lookup (media_groups[media->mtype], media->group_id);
1414
1415       /* make sure there isn't already a media with the same name */
1416       if (!g_list_find_custom (list, media->name, hls_media_name_compare_func)) {
1417         g_hash_table_replace (media_groups[media->mtype],
1418             g_strdup (media->group_id), g_list_append (list, media));
1419         GST_INFO ("Added media %s to group %s", media->name, media->group_id);
1420       } else {
1421         GST_WARNING ("  media with name '%s' already exists in group '%s'!",
1422             media->name, media->group_id);
1423         gst_hls_media_unref (media);
1424       }
1425     } else if (*data != '\0') {
1426       GST_LOG ("Ignored line: %s", data);
1427     }
1428
1429   next_line:
1430     if (!end)
1431       break;
1432     data = g_utf8_next_char (end);      /* skip \n */
1433   }
1434
1435   if (pending_stream != NULL) {
1436     GST_WARNING ("#EXT-X-STREAM-INF without uri, dropping");
1437     gst_hls_variant_stream_unref (pending_stream);
1438   }
1439
1440   g_free (free_data);
1441
1442   /* Add alternative renditions media to variant streams */
1443   for (l = playlist->variants; l != NULL; l = l->next) {
1444     GstHLSVariantStream *stream = l->data;
1445     GList *mlist;
1446
1447     for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1448       if (stream->media_groups[i] != NULL && media_groups[i] != NULL) {
1449         GST_INFO ("Adding %s group '%s' to stream '%s'",
1450             GST_HLS_MEDIA_TYPE_NAME (i), stream->media_groups[i], stream->name);
1451
1452         mlist = g_hash_table_lookup (media_groups[i], stream->media_groups[i]);
1453
1454         if (mlist == NULL)
1455           GST_WARNING ("Group '%s' does not exist!", stream->media_groups[i]);
1456
1457         while (mlist != NULL) {
1458           GstHLSMedia *media = mlist->data;
1459
1460           GST_DEBUG ("  %s media %s, uri: %s", GST_HLS_MEDIA_TYPE_NAME (i),
1461               media->name, media->uri);
1462
1463           stream->media[i] =
1464               g_list_append (stream->media[i], gst_hls_media_ref (media));
1465           mlist = mlist->next;
1466         }
1467       }
1468     }
1469   }
1470
1471   /* clean up our temporary alternative rendition groups hash tables */
1472   for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1473     if (media_groups[i] != NULL) {
1474       GList *groups, *mlist;
1475
1476       groups = g_hash_table_get_keys (media_groups[i]);
1477       for (l = groups; l != NULL; l = l->next) {
1478         mlist = g_hash_table_lookup (media_groups[i], l->data);
1479         g_list_free_full (mlist, (GDestroyNotify) gst_hls_media_unref);
1480       }
1481       g_list_free (groups);
1482       g_hash_table_unref (media_groups[i]);
1483     }
1484   }
1485
1486   if (playlist->variants == NULL) {
1487     GST_WARNING ("Master playlist without any media playlists!");
1488     gst_hls_master_playlist_unref (playlist);
1489     return NULL;
1490   }
1491
1492   /* reorder variants by bitrate */
1493   playlist->variants =
1494       g_list_sort (playlist->variants,
1495       (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
1496
1497   playlist->iframe_variants =
1498       g_list_sort (playlist->iframe_variants,
1499       (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
1500
1501   /* FIXME: restore old current_variant after master playlist update
1502    * (move into code that does that update) */
1503 #if 0
1504   {
1505     gchar *top_variant_uri = NULL;
1506     gboolean iframe = FALSE;
1507
1508     if (!self->current_variant) {
1509       top_variant_uri = GST_M3U8 (self->lists->data)->uri;
1510     } else {
1511       top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
1512       iframe = GST_M3U8 (self->current_variant->data)->iframe;
1513     }
1514
1515     /* here we sorted the lists */
1516
1517     if (iframe)
1518       playlist->current_variant =
1519           find_variant_stream_by_uri (playlist->iframe_variants,
1520           top_variant_uri);
1521     else
1522       playlist->current_variant =
1523           find_variant_stream_by_uri (playlist->variants, top_variant_uri);
1524   }
1525 #endif
1526
1527   GST_DEBUG ("parsed master playlist with %d streams and %d I-frame streams",
1528       g_list_length (playlist->variants),
1529       g_list_length (playlist->iframe_variants));
1530
1531
1532   return playlist;
1533 }
1534
1535 gboolean
1536 gst_hls_variant_stream_is_live (GstHLSVariantStream * variant)
1537 {
1538   gboolean is_live;
1539
1540   g_return_val_if_fail (variant != NULL, FALSE);
1541
1542   is_live = gst_m3u8_is_live (variant->m3u8);
1543
1544   return is_live;
1545 }
1546
1547 static gint
1548 compare_media (const GstHLSMedia * a, const GstHLSMedia * b)
1549 {
1550   return strcmp (a->name, b->name);
1551 }
1552
1553 GstHLSMedia *
1554 gst_hls_variant_find_matching_media (GstHLSVariantStream * stream,
1555     GstHLSMedia * media)
1556 {
1557   GList *mlist = stream->media[media->mtype];
1558   GList *match;
1559
1560   if (mlist == NULL)
1561     return NULL;
1562
1563   match = g_list_find_custom (mlist, media, (GCompareFunc) compare_media);
1564   if (match == NULL)
1565     return NULL;
1566
1567   return match->data;
1568 }
1569
1570 GstHLSVariantStream *
1571 gst_hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist *
1572     playlist, GstHLSVariantStream * current_variant, guint bitrate)
1573 {
1574   GstHLSVariantStream *variant = current_variant;
1575   GList *l;
1576
1577   /* variant lists are sorted low to high, so iterate from highest to lowest */
1578   if (current_variant == NULL || !current_variant->iframe)
1579     l = g_list_last (playlist->variants);
1580   else
1581     l = g_list_last (playlist->iframe_variants);
1582
1583   while (l != NULL) {
1584     variant = l->data;
1585     if (variant->bandwidth <= bitrate)
1586       break;
1587     l = l->prev;
1588   }
1589
1590   return variant;
1591 }
1592
1593 GstHLSVariantStream *
1594 gst_hls_master_playlist_get_matching_variant (GstHLSMasterPlaylist * playlist,
1595     GstHLSVariantStream * current_variant)
1596 {
1597   if (current_variant->iframe) {
1598     return find_variant_stream_by_uri (playlist->iframe_variants,
1599         current_variant->uri);
1600   }
1601
1602   return find_variant_stream_by_uri (playlist->variants, current_variant->uri);
1603 }