m3u8: Fix parsing EXT-X-DISCONTINUITY
[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 static 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 static gboolean
310 gst_m3u8_update_check_consistent_media_seqnums (GstM3U8 * self,
311     gboolean have_mediasequence, GList * previous_files)
312 {
313   if (!previous_files)
314     return TRUE;
315
316   /* If we have MEDIA-SEQUENCE, ensure that it's consistent. If it is not,
317    * the client SHOULD halt playback (6.3.4), which is what we do then.
318    *
319    * If we don't have MEDIA-SEQUENCE, we check URIs in the previous and
320    * current playlist to calculate the/a correct MEDIA-SEQUENCE for the new
321    * playlist in relation to the old. That is, same URIs get the same number
322    * and later URIs get higher numbers */
323   if (have_mediasequence) {
324     GList *l, *m;
325     GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
326
327     /* Find first case of higher/equal sequence number in new playlist or
328      * same URI. From there on we can linearly step ahead */
329     for (l = self->files; l; l = l->next) {
330       gboolean match = FALSE;
331
332       f1 = l->data;
333       for (m = previous_files; m; m = m->next) {
334         f2 = m->data;
335
336         if (f1->sequence >= f2->sequence || g_str_equal (f1->uri, f2->uri)) {
337           match = TRUE;
338           break;
339         }
340       }
341       if (match)
342         break;
343     }
344
345     if (!l) {
346       /* No match, no sequence in the new playlist was higher than
347        * any in the old, and no URI was found again. This is bad! */
348       GST_ERROR ("Media sequences inconsistent, ignoring");
349       return FALSE;
350     } else {
351       g_assert (f1 != NULL);
352       g_assert (f2 != NULL);
353
354       for (; l && m; l = l->next, m = m->next) {
355         f1 = l->data;
356         f2 = m->data;
357
358         if (f1->sequence == f2->sequence) {
359           if (!g_str_equal (f1->uri, f2->uri)) {
360             /* Same sequence, different URI. This is bad! */
361             GST_ERROR ("Media sequences inconsistent, ignoring");
362             return FALSE;
363           } else {
364             /* Good case, we advance and check the next one */
365           }
366         } else if (g_str_equal (f1->uri, f2->uri)) {
367           /* Same URIs but different sequences, this is bad! */
368           GST_ERROR ("Media sequences inconsistent, ignoring");
369           return FALSE;
370         } else {
371           /* Not same URI, not same sequence but by construction sequence
372            * must be higher in the new one. All good in that case, if it
373            * isn't then this means that sequence numbers are decreasing
374            * or files were inserted */
375           if (f1->sequence < f2->sequence) {
376             GST_ERROR ("Media sequences inconsistent, ignoring");
377             return FALSE;
378           }
379         }
380       }
381
382       /* All good if we're getting here */
383     }
384   } else {
385     GList *l, *m;
386     GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
387     gint64 mediasequence;
388
389     for (l = self->files; l; l = l->next) {
390       gboolean match = FALSE;
391
392       f1 = l->data;
393       for (m = previous_files; m; m = m->next) {
394         f2 = m->data;
395
396         if (g_str_equal (f1->uri, f2->uri)) {
397           match = TRUE;
398           break;
399         }
400       }
401
402       if (match)
403         break;
404     }
405
406     if (!l) {
407       /* No match, this means f2 is the last item in the previous playlist
408        * and we have to start our new playlist at that sequence */
409       mediasequence = f2->sequence + 1;
410
411       for (l = self->files; l; l = l->next) {
412         f1 = l->data;
413         f1->sequence = mediasequence;
414         mediasequence++;
415       }
416     } else {
417       /* Match, check that all following ones are matching too and continue
418        * sequence numbers from there on */
419
420       mediasequence = f2->sequence;
421
422       for (; l; l = l->next) {
423         f1 = l->data;
424         f2 = m ? m->data : NULL;
425
426         f1->sequence = mediasequence;
427         mediasequence++;
428
429         if (f2) {
430           if (!g_str_equal (f1->uri, f2->uri)) {
431             GST_WARNING ("Inconsistent URIs after playlist update");
432           }
433         }
434
435         if (m)
436           m = m->next;
437       }
438     }
439   }
440
441   return TRUE;
442 }
443
444 /*
445  * @data: a m3u8 playlist text data, taking ownership
446  */
447 gboolean
448 gst_m3u8_update (GstM3U8 * self, gchar * data)
449 {
450   gint val;
451   GstClockTime duration;
452   gchar *title, *end;
453   gboolean discontinuity = FALSE;
454   gchar *current_key = NULL;
455   gboolean have_iv = FALSE;
456   guint8 iv[16] = { 0, };
457   gint64 size = -1, offset = -1;
458   gint64 mediasequence;
459   GList *previous_files = NULL;
460   gboolean have_mediasequence = FALSE;
461
462   g_return_val_if_fail (self != NULL, FALSE);
463   g_return_val_if_fail (data != NULL, FALSE);
464
465   GST_M3U8_LOCK (self);
466
467   /* check if the data changed since last update */
468   if (self->last_data && g_str_equal (self->last_data, data)) {
469     GST_DEBUG ("Playlist is the same as previous one");
470     g_free (data);
471     GST_M3U8_UNLOCK (self);
472     return TRUE;
473   }
474
475   if (!g_str_has_prefix (data, "#EXTM3U")) {
476     GST_WARNING ("Data doesn't start with #EXTM3U");
477     g_free (data);
478     GST_M3U8_UNLOCK (self);
479     return FALSE;
480   }
481
482   if (g_strrstr (data, "\n#EXT-X-STREAM-INF:") != NULL) {
483     GST_WARNING ("Not a media playlist, but a master playlist!");
484     GST_M3U8_UNLOCK (self);
485     return FALSE;
486   }
487
488   GST_TRACE ("data:\n%s", data);
489
490   g_free (self->last_data);
491   self->last_data = data;
492
493   self->current_file = NULL;
494   previous_files = self->files;
495   self->files = NULL;
496   self->duration = GST_CLOCK_TIME_NONE;
497   mediasequence = 0;
498
499   /* By default, allow caching */
500   self->allowcache = TRUE;
501
502   duration = 0;
503   title = NULL;
504   data += 7;
505   while (TRUE) {
506     gchar *r;
507
508     end = g_utf8_strchr (data, -1, '\n');
509     if (end)
510       *end = '\0';
511
512     r = g_utf8_strchr (data, -1, '\r');
513     if (r)
514       *r = '\0';
515
516     if (data[0] != '#' && data[0] != '\0') {
517       if (duration <= 0) {
518         GST_LOG ("%s: got line without EXTINF, dropping", data);
519         goto next_line;
520       }
521
522       data = uri_join (self->base_uri ? self->base_uri : self->uri, data);
523       if (data != NULL) {
524         GstM3U8MediaFile *file;
525         file = gst_m3u8_media_file_new (data, title, duration, mediasequence++);
526
527         /* set encryption params */
528         file->key = current_key ? g_strdup (current_key) : NULL;
529         if (file->key) {
530           if (have_iv) {
531             memcpy (file->iv, iv, sizeof (iv));
532           } else {
533             guint8 *iv = file->iv + 12;
534             GST_WRITE_UINT32_BE (iv, file->sequence);
535           }
536         }
537
538         if (size != -1) {
539           file->size = size;
540           if (offset != -1) {
541             file->offset = offset;
542           } else {
543             GstM3U8MediaFile *prev = self->files ? self->files->data : NULL;
544
545             if (!prev) {
546               offset = 0;
547             } else {
548               offset = prev->offset + prev->size;
549             }
550             file->offset = offset;
551           }
552         } else {
553           file->size = -1;
554           file->offset = 0;
555         }
556
557         file->discont = discontinuity;
558
559         duration = 0;
560         title = NULL;
561         discontinuity = FALSE;
562         size = offset = -1;
563         self->files = g_list_prepend (self->files, file);
564       }
565
566     } else if (g_str_has_prefix (data, "#EXTINF:")) {
567       gdouble fval;
568       if (!double_from_string (data + 8, &data, &fval)) {
569         GST_WARNING ("Can't read EXTINF duration");
570         goto next_line;
571       }
572       duration = fval * (gdouble) GST_SECOND;
573       if (self->targetduration > 0 && duration > self->targetduration) {
574         GST_WARNING ("EXTINF duration (%" GST_TIME_FORMAT
575             ") > TARGETDURATION (%" GST_TIME_FORMAT ")",
576             GST_TIME_ARGS (duration), GST_TIME_ARGS (self->targetduration));
577       }
578       if (!data || *data != ',')
579         goto next_line;
580       data = g_utf8_next_char (data);
581       if (data != end) {
582         g_free (title);
583         title = g_strdup (data);
584       }
585     } else if (g_str_has_prefix (data, "#EXT-X-")) {
586       gchar *data_ext_x = data + 7;
587
588       /* All these entries start with #EXT-X- */
589       if (g_str_has_prefix (data_ext_x, "ENDLIST")) {
590         self->endlist = TRUE;
591       } else if (g_str_has_prefix (data_ext_x, "VERSION:")) {
592         if (int_from_string (data + 15, &data, &val))
593           self->version = val;
594       } else if (g_str_has_prefix (data_ext_x, "TARGETDURATION:")) {
595         if (int_from_string (data + 22, &data, &val))
596           self->targetduration = val * GST_SECOND;
597       } else if (g_str_has_prefix (data_ext_x, "MEDIA-SEQUENCE:")) {
598         if (int_from_string (data + 22, &data, &val)) {
599           mediasequence = val;
600           have_mediasequence = TRUE;
601         }
602       } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY-SEQUENCE:")) {
603         if (int_from_string (data + 30, &data, &val)
604             && val != self->discont_sequence) {
605           self->discont_sequence = val;
606           discontinuity = TRUE;
607         }
608       } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY")) {
609         self->discont_sequence++;
610         discontinuity = TRUE;
611       } else if (g_str_has_prefix (data_ext_x, "PROGRAM-DATE-TIME:")) {
612         /* <YYYY-MM-DDThh:mm:ssZ> */
613         GST_DEBUG ("FIXME parse date");
614       } else if (g_str_has_prefix (data_ext_x, "ALLOW-CACHE:")) {
615         self->allowcache = g_ascii_strcasecmp (data + 19, "YES") == 0;
616       } else if (g_str_has_prefix (data_ext_x, "KEY:")) {
617         gchar *v, *a;
618
619         data = data + 11;
620
621         /* IV and KEY are only valid until the next #EXT-X-KEY */
622         have_iv = FALSE;
623         g_free (current_key);
624         current_key = NULL;
625         while (data && parse_attributes (&data, &a, &v)) {
626           if (g_str_equal (a, "URI")) {
627             current_key =
628                 uri_join (self->base_uri ? self->base_uri : self->uri, v);
629           } else if (g_str_equal (a, "IV")) {
630             gchar *ivp = v;
631             gint i;
632
633             if (strlen (ivp) < 32 + 2 || (!g_str_has_prefix (ivp, "0x")
634                     && !g_str_has_prefix (ivp, "0X"))) {
635               GST_WARNING ("Can't read IV");
636               continue;
637             }
638
639             ivp += 2;
640             for (i = 0; i < 16; i++) {
641               gint h, l;
642
643               h = g_ascii_xdigit_value (*ivp);
644               ivp++;
645               l = g_ascii_xdigit_value (*ivp);
646               ivp++;
647               if (h == -1 || l == -1) {
648                 i = -1;
649                 break;
650               }
651               iv[i] = (h << 4) | l;
652             }
653
654             if (i == -1) {
655               GST_WARNING ("Can't read IV");
656               continue;
657             }
658             have_iv = TRUE;
659           } else if (g_str_equal (a, "METHOD")) {
660             if (!g_str_equal (v, "AES-128")) {
661               GST_WARNING ("Encryption method %s not supported", v);
662               continue;
663             }
664           }
665         }
666       } else if (g_str_has_prefix (data_ext_x, "BYTERANGE:")) {
667         gchar *v = data + 17;
668
669         if (int64_from_string (v, &v, &size)) {
670           if (*v == '@' && !int64_from_string (v + 1, &v, &offset))
671             goto next_line;
672         } else {
673           goto next_line;
674         }
675       } else {
676         GST_LOG ("Ignored line: %s", data);
677       }
678     } else {
679       GST_LOG ("Ignored line: %s", data);
680     }
681
682   next_line:
683     if (!end)
684       break;
685     data = g_utf8_next_char (end);      /* skip \n */
686   }
687
688   g_free (current_key);
689   current_key = NULL;
690
691   self->files = g_list_reverse (self->files);
692
693   if (previous_files) {
694     gboolean consistent = gst_m3u8_update_check_consistent_media_seqnums (self,
695         have_mediasequence, previous_files);
696
697     g_list_foreach (previous_files, (GFunc) gst_m3u8_media_file_unref, NULL);
698     g_list_free (previous_files);
699     previous_files = NULL;
700
701     /* error was reported above already */
702     if (!consistent) {
703       GST_M3U8_UNLOCK (self);
704       return FALSE;
705     }
706   }
707
708   if (self->files == NULL) {
709     GST_ERROR ("Invalid media playlist, it does not contain any media files");
710     GST_M3U8_UNLOCK (self);
711     return FALSE;
712   }
713
714   /* calculate the start and end times of this media playlist. */
715   {
716     GList *walk;
717     GstM3U8MediaFile *file;
718     GstClockTime duration = 0;
719
720     mediasequence = -1;
721
722     for (walk = self->files; walk; walk = walk->next) {
723       file = walk->data;
724
725       if (mediasequence == -1) {
726         mediasequence = file->sequence;
727       } else if (mediasequence >= file->sequence) {
728         GST_ERROR ("Non-increasing media sequence");
729         GST_M3U8_UNLOCK (self);
730         return FALSE;
731       } else {
732         mediasequence = file->sequence;
733       }
734
735       duration += file->duration;
736       if (file->sequence > self->highest_sequence_number) {
737         if (self->highest_sequence_number >= 0) {
738           /* if an update of the media playlist has been missed, there
739              will be a gap between self->highest_sequence_number and the
740              first sequence number in this media playlist. In this situation
741              assume that the missing fragments had a duration of
742              targetduration each */
743           self->last_file_end +=
744               (file->sequence - self->highest_sequence_number -
745               1) * self->targetduration;
746         }
747         self->last_file_end += file->duration;
748         self->highest_sequence_number = file->sequence;
749       }
750     }
751     if (GST_M3U8_IS_LIVE (self)) {
752       self->first_file_start = self->last_file_end - duration;
753       GST_DEBUG ("Live playlist range %" GST_TIME_FORMAT " -> %"
754           GST_TIME_FORMAT, GST_TIME_ARGS (self->first_file_start),
755           GST_TIME_ARGS (self->last_file_end));
756     }
757     self->duration = duration;
758   }
759
760   /* first-time setup */
761   if (self->files && self->sequence == -1) {
762     GList *file;
763
764     if (GST_M3U8_IS_LIVE (self)) {
765       gint i;
766       GstClockTime sequence_pos = 0;
767
768       file = g_list_last (self->files);
769
770       if (self->last_file_end >= GST_M3U8_MEDIA_FILE (file->data)->duration) {
771         sequence_pos =
772             self->last_file_end - GST_M3U8_MEDIA_FILE (file->data)->duration;
773       }
774
775       /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
776        * the end of the playlist. See section 6.3.3 of HLS draft */
777       for (i = 0; i < GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE && file->prev &&
778           GST_M3U8_MEDIA_FILE (file->prev->data)->duration <= sequence_pos;
779           ++i) {
780         file = file->prev;
781         sequence_pos -= GST_M3U8_MEDIA_FILE (file->data)->duration;
782       }
783       self->sequence_position = sequence_pos;
784     } else {
785       file = g_list_first (self->files);
786       self->sequence_position = 0;
787     }
788     self->current_file = file;
789     self->sequence = GST_M3U8_MEDIA_FILE (file->data)->sequence;
790     GST_DEBUG ("first sequence: %u", (guint) self->sequence);
791   }
792
793   GST_LOG ("processed media playlist %s, %u fragments", self->name,
794       g_list_length (self->files));
795
796   GST_M3U8_UNLOCK (self);
797
798   return TRUE;
799 }
800
801 /* call with M3U8_LOCK held */
802 static GList *
803 m3u8_find_next_fragment (GstM3U8 * m3u8, gboolean forward)
804 {
805   GstM3U8MediaFile *file;
806   GList *l = m3u8->files;
807
808   if (forward) {
809     while (l) {
810       file = l->data;
811
812       if (file->sequence >= m3u8->sequence)
813         break;
814
815       l = l->next;
816     }
817   } else {
818     l = g_list_last (l);
819
820     while (l) {
821       file = l->data;
822
823       if (file->sequence <= m3u8->sequence)
824         break;
825
826       l = l->prev;
827     }
828   }
829
830   return l;
831 }
832
833 GstM3U8MediaFile *
834 gst_m3u8_get_next_fragment (GstM3U8 * m3u8, gboolean forward,
835     GstClockTime * sequence_position, gboolean * discont)
836 {
837   GstM3U8MediaFile *file = NULL;
838
839   g_return_val_if_fail (m3u8 != NULL, NULL);
840
841   GST_M3U8_LOCK (m3u8);
842
843   GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
844
845   if (m3u8->sequence < 0)       /* can't happen really */
846     goto out;
847
848   if (m3u8->current_file == NULL)
849     m3u8->current_file = m3u8_find_next_fragment (m3u8, forward);
850
851   if (m3u8->current_file == NULL)
852     goto out;
853
854   file = gst_m3u8_media_file_ref (m3u8->current_file->data);
855
856   GST_DEBUG ("Got fragment with sequence %u (current sequence %u)",
857       (guint) file->sequence, (guint) m3u8->sequence);
858
859   if (sequence_position)
860     *sequence_position = m3u8->sequence_position;
861   if (discont)
862     *discont = file->discont || (m3u8->sequence != file->sequence);
863
864   m3u8->current_file_duration = file->duration;
865   m3u8->sequence = file->sequence;
866
867 out:
868
869   GST_M3U8_UNLOCK (m3u8);
870
871   return file;
872 }
873
874 gboolean
875 gst_m3u8_has_next_fragment (GstM3U8 * m3u8, gboolean forward)
876 {
877   gboolean have_next;
878   GList *cur;
879
880   g_return_val_if_fail (m3u8 != NULL, FALSE);
881
882   GST_M3U8_LOCK (m3u8);
883
884   GST_DEBUG ("Checking next fragment %" G_GINT64_FORMAT,
885       m3u8->sequence + (forward ? 1 : -1));
886
887   if (m3u8->current_file) {
888     cur = m3u8->current_file;
889   } else {
890     cur = m3u8_find_next_fragment (m3u8, forward);
891   }
892
893   have_next = cur && ((forward && cur->next) || (!forward && cur->prev));
894
895   GST_M3U8_UNLOCK (m3u8);
896
897   return have_next;
898 }
899
900 /* call with M3U8_LOCK held */
901 static void
902 m3u8_alternate_advance (GstM3U8 * m3u8, gboolean forward)
903 {
904   gint targetnum = m3u8->sequence;
905   GList *tmp;
906   GstM3U8MediaFile *mf;
907
908   /* figure out the target seqnum */
909   if (forward)
910     targetnum += 1;
911   else
912     targetnum -= 1;
913
914   for (tmp = m3u8->files; tmp; tmp = tmp->next) {
915     mf = (GstM3U8MediaFile *) tmp->data;
916     if (mf->sequence == targetnum)
917       break;
918   }
919   if (tmp == NULL) {
920     GST_WARNING ("Can't find next fragment");
921     return;
922   }
923   m3u8->current_file = tmp;
924   m3u8->sequence = targetnum;
925   m3u8->current_file_duration = GST_M3U8_MEDIA_FILE (tmp->data)->duration;
926 }
927
928 void
929 gst_m3u8_advance_fragment (GstM3U8 * m3u8, gboolean forward)
930 {
931   GstM3U8MediaFile *file;
932
933   g_return_if_fail (m3u8 != NULL);
934
935   GST_M3U8_LOCK (m3u8);
936
937   GST_DEBUG ("Sequence position was %" GST_TIME_FORMAT,
938       GST_TIME_ARGS (m3u8->sequence_position));
939   if (GST_CLOCK_TIME_IS_VALID (m3u8->current_file_duration)) {
940     /* Advance our position based on the previous fragment we played */
941     if (forward)
942       m3u8->sequence_position += m3u8->current_file_duration;
943     else if (m3u8->current_file_duration < m3u8->sequence_position)
944       m3u8->sequence_position -= m3u8->current_file_duration;
945     else
946       m3u8->sequence_position = 0;
947     m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
948     GST_DEBUG ("Sequence position now %" GST_TIME_FORMAT,
949         GST_TIME_ARGS (m3u8->sequence_position));
950   }
951   if (!m3u8->current_file) {
952     GList *l;
953
954     GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
955     for (l = m3u8->files; l != NULL; l = l->next) {
956       if (GST_M3U8_MEDIA_FILE (l->data)->sequence == m3u8->sequence) {
957         m3u8->current_file = l;
958         break;
959       }
960     }
961     if (m3u8->current_file == NULL) {
962       GST_DEBUG
963           ("Could not find current fragment, trying next fragment directly");
964       m3u8_alternate_advance (m3u8, forward);
965
966       /* Resync sequence number if the above has failed for live streams */
967       if (m3u8->current_file == NULL && GST_M3U8_IS_LIVE (m3u8)) {
968         /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
969            the end of the playlist. See section 6.3.3 of HLS draft */
970         gint pos =
971             g_list_length (m3u8->files) - GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
972         m3u8->current_file = g_list_nth (m3u8->files, pos >= 0 ? pos : 0);
973         m3u8->current_file_duration =
974             GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
975
976         GST_WARNING ("Resyncing live playlist");
977       }
978       goto out;
979     }
980   }
981
982   file = GST_M3U8_MEDIA_FILE (m3u8->current_file->data);
983   GST_DEBUG ("Advancing from sequence %u", (guint) file->sequence);
984   if (forward) {
985     m3u8->current_file = m3u8->current_file->next;
986     if (m3u8->current_file) {
987       m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
988     } else {
989       m3u8->sequence = file->sequence + 1;
990     }
991   } else {
992     m3u8->current_file = m3u8->current_file->prev;
993     if (m3u8->current_file) {
994       m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
995     } else {
996       m3u8->sequence = file->sequence - 1;
997     }
998   }
999   if (m3u8->current_file) {
1000     /* Store duration of the fragment we're using to update the position 
1001      * the next time we advance */
1002     m3u8->current_file_duration =
1003         GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
1004   }
1005
1006 out:
1007
1008   GST_M3U8_UNLOCK (m3u8);
1009 }
1010
1011 GstClockTime
1012 gst_m3u8_get_duration (GstM3U8 * m3u8)
1013 {
1014   GstClockTime duration = GST_CLOCK_TIME_NONE;
1015
1016   g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
1017
1018   GST_M3U8_LOCK (m3u8);
1019
1020   /* We can only get the duration for on-demand streams */
1021   if (!m3u8->endlist)
1022     goto out;
1023
1024   if (!GST_CLOCK_TIME_IS_VALID (m3u8->duration) && m3u8->files != NULL) {
1025     GList *f;
1026
1027     m3u8->duration = 0;
1028     for (f = m3u8->files; f != NULL; f = f->next)
1029       m3u8->duration += GST_M3U8_MEDIA_FILE (f)->duration;
1030   }
1031   duration = m3u8->duration;
1032
1033 out:
1034
1035   GST_M3U8_UNLOCK (m3u8);
1036
1037   return duration;
1038 }
1039
1040 GstClockTime
1041 gst_m3u8_get_target_duration (GstM3U8 * m3u8)
1042 {
1043   GstClockTime target_duration;
1044
1045   g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
1046
1047   GST_M3U8_LOCK (m3u8);
1048   target_duration = m3u8->targetduration;
1049   GST_M3U8_UNLOCK (m3u8);
1050
1051   return target_duration;
1052 }
1053
1054 gchar *
1055 gst_m3u8_get_uri (GstM3U8 * m3u8)
1056 {
1057   gchar *uri;
1058
1059   GST_M3U8_LOCK (m3u8);
1060   uri = g_strdup (m3u8->uri);
1061   GST_M3U8_UNLOCK (m3u8);
1062
1063   return uri;
1064 }
1065
1066 gboolean
1067 gst_m3u8_is_live (GstM3U8 * m3u8)
1068 {
1069   gboolean is_live;
1070
1071   g_return_val_if_fail (m3u8 != NULL, FALSE);
1072
1073   GST_M3U8_LOCK (m3u8);
1074   is_live = GST_M3U8_IS_LIVE (m3u8);
1075   GST_M3U8_UNLOCK (m3u8);
1076
1077   return is_live;
1078 }
1079
1080 gchar *
1081 uri_join (const gchar * uri1, const gchar * uri2)
1082 {
1083   gchar *uri_copy, *tmp, *ret = NULL;
1084
1085   if (gst_uri_is_valid (uri2))
1086     return g_strdup (uri2);
1087
1088   uri_copy = g_strdup (uri1);
1089   if (uri2[0] != '/') {
1090     /* uri2 is a relative uri2 */
1091     /* look for query params */
1092     tmp = g_utf8_strchr (uri_copy, -1, '?');
1093     if (tmp) {
1094       /* find last / char, ignoring query params */
1095       tmp = g_utf8_strrchr (uri_copy, tmp - uri_copy, '/');
1096     } else {
1097       /* find last / char in URL */
1098       tmp = g_utf8_strrchr (uri_copy, -1, '/');
1099     }
1100     if (!tmp) {
1101       GST_WARNING ("Can't build a valid uri_copy");
1102       goto out;
1103     }
1104
1105     *tmp = '\0';
1106     ret = g_strdup_printf ("%s/%s", uri_copy, uri2);
1107   } else {
1108     /* uri2 is an absolute uri2 */
1109     char *scheme, *hostname;
1110
1111     scheme = uri_copy;
1112     /* find the : in <scheme>:// */
1113     tmp = g_utf8_strchr (uri_copy, -1, ':');
1114     if (!tmp) {
1115       GST_WARNING ("Can't build a valid uri_copy");
1116       goto out;
1117     }
1118
1119     *tmp = '\0';
1120
1121     /* skip :// */
1122     hostname = tmp + 3;
1123
1124     tmp = g_utf8_strchr (hostname, -1, '/');
1125     if (tmp)
1126       *tmp = '\0';
1127
1128     ret = g_strdup_printf ("%s://%s%s", scheme, hostname, uri2);
1129   }
1130
1131 out:
1132   g_free (uri_copy);
1133   return ret;
1134 }
1135
1136 gboolean
1137 gst_m3u8_get_seek_range (GstM3U8 * m3u8, gint64 * start, gint64 * stop)
1138 {
1139   GstClockTime duration = 0;
1140   GList *walk;
1141   GstM3U8MediaFile *file;
1142   guint count;
1143   guint min_distance = 0;
1144
1145   g_return_val_if_fail (m3u8 != NULL, FALSE);
1146
1147   GST_M3U8_LOCK (m3u8);
1148
1149   if (m3u8->files == NULL)
1150     goto out;
1151
1152   if (GST_M3U8_IS_LIVE (m3u8)) {
1153     /* min_distance is used to make sure the seek range is never closer than
1154        GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE fragments from the end of a live
1155        playlist - see 6.3.3. "Playing the Playlist file" of the HLS draft */
1156     min_distance = GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
1157   }
1158   count = g_list_length (m3u8->files);
1159
1160   for (walk = m3u8->files; walk && count > min_distance; walk = walk->next) {
1161     file = walk->data;
1162     --count;
1163     duration += file->duration;
1164   }
1165
1166   if (duration <= 0)
1167     goto out;
1168
1169   *start = m3u8->first_file_start;
1170   *stop = *start + duration;
1171
1172 out:
1173
1174   GST_M3U8_UNLOCK (m3u8);
1175   return (duration > 0);
1176 }
1177
1178 GstHLSMedia *
1179 gst_hls_media_ref (GstHLSMedia * media)
1180 {
1181   g_assert (media != NULL && media->ref_count > 0);
1182   g_atomic_int_add (&media->ref_count, 1);
1183   return media;
1184 }
1185
1186 void
1187 gst_hls_media_unref (GstHLSMedia * media)
1188 {
1189   g_assert (media != NULL && media->ref_count > 0);
1190   if (g_atomic_int_dec_and_test (&media->ref_count)) {
1191     if (media->playlist)
1192       gst_m3u8_unref (media->playlist);
1193     g_free (media->group_id);
1194     g_free (media->name);
1195     g_free (media->uri);
1196     g_free (media->lang);
1197     g_free (media);
1198   }
1199 }
1200
1201 static GstHLSMediaType
1202 gst_m3u8_get_hls_media_type_from_string (const gchar * type_name)
1203 {
1204   if (strcmp (type_name, "AUDIO") == 0)
1205     return GST_HLS_MEDIA_TYPE_AUDIO;
1206   if (strcmp (type_name, "VIDEO") == 0)
1207     return GST_HLS_MEDIA_TYPE_VIDEO;
1208   if (strcmp (type_name, "SUBTITLES") == 0)
1209     return GST_HLS_MEDIA_TYPE_SUBTITLES;
1210   if (strcmp (type_name, "CLOSED_CAPTIONS") == 0)
1211     return GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS;
1212
1213   return GST_HLS_MEDIA_TYPE_INVALID;
1214 }
1215
1216 #define GST_HLS_MEDIA_TYPE_NAME(mtype) gst_m3u8_hls_media_type_get_nick(mtype)
1217 static inline const gchar *
1218 gst_m3u8_hls_media_type_get_nick (GstHLSMediaType mtype)
1219 {
1220   static const gchar *nicks[GST_HLS_N_MEDIA_TYPES] = { "audio", "video",
1221     "subtitle", "closed-captions"
1222   };
1223
1224   if (mtype < 0 || mtype >= GST_HLS_N_MEDIA_TYPES)
1225     return "invalid";
1226
1227   return nicks[mtype];
1228 }
1229
1230 /* returns unquoted copy of string */
1231 static gchar *
1232 gst_m3u8_unquote (const gchar * str)
1233 {
1234   const gchar *start, *end;
1235
1236   start = strchr (str, '"');
1237   if (start == NULL)
1238     return g_strdup (str);
1239   end = strchr (start + 1, '"');
1240   if (end == NULL) {
1241     GST_WARNING ("Broken quoted string [%s] - can't find end quote", str);
1242     return g_strdup (start + 1);
1243   }
1244   return g_strndup (start + 1, (gsize) (end - (start + 1)));
1245 }
1246
1247 static GstHLSMedia *
1248 gst_m3u8_parse_media (gchar * desc, const gchar * base_uri)
1249 {
1250   GstHLSMedia *media;
1251   gchar *a, *v;
1252
1253   media = g_new0 (GstHLSMedia, 1);
1254   media->ref_count = 1;
1255   media->playlist = gst_m3u8_new ();
1256   media->mtype = GST_HLS_MEDIA_TYPE_INVALID;
1257
1258   GST_LOG ("parsing %s", desc);
1259   while (desc != NULL && parse_attributes (&desc, &a, &v)) {
1260     if (strcmp (a, "TYPE") == 0) {
1261       media->mtype = gst_m3u8_get_hls_media_type_from_string (v);
1262     } else if (strcmp (a, "GROUP-ID") == 0) {
1263       g_free (media->group_id);
1264       media->group_id = gst_m3u8_unquote (v);
1265     } else if (strcmp (a, "NAME") == 0) {
1266       g_free (media->name);
1267       media->name = gst_m3u8_unquote (v);
1268     } else if (strcmp (a, "URI") == 0) {
1269       gchar *uri;
1270
1271       g_free (media->uri);
1272       uri = gst_m3u8_unquote (v);
1273       media->uri = uri_join (base_uri, uri);
1274       g_free (uri);
1275     } else if (strcmp (a, "LANGUAGE") == 0) {
1276       g_free (media->lang);
1277       media->lang = gst_m3u8_unquote (v);
1278     } else if (strcmp (a, "DEFAULT") == 0) {
1279       media->is_default = g_ascii_strcasecmp (v, "yes") == 0;
1280     } else if (strcmp (a, "FORCED") == 0) {
1281       media->forced = g_ascii_strcasecmp (v, "yes") == 0;
1282     } else if (strcmp (a, "AUTOSELECT") == 0) {
1283       media->autoselect = g_ascii_strcasecmp (v, "yes") == 0;
1284     } else {
1285       /* unhandled: ASSOC-LANGUAGE, INSTREAM-ID, CHARACTERISTICS */
1286       GST_FIXME ("EXT-X-MEDIA: unhandled attribute: %s = %s", a, v);
1287     }
1288   }
1289
1290   if (media->mtype == GST_HLS_MEDIA_TYPE_INVALID)
1291     goto required_attributes_missing;
1292
1293   if (media->uri == NULL)
1294     goto existing_stream;
1295
1296   if (media->group_id == NULL || media->name == NULL)
1297     goto required_attributes_missing;
1298
1299   if (media->mtype == GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS)
1300     goto uri_with_cc;
1301
1302   GST_DEBUG ("media: %s, group '%s', name '%s', uri '%s', %s %s %s, lang=%s",
1303       GST_HLS_MEDIA_TYPE_NAME (media->mtype), media->group_id, media->name,
1304       media->uri, media->is_default ? "default" : "-",
1305       media->autoselect ? "autoselect" : "-",
1306       media->forced ? "forced" : "-", media->lang ? media->lang : "??");
1307
1308   return media;
1309
1310 uri_with_cc:
1311   {
1312     GST_WARNING ("closed captions EXT-X-MEDIA should not have URI specified");
1313     goto out_error;
1314   }
1315 required_attributes_missing:
1316   {
1317     GST_WARNING ("EXT-X-MEDIA description is missing required attributes");
1318     goto out_error;
1319     /* fall through */
1320   }
1321 existing_stream:
1322   {
1323     GST_DEBUG ("EXT-X-MEDIA without URI, describes embedded stream, skipping");
1324     /* fall through */
1325   }
1326
1327 out_error:
1328   {
1329     gst_hls_media_unref (media);
1330     return NULL;
1331   }
1332 }
1333
1334 static GstHLSVariantStream *
1335 gst_hls_variant_stream_new (void)
1336 {
1337   GstHLSVariantStream *stream;
1338
1339   stream = g_new0 (GstHLSVariantStream, 1);
1340   stream->m3u8 = gst_m3u8_new ();
1341   stream->refcount = 1;
1342   return stream;
1343 }
1344
1345 GstHLSVariantStream *
1346 gst_hls_variant_stream_ref (GstHLSVariantStream * stream)
1347 {
1348   g_atomic_int_inc (&stream->refcount);
1349   return stream;
1350 }
1351
1352 void
1353 gst_hls_variant_stream_unref (GstHLSVariantStream * stream)
1354 {
1355   if (g_atomic_int_dec_and_test (&stream->refcount)) {
1356     gint i;
1357
1358     g_free (stream->name);
1359     g_free (stream->uri);
1360     g_free (stream->codecs);
1361     gst_m3u8_unref (stream->m3u8);
1362     for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1363       g_free (stream->media_groups[i]);
1364       g_list_free_full (stream->media[i], (GDestroyNotify) gst_hls_media_unref);
1365     }
1366     g_free (stream);
1367   }
1368 }
1369
1370 static GstHLSVariantStream *
1371 find_variant_stream_by_name (GList * list, const gchar * name)
1372 {
1373   for (; list != NULL; list = list->next) {
1374     GstHLSVariantStream *variant_stream = list->data;
1375
1376     if (variant_stream->name != NULL && !strcmp (variant_stream->name, name))
1377       return variant_stream;
1378   }
1379   return NULL;
1380 }
1381
1382 static GstHLSVariantStream *
1383 find_variant_stream_by_uri (GList * list, const gchar * uri)
1384 {
1385   for (; list != NULL; list = list->next) {
1386     GstHLSVariantStream *variant_stream = list->data;
1387
1388     if (variant_stream->uri != NULL && !strcmp (variant_stream->uri, uri))
1389       return variant_stream;
1390   }
1391   return NULL;
1392 }
1393
1394 static GstHLSMasterPlaylist *
1395 gst_hls_master_playlist_new (void)
1396 {
1397   GstHLSMasterPlaylist *playlist;
1398
1399   playlist = g_new0 (GstHLSMasterPlaylist, 1);
1400   playlist->refcount = 1;
1401   playlist->is_simple = FALSE;
1402
1403   return playlist;
1404 }
1405
1406 void
1407 gst_hls_master_playlist_unref (GstHLSMasterPlaylist * playlist)
1408 {
1409   if (g_atomic_int_dec_and_test (&playlist->refcount)) {
1410     g_list_free_full (playlist->variants,
1411         (GDestroyNotify) gst_hls_variant_stream_unref);
1412     g_list_free_full (playlist->iframe_variants,
1413         (GDestroyNotify) gst_hls_variant_stream_unref);
1414     if (playlist->default_variant)
1415       gst_hls_variant_stream_unref (playlist->default_variant);
1416     g_free (playlist->last_data);
1417     g_free (playlist);
1418   }
1419 }
1420
1421 static gint
1422 hls_media_name_compare_func (gconstpointer media, gconstpointer name)
1423 {
1424   return strcmp (((GstHLSMedia *) media)->name, (const gchar *) name);
1425 }
1426
1427 /* Takes ownership of @data */
1428 GstHLSMasterPlaylist *
1429 gst_hls_master_playlist_new_from_data (gchar * data, const gchar * base_uri)
1430 {
1431   GHashTable *media_groups[GST_HLS_N_MEDIA_TYPES] = { NULL, };
1432   GstHLSMasterPlaylist *playlist;
1433   GstHLSVariantStream *pending_stream;
1434   gchar *end, *free_data = data;
1435   gint val, i;
1436   GList *l;
1437
1438   if (!g_str_has_prefix (data, "#EXTM3U")) {
1439     GST_WARNING ("Data doesn't start with #EXTM3U");
1440     g_free (free_data);
1441     return NULL;
1442   }
1443
1444   playlist = gst_hls_master_playlist_new ();
1445
1446   /* store data before we modify it for parsing */
1447   playlist->last_data = g_strdup (data);
1448
1449   GST_TRACE ("data:\n%s", data);
1450
1451   if (strstr (data, "\n#EXTINF:") != NULL) {
1452     GST_INFO ("This is a simple media playlist, not a master playlist");
1453
1454     pending_stream = gst_hls_variant_stream_new ();
1455     pending_stream->name = g_strdup (base_uri);
1456     pending_stream->uri = g_strdup (base_uri);
1457     gst_m3u8_set_uri (pending_stream->m3u8, base_uri, NULL, base_uri);
1458     playlist->variants = g_list_append (playlist->variants, pending_stream);
1459     playlist->default_variant = gst_hls_variant_stream_ref (pending_stream);
1460     playlist->is_simple = TRUE;
1461
1462     if (!gst_m3u8_update (pending_stream->m3u8, data)) {
1463       GST_WARNING ("Failed to parse media playlist");
1464       gst_hls_master_playlist_unref (playlist);
1465       playlist = NULL;
1466     }
1467     return playlist;
1468   }
1469
1470   pending_stream = NULL;
1471   data += 7;
1472   while (TRUE) {
1473     gchar *r;
1474
1475     end = g_utf8_strchr (data, -1, '\n');
1476     if (end)
1477       *end = '\0';
1478
1479     r = g_utf8_strchr (data, -1, '\r');
1480     if (r)
1481       *r = '\0';
1482
1483     if (data[0] != '#' && data[0] != '\0') {
1484       gchar *name, *uri;
1485
1486       if (pending_stream == NULL) {
1487         GST_LOG ("%s: got line without EXT-STREAM-INF, dropping", data);
1488         goto next_line;
1489       }
1490
1491       name = data;
1492       uri = uri_join (base_uri, name);
1493       if (uri == NULL)
1494         goto next_line;
1495
1496       pending_stream->name = g_strdup (name);
1497       pending_stream->uri = uri;
1498
1499       if (find_variant_stream_by_name (playlist->variants, name)
1500           || find_variant_stream_by_uri (playlist->variants, uri)) {
1501         GST_DEBUG ("Already have a list with this name or URI: %s", name);
1502         gst_hls_variant_stream_unref (pending_stream);
1503       } else {
1504         GST_INFO ("stream %s @ %u: %s", name, pending_stream->bandwidth, uri);
1505         gst_m3u8_set_uri (pending_stream->m3u8, uri, NULL, name);
1506         playlist->variants = g_list_append (playlist->variants, pending_stream);
1507         /* use first stream in the playlist as default */
1508         if (playlist->default_variant == NULL) {
1509           playlist->default_variant =
1510               gst_hls_variant_stream_ref (pending_stream);
1511         }
1512       }
1513       pending_stream = NULL;
1514     } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
1515       if (int_from_string (data + 15, &data, &val))
1516         playlist->version = val;
1517     } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:") ||
1518         g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:")) {
1519       GstHLSVariantStream *stream;
1520       gchar *v, *a;
1521
1522       stream = gst_hls_variant_stream_new ();
1523       stream->iframe = g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:");
1524       data += stream->iframe ? 26 : 18;
1525       while (data && parse_attributes (&data, &a, &v)) {
1526         if (g_str_equal (a, "BANDWIDTH")) {
1527           if (!int_from_string (v, NULL, &stream->bandwidth))
1528             GST_WARNING ("Error while reading BANDWIDTH");
1529         } else if (g_str_equal (a, "PROGRAM-ID")) {
1530           if (!int_from_string (v, NULL, &stream->program_id))
1531             GST_WARNING ("Error while reading PROGRAM-ID");
1532         } else if (g_str_equal (a, "CODECS")) {
1533           g_free (stream->codecs);
1534           stream->codecs = g_strdup (v);
1535         } else if (g_str_equal (a, "RESOLUTION")) {
1536           if (!int_from_string (v, &v, &stream->width))
1537             GST_WARNING ("Error while reading RESOLUTION width");
1538           if (!v || *v != 'x') {
1539             GST_WARNING ("Missing height");
1540           } else {
1541             v = g_utf8_next_char (v);
1542             if (!int_from_string (v, NULL, &stream->height))
1543               GST_WARNING ("Error while reading RESOLUTION height");
1544           }
1545         } else if (stream->iframe && g_str_equal (a, "URI")) {
1546           stream->uri = uri_join (base_uri, v);
1547           if (stream->uri != NULL) {
1548             stream->name = g_strdup (stream->uri);
1549             gst_m3u8_set_uri (stream->m3u8, stream->uri, NULL, stream->name);
1550           } else {
1551             gst_hls_variant_stream_unref (stream);
1552           }
1553         } else if (g_str_equal (a, "AUDIO")) {
1554           g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO]);
1555           stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO] = gst_m3u8_unquote (v);
1556         } else if (g_str_equal (a, "SUBTITLES")) {
1557           g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES]);
1558           stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES] =
1559               gst_m3u8_unquote (v);
1560         } else if (g_str_equal (a, "VIDEO")) {
1561           g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO]);
1562           stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO] = gst_m3u8_unquote (v);
1563         } else if (g_str_equal (a, "CLOSED-CAPTIONS")) {
1564           /* closed captions will be embedded inside the video stream, ignore */
1565         }
1566       }
1567
1568       if (stream->iframe) {
1569         if (find_variant_stream_by_uri (playlist->iframe_variants, stream->uri)) {
1570           GST_DEBUG ("Already have a list with this URI");
1571           gst_hls_variant_stream_unref (stream);
1572         } else {
1573           playlist->iframe_variants =
1574               g_list_append (playlist->iframe_variants, stream);
1575         }
1576       } else {
1577         if (pending_stream != NULL) {
1578           GST_WARNING ("variant stream without uri, dropping");
1579           gst_hls_variant_stream_unref (pending_stream);
1580         }
1581         pending_stream = stream;
1582       }
1583     } else if (g_str_has_prefix (data, "#EXT-X-MEDIA:")) {
1584       GstHLSMedia *media;
1585       GList *list;
1586
1587       media = gst_m3u8_parse_media (data + strlen ("#EXT-X-MEDIA:"), base_uri);
1588
1589       if (media == NULL)
1590         goto next_line;
1591
1592       if (media_groups[media->mtype] == NULL) {
1593         media_groups[media->mtype] =
1594             g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1595       }
1596
1597       list = g_hash_table_lookup (media_groups[media->mtype], media->group_id);
1598
1599       /* make sure there isn't already a media with the same name */
1600       if (!g_list_find_custom (list, media->name, hls_media_name_compare_func)) {
1601         g_hash_table_replace (media_groups[media->mtype],
1602             g_strdup (media->group_id), g_list_append (list, media));
1603         GST_INFO ("Added media %s to group %s", media->name, media->group_id);
1604       } else {
1605         GST_WARNING ("  media with name '%s' already exists in group '%s'!",
1606             media->name, media->group_id);
1607         gst_hls_media_unref (media);
1608       }
1609     } else if (*data != '\0') {
1610       GST_LOG ("Ignored line: %s", data);
1611     }
1612
1613   next_line:
1614     if (!end)
1615       break;
1616     data = g_utf8_next_char (end);      /* skip \n */
1617   }
1618
1619   if (pending_stream != NULL) {
1620     GST_WARNING ("#EXT-X-STREAM-INF without uri, dropping");
1621     gst_hls_variant_stream_unref (pending_stream);
1622   }
1623
1624   g_free (free_data);
1625
1626   /* Add alternative renditions media to variant streams */
1627   for (l = playlist->variants; l != NULL; l = l->next) {
1628     GstHLSVariantStream *stream = l->data;
1629     GList *mlist;
1630
1631     for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1632       if (stream->media_groups[i] != NULL && media_groups[i] != NULL) {
1633         GST_INFO ("Adding %s group '%s' to stream '%s'",
1634             GST_HLS_MEDIA_TYPE_NAME (i), stream->media_groups[i], stream->name);
1635
1636         mlist = g_hash_table_lookup (media_groups[i], stream->media_groups[i]);
1637
1638         if (mlist == NULL)
1639           GST_WARNING ("Group '%s' does not exist!", stream->media_groups[i]);
1640
1641         while (mlist != NULL) {
1642           GstHLSMedia *media = mlist->data;
1643
1644           GST_DEBUG ("  %s media %s, uri: %s", GST_HLS_MEDIA_TYPE_NAME (i),
1645               media->name, media->uri);
1646
1647           stream->media[i] =
1648               g_list_append (stream->media[i], gst_hls_media_ref (media));
1649           mlist = mlist->next;
1650         }
1651       }
1652     }
1653   }
1654
1655   /* clean up our temporary alternative rendition groups hash tables */
1656   for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1657     if (media_groups[i] != NULL) {
1658       GList *groups, *mlist;
1659
1660       groups = g_hash_table_get_keys (media_groups[i]);
1661       for (l = groups; l != NULL; l = l->next) {
1662         mlist = g_hash_table_lookup (media_groups[i], l->data);
1663         g_list_free_full (mlist, (GDestroyNotify) gst_hls_media_unref);
1664       }
1665       g_list_free (groups);
1666       g_hash_table_unref (media_groups[i]);
1667     }
1668   }
1669
1670   if (playlist->variants == NULL) {
1671     GST_WARNING ("Master playlist without any media playlists!");
1672     gst_hls_master_playlist_unref (playlist);
1673     return NULL;
1674   }
1675
1676   /* reorder variants by bitrate */
1677   playlist->variants =
1678       g_list_sort (playlist->variants,
1679       (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
1680
1681   playlist->iframe_variants =
1682       g_list_sort (playlist->iframe_variants,
1683       (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
1684
1685   /* FIXME: restore old current_variant after master playlist update
1686    * (move into code that does that update) */
1687 #if 0
1688   {
1689     gchar *top_variant_uri = NULL;
1690     gboolean iframe = FALSE;
1691
1692     if (!self->current_variant) {
1693       top_variant_uri = GST_M3U8 (self->lists->data)->uri;
1694     } else {
1695       top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
1696       iframe = GST_M3U8 (self->current_variant->data)->iframe;
1697     }
1698
1699     /* here we sorted the lists */
1700
1701     if (iframe)
1702       playlist->current_variant =
1703           find_variant_stream_by_uri (playlist->iframe_variants,
1704           top_variant_uri);
1705     else
1706       playlist->current_variant =
1707           find_variant_stream_by_uri (playlist->variants, top_variant_uri);
1708   }
1709 #endif
1710
1711   GST_DEBUG ("parsed master playlist with %d streams and %d I-frame streams",
1712       g_list_length (playlist->variants),
1713       g_list_length (playlist->iframe_variants));
1714
1715
1716   return playlist;
1717 }
1718
1719 gboolean
1720 gst_hls_variant_stream_is_live (GstHLSVariantStream * variant)
1721 {
1722   gboolean is_live;
1723
1724   g_return_val_if_fail (variant != NULL, FALSE);
1725
1726   is_live = gst_m3u8_is_live (variant->m3u8);
1727
1728   return is_live;
1729 }
1730
1731 static gint
1732 compare_media (const GstHLSMedia * a, const GstHLSMedia * b)
1733 {
1734   return strcmp (a->name, b->name);
1735 }
1736
1737 GstHLSMedia *
1738 gst_hls_variant_find_matching_media (GstHLSVariantStream * stream,
1739     GstHLSMedia * media)
1740 {
1741   GList *mlist = stream->media[media->mtype];
1742   GList *match;
1743
1744   if (mlist == NULL)
1745     return NULL;
1746
1747   match = g_list_find_custom (mlist, media, (GCompareFunc) compare_media);
1748   if (match == NULL)
1749     return NULL;
1750
1751   return match->data;
1752 }
1753
1754 GstHLSVariantStream *
1755 gst_hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist *
1756     playlist, GstHLSVariantStream * current_variant, guint bitrate)
1757 {
1758   GstHLSVariantStream *variant = current_variant;
1759   GList *l;
1760
1761   /* variant lists are sorted low to high, so iterate from highest to lowest */
1762   if (current_variant == NULL || !current_variant->iframe)
1763     l = g_list_last (playlist->variants);
1764   else
1765     l = g_list_last (playlist->iframe_variants);
1766
1767   while (l != NULL) {
1768     variant = l->data;
1769     if (variant->bandwidth <= bitrate)
1770       break;
1771     l = l->prev;
1772   }
1773
1774   return variant;
1775 }
1776
1777 GstHLSVariantStream *
1778 gst_hls_master_playlist_get_matching_variant (GstHLSMasterPlaylist * playlist,
1779     GstHLSVariantStream * current_variant)
1780 {
1781   if (current_variant->iframe) {
1782     return find_variant_stream_by_uri (playlist->iframe_variants,
1783         current_variant->uri);
1784   }
1785
1786   return find_variant_stream_by_uri (playlist->variants, current_variant->uri);
1787 }