Move the defines from spec to meson.build
[platform/upstream/gstreamer.git] / ext / hls / m3u8.c
old mode 100755 (executable)
new mode 100644 (file)
index b931749..892d1b6
@@ -1,5 +1,6 @@
 /* GStreamer
  * Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
+ * Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
  *
  * m3u8.c:
  *
  * Boston, MA 02110-1301, USA.
  */
 
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
 #include <stdlib.h>
 #include <math.h>
 #include <errno.h>
 #include <glib.h>
 #include <string.h>
 
-#include "gstfragmented.h"
 #include "m3u8.h"
+#include "gsthlselements.h"
 
-#define GST_CAT_DEFAULT fragmented_debug
-
-#if !GLIB_CHECK_VERSION (2, 33, 4)
-#define g_list_copy_deep gst_g_list_copy_deep
-static GList *
-gst_g_list_copy_deep (GList * list, GCopyFunc func, gpointer user_data)
-{
-  list = g_list_copy (list);
-
-  if (func != NULL) {
-    GList *l;
-
-    for (l = list; l != NULL; l = l->next) {
-      l->data = func (l->data, user_data);
-    }
-  }
-
-  return list;
-}
+#define GST_CAT_DEFAULT hls_debug
+#ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
+#define DEFAULT_RESOLUTION_LIMIT -1
+#define DEFAULT_BANDWIDTH_LIMIT -1
 #endif
 
-static GstM3U8 *gst_m3u8_new (void);
-static void gst_m3u8_free (GstM3U8 * m3u8);
-static gboolean gst_m3u8_update (GstM3U8Client * client, GstM3U8 * m3u8,
-    gchar * data, gboolean * updated);
 static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
     gchar * title, GstClockTime duration, guint sequence);
-static void gst_m3u8_media_file_free (GstM3U8MediaFile * self);
-gchar *uri_join (const gchar * uri, const gchar * path);
+static void gst_m3u8_init_file_unref (GstM3U8InitFile * self);
+static gchar *uri_join (const gchar * uri, const gchar * path);
 
-static GstM3U8 *
+GstM3U8 *
 gst_m3u8_new (void)
 {
   GstM3U8 *m3u8;
 
   m3u8 = g_new0 (GstM3U8, 1);
 
+  m3u8->current_file = NULL;
+  m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
+  m3u8->sequence = -1;
+  m3u8->sequence_position = 0;
+  m3u8->highest_sequence_number = -1;
+  m3u8->duration = GST_CLOCK_TIME_NONE;
+#ifdef TIZEN_FEATURE_AD
+  m3u8->ad_info = g_new0 (GstM3U8AdInfo, 1);
+#endif
+
+  g_mutex_init (&m3u8->lock);
+  m3u8->ref_count = 1;
+
   return m3u8;
 }
 
+/* call with M3U8_LOCK held */
 static void
-gst_m3u8_set_uri (GstM3U8 * self, gchar * uri, gchar * base_uri, gchar * name)
+gst_m3u8_take_uri (GstM3U8 * self, gchar * uri, gchar * base_uri, gchar * name)
 {
   g_return_if_fail (self != NULL);
 
-  g_free (self->uri);
-  self->uri = uri;
+  if (self->uri != uri) {
+    g_free (self->uri);
+    self->uri = uri;
+  }
+  if (self->base_uri != base_uri) {
+    g_free (self->base_uri);
+    self->base_uri = base_uri;
+  }
+  if (self->name != name) {
+    g_free (self->name);
+    self->name = name;
+  }
+}
+
+void
+gst_m3u8_set_uri (GstM3U8 * m3u8, const gchar * uri, const gchar * base_uri,
+    const gchar * name)
+{
+  GST_M3U8_LOCK (m3u8);
+  gst_m3u8_take_uri (m3u8, g_strdup (uri), g_strdup (base_uri),
+      g_strdup (name));
+  GST_M3U8_UNLOCK (m3u8);
+}
+
+#ifdef TIZEN_FEATURE_AD
+static GstM3U8Cue *
+gst_m3u8_cue_info_new (GstClockTime start_time, GstClockTime duration)
+{
+  GstM3U8Cue *ad;
 
-  g_free (self->base_uri);
-  self->base_uri = base_uri;
+  ad = g_new0 (GstM3U8Cue, 1);
+  ad->start_time = start_time;
+  ad->end_time = 0;
+  ad->duration = duration;
 
-  g_free (self->name);
-  self->name = name;
+  return ad;
 }
 
-static void
-gst_m3u8_free (GstM3U8 * self)
+void
+gst_m3u8_cue_cont_free (GstM3U8CueOutCont * self)
 {
   g_return_if_fail (self != NULL);
+  g_free (self->cont_data);
+}
+#endif
+
+GstM3U8 *
+gst_m3u8_ref (GstM3U8 * m3u8)
+{
+  g_assert (m3u8 != NULL && m3u8->ref_count > 0);
+
+  g_atomic_int_add (&m3u8->ref_count, 1);
+  return m3u8;
+}
+
+void
+gst_m3u8_unref (GstM3U8 * self)
+{
+  g_return_if_fail (self != NULL && self->ref_count > 0);
 
-  g_free (self->uri);
-  g_free (self->base_uri);
-  g_free (self->name);
-  g_free (self->codecs);
+  if (g_atomic_int_dec_and_test (&self->ref_count)) {
+    g_free (self->uri);
+    g_free (self->base_uri);
+    g_free (self->name);
 
-  g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
-  g_list_free (self->files);
+    g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_unref, NULL);
+    g_list_free (self->files);
 
-  g_free (self->last_data);
-  g_list_foreach (self->lists, (GFunc) gst_m3u8_free, NULL);
-  g_list_free (self->lists);
-  g_list_foreach (self->iframe_lists, (GFunc) gst_m3u8_free, NULL);
-  g_list_free (self->iframe_lists);
+#ifdef TIZEN_FEATURE_AD
+    if (self->ad_info) {
+      g_list_free (self->ad_info->cue);
+      g_list_free_full(self->ad_info->cue_cont, (GDestroyNotify) gst_m3u8_cue_cont_free);
+      g_free (self->ad_info);
+    }
+#endif
 
-  g_free (self);
+    g_free (self->last_data);
+    g_mutex_clear (&self->lock);
+    g_free (self);
+  }
 }
 
 static GstM3U8MediaFile *
@@ -116,97 +166,65 @@ gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration,
   file->title = title;
   file->duration = duration;
   file->sequence = sequence;
+  file->ref_count = 1;
 
   return file;
 }
 
-static void
-gst_m3u8_media_file_free (GstM3U8MediaFile * self)
+GstM3U8MediaFile *
+gst_m3u8_media_file_ref (GstM3U8MediaFile * mfile)
 {
-  g_return_if_fail (self != NULL);
+  g_assert (mfile != NULL && mfile->ref_count > 0);
 
-  g_free (self->title);
-  g_free (self->uri);
-  g_free (self->key);
-  g_free (self);
+  g_atomic_int_add (&mfile->ref_count, 1);
+  return mfile;
 }
 
-static GstM3U8MediaFile *
-gst_m3u8_media_file_copy (const GstM3U8MediaFile * self, gpointer user_data)
+void
+gst_m3u8_media_file_unref (GstM3U8MediaFile * self)
 {
-  g_return_val_if_fail (self != NULL, NULL);
-
-  return gst_m3u8_media_file_new (g_strdup (self->uri), g_strdup (self->title),
-      self->duration, self->sequence);
+  g_return_if_fail (self != NULL && self->ref_count > 0);
+
+  if (g_atomic_int_dec_and_test (&self->ref_count)) {
+    if (self->init_file)
+      gst_m3u8_init_file_unref (self->init_file);
+    g_free (self->title);
+    g_free (self->uri);
+    g_free (self->key);
+    g_free (self);
+  }
 }
 
-static GstM3U8 *
-_m3u8_copy (const GstM3U8 * self, GstM3U8 * parent)
+static GstM3U8InitFile *
+gst_m3u8_init_file_new (gchar * uri)
 {
-  GstM3U8 *dup;
-
-  g_return_val_if_fail (self != NULL, NULL);
-
-  dup = gst_m3u8_new ();
-  dup->uri = g_strdup (self->uri);
-  dup->base_uri = g_strdup (self->base_uri);
-  dup->name = g_strdup (self->name);
-  dup->endlist = self->endlist;
-  dup->version = self->version;
-  dup->targetduration = self->targetduration;
-  dup->allowcache = self->allowcache;
-  dup->bandwidth = self->bandwidth;
-  dup->program_id = self->program_id;
-  dup->codecs = g_strdup (self->codecs);
-  dup->width = self->width;
-  dup->height = self->height;
-  dup->iframe = self->iframe;
-  dup->files =
-      g_list_copy_deep (self->files, (GCopyFunc) gst_m3u8_media_file_copy,
-      NULL);
-
-  /* private */
-  dup->last_data = g_strdup (self->last_data);
-  dup->lists = g_list_copy_deep (self->lists, (GCopyFunc) _m3u8_copy, dup);
-  dup->iframe_lists =
-      g_list_copy_deep (self->iframe_lists, (GCopyFunc) _m3u8_copy, dup);
-  /* NOTE: current_variant will get set in gst_m3u8_copy () */
-  dup->parent = parent;
-  dup->mediasequence = self->mediasequence;
-  return dup;
+  GstM3U8InitFile *file;
+
+  file = g_new0 (GstM3U8InitFile, 1);
+  file->uri = uri;
+  file->ref_count = 1;
+
+  return file;
 }
 
