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