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