-static GstM3U8 *
-gst_m3u8_copy (const GstM3U8 * self)
+static GstM3U8InitFile *
+gst_m3u8_init_file_ref (GstM3U8InitFile * ifile)
 {
-  GList *entry;
-  guint n;
+  g_assert (ifile != NULL && ifile->ref_count > 0);
 
-  GstM3U8 *dup = _m3u8_copy (self, NULL);
-
-  if (self->current_variant != NULL) {
-    for (n = 0, entry = self->lists; entry; entry = entry->next, n++) {
-      if (entry == self->current_variant) {
-        dup->current_variant = g_list_nth (dup->lists, n);
-        break;
-      }
-    }
+  g_atomic_int_add (&ifile->ref_count, 1);
+  return ifile;
+}
 
-    if (!dup->current_variant) {
-      for (n = 0, entry = self->iframe_lists; entry; entry = entry->next, n++) {
-        if (entry == self->current_variant) {
-          dup->current_variant = g_list_nth (dup->iframe_lists, n);
-          break;
-        }
-      }
+static void
+gst_m3u8_init_file_unref (GstM3U8InitFile * self)
+{
+  g_return_if_fail (self != NULL && self->ref_count > 0);
 
-      if (!dup->current_variant) {
-        GST_ERROR ("Failed to determine current playlist");
-      }
-    }
+  if (g_atomic_int_dec_and_test (&self->ref_count)) {
+    g_free (self->uri);
+    g_free (self);
   }
-
-  return dup;
 }
 
 static gboolean
@@ -297,7 +315,7 @@ double_from_string (gchar * ptr, gchar ** endptr, gdouble * val)
 static gboolean
 parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
 {
-  gchar *end = NULL, *p;
+  gchar *end = NULL, *p, *ve;
 
   g_return_val_if_fail (ptr != NULL, FALSE);
   g_return_val_if_fail (*ptr != NULL, FALSE);
@@ -330,8 +348,20 @@ parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
 
   *v = p = g_utf8_strchr (*ptr, -1, '=');
   if (*v) {
-    *v = g_utf8_next_char (*v);
     *p = '\0';
+    *v = g_utf8_next_char (*v);
+    if (**v == '"') {
+      ve = g_utf8_next_char (*v);
+      if (ve) {
+        ve = g_utf8_strchr (ve, -1, '"');
+      }
+      if (ve) {
+        *v = g_utf8_next_char (*v);
+        *ve = '\0';
+      } else {
+        GST_WARNING ("Cannot remove quotation marks from %s", *a);
+      }
+    }
   } else {
     GST_WARNING ("missing = after attribute");
     return FALSE;
@@ -341,96 +371,214 @@ parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
   return TRUE;
 }
 
-static gchar *
-unquote_string (gchar * string)
+static gint
+gst_hls_variant_stream_compare_by_bitrate (gconstpointer a, gconstpointer b)
 {
-  gchar *string_ret;
-
-  string_ret = strchr (string, '"');
-  if (string_ret != NULL) {
-    /* found initialization quotation mark of string */
-    string = string_ret + 1;
-    string_ret = strchr (string, '"');
-    if (string_ret != NULL) {
-      /* found finalizing quotation mark of string */
-      string_ret[0] = '\0';
-    } else {
-      GST_WARNING
-          ("wrong string unqouting - cannot find finalizing quotation mark");
-      return NULL;
-    }
-  }
-  return string;
+  const GstHLSVariantStream *vs_a = (const GstHLSVariantStream *) a;
+  const GstHLSVariantStream *vs_b = (const GstHLSVariantStream *) b;
+
+  if (vs_a->bandwidth == vs_b->bandwidth)
+    return g_strcmp0 (vs_a->name, vs_b->name);
+
+  return vs_a->bandwidth - vs_b->bandwidth;
 }
 
-static gint
-_m3u8_compare_uri (GstM3U8 * a, gchar * uri)
+/* If we have MEDIA-SEQUENCE, ensure that it's consistent. If it is not,
+ * the client SHOULD halt playback (6.3.4), which is what we do then. */
+static gboolean
+check_media_seqnums (GstM3U8 * self, GList * previous_files)
 {
-  g_return_val_if_fail (a != NULL, 0);
-  g_return_val_if_fail (uri != NULL, 0);
+  GList *l, *m;
+  GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
+
+  g_return_val_if_fail (previous_files, FALSE);
+
+  if (!self->files) {
+    /* Empty playlists are trivially consistent */
+    return TRUE;
+  }
+
+  /* Find first case of higher/equal sequence number in new playlist.
+   * From there on we can linearly step ahead */
+  for (l = self->files; l; l = l->next) {
+    gboolean match = FALSE;
+
+    f1 = l->data;
+    for (m = previous_files; m; m = m->next) {
+      f2 = m->data;
+
+      if (f1->sequence >= f2->sequence) {
+        match = TRUE;
+        break;
+      }
+    }
+    if (match)
+      break;
+  }
+
+  /* We must have seen at least one entry on each list */
+  g_assert (f1 != NULL);
+  g_assert (f2 != NULL);
+
+  if (!l) {
+    /* No match, no sequence in the new playlist was higher than
+     * any in the old. This is bad! */
+    GST_ERROR ("Media sequence doesn't continue: last new %" G_GINT64_FORMAT
+        " < last old %" G_GINT64_FORMAT, f1->sequence, f2->sequence);
+    return FALSE;
+  }
+
+  for (; l && m; l = l->next, m = m->next) {
+    f1 = l->data;
+    f2 = m->data;
 
-  return g_strcmp0 (a->uri, uri);
+    if (f1->sequence == f2->sequence && !g_str_equal (f1->uri, f2->uri)) {
+      /* Same sequence, different URI. This is bad! */
+      GST_ERROR ("Media URIs inconsistent (sequence %" G_GINT64_FORMAT
+          "): had '%s', got '%s'", f1->sequence, f2->uri, f1->uri);
+      return FALSE;
+    } else if (f1->sequence < f2->sequence) {
+      /* Not same sequence but by construction sequence must be higher in the
+       * new one. All good in that case, if it isn't then this means that
+       * sequence numbers are decreasing or files were inserted */
+      GST_ERROR ("Media sequences inconsistent: %" G_GINT64_FORMAT " < %"
+          G_GINT64_FORMAT ": URIs new '%s' old '%s'", f1->sequence,
+          f2->sequence, f1->uri, f2->uri);
+      return FALSE;
+    }
+  }
+
+  /* All good if we're getting here */
+  return TRUE;
 }
 
-static gint
-gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b)
+/* If we don't have MEDIA-SEQUENCE, we check URIs in the previous and
+ * current playlist to calculate the/a correct MEDIA-SEQUENCE for the new
+ * playlist in relation to the old. That is, same URIs get the same number
+ * and later URIs get higher numbers */
+static void
+generate_media_seqnums (GstM3U8 * self, GList * previous_files)
 {
-  return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth;
+  GList *l, *m;
+  GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
+  gint64 mediasequence;
+
+  g_return_if_fail (previous_files);
+
+  /* Find first case of same URI in new playlist.
+   * From there on we can linearly step ahead */
+  for (l = self->files; l; l = l->next) {
+    gboolean match = FALSE;
+
+    f1 = l->data;
+    for (m = previous_files; m; m = m->next) {
+      f2 = m->data;
+
+      if (g_str_equal (f1->uri, f2->uri)) {
+        match = TRUE;
+        break;
+      }
+    }
+
+    if (match)
+      break;
+  }
+
+  if (l) {
+    /* Match, check that all following ones are matching too and continue
+     * sequence numbers from there on */
+
+    mediasequence = f2->sequence;
+
+    for (; l && m; l = l->next, m = m->next) {
+      f1 = l->data;
+      f2 = m->data;
+
+      f1->sequence = mediasequence;
+      mediasequence++;
+
+      if (!g_str_equal (f1->uri, f2->uri)) {
+        GST_WARNING ("Inconsistent URIs after playlist update: '%s' != '%s'",
+            f1->uri, f2->uri);
+      }
+    }
+  } else {
+    /* No match, this means f2 is the last item in the previous playlist
+     * and we have to start our new playlist at that sequence */
+    mediasequence = f2->sequence + 1;
+    l = self->files;
+  }
+
+  for (; l; l = l->next) {
+    f1 = l->data;
+
+    f1->sequence = mediasequence;
+    mediasequence++;
+  }
 }
 
 /*
  * @data: a m3u8 playlist text data, taking ownership
  */
-static gboolean
-gst_m3u8_update (GstM3U8Client * client, GstM3U8 * self, gchar * data,
-    gboolean * updated)
+gboolean
+gst_m3u8_update (GstM3U8 * self, gchar * data)
 {
   gint val;
   GstClockTime duration;
   gchar *title, *end;
   gboolean discontinuity = FALSE;
-  GstM3U8 *list;
   gchar *current_key = NULL;
   gboolean have_iv = FALSE;
   guint8 iv[16] = { 0, };
   gint64 size = -1, offset = -1;
+  gint64 mediasequence;
+  GList *previous_files = NULL;
+  gboolean have_mediasequence = FALSE;
+  GstM3U8InitFile *last_init_file = NULL;
+#ifdef TIZEN_FEATURE_AD
+  GstClockTime timestamp = 0;
+#endif
 
   g_return_val_if_fail (self != NULL, FALSE);
   g_return_val_if_fail (data != NULL, FALSE);
-  g_return_val_if_fail (updated != NULL, FALSE);
 
-  *updated = TRUE;
+  GST_M3U8_LOCK (self);
 
   /* check if the data changed since last update */
   if (self->last_data && g_str_equal (self->last_data, data)) {
     GST_DEBUG ("Playlist is the same as previous one");
-    *updated = FALSE;
     g_free (data);
+    GST_M3U8_UNLOCK (self);
     return TRUE;
   }
 
   if (!g_str_has_prefix (data, "#EXTM3U")) {
     GST_WARNING ("Data doesn't start with #EXTM3U");
-    *updated = FALSE;
     g_free (data);
+    GST_M3U8_UNLOCK (self);
+    return FALSE;
+  }
+
+  if (g_strrstr (data, "\n#EXT-X-STREAM-INF:") != NULL) {
+    GST_WARNING ("Not a media playlist, but a master playlist!");
+    GST_M3U8_UNLOCK (self);
     return FALSE;
   }
 
+  GST_TRACE ("data:\n%s", data);
+
   g_free (self->last_data);
   self->last_data = data;
 
-  client->current_file = NULL;
-  if (self->files) {
-    g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
-    g_list_free (self->files);
-    self->files = NULL;
-  }
-  client->duration = GST_CLOCK_TIME_NONE;
+  self->current_file = NULL;
+  previous_files = self->files;
+  self->files = NULL;
+  self->duration = GST_CLOCK_TIME_NONE;
+  mediasequence = 0;
 
   /* By default, allow caching */
   self->allowcache = TRUE;
 
-  list = NULL;
   duration = 0;
   title = NULL;
   data += 7;
@@ -446,32 +594,15 @@ gst_m3u8_update (GstM3U8Client * client, GstM3U8 * self, gchar * data,
       *r = '\0';
 
     if (data[0] != '#' && data[0] != '\0') {
-      gchar *name = data;
-      if (duration <= 0 && list == NULL) {
-        GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data);
+      if (duration <= 0) {
+        GST_LOG ("%s: got line without EXTINF, dropping", data);
         goto next_line;
       }
 
       data = uri_join (self->base_uri ? self->base_uri : self->uri, data);
-      if (data == NULL)
-        goto next_line;
-
-      if (list != NULL) {
-        if (g_list_find_custom (self->lists, data,
-                (GCompareFunc) _m3u8_compare_uri)) {
-          GST_DEBUG ("Already have a list with this URI");
-          gst_m3u8_free (list);
-          g_free (data);
-        } else {
-          gst_m3u8_set_uri (list, data, NULL, g_strdup (name));
-          self->lists = g_list_append (self->lists, list);
-        }
-        list = NULL;
-      } else {
+      if (data != NULL) {
         GstM3U8MediaFile *file;
-        file =
-            gst_m3u8_media_file_new (data, title, duration,
-            self->mediasequence++);
+        file = gst_m3u8_media_file_new (data, title, duration, mediasequence++);
 
         /* set encryption params */
         file->key = current_key ? g_strdup (current_key) : NULL;
@@ -504,7 +635,12 @@ gst_m3u8_update (GstM3U8Client * client, GstM3U8 * self, gchar * data,
         }
 
         file->discont = discontinuity;
+        if (last_init_file)
+          file->init_file = gst_m3u8_init_file_ref (last_init_file);
 
+#ifdef TIZEN_FEATURE_AD
+        timestamp += duration;
+#endif
         duration = 0;
         title = NULL;
         discontinuity = FALSE;
@@ -540,80 +676,22 @@ gst_m3u8_update (GstM3U8Client * client, GstM3U8 * self, gchar * data,
       } else if (g_str_has_prefix (data_ext_x, "VERSION:")) {
         if (int_from_string (data + 15, &data, &val))
           self->version = val;
-      } else if (g_str_has_prefix (data_ext_x, "STREAM-INF:") ||
-          g_str_has_prefix (data_ext_x, "I-FRAME-STREAM-INF:")) {
-        gchar *v, *a;
-        gboolean iframe = g_str_has_prefix (data_ext_x, "I-FRAME-STREAM-INF:");
-        GstM3U8 *new_list;
-
-        new_list = gst_m3u8_new ();
-        new_list->parent = self;
-        new_list->iframe = iframe;
-        data = data + (iframe ? 26 : 18);
-        while (data && parse_attributes (&data, &a, &v)) {
-          if (g_str_equal (a, "BANDWIDTH")) {
-            if (!int_from_string (v, NULL, &new_list->bandwidth))
-              GST_WARNING ("Error while reading BANDWIDTH");
-          } else if (g_str_equal (a, "PROGRAM-ID")) {
-            if (!int_from_string (v, NULL, &new_list->program_id))
-              GST_WARNING ("Error while reading PROGRAM-ID");
-          } else if (g_str_equal (a, "CODECS")) {
-            g_free (new_list->codecs);
-            new_list->codecs = g_strdup (v);
-          } else if (g_str_equal (a, "RESOLUTION")) {
-            if (!int_from_string (v, &v, &new_list->width))
-              GST_WARNING ("Error while reading RESOLUTION width");
-            if (!v || *v != 'x') {
-              GST_WARNING ("Missing height");
-            } else {
-              v = g_utf8_next_char (v);
-              if (!int_from_string (v, NULL, &new_list->height))
-                GST_WARNING ("Error while reading RESOLUTION height");
-            }
-          } else if (iframe && g_str_equal (a, "URI")) {
-            gchar *name;
-            gchar *uri = g_strdup (v);
-            gchar *urip = uri;
-
-            uri = unquote_string (uri);
-            if (uri) {
-              uri = uri_join (self->base_uri ? self->base_uri : self->uri, uri);
-              if (uri == NULL) {
-                g_free (urip);
-                continue;
-              }
-              name = g_strdup (uri);
-
-              gst_m3u8_set_uri (new_list, uri, NULL, name);
-            } else {
-              GST_WARNING
-                  ("Cannot remove quotation marks from i-frame-stream URI");
-            }
-            g_free (urip);
-          }
-        }
-
-        if (iframe) {
-          if (g_list_find_custom (self->iframe_lists, new_list->uri,
-                  (GCompareFunc) _m3u8_compare_uri)) {
-            GST_DEBUG ("Already have a list with this URI");
-            gst_m3u8_free (new_list);
-          } else {
-            self->iframe_lists = g_list_append (self->iframe_lists, new_list);
-          }
-        } else if (list != NULL) {
-          GST_WARNING ("Found a list without a uri..., dropping");
-          gst_m3u8_free (list);
-        } else {
-          list = new_list;
-        }
       } else if (g_str_has_prefix (data_ext_x, "TARGETDURATION:")) {
         if (int_from_string (data + 22, &data, &val))
           self->targetduration = val * GST_SECOND;
       } else if (g_str_has_prefix (data_ext_x, "MEDIA-SEQUENCE:")) {
-        if (int_from_string (data + 22, &data, &val))
-          self->mediasequence = val;
+        if (int_from_string (data + 22, &data, &val)) {
+          mediasequence = val;
+          have_mediasequence = TRUE;
+        }
+      } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY-SEQUENCE:")) {
+        if (int_from_string (data + 30, &data, &val)
+            && val != self->discont_sequence) {
+          self->discont_sequence = val;
+          discontinuity = TRUE;
+        }
       } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY")) {
+        self->discont_sequence++;
         discontinuity = TRUE;
       } else if (g_str_has_prefix (data_ext_x, "PROGRAM-DATE-TIME:")) {
         /* <YYYY-MM-DDThh:mm:ssZ> */
@@ -631,18 +709,8 @@ gst_m3u8_update (GstM3U8Client * client, GstM3U8 * self, gchar * data,
         current_key = NULL;
         while (data && parse_attributes (&data, &a, &v)) {
           if (g_str_equal (a, "URI")) {
-            gchar *key = g_strdup (v);
-            gchar *keyp = key;
-
-            key = unquote_string (key);
-            if (key) {
-              current_key =
-                  uri_join (self->base_uri ? self->base_uri : self->uri, key);
-            } else {
-              GST_WARNING
-                  ("Cannot remove quotation marks from decryption key URI");
-            }
-            g_free (keyp);
+            current_key =
+                uri_join (self->base_uri ? self->base_uri : self->uri, v);
           } else if (g_str_equal (a, "IV")) {
             gchar *ivp = v;
             gint i;
@@ -689,7 +757,109 @@ gst_m3u8_update (GstM3U8Client * client, GstM3U8 * self, gchar * data,
         } else {
           goto next_line;
         }
-      } else {
+      } else if (g_str_has_prefix (data_ext_x, "MAP:")) {
+        gchar *v, *a, *header_uri = NULL;
+
+        data = data + 11;
+
+        while (data != NULL && parse_attributes (&data, &a, &v)) {
+          if (strcmp (a, "URI") == 0) {
+            header_uri =
+                uri_join (self->base_uri ? self->base_uri : self->uri, v);
+          } else if (strcmp (a, "BYTERANGE") == 0) {
+            if (int64_from_string (v, &v, &size)) {
+              if (*v == '@' && !int64_from_string (v + 1, &v, &offset)) {
+                g_free (header_uri);
+                goto next_line;
+              }
+            } else {
+              g_free (header_uri);
+              goto next_line;
+            }
+          }
+        }
+
+        if (header_uri) {
+          GstM3U8InitFile *init_file;
+          init_file = gst_m3u8_init_file_new (header_uri);
+
+          if (size != -1) {
+            init_file->size = size;
+            if (offset != -1)
+              init_file->offset = offset;
+            else
+              init_file->offset = 0;
+          } else {
+            init_file->size = -1;
+            init_file->offset = 0;
+          }
+          if (last_init_file)
+            gst_m3u8_init_file_unref (last_init_file);
+
+          last_init_file = init_file;
+        }
+      }
+#ifdef TIZEN_FEATURE_AD
+      else if (g_str_has_prefix (data_ext_x, "CUE-OUT:")) {
+        GstM3U8Cue *cue;
+        gdouble fval;
+
+        GST_LOG ("cue out: %" GST_TIME_FORMAT ", %s", GST_TIME_ARGS (timestamp), data);
+
+        data = data + strlen ("#EXT-X-CUE-OUT:");
+        if (g_str_has_prefix (data, "DURATION="))
+          data = data + strlen ("DURATION=");
+
+        if (!double_from_string (data, &data, &fval)) {
+          GST_WARNING ("Can't read CUE-OUT duration");
+          goto next_line;
+        }
+
+        duration = fval * (gdouble) GST_SECOND;
+
+        cue = gst_m3u8_cue_info_new (timestamp, duration);
+        GST_LOG ("cue out start %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT
+                      , GST_TIME_ARGS (cue->start_time), GST_TIME_ARGS (cue->duration));
+        self->ad_info->cue = g_list_append (self->ad_info->cue, cue);
+        duration = 0;
+      } else if (g_str_has_prefix (data_ext_x, "CUE-IN")) {
+        GList *cue;
+        GstM3U8Cue *cue_data;
+
+        GST_LOG ("cue in: %" GST_TIME_FORMAT ", %s", GST_TIME_ARGS (timestamp), data);
+
+        cue = g_list_last (self->ad_info->cue);
+        if (!cue || !(cue->data)) {
+          GST_WARNING ("there is no valid data");
+          goto next_line;
+        }
+
+        cue_data = cue->data;
+        GST_LOG ("start %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT,
+                GST_TIME_ARGS (cue_data->start_time), GST_TIME_ARGS (cue_data->duration));
+
+        if (cue_data->end_time != 0) {
+          GST_WARNING ("cue syntax err, skip this tag.");
+          goto next_line;
+        }
+
+        cue_data->end_time = timestamp;
+
+        GST_LOG ("cue start %" GST_TIME_FORMAT ", end %" GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
+                 GST_TIME_ARGS (cue_data->start_time), GST_TIME_ARGS (cue_data->end_time),
+                 GST_TIME_ARGS (cue_data->duration));
+      } else if (g_str_has_prefix (data_ext_x, "CUE-OUT-CONT:")) {
+        GstM3U8CueOutCont *cont = g_new0 (GstM3U8CueOutCont, 1);
+
+        GST_LOG ("cue cont: %" GST_TIME_FORMAT ", %s", GST_TIME_ARGS (timestamp), data);
+
+        data = data + strlen ("#EXT-X-CUE-OUT-CONT:");
+        cont->timestamp = timestamp;
+        cont->cont_data = g_strdup (data);
+        self->ad_info->cue_cont = g_list_append (self->ad_info->cue_cont, cont);
+      }
+#endif
+      else {
         GST_LOG ("Ignored line: %s", data);
       }
     } else {
@@ -707,276 +877,135 @@ gst_m3u8_update (GstM3U8Client * client, GstM3U8 * self, gchar * data,
 
   self->files = g_list_reverse (self->files);
 
-  /* reorder playlists by bitrate */
-  if (self->lists) {
-    gchar *top_variant_uri = NULL;
-    gboolean iframe = FALSE;
+  if (last_init_file)
+    gst_m3u8_init_file_unref (last_init_file);
 
-    if (!self->current_variant) {
-      top_variant_uri = GST_M3U8 (self->lists->data)->uri;
+  if (previous_files) {
+    gboolean consistent = TRUE;
+
+    if (have_mediasequence) {
+      consistent = check_media_seqnums (self, previous_files);
     } else {
-      top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
-      iframe = GST_M3U8 (self->current_variant->data)->iframe;
+      generate_media_seqnums (self, previous_files);
     }
 
-    self->lists =
-        g_list_sort (self->lists,
-        (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
+    g_list_foreach (previous_files, (GFunc) gst_m3u8_media_file_unref, NULL);
+    g_list_free (previous_files);
+    previous_files = NULL;
 
-    self->iframe_lists =
-        g_list_sort (self->iframe_lists,
-        (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
+    /* error was reported above already */
+    if (!consistent) {
+      GST_M3U8_UNLOCK (self);
+      return FALSE;
+    }
+  }
 
-    if (iframe)
-      self->current_variant =
-          g_list_find_custom (self->iframe_lists, top_variant_uri,
-          (GCompareFunc) _m3u8_compare_uri);
-    else
-      self->current_variant = g_list_find_custom (self->lists, top_variant_uri,
-          (GCompareFunc) _m3u8_compare_uri);
+  if (self->files == NULL) {
+    GST_ERROR ("Invalid media playlist, it does not contain any media files");
+    GST_M3U8_UNLOCK (self);
+    return FALSE;
   }
+
   /* calculate the start and end times of this media playlist. */
-  if (self->files) {
+  {
     GList *walk;
     GstM3U8MediaFile *file;
     GstClockTime duration = 0;
 
+    mediasequence = -1;
+
     for (walk = self->files; walk; walk = walk->next) {
       file = walk->data;
+
+      if (mediasequence == -1) {
+        mediasequence = file->sequence;
+      } else if (mediasequence >= file->sequence) {
+        GST_ERROR ("Non-increasing media sequence");
+        GST_M3U8_UNLOCK (self);
+        return FALSE;
+      } else {
+        mediasequence = file->sequence;
+      }
+
       duration += file->duration;
-      if (file->sequence > client->highest_sequence_number) {
-        if (client->highest_sequence_number >= 0) {
+      if (file->sequence > self->highest_sequence_number) {
+        if (self->highest_sequence_number >= 0) {
           /* if an update of the media playlist has been missed, there
              will be a gap between self->highest_sequence_number and the
              first sequence number in this media playlist. In this situation
              assume that the missing fragments had a duration of
              targetduration each */
-          client->last_file_end +=
-              (file->sequence - client->highest_sequence_number -
+          self->last_file_end +=
+              (file->sequence - self->highest_sequence_number -
               1) * self->targetduration;
         }
-        client->last_file_end += file->duration;
-        client->highest_sequence_number = file->sequence;
+        self->last_file_end += file->duration;
+        self->highest_sequence_number = file->sequence;
       }
     }
-    if (GST_M3U8_CLIENT_IS_LIVE (client)) {
-      client->first_file_start = client->last_file_end - duration;
+    if (GST_M3U8_IS_LIVE (self)) {
+      self->first_file_start = self->last_file_end - duration;
       GST_DEBUG ("Live playlist range %" GST_TIME_FORMAT " -> %"
-          GST_TIME_FORMAT, GST_TIME_ARGS (client->first_file_start),
-          GST_TIME_ARGS (client->last_file_end));
+          GST_TIME_FORMAT, GST_TIME_ARGS (self->first_file_start),
+          GST_TIME_ARGS (self->last_file_end));
     }
-    client->duration = duration;
+    self->duration = duration;
   }
 
-  return TRUE;
-}
-
-GstM3U8Client *
-gst_m3u8_client_new (const gchar * uri, const gchar * base_uri)
-{
-  GstM3U8Client *client;
-
-  g_return_val_if_fail (uri != NULL, NULL);
-
-  client = g_new0 (GstM3U8Client, 1);
-  client->main = gst_m3u8_new ();
-  client->current = NULL;
-  client->current_file = NULL;
-  client->sequence = -1;
-  client->sequence_position = 0;
-  client->update_failed_count = 0;
-  client->highest_sequence_number = -1;
-  client->duration = GST_CLOCK_TIME_NONE;
-  g_mutex_init (&client->lock);
-  gst_m3u8_set_uri (client->main, g_strdup (uri), g_strdup (base_uri), NULL);
-
-  return client;
-}
+  /* first-time setup */
+  if (self->files && self->sequence == -1) {
+    GList *file;
 
-void
-gst_m3u8_client_free (GstM3U8Client * self)
-{
-  g_return_if_fail (self != NULL);
+    if (GST_M3U8_IS_LIVE (self)) {
+      gint i;
+      GstClockTime sequence_pos = 0;
 
-  gst_m3u8_free (self->main);
-  g_mutex_clear (&self->lock);
-  g_free (self);
-}
+      file = g_list_last (self->files);
 
-void
-gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8)
-{
-  g_return_if_fail (self != NULL);
+      if (self->last_file_end >= GST_M3U8_MEDIA_FILE (file->data)->duration) {
+        sequence_pos =
+            self->last_file_end - GST_M3U8_MEDIA_FILE (file->data)->duration;
+      }
 
-  GST_M3U8_CLIENT_LOCK (self);
-  if (m3u8 != self->current) {
-    self->current = m3u8;
-    self->update_failed_count = 0;
-    self->duration = GST_CLOCK_TIME_NONE;
-    self->current_file = NULL;
+      /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
+       * the end of the playlist. See section 6.3.3 of HLS draft */
+      for (i = 0; i < GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE && file->prev &&
+          GST_M3U8_MEDIA_FILE (file->prev->data)->duration <= sequence_pos;
+          ++i) {
+        file = file->prev;
+        sequence_pos -= GST_M3U8_MEDIA_FILE (file->data)->duration;
+      }
+      self->sequence_position = sequence_pos;
+    } else {
+      file = g_list_first (self->files);
+      self->sequence_position = 0;
+    }
+    self->current_file = file;
+    self->sequence = GST_M3U8_MEDIA_FILE (file->data)->sequence;
+    GST_DEBUG ("first sequence: %u", (guint) self->sequence);
   }
-  GST_M3U8_CLIENT_UNLOCK (self);
+
+  GST_LOG ("processed media playlist %s, %u fragments", self->name,
+      g_list_length (self->files));
+
+  GST_M3U8_UNLOCK (self);
+
+  return TRUE;
 }
 
-gboolean
-gst_m3u8_client_update (GstM3U8Client * self, gchar * data)
+/* call with M3U8_LOCK held */
+static GList *
+m3u8_find_next_fragment (GstM3U8 * m3u8, gboolean forward)
 {
-  GstM3U8 *m3u8;
-  gboolean updated = FALSE;
-  gboolean ret = FALSE;
-
-  g_return_val_if_fail (self != NULL, FALSE);
+  GstM3U8MediaFile *file;
+  GList *l = m3u8->files;
 
-  GST_M3U8_CLIENT_LOCK (self);
-  m3u8 = self->current ? self->current : self->main;
+  if (forward) {
+    while (l) {
+      file = l->data;
 
-  if (!gst_m3u8_update (self, m3u8, data, &updated))
-    goto out;
-
-  if (!updated) {
-    self->update_failed_count++;
-    goto out;
-  }
-
-  if (self->current && !self->current->files) {
-    GST_ERROR ("Invalid media playlist, it does not contain any media files");
-    goto out;
-  }
-
-  /* select the first playlist, for now */
-  if (!self->current) {
-    if (self->main->lists) {
-      self->current = self->main->current_variant->data;
-    } else {
-      self->current = self->main;
-    }
-  }
-
-  if (m3u8->files && self->sequence == -1) {
-    if (GST_M3U8_CLIENT_IS_LIVE (self)) {
-      /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
-         the end of the playlist. See section 6.3.3 of HLS draft */
-      gint pos =
-          g_list_length (m3u8->files) - GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
-      self->current_file = g_list_nth (m3u8->files, pos >= 0 ? pos : 0);
-    } else {
-      self->current_file = g_list_first (m3u8->files);
-    }
-    self->sequence = GST_M3U8_MEDIA_FILE (self->current_file->data)->sequence;
-    self->sequence_position = 0;
-    GST_DEBUG ("Setting first sequence at %u", (guint) self->sequence);
-  }
-
-  ret = TRUE;
-out:
-  GST_M3U8_CLIENT_UNLOCK (self);
-  return ret;
-}
-
-static gint
-_find_m3u8_list_match (const GstM3U8 * a, const GstM3U8 * b)
-{
-  if (g_strcmp0 (a->name, b->name) == 0 &&
-      a->bandwidth == b->bandwidth &&
-      a->program_id == b->program_id &&
-      g_strcmp0 (a->codecs, b->codecs) == 0 &&
-      a->width == b->width &&
-      a->height == b->height && a->iframe == b->iframe) {
-    return 0;
-  }
-
-  return 1;
-}
-
-gboolean
-gst_m3u8_client_update_variant_playlist (GstM3U8Client * self, gchar * data,
-    const gchar * uri, const gchar * base_uri)
-{
-  gboolean ret = FALSE;
-  GList *list_entry, *unmatched_lists;
-  GstM3U8Client *new_client;
-  GstM3U8 *old;
-
-  g_return_val_if_fail (self != NULL, FALSE);
-
-  new_client = gst_m3u8_client_new (uri, base_uri);
-  if (gst_m3u8_client_update (new_client, data)) {
-    if (!new_client->main->lists) {
-      GST_ERROR
-          ("Cannot update variant playlist: New playlist is not a variant playlist");
-      gst_m3u8_client_free (new_client);
-      return FALSE;
-    }
-
-    GST_M3U8_CLIENT_LOCK (self);
-
-    if (!self->main->lists) {
-      GST_ERROR
-          ("Cannot update variant playlist: Current playlist is not a variant playlist");
-      goto out;
-    }
-
-    /* Now see if the variant playlist still has the same lists */
-    unmatched_lists = g_list_copy (self->main->lists);
-    for (list_entry = new_client->main->lists; list_entry;
-        list_entry = list_entry->next) {
-      GList *match = g_list_find_custom (unmatched_lists, list_entry->data,
-          (GCompareFunc) _find_m3u8_list_match);
-      if (match)
-        unmatched_lists = g_list_remove_link (unmatched_lists, match);
-    }
-
-    if (unmatched_lists != NULL) {
-      GST_WARNING ("Unable to match all playlists");
-
-      for (list_entry = unmatched_lists; list_entry;
-          list_entry = list_entry->next) {
-        if (list_entry->data == self->current) {
-          GST_WARNING ("Unable to match current playlist");
-        }
-      }
-
-      g_list_free (unmatched_lists);
-    }
-
-    /* Switch out the variant playlist */
-    old = self->main;
-
-    self->main = gst_m3u8_copy (new_client->main);
-    if (self->main->lists)
-      self->current = self->main->current_variant->data;
-    else
-      self->current = self->main;
-
-    gst_m3u8_free (old);
-
-    ret = TRUE;
-
-  out:
-    GST_M3U8_CLIENT_UNLOCK (self);
-  }
-
-  gst_m3u8_client_free (new_client);
-  return ret;
-}
-
-static gboolean
-_find_current (GstM3U8MediaFile * file, GstM3U8Client * client)
-{
-  return file->sequence != client->sequence;
-}
-
-static GList *
-find_next_fragment (GstM3U8Client * client, GList * l, gboolean forward)
-{
-  GstM3U8MediaFile *file;
-
-  if (forward) {
-    while (l) {
-      file = l->data;
-
-      if (file->sequence >= client->sequence)
-        break;
+      if (file->sequence >= m3u8->sequence)
+        break;
 
       l = l->next;
     }
@@ -986,7 +1015,7 @@ find_next_fragment (GstM3U8Client * client, GList * l, gboolean forward)
     while (l) {
       file = l->data;
 
-      if (file->sequence <= client->sequence)
+      if (file->sequence <= m3u8->sequence)
         break;
 
       l = l->prev;
@@ -996,101 +1025,78 @@ find_next_fragment (GstM3U8Client * client, GList * l, gboolean forward)
   return l;
 }
 
-static gboolean
-has_next_fragment (GstM3U8Client * client, GList * l, gboolean forward)
+GstM3U8MediaFile *
+gst_m3u8_get_next_fragment (GstM3U8 * m3u8, gboolean forward,
+    GstClockTime * sequence_position, gboolean * discont)
 {
-  l = find_next_fragment (client, l, forward);
+  GstM3U8MediaFile *file = NULL;
 
-  if (l) {
-    return (forward && l->next) || (!forward && l->prev);
-  }
+  g_return_val_if_fail (m3u8 != NULL, NULL);
 
-  return FALSE;
-}
+  GST_M3U8_LOCK (m3u8);
 
-gboolean
-gst_m3u8_client_get_next_fragment (GstM3U8Client * client,
-    gboolean * discontinuity, gchar ** uri, GstClockTime * duration,
-    GstClockTime * timestamp, gint64 * range_start, gint64 * range_end,
-    gchar ** key, guint8 ** iv, gboolean forward)
-{
-  GstM3U8MediaFile *file;
+  GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
 
-  g_return_val_if_fail (client != NULL, FALSE);
-  g_return_val_if_fail (client->current != NULL, FALSE);
+  if (m3u8->sequence < 0)       /* can't happen really */
+    goto out;
 
-  GST_M3U8_CLIENT_LOCK (client);
-  GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, client->sequence);
-  if (client->sequence < 0) {
-    GST_M3U8_CLIENT_UNLOCK (client);
-    return FALSE;
-  }
-  if (!client->current_file) {
-    client->current_file =
-        find_next_fragment (client, client->current->files, forward);
-  }
+  if (m3u8->current_file == NULL)
+    m3u8->current_file = m3u8_find_next_fragment (m3u8, forward);
 
-  if (!client->current_file) {
-    GST_M3U8_CLIENT_UNLOCK (client);
-    return FALSE;
-  }
+  if (m3u8->current_file == NULL)
+    goto out;
 
-  file = GST_M3U8_MEDIA_FILE (client->current_file->data);
-  GST_DEBUG ("Got fragment with sequence %u (client sequence %u)",
-      (guint) file->sequence, (guint) client->sequence);
+  file = gst_m3u8_media_file_ref (m3u8->current_file->data);
 
-  if (timestamp)
-    *timestamp = client->sequence_position;
+  GST_DEBUG ("Got fragment with sequence %u (current sequence %u)",
+      (guint) file->sequence, (guint) m3u8->sequence);
 
-  if (discontinuity)
-    *discontinuity = client->sequence != file->sequence || file->discont;
-  if (uri)
-    *uri = g_strdup (file->uri);
-  if (duration)
-    *duration = file->duration;
-  if (range_start)
-    *range_start = file->offset;
-  if (range_end)
-    *range_end = file->size != -1 ? file->offset + file->size - 1 : -1;
-  if (key)
-    *key = g_strdup (file->key);
-  if (iv) {
-    *iv = g_new (guint8, sizeof (file->iv));
-    memcpy (*iv, file->iv, sizeof (file->iv));
-  }
+  if (sequence_position)
+    *sequence_position = m3u8->sequence_position;
+  if (discont)
+    *discont = file->discont || (m3u8->sequence != file->sequence);
 
-  client->sequence = file->sequence;
+  m3u8->current_file_duration = file->duration;
+  m3u8->sequence = file->sequence;
 
-  GST_M3U8_CLIENT_UNLOCK (client);
-  return TRUE;
+out:
+
+  GST_M3U8_UNLOCK (m3u8);
+
+  return file;
 }
 
 gboolean
-gst_m3u8_client_has_next_fragment (GstM3U8Client * client, gboolean forward)
+gst_m3u8_has_next_fragment (GstM3U8 * m3u8, gboolean forward)
 {
-  gboolean ret;
-
-  g_return_val_if_fail (client != NULL, FALSE);
-  g_return_val_if_fail (client->current != NULL, FALSE);
-
-  GST_M3U8_CLIENT_LOCK (client);
-  GST_DEBUG ("Checking if has next fragment %" G_GINT64_FORMAT,
-      client->sequence + (forward ? 1 : -1));
-  if (client->current_file) {
-    ret =
-        (forward ? client->current_file->next : client->current_file->prev) !=
-        NULL;
+  gboolean have_next;
+  GList *cur;
+
+  g_return_val_if_fail (m3u8 != NULL, FALSE);
+
+  GST_M3U8_LOCK (m3u8);
+
+  GST_DEBUG ("Checking next fragment %" G_GINT64_FORMAT,
+      m3u8->sequence + (forward ? 1 : -1));
+
+  if (m3u8->current_file) {
+    cur = m3u8->current_file;
   } else {
-    ret = has_next_fragment (client, client->current->files, forward);
+    cur = m3u8_find_next_fragment (m3u8, forward);
   }
-  GST_M3U8_CLIENT_UNLOCK (client);
-  return ret;
+
+  have_next = cur && ((forward && cur->next) || (!forward && cur->prev));
+
+  GST_M3U8_UNLOCK (m3u8);
+
+  return have_next;
 }
 
+/* call with M3U8_LOCK held */
 static void
-alternate_advance (GstM3U8Client * client, gboolean forward)
+m3u8_alternate_advance (GstM3U8 * m3u8, gboolean forward)
 {
-  gint targetnum = client->sequence;
+  gint targetnum = m3u8->sequence;
   GList *tmp;
   GstM3U8MediaFile *mf;
 
@@ -1100,7 +1106,7 @@ alternate_advance (GstM3U8Client * client, gboolean forward)
   else
     targetnum -= 1;
 
-  for (tmp = client->current->files; tmp; tmp = tmp->next) {
+  for (tmp = m3u8->files; tmp; tmp = tmp->next) {
     mf = (GstM3U8MediaFile *) tmp->data;
     if (mf->sequence == targetnum)
       break;
@@ -1109,209 +1115,161 @@ alternate_advance (GstM3U8Client * client, gboolean forward)
     GST_WARNING ("Can't find next fragment");
     return;
   }
-  client->current_file = tmp;
-  client->sequence = targetnum;
-  if (forward)
-    client->sequence_position += mf->duration;
-  else {
-    if (client->sequence_position > mf->duration)
-      client->sequence_position -= mf->duration;
-    else
-      client->sequence_position = 0;
-  }
+  m3u8->current_file = tmp;
+  m3u8->sequence = targetnum;
+  m3u8->current_file_duration = GST_M3U8_MEDIA_FILE (tmp->data)->duration;
 }
 
 void
-gst_m3u8_client_advance_fragment (GstM3U8Client * client, gboolean forward)
+gst_m3u8_advance_fragment (GstM3U8 * m3u8, gboolean forward)
 {
   GstM3U8MediaFile *file;
 
-  g_return_if_fail (client != NULL);
-  g_return_if_fail (client->current != NULL);
+  g_return_if_fail (m3u8 != NULL);
+
+  GST_M3U8_LOCK (m3u8);
 
-  GST_M3U8_CLIENT_LOCK (client);
-  if (!client->current_file) {
+  GST_DEBUG ("Sequence position was %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (m3u8->sequence_position));
+  if (GST_CLOCK_TIME_IS_VALID (m3u8->current_file_duration)) {
+    /* Advance our position based on the previous fragment we played */
+    if (forward)
+      m3u8->sequence_position += m3u8->current_file_duration;
+    else if (m3u8->current_file_duration < m3u8->sequence_position)
+      m3u8->sequence_position -= m3u8->current_file_duration;
+    else
+      m3u8->sequence_position = 0;
+    m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
+    GST_DEBUG ("Sequence position now %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (m3u8->sequence_position));
+  }
+  if (!m3u8->current_file) {
     GList *l;
 
-    GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, client->sequence);
-    l = g_list_find_custom (client->current->files, client,
-        (GCompareFunc) _find_current);
-    if (l == NULL) {
+    GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
+    for (l = m3u8->files; l != NULL; l = l->next) {
+      if (GST_M3U8_MEDIA_FILE (l->data)->sequence == m3u8->sequence) {
+        m3u8->current_file = l;
+        break;
+      }
+    }
+    if (m3u8->current_file == NULL) {
       GST_DEBUG
           ("Could not find current fragment, trying next fragment directly");
-      alternate_advance (client, forward);
-      GST_M3U8_CLIENT_UNLOCK (client);
-      return;
+      m3u8_alternate_advance (m3u8, forward);
+
+      /* Resync sequence number if the above has failed for live streams */
+      if (m3u8->current_file == NULL && GST_M3U8_IS_LIVE (m3u8)) {
+        /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
+           the end of the playlist. See section 6.3.3 of HLS draft */
+        gint pos =
+            g_list_length (m3u8->files) - GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
+        m3u8->current_file = g_list_nth (m3u8->files, pos >= 0 ? pos : 0);
+        m3u8->current_file_duration =
+            GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
+
+        GST_WARNING ("Resyncing live playlist");
+      }
+      goto out;
     }
-    client->current_file = l;
   }
 
-  file = GST_M3U8_MEDIA_FILE (client->current_file->data);
+  file = GST_M3U8_MEDIA_FILE (m3u8->current_file->data);
   GST_DEBUG ("Advancing from sequence %u", (guint) file->sequence);
   if (forward) {
-    client->current_file = client->current_file->next;
-    if (client->current_file) {
-      client->sequence =
-          GST_M3U8_MEDIA_FILE (client->current_file->data)->sequence;
+    m3u8->current_file = m3u8->current_file->next;
+    if (m3u8->current_file) {
+      m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
     } else {
-      client->sequence = file->sequence + 1;
+      m3u8->sequence = file->sequence + 1;
     }
-
-    client->sequence_position += file->duration;
   } else {
-    client->current_file = client->current_file->prev;
-    if (client->current_file) {
-      client->sequence =
-          GST_M3U8_MEDIA_FILE (client->current_file->data)->sequence;
+    m3u8->current_file = m3u8->current_file->prev;
+    if (m3u8->current_file) {
+      m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
     } else {
-      client->sequence = file->sequence - 1;
+      m3u8->sequence = file->sequence - 1;
     }
-
-    if (client->sequence_position > file->duration)
-      client->sequence_position -= file->duration;
-    else
-      client->sequence_position = 0;
   }
-  GST_M3U8_CLIENT_UNLOCK (client);
-}
+  if (m3u8->current_file) {
+    /* Store duration of the fragment we're using to update the position
+     * the next time we advance */
+    m3u8->current_file_duration =
+        GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
+  }
 
-static void
-_sum_duration (GstM3U8MediaFile * self, GstClockTime * duration)
-{
-  *duration += self->duration;
+out:
+
+  GST_M3U8_UNLOCK (m3u8);
 }
 
 GstClockTime
-gst_m3u8_client_get_duration (GstM3U8Client * client)
+gst_m3u8_get_duration (GstM3U8 * m3u8)
 {
   GstClockTime duration = GST_CLOCK_TIME_NONE;
 
-  g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
+  g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
+
+  GST_M3U8_LOCK (m3u8);
 
-  GST_M3U8_CLIENT_LOCK (client);
   /* We can only get the duration for on-demand streams */
-  if (!client->current || !client->current->endlist) {
-    GST_M3U8_CLIENT_UNLOCK (client);
-    return GST_CLOCK_TIME_NONE;
-  }
+  if (!m3u8->endlist)
+    goto out;
 
-  if (!GST_CLOCK_TIME_IS_VALID (client->duration) && client->current->files) {
-    client->duration = 0;
-    g_list_foreach (client->current->files, (GFunc) _sum_duration,
-        &client->duration);
-  }
-  duration = client->duration;
-  GST_M3U8_CLIENT_UNLOCK (client);
+  if (!GST_CLOCK_TIME_IS_VALID (m3u8->duration) && m3u8->files != NULL) {
+    GList *f;
 
-  return duration;
-}
+    m3u8->duration = 0;
+    for (f = m3u8->files; f != NULL; f = f->next)
+      m3u8->duration += GST_M3U8_MEDIA_FILE (f)->duration;
+  }
+  duration = m3u8->duration;
 
-GstClockTime
-gst_m3u8_client_get_target_duration (GstM3U8Client * client)
-{
-  GstClockTime duration = 0;
+out:
 
-  g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
+  GST_M3U8_UNLOCK (m3u8);
 
-  GST_M3U8_CLIENT_LOCK (client);
-  duration = client->current->targetduration;
-  GST_M3U8_CLIENT_UNLOCK (client);
   return duration;
 }
 
-gchar *
-gst_m3u8_client_get_uri (GstM3U8Client * client)
+GstClockTime
+gst_m3u8_get_target_duration (GstM3U8 * m3u8)
 {
-  gchar *uri;
+  GstClockTime target_duration;
 
-  g_return_val_if_fail (client != NULL, NULL);
+  g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
 
-  GST_M3U8_CLIENT_LOCK (client);
-  uri = client->main ? g_strdup (client->main->uri) : NULL;
-  GST_M3U8_CLIENT_UNLOCK (client);
-  return uri;
+  GST_M3U8_LOCK (m3u8);
+  target_duration = m3u8->targetduration;
+  GST_M3U8_UNLOCK (m3u8);
+
+  return target_duration;
 }
 
 gchar *
-gst_m3u8_client_get_current_uri (GstM3U8Client * client)
+gst_m3u8_get_uri (GstM3U8 * m3u8)
 {
   gchar *uri;
 
-  g_return_val_if_fail (client != NULL, NULL);
+  GST_M3U8_LOCK (m3u8);
+  uri = g_strdup (m3u8->uri);
+  GST_M3U8_UNLOCK (m3u8);
 
-  GST_M3U8_CLIENT_LOCK (client);
-  uri = g_strdup (client->current->uri);
-  GST_M3U8_CLIENT_UNLOCK (client);
   return uri;
 }
 
 gboolean
-gst_m3u8_client_has_main (GstM3U8Client * client)
-{
-  gboolean ret;
-
-  g_return_val_if_fail (client != NULL, FALSE);
-
-  GST_M3U8_CLIENT_LOCK (client);
-  if (client->main)
-    ret = TRUE;
-  else
-    ret = FALSE;
-  GST_M3U8_CLIENT_UNLOCK (client);
-  return ret;
-}
-
-gboolean
-gst_m3u8_client_has_variant_playlist (GstM3U8Client * client)
-{
-  gboolean ret;
-
-  g_return_val_if_fail (client != NULL, FALSE);
-
-  GST_M3U8_CLIENT_LOCK (client);
-  ret = (client->main->lists != NULL);
-  GST_M3U8_CLIENT_UNLOCK (client);
-  return ret;
-}
-
-gboolean
-gst_m3u8_client_is_live (GstM3U8Client * client)
-{
-  gboolean ret;
-
-  g_return_val_if_fail (client != NULL, FALSE);
-
-  GST_M3U8_CLIENT_LOCK (client);
-  ret = GST_M3U8_CLIENT_IS_LIVE (client);
-  GST_M3U8_CLIENT_UNLOCK (client);
-  return ret;
-}
-
-GList *
-gst_m3u8_client_get_playlist_for_bitrate (GstM3U8Client * client, guint bitrate)
+gst_m3u8_is_live (GstM3U8 * m3u8)
 {
-  GList *list, *current_variant;
+  gboolean is_live;
 
-  GST_M3U8_CLIENT_LOCK (client);
-  current_variant = client->main->current_variant;
-
-  /*  Go to the highest possible bandwidth allowed */
-  while (GST_M3U8 (current_variant->data)->bandwidth <= bitrate) {
-    list = g_list_next (current_variant);
-    if (!list)
-      break;
-    current_variant = list;
-  }
+  g_return_val_if_fail (m3u8 != NULL, FALSE);
 
-  while (GST_M3U8 (current_variant->data)->bandwidth > bitrate) {
-    list = g_list_previous (current_variant);
-    if (!list)
-      break;
-    current_variant = list;
-  }
-  GST_M3U8_CLIENT_UNLOCK (client);
+  GST_M3U8_LOCK (m3u8);
+  is_live = GST_M3U8_IS_LIVE (m3u8);
+  GST_M3U8_UNLOCK (m3u8);
 
-  return current_variant;
+  return is_live;
 }
 
 gchar *
@@ -1370,64 +1328,931 @@ out:
   return ret;
 }
 
-guint64
-gst_m3u8_client_get_current_fragment_duration (GstM3U8Client * client)
+gboolean
+gst_m3u8_get_seek_range (GstM3U8 * m3u8, gint64 * start, gint64 * stop)
 {
-  guint64 dur;
-  GList *list;
+  GstClockTime duration = 0;
+  GList *walk;
+  GstM3U8MediaFile *file;
+  guint count;
+  guint min_distance = 0;
 
-  g_return_val_if_fail (client != NULL, 0);
+  g_return_val_if_fail (m3u8 != NULL, FALSE);
 
-  GST_M3U8_CLIENT_LOCK (client);
+  GST_M3U8_LOCK (m3u8);
 
-  list = g_list_find_custom (client->current->files, client,
-      (GCompareFunc) _find_current);
-  if (list == NULL) {
-    dur = -1;
-  } else {
-    dur = GST_M3U8_MEDIA_FILE (list->data)->duration;
+  if (m3u8->files == NULL)
+    goto out;
+
+  if (GST_M3U8_IS_LIVE (m3u8)) {
+    /* min_distance is used to make sure the seek range is never closer than
+       GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE fragments from the end of a live
+       playlist - see 6.3.3. "Playing the Playlist file" of the HLS draft */
+    min_distance = GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
   }
+  count = g_list_length (m3u8->files);
 
-  GST_M3U8_CLIENT_UNLOCK (client);
-  return dur;
+  for (walk = m3u8->files; walk && count > min_distance; walk = walk->next) {
+    file = walk->data;
+    --count;
+    duration += file->duration;
+  }
+
+  if (duration <= 0)
+    goto out;
+
+  *start = m3u8->first_file_start;
+  *stop = *start + duration;
+
+out:
+
+  GST_M3U8_UNLOCK (m3u8);
+  return (duration > 0);
 }
 
-gboolean
-gst_m3u8_client_get_seek_range (GstM3U8Client * client, gint64 * start,
-    gint64 * stop)
+GstHLSMedia *
+gst_hls_media_ref (GstHLSMedia * media)
 {
-  GstClockTime duration = 0;
-  GList *walk;
-  GstM3U8MediaFile *file;
-  guint count;
+  g_assert (media != NULL && media->ref_count > 0);
+  g_atomic_int_add (&media->ref_count, 1);
+  return media;
+}
 
-  g_return_val_if_fail (client != NULL, FALSE);
+void
+gst_hls_media_unref (GstHLSMedia * media)
+{
+  g_assert (media != NULL && media->ref_count > 0);
+  if (g_atomic_int_dec_and_test (&media->ref_count)) {
+    if (media->playlist)
+      gst_m3u8_unref (media->playlist);
+    g_free (media->group_id);
+    g_free (media->name);
+    g_free (media->uri);
+    g_free (media->lang);
+    g_free (media);
+  }
+}
 
-  GST_M3U8_CLIENT_LOCK (client);
+static GstHLSMediaType
+gst_m3u8_get_hls_media_type_from_string (const gchar * type_name)
+{
+  if (strcmp (type_name, "AUDIO") == 0)
+    return GST_HLS_MEDIA_TYPE_AUDIO;
+  if (strcmp (type_name, "VIDEO") == 0)
+    return GST_HLS_MEDIA_TYPE_VIDEO;
+  if (strcmp (type_name, "SUBTITLES") == 0)
+    return GST_HLS_MEDIA_TYPE_SUBTITLES;
+  if (strcmp (type_name, "CLOSED_CAPTIONS") == 0)
+    return GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS;
+
+  return GST_HLS_MEDIA_TYPE_INVALID;
+}
 
-  if (client->current == NULL || client->current->files == NULL) {
-    GST_M3U8_CLIENT_UNLOCK (client);
-    return FALSE;
+#define GST_HLS_MEDIA_TYPE_NAME(mtype) gst_hls_media_type_get_name(mtype)
+const gchar *
+gst_hls_media_type_get_name (GstHLSMediaType mtype)
+{
+  static const gchar *nicks[GST_HLS_N_MEDIA_TYPES] = { "audio", "video",
+    "subtitle", "closed-captions"
+  };
+
+  if (mtype < 0 || mtype >= GST_HLS_N_MEDIA_TYPES)
+    return "invalid";
+
+  return nicks[mtype];
+}
+
+/* returns unquoted copy of string */
+static gchar *
+gst_m3u8_unquote (const gchar * str)
+{
+  const gchar *start, *end;
+
+  start = strchr (str, '"');
+  if (start == NULL)
+    return g_strdup (str);
+  end = strchr (start + 1, '"');
+  if (end == NULL) {
+    GST_WARNING ("Broken quoted string [%s] - can't find end quote", str);
+    return g_strdup (start + 1);
   }
+  return g_strndup (start + 1, (gsize) (end - (start + 1)));
+}
 
-  count = g_list_length (client->current->files);
+static GstHLSMedia *
+gst_m3u8_parse_media (gchar * desc, const gchar * base_uri)
+{
+  GstHLSMedia *media;
+  gchar *a, *v;
+
+  media = g_new0 (GstHLSMedia, 1);
+  media->ref_count = 1;
+  media->playlist = gst_m3u8_new ();
+  media->mtype = GST_HLS_MEDIA_TYPE_INVALID;
+
+  GST_LOG ("parsing %s", desc);
+  while (desc != NULL && parse_attributes (&desc, &a, &v)) {
+    if (strcmp (a, "TYPE") == 0) {
+      media->mtype = gst_m3u8_get_hls_media_type_from_string (v);
+    } else if (strcmp (a, "GROUP-ID") == 0) {
+      g_free (media->group_id);
+      media->group_id = gst_m3u8_unquote (v);
+    } else if (strcmp (a, "NAME") == 0) {
+      g_free (media->name);
+      media->name = gst_m3u8_unquote (v);
+    } else if (strcmp (a, "URI") == 0) {
+      gchar *uri;
+
+      g_free (media->uri);
+      uri = gst_m3u8_unquote (v);
+      media->uri = uri_join (base_uri, uri);
+      g_free (uri);
+    } else if (strcmp (a, "LANGUAGE") == 0) {
+      g_free (media->lang);
+      media->lang = gst_m3u8_unquote (v);
+    } else if (strcmp (a, "DEFAULT") == 0) {
+      media->is_default = g_ascii_strcasecmp (v, "yes") == 0;
+    } else if (strcmp (a, "FORCED") == 0) {
+      media->forced = g_ascii_strcasecmp (v, "yes") == 0;
+    } else if (strcmp (a, "AUTOSELECT") == 0) {
+      media->autoselect = g_ascii_strcasecmp (v, "yes") == 0;
+    } else {
+      /* unhandled: ASSOC-LANGUAGE, INSTREAM-ID, CHARACTERISTICS */
+      GST_FIXME ("EXT-X-MEDIA: unhandled attribute: %s = %s", a, v);
+    }
+  }
 
-  /* count is used to make sure the seek range is never closer than
-     GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE fragments from the end of the
-     playlist - see 6.3.3. "Playing the Playlist file" of the HLS draft */
-  for (walk = client->current->files;
-      walk && count >= GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE; walk = walk->next) {
-    file = walk->data;
-    --count;
-    duration += file->duration;
+  if (media->mtype == GST_HLS_MEDIA_TYPE_INVALID)
+    goto required_attributes_missing;
+
+#ifndef TIZEN_FEATURE_HLSDEMUX_LANG_TAG
+  if (media->uri == NULL)
+    goto existing_stream;
+#endif
+
+  if (media->group_id == NULL || media->name == NULL)
+    goto required_attributes_missing;
+
+#ifndef TIZEN_FEATURE_HLSDEMUX_LANG_TAG
+  if (media->mtype == GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS)
+    goto uri_with_cc;
+#endif
+
+  GST_DEBUG ("media: %s, group '%s', name '%s', uri '%s', %s %s %s, lang=%s",
+      GST_HLS_MEDIA_TYPE_NAME (media->mtype), media->group_id, media->name,
+      media->uri, media->is_default ? "default" : "-",
+      media->autoselect ? "autoselect" : "-",
+      media->forced ? "forced" : "-", media->lang ? media->lang : "??");
+
+  return media;
+
+#ifndef TIZEN_FEATURE_HLSDEMUX_LANG_TAG
+uri_with_cc:
+  {
+    GST_WARNING ("closed captions EXT-X-MEDIA should not have URI specified");
+    goto out_error;
+  }
+#endif
+required_attributes_missing:
+  {
+    GST_WARNING ("EXT-X-MEDIA description is missing required attributes");
+    goto out_error;
+    /* fall through */
+  }
+#ifndef TIZEN_FEATURE_HLSDEMUX_LANG_TAG
+existing_stream:
+  {
+    GST_DEBUG ("EXT-X-MEDIA without URI, describes embedded stream, skipping");
+    /* fall through */
+  }
+#endif
+
+out_error:
+  {
+    gst_hls_media_unref (media);
+    return NULL;
+  }
+}
+
+static GstHLSVariantStream *
+gst_hls_variant_stream_new (void)
+{
+  GstHLSVariantStream *stream;
+
+  stream = g_new0 (GstHLSVariantStream, 1);
+  stream->m3u8 = gst_m3u8_new ();
+  stream->refcount = 1;
+  return stream;
+}
+
+GstHLSVariantStream *
+gst_hls_variant_stream_ref (GstHLSVariantStream * stream)
+{
+  g_atomic_int_inc (&stream->refcount);
+  return stream;
+}
+
+void
+gst_hls_variant_stream_unref (GstHLSVariantStream * stream)
+{
+  if (g_atomic_int_dec_and_test (&stream->refcount)) {
+    gint i;
+
+    g_free (stream->name);
+    g_free (stream->uri);
+    g_free (stream->codecs);
+    gst_m3u8_unref (stream->m3u8);
+    for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
+      g_free (stream->media_groups[i]);
+      g_list_free_full (stream->media[i], (GDestroyNotify) gst_hls_media_unref);
+    }
+    g_free (stream);
+  }
+}
+
+static GstHLSVariantStream *
+find_variant_stream_by_name (GList * list, const gchar * name)
+{
+  for (; list != NULL; list = list->next) {
+    GstHLSVariantStream *variant_stream = list->data;
+
+    if (variant_stream->name != NULL && !strcmp (variant_stream->name, name))
+      return variant_stream;
+  }
+  return NULL;
+}
+
+static GstHLSVariantStream *
+find_variant_stream_by_uri (GList * list, const gchar * uri)
+{
+  for (; list != NULL; list = list->next) {
+    GstHLSVariantStream *variant_stream = list->data;
+
+    if (variant_stream->uri != NULL && !strcmp (variant_stream->uri, uri))
+      return variant_stream;
+  }
+  return NULL;
+}
+
+static GstHLSMasterPlaylist *
+gst_hls_master_playlist_new (void)
+{
+  GstHLSMasterPlaylist *playlist;
+
+  playlist = g_new0 (GstHLSMasterPlaylist, 1);
+  playlist->refcount = 1;
+  playlist->is_simple = FALSE;
+
+  return playlist;
+}
+
+void
+gst_hls_master_playlist_unref (GstHLSMasterPlaylist * playlist)
+{
+  if (g_atomic_int_dec_and_test (&playlist->refcount)) {
+    g_list_free_full (playlist->variants,
+        (GDestroyNotify) gst_hls_variant_stream_unref);
+    g_list_free_full (playlist->iframe_variants,
+        (GDestroyNotify) gst_hls_variant_stream_unref);
+#ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
+    g_list_free_full (playlist->variant_info, g_free);
+#endif
+    if (playlist->default_variant)
+      gst_hls_variant_stream_unref (playlist->default_variant);
+    g_free (playlist->last_data);
+    g_free (playlist);
+  }
+}
+
+static gint
+hls_media_name_compare_func (gconstpointer media, gconstpointer name)
+{
+  return strcmp (((GstHLSMedia *) media)->name, (const gchar *) name);
+}
+
+/* Takes ownership of @data */
+GstHLSMasterPlaylist *
+gst_hls_master_playlist_new_from_data (gchar * data, const gchar * base_uri)
+{
+  GHashTable *media_groups[GST_HLS_N_MEDIA_TYPES] = { NULL, };
+  GstHLSMasterPlaylist *playlist;
+  GstHLSVariantStream *pending_stream;
+  gchar *end, *free_data = data;
+  gint val, i;
+  GList *l;
+
+  if (!g_str_has_prefix (data, "#EXTM3U")) {
+    GST_WARNING ("Data doesn't start with #EXTM3U");
+    g_free (free_data);
+    return NULL;
+  }
+
+  playlist = gst_hls_master_playlist_new ();
+
+  /* store data before we modify it for parsing */
+  playlist->last_data = g_strdup (data);
+
+  GST_TRACE ("data:\n%s", data);
+
+  if (strstr (data, "\n#EXTINF:") != NULL) {
+    GST_INFO ("This is a simple media playlist, not a master playlist");
+
+    pending_stream = gst_hls_variant_stream_new ();
+    pending_stream->name = g_strdup (base_uri);
+    pending_stream->uri = g_strdup (base_uri);
+    gst_m3u8_set_uri (pending_stream->m3u8, base_uri, NULL, base_uri);
+    playlist->variants = g_list_append (playlist->variants, pending_stream);
+    playlist->default_variant = gst_hls_variant_stream_ref (pending_stream);
+    playlist->is_simple = TRUE;
+
+    if (!gst_m3u8_update (pending_stream->m3u8, data)) {
+      GST_WARNING ("Failed to parse media playlist");
+      gst_hls_master_playlist_unref (playlist);
+      playlist = NULL;
+    }
+    return playlist;
+  }
+
+  pending_stream = NULL;
+  data += 7;
+  while (TRUE) {
+    gchar *r;
+
+    end = g_utf8_strchr (data, -1, '\n');
+    if (end)
+      *end = '\0';
+
+    r = g_utf8_strchr (data, -1, '\r');
+    if (r)
+      *r = '\0';
+
+    if (data[0] != '#' && data[0] != '\0') {
+      gchar *name, *uri;
+
+      if (pending_stream == NULL) {
+        GST_LOG ("%s: got line without EXT-STREAM-INF, dropping", data);
+        goto next_line;
+      }
+
+      name = data;
+      uri = uri_join (base_uri, name);
+      if (uri == NULL)
+        goto next_line;
+
+      pending_stream->name = g_strdup (name);
+      pending_stream->uri = uri;
+
+      if (find_variant_stream_by_name (playlist->variants, name)
+          || find_variant_stream_by_uri (playlist->variants, uri)) {
+        GST_DEBUG ("Already have a list with this name or URI: %s", name);
+        gst_hls_variant_stream_unref (pending_stream);
+      } else {
+        GST_INFO ("stream %s @ %u: %s", name, pending_stream->bandwidth, uri);
+        gst_m3u8_set_uri (pending_stream->m3u8, uri, NULL, name);
+        playlist->variants = g_list_append (playlist->variants, pending_stream);
+        /* use first stream in the playlist as default */
+        if (playlist->default_variant == NULL) {
+          playlist->default_variant =
+              gst_hls_variant_stream_ref (pending_stream);
+        }
+      }
+      pending_stream = NULL;
+    } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
+      if (int_from_string (data + 15, &data, &val))
+        playlist->version = val;
+    } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:") ||
+        g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:")) {
+      GstHLSVariantStream *stream;
+      gchar *v, *a;
+
+      stream = gst_hls_variant_stream_new ();
+      stream->iframe = g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:");
+      data += stream->iframe ? 26 : 18;
+      while (data && parse_attributes (&data, &a, &v)) {
+        if (g_str_equal (a, "BANDWIDTH")) {
+          if (!stream->bandwidth) {
+            if (!int_from_string (v, NULL, &stream->bandwidth))
+              GST_WARNING ("Error while reading BANDWIDTH");
+          }
+        } else if (g_str_equal (a, "AVERAGE-BANDWIDTH")) {
+          GST_DEBUG
+              ("AVERAGE-BANDWIDTH attribute available. Using it as stream bandwidth");
+          if (!int_from_string (v, NULL, &stream->bandwidth))
+            GST_WARNING ("Error while reading AVERAGE-BANDWIDTH");
+        } else if (g_str_equal (a, "PROGRAM-ID")) {
+          if (!int_from_string (v, NULL, &stream->program_id))
+            GST_WARNING ("Error while reading PROGRAM-ID");
+        } else if (g_str_equal (a, "CODECS")) {
+          g_free (stream->codecs);
+          stream->codecs = g_strdup (v);
+        } else if (g_str_equal (a, "RESOLUTION")) {
+          if (!int_from_string (v, &v, &stream->width))
+            GST_WARNING ("Error while reading RESOLUTION width");
+          if (!v || *v != 'x') {
+            GST_WARNING ("Missing height");
+          } else {
+            v = g_utf8_next_char (v);
+            if (!int_from_string (v, NULL, &stream->height))
+              GST_WARNING ("Error while reading RESOLUTION height");
+          }
+        } else if (stream->iframe && g_str_equal (a, "URI")) {
+          stream->uri = uri_join (base_uri, v);
+          if (stream->uri != NULL) {
+            stream->name = g_strdup (stream->uri);
+            gst_m3u8_set_uri (stream->m3u8, stream->uri, NULL, stream->name);
+          } else {
+            gst_hls_variant_stream_unref (stream);
+          }
+        } else if (g_str_equal (a, "AUDIO")) {
+          g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO]);
+          stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO] = gst_m3u8_unquote (v);
+        } else if (g_str_equal (a, "SUBTITLES")) {
+          g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES]);
+          stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES] =
+              gst_m3u8_unquote (v);
+        } else if (g_str_equal (a, "VIDEO")) {
+          g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO]);
+          stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO] = gst_m3u8_unquote (v);
+        } else if (g_str_equal (a, "CLOSED-CAPTIONS")) {
+          /* closed captions will be embedded inside the video stream, ignore */
+        }
+      }
+
+      if (stream->iframe) {
+        if (find_variant_stream_by_uri (playlist->iframe_variants, stream->uri)) {
+          GST_DEBUG ("Already have a list with this URI");
+          gst_hls_variant_stream_unref (stream);
+        } else {
+          playlist->iframe_variants =
+              g_list_append (playlist->iframe_variants, stream);
+        }
+      } else {
+        if (pending_stream != NULL) {
+          GST_WARNING ("variant stream without uri, dropping");
+          gst_hls_variant_stream_unref (pending_stream);
+        }
+        pending_stream = stream;
+      }
+    } else if (g_str_has_prefix (data, "#EXT-X-MEDIA:")) {
+      GstHLSMedia *media;
+      GList *list;
+
+      media = gst_m3u8_parse_media (data + strlen ("#EXT-X-MEDIA:"), base_uri);
+
+      if (media == NULL)
+        goto next_line;
+
+      if (media_groups[media->mtype] == NULL) {
+        media_groups[media->mtype] =
+            g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+      }
+
+      list = g_hash_table_lookup (media_groups[media->mtype], media->group_id);
+
+      /* make sure there isn't already a media with the same name */
+      if (!g_list_find_custom (list, media->name, hls_media_name_compare_func)) {
+        g_hash_table_replace (media_groups[media->mtype],
+            g_strdup (media->group_id), g_list_append (list, media));
+        GST_INFO ("Added media %s to group %s", media->name, media->group_id);
+      } else {
+        GST_WARNING ("  media with name '%s' already exists in group '%s'!",
+            media->name, media->group_id);
+        gst_hls_media_unref (media);
+      }
+    } else if (*data != '\0') {
+      GST_LOG ("Ignored line: %s", data);
+    }
+
+  next_line:
+    if (!end)
+      break;
+    data = g_utf8_next_char (end);      /* skip \n */
+  }
+
+  if (pending_stream != NULL) {
+    GST_WARNING ("#EXT-X-STREAM-INF without uri, dropping");
+    gst_hls_variant_stream_unref (pending_stream);
+  }
+
+  g_free (free_data);
+
+  /* Add alternative renditions media to variant streams */
+  for (l = playlist->variants; l != NULL; l = l->next) {
+    GstHLSVariantStream *stream = l->data;
+    GList *mlist;
+
+    for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
+      if (stream->media_groups[i] != NULL && media_groups[i] != NULL) {
+        GST_INFO ("Adding %s group '%s' to stream '%s'",
+            GST_HLS_MEDIA_TYPE_NAME (i), stream->media_groups[i], stream->name);
+
+        mlist = g_hash_table_lookup (media_groups[i], stream->media_groups[i]);
+
+        if (mlist == NULL)
+          GST_WARNING ("Group '%s' does not exist!", stream->media_groups[i]);
+
+        while (mlist != NULL) {
+          GstHLSMedia *media = mlist->data;
+
+          GST_DEBUG ("  %s media %s, uri: %s", GST_HLS_MEDIA_TYPE_NAME (i),
+              media->name, media->uri);
+
+          stream->media[i] =
+              g_list_append (stream->media[i], gst_hls_media_ref (media));
+          mlist = mlist->next;
+        }
+      }
+    }
+  }
+
+  /* clean up our temporary alternative rendition groups hash tables */
+  for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
+    if (media_groups[i] != NULL) {
+      GList *groups, *mlist;
+
+      groups = g_hash_table_get_keys (media_groups[i]);
+      for (l = groups; l != NULL; l = l->next) {
+        mlist = g_hash_table_lookup (media_groups[i], l->data);
+        g_list_free_full (mlist, (GDestroyNotify) gst_hls_media_unref);
+      }
+      g_list_free (groups);
+      g_hash_table_unref (media_groups[i]);
+    }
+  }
+
+  if (playlist->variants == NULL) {
+    GST_WARNING ("Master playlist without any media playlists!");
+    gst_hls_master_playlist_unref (playlist);
+    return NULL;
+  }
+
+  /* reorder variants by bitrate */
+  playlist->variants =
+      g_list_sort (playlist->variants,
+      (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
+
+  playlist->iframe_variants =
+      g_list_sort (playlist->iframe_variants,
+      (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
+
+  /* FIXME: restore old current_variant after master playlist update
+   * (move into code that does that update) */
+#if 0
+  {
+    gchar *top_variant_uri = NULL;
+    gboolean iframe = FALSE;
+
+    if (!self->current_variant) {
+      top_variant_uri = GST_M3U8 (self->lists->data)->uri;
+    } else {
+      top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
+      iframe = GST_M3U8 (self->current_variant->data)->iframe;
+    }
+
+    /* here we sorted the lists */
+
+    if (iframe)
+      playlist->current_variant =
+          find_variant_stream_by_uri (playlist->iframe_variants,
+          top_variant_uri);
+    else
+      playlist->current_variant =
+          find_variant_stream_by_uri (playlist->variants, top_variant_uri);
   }
+#endif
+
+  GST_DEBUG ("parsed master playlist with %d streams and %d I-frame streams",
+      g_list_length (playlist->variants),
+      g_list_length (playlist->iframe_variants));
+
+#ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
+    GList *v = (playlist->iframe_variants)?(playlist->iframe_variants):(playlist->variants);
+
+    /* update variant stream info */
+    for (; v != NULL; v = v->next) {
+      GstHLSVariantStream *data = v->data;
+      GstM3U8VideoVariantInfo *var_info = g_new0 (GstM3U8VideoVariantInfo, 1);
+
+      GST_LOG ("variant info %d, %d x %d", data->bandwidth, data->width,
+          data->height);
+      var_info->bandwidth = data->bandwidth;
+      var_info->width = data->width;
+      var_info->height = data->height;
+
+      playlist->variant_info = g_list_append (playlist->variant_info, var_info);
+    }
+#endif
+
+  return playlist;
+}
+
+gboolean
+gst_hls_variant_stream_is_live (GstHLSVariantStream * variant)
+{
+  gboolean is_live;
+
+  g_return_val_if_fail (variant != NULL, FALSE);
+
+  is_live = gst_m3u8_is_live (variant->m3u8);
+
+  return is_live;
+}
+
+static gint
+compare_media (const GstHLSMedia * a, const GstHLSMedia * b)
+{
+  return strcmp (a->name, b->name);
+}
+
+GstHLSMedia *
+gst_hls_variant_find_matching_media (GstHLSVariantStream * stream,
+    GstHLSMedia * media)
+{
+  GList *mlist = stream->media[media->mtype];
+  GList *match;
+
+  if (mlist == NULL)
+    return NULL;
+
+  match = g_list_find_custom (mlist, media, (GCompareFunc) compare_media);
+  if (match == NULL)
+    return NULL;
+
+  return match->data;
+}
+
+#ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
+static guint
+get_num_of_codec(GstHLSVariantStream * variant)
+{
+#define MAX_NUM_OF_CODEC 10
+
+  guint cnt = 0;
+  gchar** codec_list = NULL;
+
+  if (!variant || !variant->codecs)
+    return 0;
+
+  codec_list = g_strsplit (variant->codecs, ",", MAX_NUM_OF_CODEC);
+  if (codec_list) {
+    cnt = g_strv_length (codec_list);
+    g_strfreev (codec_list);
+  }
+
+  return cnt;
+}
+
+static gboolean
+check_num_of_codec(GstHLSVariantStream * variant, guint req_num)
+{
+  guint num_of_codec = 0;
 
-  if (duration <= 0) {
-    GST_M3U8_CLIENT_UNLOCK (client);
+  if (!variant)
+    return FALSE;
+
+  num_of_codec = get_num_of_codec (variant);
+
+  if (num_of_codec > 0 && req_num > 0 && num_of_codec != req_num) {
+    GST_WARNING ("can not support to change codec");
     return FALSE;
   }
-  *start = client->first_file_start;
-  *stop = *start + duration;
-  GST_M3U8_CLIENT_UNLOCK (client);
+
   return TRUE;
 }
+
+static gint
+update_max_limit(gint min_limit, gint max_limit, gint start_limit, gint bitrate)
+{
+  if (start_limit > DEFAULT_BANDWIDTH_LIMIT && start_limit >= min_limit &&
+      (start_limit <= max_limit || max_limit == DEFAULT_BANDWIDTH_LIMIT)) {
+    GST_DEBUG ("set max to start : %d ", start_limit);
+    return start_limit;
+  }
+
+  if (bitrate > 0 && bitrate >= min_limit &&
+      (bitrate <= max_limit || max_limit == DEFAULT_BANDWIDTH_LIMIT)) {
+    GST_DEBUG ("set max to bitrate : %d ", bitrate);
+    return bitrate;
+  }
+
+  if (bitrate == 0 || bitrate < min_limit) {
+    GST_DEBUG ("set max to min : %d", min_limit);
+    return min_limit;
+  }
+
+  return max_limit;
+}
+
+GstHLSVariantStream *
+get_average_variant(GList *variants, guint num_of_codec, gint min_limit, gint max_limit)
+{
+  GList *l = NULL;
+  GList *valid_list = NULL;
+  gint cnt = 0;
+  guint num_of_valid_variant = 0;
+  GstHLSVariantStream *tmp = NULL;
+
+  for (l = g_list_first (variants); l; l = g_list_next (l)) {
+    if (!check_num_of_codec ((GstHLSVariantStream *)l->data, num_of_codec))
+      continue;
+
+    tmp = l->data;
+
+    if (max_limit != DEFAULT_BANDWIDTH_LIMIT && tmp->bandwidth > max_limit) {
+      GST_DEBUG ("over max limit");
+      break;
+    }
+
+    if (min_limit != DEFAULT_BANDWIDTH_LIMIT && tmp->bandwidth < min_limit) {
+      GST_DEBUG ("skip to next");
+      continue;
+    }
+
+    num_of_valid_variant++;
+    valid_list = l;
+  }
+
+  GST_DEBUG ("num of valid variant %d / %d", num_of_valid_variant, g_list_length (variants));
+
+  for (; valid_list; valid_list = g_list_previous (valid_list)) {
+    if (!check_num_of_codec ((GstHLSVariantStream *)valid_list->data, num_of_codec))
+      continue;
+
+    tmp = valid_list->data;
+    if (num_of_valid_variant/2 == cnt) {
+      GST_DEBUG ("get this stream %d", tmp->bandwidth);
+      return tmp;
+    }
+    cnt++;
+  }
+  return NULL;
+}
+
+GstHLSVariantStream *
+gst_hls_master_playlist_get_variant_for_bandwitdh_limit (GstHLSMasterPlaylist * playlist,
+  GstHLSVariantStream * current_variant, guint bitrate, gchar * start_bandwidth,
+  gint min_bandwidth, gint max_bandwidth, gint width, gint height)
+{
+  GstHLSVariantStream *tmp = current_variant;
+  GstHLSVariantStream *variant = NULL;
+  GstHLSVariantStream *min_variant = NULL; // lowest
+  GstHLSVariantStream *max_variant = NULL; // highest
+  GList *variants = NULL;
+  GList *l = NULL;
+  gint max_limit = DEFAULT_BANDWIDTH_LIMIT;
+  gint min_limit = DEFAULT_BANDWIDTH_LIMIT;
+  gint start_limit = DEFAULT_BANDWIDTH_LIMIT;
+  gint adj_max_limit = DEFAULT_BANDWIDTH_LIMIT;
+  guint num_of_valid_variant = 0;
+  guint num_of_codec = 0;
+
+  num_of_codec = get_num_of_codec (current_variant);
+
+  GST_DEBUG ("bitrate: %u, bandwidth: %s, %d ~ %d, resolution: %d X %d",
+      bitrate, start_bandwidth, min_bandwidth, max_bandwidth, width, height);
+
+  /* get variant list */
+  if (current_variant == NULL || !current_variant->iframe)
+    variants = playlist->variants;
+  else
+    variants = playlist->iframe_variants;
+
+  if (!variants) {
+    GST_ERROR ("invalid playlist");
+    return current_variant;
+  }
+
+  /* get valid min/max variant */
+  for (l = g_list_first (variants); l; l = g_list_next (l)) {
+    if (!check_num_of_codec ((GstHLSVariantStream *)l->data, num_of_codec))
+      continue;
+    tmp = l->data;
+    num_of_valid_variant++;
+
+    if (!min_variant) {
+      min_variant = tmp;
+    }
+  }
+  max_variant = tmp;
+
+  GST_DEBUG("num of valid variant %d / %d", num_of_valid_variant, g_list_length (variants));
+  if (num_of_valid_variant <= 1)
+    return tmp;
+
+  /* get valid range limit */
+  if (max_bandwidth == DEFAULT_BANDWIDTH_LIMIT || min_bandwidth <= max_bandwidth) {
+    if (min_variant->bandwidth <= max_bandwidth)
+      max_limit = adj_max_limit = max_bandwidth;
+
+    if (max_variant->bandwidth >= min_bandwidth)
+      min_limit = min_bandwidth;
+  }
+
+  GST_DEBUG ("range limit: %d ~ %d", min_limit, max_limit);
+
+  if (start_bandwidth) {
+    if (!g_strcmp0 (start_bandwidth, "LOWEST")) {
+      if (min_limit == DEFAULT_BANDWIDTH_LIMIT)
+        return min_variant;
+      adj_max_limit = min_limit;
+    } else if (!g_strcmp0 (start_bandwidth, "HIGHEST")) {
+      if (max_limit == DEFAULT_BANDWIDTH_LIMIT)
+        return max_variant;
+    } else if (!g_strcmp0 (start_bandwidth, "AVERAGE")) {
+      variant = get_average_variant (variants, num_of_codec, min_limit, max_limit);
+      if (variant)
+        return variant;
+    } else {
+      start_limit = atoi (start_bandwidth);
+      /* update max limit based on the start_bandwidth or network bitrate */
+      adj_max_limit = update_max_limit (min_limit, max_limit, start_limit, bitrate);
+    }
+  } else {
+    /* update max limit based on the network bitrate */
+    adj_max_limit = update_max_limit (min_limit, max_limit, DEFAULT_BANDWIDTH_LIMIT, bitrate);
+  }
+
+  if (min_limit < 0 && adj_max_limit < 0 && width < 0 && height < 0) {
+    GST_WARNING ("invalid condition, get default variant");
+    return NULL;
+  }
+
+  GST_DEBUG ("adj range limit: %d ~ %d (origin: %d)", min_limit, adj_max_limit, max_limit);
+
+  /* variant lists are sorted low to high, so iterate from highest to lowest */
+  tmp = NULL;
+  for (l = g_list_last (variants); l; l = g_list_previous (l)) {
+    if (!check_num_of_codec ((GstHLSVariantStream *)l->data, num_of_codec))
+      continue;
+
+    tmp = l->data;
+    GST_DEBUG ("stream info: %d, %d x %d", tmp->bandwidth, tmp->width, tmp->height);
+
+    if (tmp->bandwidth < min_limit) {
+      GList *j = g_list_next(l);
+      if (variant)
+        break;
+
+      if (j &&
+          ((max_limit == DEFAULT_BANDWIDTH_LIMIT) ||
+           ((GstHLSVariantStream*)j->data)->bandwidth <= max_limit))
+        variant = j->data; /* get the lowest one in the valid range */
+      else
+        variant = tmp;
+      break;
+    }
+
+    if (adj_max_limit > DEFAULT_BANDWIDTH_LIMIT && adj_max_limit < tmp->bandwidth)
+      continue;
+
+    if (((width > DEFAULT_RESOLUTION_LIMIT) && (tmp->width > width)) ||
+        ((height > DEFAULT_RESOLUTION_LIMIT) && (tmp->height > height))) {
+      if (adj_max_limit > DEFAULT_BANDWIDTH_LIMIT && !variant) { /* will be kept with the first one with the same bitrate */
+        variant = tmp;
+      }
+    } else {
+      variant = tmp;
+      GST_DEBUG ("get this stream %d", variant->bandwidth);
+      break;
+    }
+  }
+
+  return (variant)?(variant):(tmp);
+}
+
+#else
+GstHLSVariantStream *
+gst_hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist *
+    playlist, GstHLSVariantStream * current_variant, guint bitrate)
+{
+  GstHLSVariantStream *variant = current_variant;
+  GList *l;
+
+  /* variant lists are sorted low to high, so iterate from highest to lowest */
+  if (current_variant == NULL || !current_variant->iframe)
+    l = g_list_last (playlist->variants);
+  else
+    l = g_list_last (playlist->iframe_variants);
+
+  while (l != NULL) {
+    variant = l->data;
+    if (variant->bandwidth <= bitrate)
+      break;
+    l = l->prev;
+  }
+
+  return variant;
+}
+#endif
+
+GstHLSVariantStream *
+gst_hls_master_playlist_get_matching_variant (GstHLSMasterPlaylist * playlist,
+    GstHLSVariantStream * current_variant)
+{
+  if (current_variant->iframe) {
+    return find_variant_stream_by_uri (playlist->iframe_variants,
+        current_variant->uri);
+  }
+
+  return find_variant_stream_by_uri (playlist->variants, current_variant->uri);
+}