/* GStreamer
 *
 * Copyright (C) 2019 Collabora Ltd.
 *   Author: Stéphane Cerveau <scerveau@collabora.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 *
 */

#include "gstmpdclient.h"
#include "gstmpdparser.h"

GST_DEBUG_CATEGORY_STATIC (gst_dash_mpd_client_debug);
#undef GST_CAT_DEFAULT
#define GST_CAT_DEFAULT gst_dash_mpd_client_debug

G_DEFINE_TYPE (GstMPDClient, gst_mpd_client, GST_TYPE_OBJECT);

static GstMPDSegmentBaseNode *gst_mpd_client_get_segment_base (GstMPDPeriodNode
    * Period, GstMPDAdaptationSetNode * AdaptationSet,
    GstMPDRepresentationNode * Representation);
static GstMPDSegmentListNode *gst_mpd_client_get_segment_list (GstMPDClient *
    client, GstMPDPeriodNode * Period, GstMPDAdaptationSetNode * AdaptationSet,
    GstMPDRepresentationNode * Representation);
/* Segments */
static guint gst_mpd_client_get_segments_counts (GstMPDClient * client,
    GstActiveStream * stream);

static GList *gst_mpd_client_fetch_external_periods (GstMPDClient * client,
    GstMPDPeriodNode * period_node);
static GList *gst_mpd_client_fetch_external_adaptation_set (GstMPDClient *
    client, GstMPDPeriodNode * period, GstMPDAdaptationSetNode * adapt_set);

static GstMPDRepresentationNode *gst_mpd_client_get_lowest_representation (GList
    * Representations);
static GstStreamPeriod *gst_mpd_client_get_stream_period (GstMPDClient *
    client);

typedef GstMPDNode *(*MpdClientStringIDFilter) (GList * list, gchar * data);
typedef GstMPDNode *(*MpdClientIDFilter) (GList * list, guint data);

static GstMPDNode *
gst_mpd_client_get_period_with_id (GList * periods, gchar * period_id)
{
  GstMPDPeriodNode *period;
  GList *list = NULL;

  for (list = g_list_first (periods); list; list = g_list_next (list)) {
    period = (GstMPDPeriodNode *) list->data;
    if (!g_strcmp0 (period->id, period_id))
      return GST_MPD_NODE (period);
  }
  return NULL;
}

static GstMPDNode *
gst_mpd_client_get_adaptation_set_with_id (GList * adaptation_sets, guint id)
{
  GstMPDAdaptationSetNode *adaptation_set;
  GList *list = NULL;

  for (list = g_list_first (adaptation_sets); list; list = g_list_next (list)) {
    adaptation_set = (GstMPDAdaptationSetNode *) list->data;
    if (adaptation_set->id == id)
      return GST_MPD_NODE (adaptation_set);
  }
  return NULL;
}

GstMPDRepresentationNode *
gst_mpd_client_get_representation_with_id (GList * representations,
    gchar * rep_id)
{
  GstMPDRepresentationNode *representation;
  GList *list = NULL;

  for (list = g_list_first (representations); list; list = g_list_next (list)) {
    representation = (GstMPDRepresentationNode *) list->data;
    if (!g_strcmp0 (representation->id, rep_id))
      return GST_MPD_REPRESENTATION_NODE (representation);
  }
  return NULL;
}

static GstMPDNode *
gst_mpd_client_get_representation_with_id_filter (GList * representations,
    gchar * rep_id)
{
  GstMPDRepresentationNode *representation =
      gst_mpd_client_get_representation_with_id (representations, rep_id);

  if (representation != NULL)
    return GST_MPD_NODE (representation);

  return NULL;
}

static gchar *
_generate_new_string_id (GList * list, const gchar * tuple,
    MpdClientStringIDFilter filter)
{
  guint i = 0;
  gchar *id = NULL;
  GstMPDNode *node;
  do {
    g_free (id);
    id = g_strdup_printf (tuple, i);
    node = filter (list, id);
    i++;
  } while (node);

  return id;
}

static guint
_generate_new_id (GList * list, MpdClientIDFilter filter)
{
  guint id = 0;
  GstMPDNode *node;
  do {
    node = filter (list, id);
    id++;
  } while (node);

  return id;
}

static GstMPDRepresentationNode *
gst_mpd_client_get_lowest_representation (GList * Representations)
{
  GList *list = NULL;
  GstMPDRepresentationNode *rep = NULL;
  GstMPDRepresentationNode *lowest = NULL;

  if (Representations == NULL)
    return NULL;

  for (list = g_list_first (Representations); list; list = g_list_next (list)) {
    rep = (GstMPDRepresentationNode *) list->data;
    if (rep && (!lowest || rep->bandwidth < lowest->bandwidth)) {
      lowest = rep;
    }
  }

  return lowest;
}

#if 0
static GstMPDRepresentationNode *
gst_mpdparser_get_highest_representation (GList * Representations)
{
  GList *list = NULL;

  if (Representations == NULL)
    return NULL;

  list = g_list_last (Representations);

  return list ? (GstMPDRepresentationNode *) list->data : NULL;
}

static GstMPDRepresentationNode *
gst_mpdparser_get_representation_with_max_bandwidth (GList * Representations,
    gint max_bandwidth)
{
  GList *list = NULL;
  GstMPDRepresentationNode *representation, *best_rep = NULL;

  if (Representations == NULL)
    return NULL;

  if (max_bandwidth <= 0)       /* 0 => get highest representation available */
    return gst_mpdparser_get_highest_representation (Representations);

  for (list = g_list_first (Representations); list; list = g_list_next (list)) {
    representation = (GstMPDRepresentationNode *) list->data;
    if (representation && representation->bandwidth <= max_bandwidth) {
      best_rep = representation;
    }
  }

  return best_rep;
}
#endif

static GstMPDSegmentListNode *
gst_mpd_client_fetch_external_segment_list (GstMPDClient * client,
    GstMPDPeriodNode * Period,
    GstMPDAdaptationSetNode * AdaptationSet,
    GstMPDRepresentationNode * Representation,
    GstMPDSegmentListNode * parent, GstMPDSegmentListNode * segment_list)
{
  GstFragment *download;
  GstBuffer *segment_list_buffer = NULL;
  GstMapInfo map;
  GError *err = NULL;

  GstUri *base_uri, *uri;
  gchar *query = NULL;
  gchar *uri_string;
  GstMPDSegmentListNode *new_segment_list = NULL;

  /* ISO/IEC 23009-1:2014 5.5.3 4)
   * Remove nodes that resolve to nothing when resolving
   */
  if (strcmp (segment_list->xlink_href,
          "urn:mpeg:dash:resolve-to-zero:2013") == 0) {
    return NULL;
  }

  if (!client->downloader) {
    return NULL;
  }

  /* Build absolute URI */

  /* Get base URI at the MPD level */
  base_uri =
      gst_uri_from_string (client->mpd_base_uri ? client->
      mpd_base_uri : client->mpd_uri);

  /* combine a BaseURL at the MPD level with the current base url */
  base_uri =
      gst_mpd_helper_combine_urls (base_uri, client->mpd_root_node->BaseURLs,
      &query, 0);

  /* combine a BaseURL at the Period level with the current base url */
  base_uri =
      gst_mpd_helper_combine_urls (base_uri, Period->BaseURLs, &query, 0);

  if (AdaptationSet) {
    /* combine a BaseURL at the AdaptationSet level with the current base url */
    base_uri =
        gst_mpd_helper_combine_urls (base_uri, AdaptationSet->BaseURLs, &query,
        0);

    if (Representation) {
      /* combine a BaseURL at the Representation level with the current base url */
      base_uri =
          gst_mpd_helper_combine_urls (base_uri, Representation->BaseURLs,
          &query, 0);
    }
  }

  uri = gst_uri_from_string_with_base (base_uri, segment_list->xlink_href);
  if (query)
    gst_uri_set_query_string (uri, query);
  g_free (query);
  uri_string = gst_uri_to_string (uri);
  gst_uri_unref (base_uri);
  gst_uri_unref (uri);

  download =
      gst_uri_downloader_fetch_uri (client->downloader,
      uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err);
  g_free (uri_string);

  if (!download) {
    GST_ERROR ("Failed to download external SegmentList node at '%s': %s",
        segment_list->xlink_href, err->message);
    g_clear_error (&err);
    return NULL;
  }

  segment_list_buffer = gst_fragment_get_buffer (download);
  g_object_unref (download);

  gst_buffer_map (segment_list_buffer, &map, GST_MAP_READ);

  new_segment_list =
      gst_mpdparser_get_external_segment_list ((const gchar *) map.data,
      map.size, parent);

  if (segment_list_buffer) {
    gst_buffer_unmap (segment_list_buffer, &map);
    gst_buffer_unref (segment_list_buffer);
  }

  return new_segment_list;
}

static GstMPDSegmentBaseNode *
gst_mpd_client_get_segment_base (GstMPDPeriodNode * Period,
    GstMPDAdaptationSetNode * AdaptationSet,
    GstMPDRepresentationNode * Representation)
{
  GstMPDSegmentBaseNode *SegmentBase = NULL;

  if (Representation && Representation->SegmentBase) {
    SegmentBase = Representation->SegmentBase;
  } else if (AdaptationSet && AdaptationSet->SegmentBase) {
    SegmentBase = AdaptationSet->SegmentBase;
  } else if (Period && Period->SegmentBase) {
    SegmentBase = Period->SegmentBase;
  }
  /* the SegmentBase element could be encoded also inside a SegmentList element */
  if (SegmentBase == NULL) {
    if (Representation && Representation->SegmentList
        && GST_MPD_MULT_SEGMENT_BASE_NODE (Representation->SegmentList)
        && GST_MPD_MULT_SEGMENT_BASE_NODE (Representation->
            SegmentList)->SegmentBase) {
      SegmentBase =
          GST_MPD_MULT_SEGMENT_BASE_NODE (Representation->
          SegmentList)->SegmentBase;
    } else if (AdaptationSet && AdaptationSet->SegmentList
        && GST_MPD_MULT_SEGMENT_BASE_NODE (AdaptationSet->SegmentList)
        && GST_MPD_MULT_SEGMENT_BASE_NODE (AdaptationSet->
            SegmentList)->SegmentBase) {
      SegmentBase =
          GST_MPD_MULT_SEGMENT_BASE_NODE (AdaptationSet->
          SegmentList)->SegmentBase;
    } else if (Period && Period->SegmentList
        && GST_MPD_MULT_SEGMENT_BASE_NODE (Period->SegmentList)
        && GST_MPD_MULT_SEGMENT_BASE_NODE (Period->SegmentList)->SegmentBase) {
      SegmentBase =
          GST_MPD_MULT_SEGMENT_BASE_NODE (Period->SegmentList)->SegmentBase;
    }
  }

  return SegmentBase;
}

static GstMPDSegmentListNode *
gst_mpd_client_get_segment_list (GstMPDClient * client,
    GstMPDPeriodNode * Period, GstMPDAdaptationSetNode * AdaptationSet,
    GstMPDRepresentationNode * Representation)
{
  GstMPDSegmentListNode **SegmentList;
  GstMPDSegmentListNode *ParentSegmentList = NULL;

  if (Representation && Representation->SegmentList) {
    SegmentList = &Representation->SegmentList;
    ParentSegmentList = AdaptationSet->SegmentList;
  } else if (AdaptationSet && AdaptationSet->SegmentList) {
    SegmentList = &AdaptationSet->SegmentList;
    ParentSegmentList = Period->SegmentList;
    Representation = NULL;
  } else {
    Representation = NULL;
    AdaptationSet = NULL;
    SegmentList = &Period->SegmentList;
  }

  /* Resolve external segment list here. */
  if (*SegmentList && (*SegmentList)->xlink_href) {
    GstMPDSegmentListNode *new_segment_list;

    /* TODO: Use SegmentList of parent if
     * - Parent has its own SegmentList
     * - Fail to get SegmentList from external xml
     */
    new_segment_list =
        gst_mpd_client_fetch_external_segment_list (client, Period,
        AdaptationSet, Representation, ParentSegmentList, *SegmentList);

    gst_mpd_segment_list_node_free (*SegmentList);
    *SegmentList = new_segment_list;
  }

  return *SegmentList;
}

static GstClockTime
gst_mpd_client_get_segment_duration (GstMPDClient * client,
    GstActiveStream * stream, guint64 * scale_dur)
{
  GstStreamPeriod *stream_period;
  GstMPDMultSegmentBaseNode *base = NULL;
  GstClockTime duration = 0;

  g_return_val_if_fail (stream != NULL, GST_CLOCK_TIME_NONE);
  stream_period = gst_mpd_client_get_stream_period (client);
  g_return_val_if_fail (stream_period != NULL, GST_CLOCK_TIME_NONE);

  if (stream->cur_segment_list) {
    base = GST_MPD_MULT_SEGMENT_BASE_NODE (stream->cur_segment_list);
  } else if (stream->cur_seg_template) {
    base = GST_MPD_MULT_SEGMENT_BASE_NODE (stream->cur_seg_template);
  }

  if (base == NULL || base->SegmentBase == NULL) {
    /* this may happen when we have a single segment */
    duration = stream_period->duration;
    if (scale_dur)
      *scale_dur = duration;
  } else {
    /* duration is guint so this cannot overflow */
    duration = base->duration * GST_SECOND;
    if (scale_dur)
      *scale_dur = duration;
    duration /= base->SegmentBase->timescale;
  }

  return duration;
}

void
gst_mpd_client_active_streams_free (GstMPDClient * client)
{
  if (client->active_streams) {
    g_list_foreach (client->active_streams,
        (GFunc) gst_mpdparser_free_active_stream, NULL);
    g_list_free (client->active_streams);
    client->active_streams = NULL;
  }
}

static void
gst_mpd_client_finalize (GObject * object)
{
  GstMPDClient *client = GST_MPD_CLIENT (object);

  if (client->mpd_root_node)
    gst_mpd_root_node_free (client->mpd_root_node);

  if (client->periods) {
    g_list_free_full (client->periods,
        (GDestroyNotify) gst_mpdparser_free_stream_period);
  }

  gst_mpd_client_active_streams_free (client);

  g_free (client->mpd_uri);
  client->mpd_uri = NULL;
  g_free (client->mpd_base_uri);
  client->mpd_base_uri = NULL;

  if (client->downloader)
    gst_object_unref (client->downloader);
  client->downloader = NULL;

  G_OBJECT_CLASS (gst_mpd_client_parent_class)->finalize (object);
}

static void
gst_mpd_client_class_init (GstMPDClientClass * klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  object_class->finalize = gst_mpd_client_finalize;
}

static void
gst_mpd_client_init (GstMPDClient * client)
{
}

GstMPDClient *
gst_mpd_client_new (void)
{
  GstMPDClient *ret;

  GST_DEBUG_CATEGORY_INIT (gst_dash_mpd_client_debug, "dashmpdclient", 0,
      "DashmMpdClient");
  ret = g_object_new (GST_TYPE_MPD_CLIENT, NULL);
  gst_object_ref_sink (ret);
  return ret;
}

GstMPDClient *
gst_mpd_client_new_static (void)
{
  GstMPDClient *client = gst_mpd_client_new ();

  client->mpd_root_node = gst_mpd_root_node_new ();
  client->mpd_root_node->default_namespace =
      g_strdup ("urn:mpeg:dash:schema:mpd:2011");
  client->mpd_root_node->profiles =
      g_strdup ("urn:mpeg:dash:profile:isoff-main:2011");
  client->mpd_root_node->type = GST_MPD_FILE_TYPE_STATIC;
  client->mpd_root_node->minBufferTime = 1500;

  return client;
}

void
gst_mpd_client_free (GstMPDClient * client)
{
  if (client)
    gst_object_unref (client);
}

gboolean
gst_mpd_client_parse (GstMPDClient * client, const gchar * data, gint size)
{
  gboolean ret = FALSE;


  ret = gst_mpdparser_get_mpd_root_node (&client->mpd_root_node, data, size);

  if (ret) {
    gst_mpd_client_check_profiles (client);
    gst_mpd_client_fetch_on_load_external_resources (client);
  }

  return ret;
}


gboolean
gst_mpd_client_get_xml_content (GstMPDClient * client, gchar ** data,
    gint * size)
{
  gboolean ret = FALSE;

  g_return_val_if_fail (client != NULL, ret);
  g_return_val_if_fail (client->mpd_root_node != NULL, ret);

  ret = gst_mpd_node_get_xml_buffer (GST_MPD_NODE (client->mpd_root_node),
      data, (int *) size);

  return ret;
}

GstDateTime *
gst_mpd_client_get_availability_start_time (GstMPDClient * client)
{
  GstDateTime *start_time;

  if (client == NULL)
    return (GstDateTime *) NULL;

  start_time = client->mpd_root_node->availabilityStartTime;
  if (start_time)
    gst_date_time_ref (start_time);
  return start_time;
}

void
gst_mpd_client_set_uri_downloader (GstMPDClient * client,
    GstUriDownloader * downloader)
{
  if (client->downloader)
    gst_object_unref (client->downloader);
  client->downloader = gst_object_ref (downloader);
}

void
gst_mpd_client_check_profiles (GstMPDClient * client)
{
  GST_DEBUG ("Profiles: %s",
      client->mpd_root_node->profiles ? client->mpd_root_node->
      profiles : "<none>");

  if (!client->mpd_root_node->profiles)
    return;

  if (g_strstr_len (client->mpd_root_node->profiles, -1,
          "urn:mpeg:dash:profile:isoff-on-demand:2011")) {
    client->profile_isoff_ondemand = TRUE;
    GST_DEBUG ("Found ISOFF on demand profile (2011)");
  }
}

void
gst_mpd_client_fetch_on_load_external_resources (GstMPDClient * client)
{
  GList *l;

  for (l = client->mpd_root_node->Periods; l; /* explicitly advanced below */ ) {
    GstMPDPeriodNode *period = l->data;
    GList *m;

    if (period->xlink_href && period->actuate == GST_MPD_XLINK_ACTUATE_ON_LOAD) {
      GList *new_periods, *prev, *next;

      new_periods = gst_mpd_client_fetch_external_periods (client, period);

      prev = l->prev;
      client->mpd_root_node->Periods =
          g_list_delete_link (client->mpd_root_node->Periods, l);
      gst_mpd_period_node_free (period);
      period = NULL;

      /* Get new next node, we will insert before this */
      if (prev)
        next = prev->next;
      else
        next = client->mpd_root_node->Periods;

      while (new_periods) {
        client->mpd_root_node->Periods =
            g_list_insert_before (client->mpd_root_node->Periods, next,
            new_periods->data);
        new_periods = g_list_delete_link (new_periods, new_periods);
      }
      next = NULL;

      /* Update our iterator to the first new period if any, or the next */
      if (prev)
        l = prev->next;
      else
        l = client->mpd_root_node->Periods;

      continue;
    }

    if (period->SegmentList && period->SegmentList->xlink_href
        && period->SegmentList->actuate == GST_MPD_XLINK_ACTUATE_ON_LOAD) {
      GstMPDSegmentListNode *new_segment_list;

      new_segment_list =
          gst_mpd_client_fetch_external_segment_list (client, period, NULL,
          NULL, NULL, period->SegmentList);

      gst_mpd_segment_list_node_free (period->SegmentList);
      period->SegmentList = new_segment_list;
    }

    for (m = period->AdaptationSets; m; /* explicitly advanced below */ ) {
      GstMPDAdaptationSetNode *adapt_set = m->data;
      GList *n;

      if (adapt_set->xlink_href
          && adapt_set->actuate == GST_MPD_XLINK_ACTUATE_ON_LOAD) {
        GList *new_adapt_sets, *prev, *next;

        new_adapt_sets =
            gst_mpd_client_fetch_external_adaptation_set (client, period,
            adapt_set);

        prev = m->prev;
        period->AdaptationSets = g_list_delete_link (period->AdaptationSets, m);
        gst_mpd_adaptation_set_node_free (adapt_set);
        adapt_set = NULL;

        /* Get new next node, we will insert before this */
        if (prev)
          next = prev->next;
        else
          next = period->AdaptationSets;

        while (new_adapt_sets) {
          period->AdaptationSets =
              g_list_insert_before (period->AdaptationSets, next,
              new_adapt_sets->data);
          new_adapt_sets = g_list_delete_link (new_adapt_sets, new_adapt_sets);
        }
        next = NULL;

        /* Update our iterator to the first new adapt_set if any, or the next */
        if (prev)
          m = prev->next;
        else
          m = period->AdaptationSets;

        continue;
      }

      if (adapt_set->SegmentList && adapt_set->SegmentList->xlink_href
          && adapt_set->SegmentList->actuate == GST_MPD_XLINK_ACTUATE_ON_LOAD) {
        GstMPDSegmentListNode *new_segment_list;

        new_segment_list =
            gst_mpd_client_fetch_external_segment_list (client, period,
            adapt_set, NULL, period->SegmentList, adapt_set->SegmentList);

        gst_mpd_segment_list_node_free (adapt_set->SegmentList);
        adapt_set->SegmentList = new_segment_list;
      }

      for (n = adapt_set->Representations; n; n = n->next) {
        GstMPDRepresentationNode *representation = n->data;

        if (representation->SegmentList
            && representation->SegmentList->xlink_href
            && representation->SegmentList->actuate ==
            GST_MPD_XLINK_ACTUATE_ON_LOAD) {

          GstMPDSegmentListNode *new_segment_list;

          new_segment_list =
              gst_mpd_client_fetch_external_segment_list (client, period,
              adapt_set, representation, adapt_set->SegmentList,
              representation->SegmentList);

          gst_mpd_segment_list_node_free (representation->SegmentList);
          representation->SegmentList = new_segment_list;

        }
      }

      m = m->next;
    }

    l = l->next;
  }
}


static GstStreamPeriod *
gst_mpd_client_get_stream_period (GstMPDClient * client)
{
  g_return_val_if_fail (client != NULL, NULL);
  g_return_val_if_fail (client->periods != NULL, NULL);

  return g_list_nth_data (client->periods, client->period_idx);
}

const gchar *
gst_mpd_client_get_baseURL (GstMPDClient * client, guint indexStream)
{
  GstActiveStream *stream;

  g_return_val_if_fail (client != NULL, NULL);
  g_return_val_if_fail (client->active_streams != NULL, NULL);
  stream = g_list_nth_data (client->active_streams, indexStream);
  g_return_val_if_fail (stream != NULL, NULL);

  return stream->baseURL;
}

/* select a stream and extract the baseURL (if present) */
gchar *
gst_mpd_client_parse_baseURL (GstMPDClient * client, GstActiveStream * stream,
    gchar ** query)
{
  GstStreamPeriod *stream_period;
  static const gchar empty[] GST_UNUSED_CHECKS = "";
  gchar *ret = NULL;
  GstUri *abs_url;

  g_return_val_if_fail (stream != NULL, g_strdup (empty));
  stream_period = gst_mpd_client_get_stream_period (client);
  g_return_val_if_fail (stream_period != NULL, g_strdup (empty));
  g_return_val_if_fail (stream_period->period != NULL, g_strdup (empty));

  /* NULLify query return before we start */
  if (query)
    *query = NULL;

  /* initialise base url */
  abs_url =
      gst_uri_from_string (client->mpd_base_uri ? client->
      mpd_base_uri : client->mpd_uri);

  /* combine a BaseURL at the MPD level with the current base url */
  abs_url =
      gst_mpd_helper_combine_urls (abs_url, client->mpd_root_node->BaseURLs,
      query, stream->baseURL_idx);

  /* combine a BaseURL at the Period level with the current base url */
  abs_url =
      gst_mpd_helper_combine_urls (abs_url, stream_period->period->BaseURLs,
      query, stream->baseURL_idx);

  GST_DEBUG ("Current adaptation set id %i (%s)", stream->cur_adapt_set->id,
      stream->cur_adapt_set->contentType);
  /* combine a BaseURL at the AdaptationSet level with the current base url */
  abs_url =
      gst_mpd_helper_combine_urls (abs_url, stream->cur_adapt_set->BaseURLs,
      query, stream->baseURL_idx);

  /* combine a BaseURL at the Representation level with the current base url */
  abs_url =
      gst_mpd_helper_combine_urls (abs_url,
      stream->cur_representation->BaseURLs, query, stream->baseURL_idx);

  ret = gst_uri_to_string (abs_url);
  gst_uri_unref (abs_url);

  return ret;
}

static GstClockTime
gst_mpd_client_get_segment_end_time (GstMPDClient * client,
    GPtrArray * segments, const GstMediaSegment * segment, gint index)
{
  const GstStreamPeriod *stream_period;
  GstClockTime end;

  if (segment->repeat >= 0)
    return segment->start + (segment->repeat + 1) * segment->duration;

  if (index < segments->len - 1) {
    const GstMediaSegment *next_segment =
        g_ptr_array_index (segments, index + 1);
    end = next_segment->start;
  } else {
    stream_period = gst_mpd_client_get_stream_period (client);
    end = stream_period->start + stream_period->duration;
  }
  return end;
}

static gboolean
gst_mpd_client_add_media_segment (GstActiveStream * stream,
    GstMPDSegmentURLNode * url_node, guint number, gint repeat,
    guint64 scale_start, guint64 scale_duration,
    GstClockTime start, GstClockTime duration)
{
  GstMediaSegment *media_segment;

  g_return_val_if_fail (stream->segments != NULL, FALSE);

  media_segment = g_slice_new0 (GstMediaSegment);

  media_segment->SegmentURL = url_node;
  media_segment->number = number;
  media_segment->scale_start = scale_start;
  media_segment->scale_duration = scale_duration;
  media_segment->start = start;
  media_segment->duration = duration;
  media_segment->repeat = repeat;

  g_ptr_array_add (stream->segments, media_segment);
  GST_LOG ("Added new segment: number %d, repeat %d, "
      "ts: %" GST_TIME_FORMAT ", dur: %"
      GST_TIME_FORMAT, number, repeat,
      GST_TIME_ARGS (start), GST_TIME_ARGS (duration));

  return TRUE;
}

static void
gst_mpd_client_stream_update_presentation_time_offset (GstMPDClient * client,
    GstActiveStream * stream)
{
  GstMPDSegmentBaseNode *segbase = NULL;

  /* Find the used segbase */
  if (stream->cur_segment_list) {
    segbase =
        GST_MPD_MULT_SEGMENT_BASE_NODE (stream->cur_segment_list)->SegmentBase;
  } else if (stream->cur_seg_template) {
    segbase =
        GST_MPD_MULT_SEGMENT_BASE_NODE (stream->cur_seg_template)->SegmentBase;
  } else if (stream->cur_segment_base) {
    segbase = stream->cur_segment_base;
  }

  if (segbase) {
    /* Avoid overflows */
    stream->presentationTimeOffset =
        gst_util_uint64_scale (segbase->presentationTimeOffset, GST_SECOND,
        segbase->timescale);
  } else {
    stream->presentationTimeOffset = 0;
  }

  GST_LOG ("Setting stream's presentation time offset to %" GST_TIME_FORMAT,
      GST_TIME_ARGS (stream->presentationTimeOffset));
}

gboolean
gst_mpd_client_setup_representation (GstMPDClient * client,
    GstActiveStream * stream, GstMPDRepresentationNode * representation)
{
  GstStreamPeriod *stream_period;
  GList *rep_list;
  GstClockTime PeriodStart, PeriodEnd, start_time, duration;
  guint i;
  guint64 start;

  if (stream->cur_adapt_set == NULL) {
    GST_WARNING ("No valid AdaptationSet node in the MPD file, aborting...");
    return FALSE;
  }

  rep_list = stream->cur_adapt_set->Representations;
  stream->cur_representation = representation;
  stream->representation_idx = g_list_index (rep_list, representation);

  /* clean the old segment list, if any */
  if (stream->segments) {
    g_ptr_array_unref (stream->segments);
    stream->segments = NULL;
  }

  stream_period = gst_mpd_client_get_stream_period (client);
  g_return_val_if_fail (stream_period != NULL, FALSE);
  g_return_val_if_fail (stream_period->period != NULL, FALSE);

  PeriodStart = stream_period->start;
  if (GST_CLOCK_TIME_IS_VALID (stream_period->duration))
    PeriodEnd = stream_period->start + stream_period->duration;
  else
    PeriodEnd = GST_CLOCK_TIME_NONE;

  GST_LOG ("Building segment list for Period from %" GST_TIME_FORMAT " to %"
      GST_TIME_FORMAT, GST_TIME_ARGS (PeriodStart), GST_TIME_ARGS (PeriodEnd));

  if (representation->SegmentBase != NULL
      || representation->SegmentList != NULL) {
    GList *SegmentURL;

    /* We have a fixed list of segments for any of the cases here,
     * init the segments list */
    gst_mpdparser_init_active_stream_segments (stream);

    /* get the first segment_base of the selected representation */
    if ((stream->cur_segment_base =
            gst_mpd_client_get_segment_base (stream_period->period,
                stream->cur_adapt_set, representation)) == NULL) {
      GST_DEBUG ("No useful SegmentBase node for the current Representation");
    }

    /* get the first segment_list of the selected representation */
    if ((stream->cur_segment_list =
            gst_mpd_client_get_segment_list (client, stream_period->period,
                stream->cur_adapt_set, representation)) == NULL) {
      GST_DEBUG ("No useful SegmentList node for the current Representation");
      /* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */
      if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, 0,
              PeriodEnd - PeriodStart, PeriodStart, PeriodEnd - PeriodStart)) {
        return FALSE;
      }
    } else {
      /* build the list of GstMediaSegment nodes from the SegmentList node */
      SegmentURL = stream->cur_segment_list->SegmentURL;
      if (SegmentURL == NULL) {
        GST_WARNING
            ("No valid list of SegmentURL nodes in the MPD file, aborting...");
        return FALSE;
      }

      /* build segment list */
      i = GST_MPD_MULT_SEGMENT_BASE_NODE (stream->
          cur_segment_list)->startNumber;
      start = 0;
      start_time = PeriodStart;

      GST_LOG ("Building media segment list using a SegmentList node");
      if (GST_MPD_MULT_SEGMENT_BASE_NODE (stream->
              cur_segment_list)->SegmentTimeline) {
        GstMPDSegmentTimelineNode *timeline;
        GstMPDSNode *S;
        GList *list;
        GstClockTime presentationTimeOffset;
        GstMPDSegmentBaseNode *segbase;

        segbase =
            GST_MPD_MULT_SEGMENT_BASE_NODE (stream->
            cur_segment_list)->SegmentBase;
        presentationTimeOffset =
            gst_util_uint64_scale (segbase->presentationTimeOffset, GST_SECOND,
            segbase->timescale);
        GST_LOG ("presentationTimeOffset = %" G_GUINT64_FORMAT,
            presentationTimeOffset);

        timeline =
            GST_MPD_MULT_SEGMENT_BASE_NODE (stream->
            cur_segment_list)->SegmentTimeline;
        for (list = g_queue_peek_head_link (&timeline->S); list;
            list = g_list_next (list)) {
          guint timescale;

          S = (GstMPDSNode *) list->data;
          GST_LOG ("Processing S node: d=%" G_GUINT64_FORMAT " r=%d t=%"
              G_GUINT64_FORMAT, S->d, S->r, S->t);
          timescale =
              GST_MPD_MULT_SEGMENT_BASE_NODE (stream->
              cur_segment_list)->SegmentBase->timescale;
          duration = gst_util_uint64_scale (S->d, GST_SECOND, timescale);

          if (S->t > 0) {
            start = S->t;
            start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale)
                + PeriodStart - presentationTimeOffset;
          }

          if (!SegmentURL) {
            GST_WARNING
                ("SegmentTimeline does not have a matching SegmentURL, aborting...");
            return FALSE;
          }

          if (!gst_mpd_client_add_media_segment (stream, SegmentURL->data, i,
                  S->r, start, S->d, start_time, duration)) {
            return FALSE;
          }
          i += S->r + 1;
          start_time += duration * (S->r + 1);
          start += S->d * (S->r + 1);
          SegmentURL = g_list_next (SegmentURL);
        }
      } else {
        guint64 scale_dur;

        duration =
            gst_mpd_client_get_segment_duration (client, stream, &scale_dur);
        if (!GST_CLOCK_TIME_IS_VALID (duration))
          return FALSE;

        while (SegmentURL) {
          if (!gst_mpd_client_add_media_segment (stream, SegmentURL->data, i,
                  0, start, scale_dur, start_time, duration)) {
            return FALSE;
          }
          i++;
          start += scale_dur;
          start_time += duration;
          SegmentURL = g_list_next (SegmentURL);
        }
      }
    }
  } else {
    if (representation->SegmentTemplate != NULL) {
      stream->cur_seg_template = representation->SegmentTemplate;
    } else if (stream->cur_adapt_set->SegmentTemplate != NULL) {
      stream->cur_seg_template = stream->cur_adapt_set->SegmentTemplate;
    } else if (stream_period->period->SegmentTemplate != NULL) {
      stream->cur_seg_template = stream_period->period->SegmentTemplate;
    }

    if (stream->cur_seg_template == NULL) {

      gst_mpdparser_init_active_stream_segments (stream);
      /* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */
      if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, 0,
              PeriodEnd - PeriodStart, 0, PeriodEnd - PeriodStart)) {
        return FALSE;
      }
    } else {
      GstClockTime presentationTimeOffset;
      GstMPDMultSegmentBaseNode *mult_seg =
          GST_MPD_MULT_SEGMENT_BASE_NODE (stream->cur_seg_template);
      presentationTimeOffset =
          gst_util_uint64_scale (mult_seg->SegmentBase->presentationTimeOffset,
          GST_SECOND, mult_seg->SegmentBase->timescale);
      GST_LOG ("presentationTimeOffset = %" GST_TIME_FORMAT,
          GST_TIME_ARGS (presentationTimeOffset));
      /* build segment list */
      i = mult_seg->startNumber;
      start = 0;
      start_time = 0;

      GST_LOG ("Building media segment list using this template: %s",
          stream->cur_seg_template->media);

      if (mult_seg->SegmentTimeline) {
        GstMPDSegmentTimelineNode *timeline;
        GstMPDSNode *S;
        GList *list;

        timeline = mult_seg->SegmentTimeline;
        gst_mpdparser_init_active_stream_segments (stream);
        for (list = g_queue_peek_head_link (&timeline->S); list;
            list = g_list_next (list)) {
          guint timescale;

          S = (GstMPDSNode *) list->data;
          GST_LOG ("Processing S node: d=%" G_GUINT64_FORMAT " r=%u t=%"
              G_GUINT64_FORMAT, S->d, S->r, S->t);
          timescale = mult_seg->SegmentBase->timescale;
          duration = gst_util_uint64_scale (S->d, GST_SECOND, timescale);
          if (S->t > 0) {
            start = S->t;
            start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale)
                + PeriodStart - presentationTimeOffset;
          }

          if (!gst_mpd_client_add_media_segment (stream, NULL, i, S->r, start,
                  S->d, start_time, duration)) {
            return FALSE;
          }
          i += S->r + 1;
          start += S->d * (S->r + 1);
          start_time += duration * (S->r + 1);
        }
      } else {
        /* NOP - The segment is created on demand with the template, no need
         * to build a list */
      }
    }
  }

  /* clip duration of segments to stop at period end */
  if (stream->segments && stream->segments->len) {
    if (GST_CLOCK_TIME_IS_VALID (PeriodEnd)) {
      guint n;

      for (n = 0; n < stream->segments->len; ++n) {
        GstMediaSegment *media_segment =
            g_ptr_array_index (stream->segments, n);
        if (media_segment) {
          if (media_segment->start + media_segment->duration > PeriodEnd) {
            GstClockTime stop = PeriodEnd;
            if (n < stream->segments->len - 1) {
              GstMediaSegment *next_segment =
                  g_ptr_array_index (stream->segments, n + 1);
              if (next_segment && next_segment->start < PeriodEnd)
                stop = next_segment->start;
            }
            media_segment->duration =
                media_segment->start > stop ? 0 : stop - media_segment->start;
            GST_LOG ("Fixed duration of segment %u: %" GST_TIME_FORMAT, n,
                GST_TIME_ARGS (media_segment->duration));

            /* If the segment was clipped entirely, we discard it and all
             * subsequent ones */
            if (media_segment->duration == 0) {
              GST_WARNING ("Discarding %u segments outside period",
                  stream->segments->len - n);
              /* _set_size should properly unref elements */
              g_ptr_array_set_size (stream->segments, n);
              break;
            }
          }
        }
      }
    }
#ifndef GST_DISABLE_GST_DEBUG
    if (stream->segments->len > 0) {
      GstMediaSegment *last_media_segment =
          g_ptr_array_index (stream->segments, stream->segments->len - 1);
      GST_LOG ("Built a list of %d segments", last_media_segment->number);
    } else {
      GST_LOG ("All media segments were clipped");
    }
#endif
  }

  g_free (stream->baseURL);
  g_free (stream->queryURL);
  stream->baseURL =
      gst_mpd_client_parse_baseURL (client, stream, &stream->queryURL);

  gst_mpd_client_stream_update_presentation_time_offset (client, stream);

  return TRUE;
}

#define CUSTOM_WRAPPER_START "<custom_wrapper>"
#define CUSTOM_WRAPPER_END "</custom_wrapper>"

static GList *
gst_mpd_client_fetch_external_periods (GstMPDClient * client,
    GstMPDPeriodNode * period_node)
{
  GstFragment *download;
  GstBuffer *period_buffer;
  GError *err = NULL;

  GstUri *base_uri, *uri;
  gchar *query = NULL;
  gchar *uri_string;
  GList *new_periods = NULL;
  gsize alloc_size, buffer_size;
  GString *data;

  /* ISO/IEC 23009-1:2014 5.5.3 4)
   * Remove nodes that resolve to nothing when resolving
   */
  if (strcmp (period_node->xlink_href,
          "urn:mpeg:dash:resolve-to-zero:2013") == 0) {
    return NULL;
  }

  if (!client->downloader) {
    return NULL;
  }

  /* Build absolute URI */

  /* Get base URI at the MPD level */
  base_uri =
      gst_uri_from_string (client->mpd_base_uri ? client->
      mpd_base_uri : client->mpd_uri);

  /* combine a BaseURL at the MPD level with the current base url */
  base_uri =
      gst_mpd_helper_combine_urls (base_uri, client->mpd_root_node->BaseURLs,
      &query, 0);
  uri = gst_uri_from_string_with_base (base_uri, period_node->xlink_href);
  if (query)
    gst_uri_set_query_string (uri, query);
  g_free (query);
  uri_string = gst_uri_to_string (uri);
  gst_uri_unref (base_uri);
  gst_uri_unref (uri);

  download =
      gst_uri_downloader_fetch_uri (client->downloader,
      uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err);
  g_free (uri_string);

  if (!download) {
    GST_ERROR ("Failed to download external Period node at '%s': %s",
        period_node->xlink_href, err->message);
    g_clear_error (&err);
    return NULL;
  }

  period_buffer = gst_fragment_get_buffer (download);
  g_object_unref (download);

  /* external xml could have multiple period without root xmlNode.
   * To avoid xml parsing error caused by no root node, wrapping it with
   * custom root node */
  buffer_size = gst_buffer_get_size (period_buffer);
  alloc_size =
      buffer_size + sizeof (CUSTOM_WRAPPER_START) +
      sizeof (CUSTOM_WRAPPER_END) - 2;
  data = g_string_sized_new (alloc_size);
  g_string_append_len (data, CUSTOM_WRAPPER_START,
      sizeof (CUSTOM_WRAPPER_START) - 1);

  gst_buffer_extract (period_buffer, 0, data->str + data->len, buffer_size);
  gst_buffer_unref (period_buffer);
  data->len += buffer_size;

  g_string_append_len (data, CUSTOM_WRAPPER_END,
      sizeof (CUSTOM_WRAPPER_END) - 1);

  new_periods = gst_mpdparser_get_external_periods (data->str, alloc_size);

  g_string_free (data, TRUE);

  return new_periods;
}

gboolean
gst_mpd_client_setup_media_presentation (GstMPDClient * client,
    GstClockTime time, gint period_idx, const gchar * period_id)
{
  GstStreamPeriod *stream_period;
  GstClockTime start, duration;
  GList *list, *next;
  guint idx;
  gboolean ret = FALSE;

  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->mpd_root_node != NULL, FALSE);

  /* Check if we set up the media presentation far enough already */
  for (list = client->periods; list; list = list->next) {
    GstStreamPeriod *stream_period = list->data;

    if ((time != GST_CLOCK_TIME_NONE
            && stream_period->duration != GST_CLOCK_TIME_NONE
            && stream_period->start + stream_period->duration >= time)
        || (time != GST_CLOCK_TIME_NONE && stream_period->start >= time))
      return TRUE;

    if (period_idx != -1 && stream_period->number >= period_idx)
      return TRUE;

    if (period_id != NULL && stream_period->period->id != NULL
        && strcmp (stream_period->period->id, period_id) == 0)
      return TRUE;

  }

  GST_DEBUG ("Building the list of Periods in the Media Presentation");
  /* clean the old period list, if any */
  /* TODO: In theory we could reuse the ones we have so far but that
   * seems more complicated than the overhead caused here
   */
  if (client->periods) {
    g_list_foreach (client->periods,
        (GFunc) gst_mpdparser_free_stream_period, NULL);
    g_list_free (client->periods);
    client->periods = NULL;
  }

  idx = 0;
  start = 0;
  duration = GST_CLOCK_TIME_NONE;

  if (client->mpd_root_node->mediaPresentationDuration <= 0 &&
      client->mpd_root_node->mediaPresentationDuration != -1) {
    /* Invalid MPD file: MPD duration is negative or zero */
    goto syntax_error;
  }

  for (list = client->mpd_root_node->Periods; list;
      /* explicitly advanced below */ ) {
    GstMPDPeriodNode *period_node = list->data;
    GstMPDPeriodNode *next_period_node = NULL;

    /* Download external period */
    if (period_node->xlink_href) {
      GList *new_periods;
      GList *prev;

      new_periods = gst_mpd_client_fetch_external_periods (client, period_node);

      prev = list->prev;
      client->mpd_root_node->Periods =
          g_list_delete_link (client->mpd_root_node->Periods, list);
      gst_mpd_period_node_free (period_node);
      period_node = NULL;

      /* Get new next node, we will insert before this */
      if (prev)
        next = prev->next;
      else
        next = client->mpd_root_node->Periods;

      while (new_periods) {
        client->mpd_root_node->Periods =
            g_list_insert_before (client->mpd_root_node->Periods, next,
            new_periods->data);
        new_periods = g_list_delete_link (new_periods, new_periods);
      }
      next = NULL;

      /* Update our iterator to the first new period if any, or the next */
      if (prev)
        list = prev->next;
      else
        list = client->mpd_root_node->Periods;

      /* And try again */
      continue;
    }

    if (period_node->start != -1) {
      /* we have a regular period */
      /* start cannot be smaller than previous start */
      if (list != g_list_first (client->mpd_root_node->Periods)
          && start >= period_node->start * GST_MSECOND) {
        /* Invalid MPD file: duration would be negative or zero */
        goto syntax_error;
      }
      start = period_node->start * GST_MSECOND;
    } else if (duration != GST_CLOCK_TIME_NONE) {
      /* start time inferred from previous period, this is still a regular period */
      start += duration;
    } else if (idx == 0
        && client->mpd_root_node->type == GST_MPD_FILE_TYPE_STATIC) {
      /* first period of a static MPD file, start time is 0 */
      start = 0;
    } else if (client->mpd_root_node->type == GST_MPD_FILE_TYPE_DYNAMIC) {
      /* this should be a live stream, let this pass */
    } else {
      /* this is an 'Early Available Period' */
      goto early;
    }

    /* compute duration.
       If there is a start time for the next period, or this is the last period
       and mediaPresentationDuration was set, those values will take precedence
       over a configured period duration in computing this period's duration

       ISO/IEC 23009-1:2014(E), chapter 5.3.2.1
       "The Period extends until the PeriodStart of the next Period, or until
       the end of the Media Presentation in the case of the last Period."
     */

    while ((next = g_list_next (list)) != NULL) {
      /* try to infer this period duration from the start time of the next period */
      next_period_node = next->data;

      if (next_period_node->xlink_href) {
        GList *new_periods;

        new_periods =
            gst_mpd_client_fetch_external_periods (client, next_period_node);

        client->mpd_root_node->Periods =
            g_list_delete_link (client->mpd_root_node->Periods, next);
        gst_mpd_period_node_free (next_period_node);
        next_period_node = NULL;
        /* Get new next node, we will insert before this */
        next = g_list_next (list);
        while (new_periods) {
          client->mpd_root_node->Periods =
              g_list_insert_before (client->mpd_root_node->Periods, next,
              new_periods->data);
          new_periods = g_list_delete_link (new_periods, new_periods);
        }

        /* And try again, getting the next list element which is now our newly
         * inserted nodes. If any */
      } else {
        /* Got the next period and it doesn't have to be downloaded first */
        break;
      }
    }

    if (next_period_node) {
      if (next_period_node->start != -1) {
        if (start >= next_period_node->start * GST_MSECOND) {
          /* Invalid MPD file: duration would be negative or zero */
          goto syntax_error;
        }
        duration = next_period_node->start * GST_MSECOND - start;
      } else if (period_node->duration != -1) {
        if (period_node->duration <= 0) {
          /* Invalid MPD file: duration would be negative or zero */
          goto syntax_error;
        }
        duration = period_node->duration * GST_MSECOND;
      } else if (client->mpd_root_node->type == GST_MPD_FILE_TYPE_DYNAMIC) {
        /* might be a live file, ignore unspecified duration */
      } else {
        /* Invalid MPD file! */
        goto syntax_error;
      }
    } else if (client->mpd_root_node->mediaPresentationDuration != -1) {
      /* last Period of the Media Presentation */
      if (client->mpd_root_node->mediaPresentationDuration * GST_MSECOND <=
          start) {
        /* Invalid MPD file: duration would be negative or zero */
        goto syntax_error;
      }
      duration =
          client->mpd_root_node->mediaPresentationDuration * GST_MSECOND -
          start;
    } else if (period_node->duration != -1) {
      duration = period_node->duration * GST_MSECOND;
    } else if (client->mpd_root_node->type == GST_MPD_FILE_TYPE_DYNAMIC) {
      /* might be a live file, ignore unspecified duration */
    } else {
      /* Invalid MPD file! */
      GST_ERROR
          ("Invalid MPD file. The MPD is static without a valid duration");
      goto syntax_error;
    }

    stream_period = g_slice_new0 (GstStreamPeriod);
    client->periods = g_list_append (client->periods, stream_period);
    stream_period->period = period_node;
    stream_period->number = idx++;
    stream_period->start = start;
    stream_period->duration = duration;
    ret = TRUE;
    GST_LOG (" - added Period %d start=%" GST_TIME_FORMAT " duration=%"
        GST_TIME_FORMAT, idx, GST_TIME_ARGS (start), GST_TIME_ARGS (duration));

    if ((time != GST_CLOCK_TIME_NONE
            && stream_period->duration != GST_CLOCK_TIME_NONE
            && stream_period->start + stream_period->duration >= time)
        || (time != GST_CLOCK_TIME_NONE && stream_period->start >= time))
      break;

    if (period_idx != -1 && stream_period->number >= period_idx)
      break;

    if (period_id != NULL && stream_period->period->id != NULL
        && strcmp (stream_period->period->id, period_id) == 0)
      break;

    list = list->next;
  }

  GST_DEBUG
      ("Found a total of %d valid Periods in the Media Presentation up to this point",
      idx);
  return ret;

early:
  GST_WARNING
      ("Found an Early Available Period, skipping the rest of the Media Presentation");
  return ret;

syntax_error:
  GST_WARNING
      ("Cannot get the duration of the Period %d, skipping the rest of the Media Presentation",
      idx);
  return ret;
}

static GList *
gst_mpd_client_fetch_external_adaptation_set (GstMPDClient * client,
    GstMPDPeriodNode * period, GstMPDAdaptationSetNode * adapt_set)
{
  GstFragment *download;
  GstBuffer *adapt_set_buffer;
  GstMapInfo map;
  GError *err = NULL;
  GstUri *base_uri, *uri;
  gchar *query = NULL;
  gchar *uri_string;
  GList *new_adapt_sets = NULL;

  /* ISO/IEC 23009-1:2014 5.5.3 4)
   * Remove nodes that resolve to nothing when resolving
   */
  if (strcmp (adapt_set->xlink_href, "urn:mpeg:dash:resolve-to-zero:2013") == 0) {
    return NULL;
  }

  if (!client->downloader) {
    return NULL;
  }

  /* Build absolute URI */

  /* Get base URI at the MPD level */
  base_uri =
      gst_uri_from_string (client->mpd_base_uri ? client->
      mpd_base_uri : client->mpd_uri);

  /* combine a BaseURL at the MPD level with the current base url */
  base_uri =
      gst_mpd_helper_combine_urls (base_uri, client->mpd_root_node->BaseURLs,
      &query, 0);

  /* combine a BaseURL at the Period level with the current base url */
  base_uri =
      gst_mpd_helper_combine_urls (base_uri, period->BaseURLs, &query, 0);

  uri = gst_uri_from_string_with_base (base_uri, adapt_set->xlink_href);
  if (query)
    gst_uri_set_query_string (uri, query);
  g_free (query);
  uri_string = gst_uri_to_string (uri);
  gst_uri_unref (base_uri);
  gst_uri_unref (uri);

  download =
      gst_uri_downloader_fetch_uri (client->downloader,
      uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err);
  g_free (uri_string);

  if (!download) {
    GST_ERROR ("Failed to download external AdaptationSet node at '%s': %s",
        adapt_set->xlink_href, err->message);
    g_clear_error (&err);
    return NULL;
  }

  adapt_set_buffer = gst_fragment_get_buffer (download);
  g_object_unref (download);

  gst_buffer_map (adapt_set_buffer, &map, GST_MAP_READ);

  new_adapt_sets =
      gst_mpdparser_get_external_adaptation_sets ((const gchar *) map.data,
      map.size, period);

  gst_buffer_unmap (adapt_set_buffer, &map);
  gst_buffer_unref (adapt_set_buffer);

  return new_adapt_sets;
}

static GList *
gst_mpd_client_get_adaptation_sets_for_period (GstMPDClient * client,
    GstStreamPeriod * period)
{
  GList *list;

  g_return_val_if_fail (period != NULL, NULL);

  /* Resolve all external adaptation sets of this period. Every user of
   * the adaptation sets would need to know the content of all adaptation sets
   * to decide which one to use, so we have to resolve them all here
   */
  for (list = period->period->AdaptationSets; list;
      /* advanced explicitly below */ ) {
    GstMPDAdaptationSetNode *adapt_set = (GstMPDAdaptationSetNode *) list->data;
    GList *new_adapt_sets = NULL, *prev, *next;

    if (!adapt_set->xlink_href) {
      list = list->next;
      continue;
    }

    new_adapt_sets =
        gst_mpd_client_fetch_external_adaptation_set (client, period->period,
        adapt_set);

    prev = list->prev;
    period->period->AdaptationSets =
        g_list_delete_link (period->period->AdaptationSets, list);
    gst_mpd_adaptation_set_node_free (adapt_set);
    adapt_set = NULL;

    /* Get new next node, we will insert before this */
    if (prev)
      next = prev->next;
    else
      next = period->period->AdaptationSets;

    while (new_adapt_sets) {
      period->period->AdaptationSets =
          g_list_insert_before (period->period->AdaptationSets, next,
          new_adapt_sets->data);
      new_adapt_sets = g_list_delete_link (new_adapt_sets, new_adapt_sets);
    }

    /* Update our iterator to the first new adaptation set if any, or the next */
    if (prev)
      list = prev->next;
    else
      list = period->period->AdaptationSets;
  }

  return period->period->AdaptationSets;
}

GList *
gst_mpd_client_get_adaptation_sets (GstMPDClient * client)
{
  GstStreamPeriod *stream_period;

  stream_period = gst_mpd_client_get_stream_period (client);
  if (stream_period == NULL || stream_period->period == NULL) {
    GST_DEBUG ("No more Period nodes in the MPD file, terminating...");
    return NULL;
  }

  return gst_mpd_client_get_adaptation_sets_for_period (client, stream_period);
}

gboolean
gst_mpd_client_setup_streaming (GstMPDClient * client,
    GstMPDAdaptationSetNode * adapt_set)
{
  GstMPDRepresentationNode *representation;
  GList *rep_list = NULL;
  GstActiveStream *stream;

  rep_list = adapt_set->Representations;
  if (!rep_list) {
    GST_WARNING ("Can not retrieve any representation, aborting...");
    return FALSE;
  }

  stream = g_slice_new0 (GstActiveStream);
  gst_mpdparser_init_active_stream_segments (stream);

  stream->baseURL_idx = 0;
  stream->cur_adapt_set = adapt_set;

  GST_DEBUG ("0. Current stream %p", stream);

#if 0
  /* fast start */
  representation =
      gst_mpdparser_get_representation_with_max_bandwidth (rep_list,
      stream->max_bandwidth);

  if (!representation) {
    GST_WARNING
        ("Can not retrieve a representation with the requested bandwidth");
    representation = gst_mpd_client_get_lowest_representation (rep_list);
  }
#else
  /* slow start */
  representation = gst_mpd_client_get_lowest_representation (rep_list);
#endif

  if (!representation) {
    GST_WARNING ("No valid representation in the MPD file, aborting...");
    gst_mpdparser_free_active_stream (stream);
    return FALSE;
  }
  stream->mimeType =
      gst_mpdparser_representation_get_mimetype (adapt_set, representation);
  if (stream->mimeType == GST_STREAM_UNKNOWN) {
    GST_WARNING ("Unknown mime type in the representation, aborting...");
    gst_mpdparser_free_active_stream (stream);
    return FALSE;
  }

  client->active_streams = g_list_append (client->active_streams, stream);
  if (!gst_mpd_client_setup_representation (client, stream, representation)) {
    GST_WARNING ("Failed to setup the representation, aborting...");
    return FALSE;
  }

  GST_INFO ("Successfully setup the download pipeline for mimeType %d",
      stream->mimeType);

  return TRUE;
}

gboolean
gst_mpd_client_stream_seek (GstMPDClient * client, GstActiveStream * stream,
    gboolean forward, GstSeekFlags flags, GstClockTime ts,
    GstClockTime * final_ts)
{
  gint index = 0;
  gint repeat_index = 0;
  GstMediaSegment *selectedChunk = NULL;

  g_return_val_if_fail (stream != NULL, 0);

  if (stream->segments) {
    for (index = 0; index < stream->segments->len; index++) {
      gboolean in_segment = FALSE;
      GstMediaSegment *segment = g_ptr_array_index (stream->segments, index);
      GstClockTime end_time;

      GST_DEBUG ("Looking at fragment sequence chunk %d / %d", index,
          stream->segments->len);

      end_time =
          gst_mpd_client_get_segment_end_time (client, stream->segments,
          segment, index);

      /* avoid downloading another fragment just for 1ns in reverse mode */
      if (forward)
        in_segment = ts < end_time;
      else
        in_segment = ts <= end_time;

      if (in_segment) {
        GstClockTime chunk_time;

        selectedChunk = segment;
        repeat_index =
            ((ts - segment->start) +
            ((GstMediaSegment *) stream->segments->pdata[0])->start) /
            segment->duration;

        chunk_time = segment->start + segment->duration * repeat_index;

        /* At the end of a segment in reverse mode, start from the previous fragment */
        if (!forward && repeat_index > 0
            && ((ts - segment->start) % segment->duration == 0))
          repeat_index--;

        if ((flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST) {
          if (repeat_index + 1 < segment->repeat) {
            if (ts - chunk_time > chunk_time + segment->duration - ts)
              repeat_index++;
          } else if (index + 1 < stream->segments->len) {
            GstMediaSegment *next_segment =
                g_ptr_array_index (stream->segments, index + 1);

            if (ts - chunk_time > next_segment->start - ts) {
              repeat_index = 0;
              selectedChunk = next_segment;
              index++;
            }
          }
        } else if (((forward && flags & GST_SEEK_FLAG_SNAP_AFTER) ||
                (!forward && flags & GST_SEEK_FLAG_SNAP_BEFORE)) &&
            ts != chunk_time) {

          if (repeat_index + 1 < segment->repeat) {
            repeat_index++;
          } else {
            repeat_index = 0;
            if (index + 1 >= stream->segments->len) {
              selectedChunk = NULL;
            } else {
              selectedChunk = g_ptr_array_index (stream->segments, ++index);
            }
          }
        }
        break;
      }
    }

    if (selectedChunk == NULL) {
      stream->segment_index = stream->segments->len;
      stream->segment_repeat_index = 0;
      GST_DEBUG ("Seek to after last segment");
      return FALSE;
    }

    if (final_ts)
      *final_ts = selectedChunk->start + selectedChunk->duration * repeat_index;
  } else {
    GstClockTime duration =
        gst_mpd_client_get_segment_duration (client, stream, NULL);
    GstStreamPeriod *stream_period = gst_mpd_client_get_stream_period (client);
    guint segments_count = gst_mpd_client_get_segments_counts (client, stream);
    GstClockTime index_time;

    g_return_val_if_fail (GST_MPD_MULT_SEGMENT_BASE_NODE
        (stream->cur_seg_template)->SegmentTimeline == NULL, FALSE);
    if (!GST_CLOCK_TIME_IS_VALID (duration) || duration == 0) {
      return FALSE;
    }

    if (ts > stream_period->start)
      ts -= stream_period->start;
    else
      ts = 0;

    index = ts / duration;

    /* At the end of a segment in reverse mode, start from the previous fragment */
    if (!forward && index > 0 && ts % duration == 0)
      index--;

    index_time = index * duration;

    if ((flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST) {
      if (ts - index_time > index_time + duration - ts)
        index++;
    } else if (((forward && flags & GST_SEEK_FLAG_SNAP_AFTER) ||
            (!forward && flags & GST_SEEK_FLAG_SNAP_BEFORE))
        && ts != index_time) {
      index++;
    }

    if (segments_count > 0 && index >= segments_count) {
      stream->segment_index = segments_count;
      stream->segment_repeat_index = 0;
      GST_DEBUG ("Seek to after last segment");
      return FALSE;
    }
    if (final_ts)
      *final_ts = index * duration;
  }

  stream->segment_repeat_index = repeat_index;
  stream->segment_index = index;

  return TRUE;
}

gint64
gst_mpd_client_calculate_time_difference (const GstDateTime * t1,
    const GstDateTime * t2)
{
  GDateTime *gdt1, *gdt2;
  GTimeSpan diff;

  g_assert (t1 != NULL && t2 != NULL);
  gdt1 = gst_date_time_to_g_date_time ((GstDateTime *) t1);
  gdt2 = gst_date_time_to_g_date_time ((GstDateTime *) t2);
  diff = g_date_time_difference (gdt2, gdt1);
  g_date_time_unref (gdt1);
  g_date_time_unref (gdt2);
  return diff * GST_USECOND;
}

GstDateTime *
gst_mpd_client_add_time_difference (GstDateTime * t1, gint64 usecs)
{
  GDateTime *gdt;
  GDateTime *gdt2;
  GstDateTime *rv;

  g_assert (t1 != NULL);
  gdt = gst_date_time_to_g_date_time (t1);
  g_assert (gdt != NULL);
  gdt2 = g_date_time_add (gdt, usecs);
  g_assert (gdt2 != NULL);
  g_date_time_unref (gdt);
  rv = gst_date_time_new_from_g_date_time (gdt2);

  /* Don't g_date_time_unref(gdt2) because gst_date_time_new_from_g_date_time takes
   * ownership of the GDateTime pointer.
   */

  return rv;
}

gboolean
gst_mpd_client_get_last_fragment_timestamp_end (GstMPDClient * client,
    guint stream_idx, GstClockTime * ts)
{
  GstActiveStream *stream;
  gint segment_idx;
  GstMediaSegment *currentChunk;
  GstStreamPeriod *stream_period;

  GST_DEBUG ("Stream index: %i", stream_idx);
  stream = g_list_nth_data (client->active_streams, stream_idx);
  g_return_val_if_fail (stream != NULL, 0);

  if (!stream->segments) {
    stream_period = gst_mpd_client_get_stream_period (client);
    *ts = stream_period->start + stream_period->duration;
  } else {
    segment_idx = gst_mpd_client_get_segments_counts (client, stream) - 1;
    if (segment_idx >= stream->segments->len) {
      GST_WARNING ("Segment index %d is outside of segment list of length %d",
          segment_idx, stream->segments->len);
      return FALSE;
    }
    currentChunk = g_ptr_array_index (stream->segments, segment_idx);

    if (currentChunk->repeat >= 0) {
      *ts =
          currentChunk->start + (currentChunk->duration * (1 +
              currentChunk->repeat));
    } else {
      /* 5.3.9.6.1: negative repeat means repeat till the end of the
       * period, or the next update of the MPD (which I think is
       * implicit, as this will all get deleted/recreated), or the
       * start of the next segment, if any. */
      stream_period = gst_mpd_client_get_stream_period (client);
      *ts = stream_period->start + stream_period->duration;
    }
  }

  return TRUE;
}

gboolean
gst_mpd_client_get_next_fragment_timestamp (GstMPDClient * client,
    guint stream_idx, GstClockTime * ts)
{
  GstActiveStream *stream;
  GstMediaSegment *currentChunk;

  GST_DEBUG ("Stream index: %i", stream_idx);
  stream = g_list_nth_data (client->active_streams, stream_idx);
  g_return_val_if_fail (stream != NULL, 0);

  if (stream->segments) {
    GST_DEBUG ("Looking for fragment sequence chunk %d / %d",
        stream->segment_index, stream->segments->len);
    if (stream->segment_index >= stream->segments->len)
      return FALSE;
    currentChunk = g_ptr_array_index (stream->segments, stream->segment_index);

    *ts =
        currentChunk->start +
        (currentChunk->duration * stream->segment_repeat_index);
  } else {
    GstClockTime duration =
        gst_mpd_client_get_segment_duration (client, stream, NULL);
    guint segments_count = gst_mpd_client_get_segments_counts (client, stream);

    g_return_val_if_fail (GST_MPD_MULT_SEGMENT_BASE_NODE
        (stream->cur_seg_template)->SegmentTimeline == NULL, FALSE);
    if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0
            && stream->segment_index >= segments_count)) {
      return FALSE;
    }
    *ts = stream->segment_index * duration;
  }

  return TRUE;
}

GstClockTime
gst_mpd_client_get_stream_presentation_offset (GstMPDClient * client,
    guint stream_idx)
{
  GstActiveStream *stream = NULL;

  g_return_val_if_fail (client != NULL, 0);
  g_return_val_if_fail (client->active_streams != NULL, 0);
  stream = g_list_nth_data (client->active_streams, stream_idx);
  g_return_val_if_fail (stream != NULL, 0);

  return stream->presentationTimeOffset;
}

GstClockTime
gst_mpd_client_get_period_start_time (GstMPDClient * client)
{
  GstStreamPeriod *stream_period = NULL;

  g_return_val_if_fail (client != NULL, 0);
  stream_period = gst_mpd_client_get_stream_period (client);
  g_return_val_if_fail (stream_period != NULL, 0);

  return stream_period->start;
}

/**
 * gst_mpd_client_get_utc_timing_sources:
 * @client: #GstMPDClient to check for UTCTiming elements
 * @methods: A bit mask of #GstMPDUTCTimingType that specifies the methods
 *     to search for.
 * @selected_method: (nullable): The selected method
 * Returns: (transfer none): A NULL terminated array of URLs of servers
 *     that use @selected_method to provide a realtime clock.
 *
 * Searches the UTCTiming elements found in the manifest for an element
 * that uses one of the UTC timing methods specified in @selected_method.
 * If multiple UTCTiming elements are present that support one of the
 * methods specified in @selected_method, the first one is returned.
 *
 * Since: 1.6
 */
gchar **
gst_mpd_client_get_utc_timing_sources (GstMPDClient * client,
    guint methods, GstMPDUTCTimingType * selected_method)
{
  GList *list;

  g_return_val_if_fail (client != NULL, NULL);
  g_return_val_if_fail (client->mpd_root_node != NULL, NULL);
  for (list = g_list_first (client->mpd_root_node->UTCTimings); list;
      list = g_list_next (list)) {
    const GstMPDUTCTimingNode *node = (const GstMPDUTCTimingNode *) list->data;
    if (node->method & methods) {
      if (selected_method) {
        *selected_method = node->method;
      }
      return node->urls;
    }
  }
  return NULL;
}


gboolean
gst_mpd_client_get_next_fragment (GstMPDClient * client,
    guint indexStream, GstMediaFragmentInfo * fragment)
{
  GstActiveStream *stream = NULL;
  GstMediaSegment *currentChunk;
  gchar *mediaURL = NULL;
  gchar *indexURL = NULL;
  GstUri *base_url, *frag_url;

  /* select stream */
  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->active_streams != NULL, FALSE);
  stream = g_list_nth_data (client->active_streams, indexStream);
  g_return_val_if_fail (stream != NULL, FALSE);
  g_return_val_if_fail (stream->cur_representation != NULL, FALSE);

  if (stream->segments) {
    GST_DEBUG ("Looking for fragment sequence chunk %d / %d",
        stream->segment_index, stream->segments->len);
    if (stream->segment_index >= stream->segments->len)
      return FALSE;
  } else {
    GstClockTime duration = gst_mpd_client_get_segment_duration (client,
        stream, NULL);
    guint segments_count = gst_mpd_client_get_segments_counts (client, stream);

    g_return_val_if_fail (GST_MPD_MULT_SEGMENT_BASE_NODE
        (stream->cur_seg_template)->SegmentTimeline == NULL, FALSE);
    if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0
            && stream->segment_index >= segments_count)) {
      return FALSE;
    }
    fragment->duration = duration;
  }

  /* FIXME rework discont checking */
  /* fragment->discontinuity = segment_idx != currentChunk.number; */
  fragment->range_start = 0;
  fragment->range_end = -1;
  fragment->index_uri = NULL;
  fragment->index_range_start = 0;
  fragment->index_range_end = -1;

  if (stream->segments) {
    currentChunk = g_ptr_array_index (stream->segments, stream->segment_index);

    GST_DEBUG ("currentChunk->SegmentURL = %p", currentChunk->SegmentURL);
    if (currentChunk->SegmentURL != NULL) {
      mediaURL = gst_mpdparser_get_mediaURL (stream, currentChunk->SegmentURL);
      indexURL = g_strdup (currentChunk->SegmentURL->index);
    } else if (stream->cur_seg_template != NULL) {
      mediaURL =
          gst_mpdparser_build_URL_from_template (stream->cur_seg_template->
          media, stream->cur_representation->id,
          currentChunk->number + stream->segment_repeat_index,
          stream->cur_representation->bandwidth,
          currentChunk->scale_start +
          stream->segment_repeat_index * currentChunk->scale_duration);
      if (stream->cur_seg_template->index) {
        indexURL =
            gst_mpdparser_build_URL_from_template (stream->cur_seg_template->
            index, stream->cur_representation->id,
            currentChunk->number + stream->segment_repeat_index,
            stream->cur_representation->bandwidth,
            currentChunk->scale_start +
            stream->segment_repeat_index * currentChunk->scale_duration);
      }
    }
    GST_DEBUG ("mediaURL = %s", mediaURL);
    GST_DEBUG ("indexURL = %s", indexURL);

    fragment->timestamp =
        currentChunk->start +
        stream->segment_repeat_index * currentChunk->duration;
    fragment->duration = currentChunk->duration;
    if (currentChunk->SegmentURL) {
      if (currentChunk->SegmentURL->mediaRange) {
        fragment->range_start =
            currentChunk->SegmentURL->mediaRange->first_byte_pos;
        fragment->range_end =
            currentChunk->SegmentURL->mediaRange->last_byte_pos;
      }
      if (currentChunk->SegmentURL->indexRange) {
        fragment->index_range_start =
            currentChunk->SegmentURL->indexRange->first_byte_pos;
        fragment->index_range_end =
            currentChunk->SegmentURL->indexRange->last_byte_pos;
      }
    }
  } else {
    if (stream->cur_seg_template != NULL) {
      mediaURL =
          gst_mpdparser_build_URL_from_template (stream->cur_seg_template->
          media, stream->cur_representation->id,
          stream->segment_index +
          GST_MPD_MULT_SEGMENT_BASE_NODE (stream->
              cur_seg_template)->startNumber,
          stream->cur_representation->bandwidth,
          stream->segment_index * fragment->duration);
      if (stream->cur_seg_template->index) {
        indexURL =
            gst_mpdparser_build_URL_from_template (stream->cur_seg_template->
            index, stream->cur_representation->id,
            stream->segment_index +
            GST_MPD_MULT_SEGMENT_BASE_NODE (stream->
                cur_seg_template)->startNumber,
            stream->cur_representation->bandwidth,
            stream->segment_index * fragment->duration);
      }
    } else {
      return FALSE;
    }

    GST_DEBUG ("mediaURL = %s", mediaURL);
    GST_DEBUG ("indexURL = %s", indexURL);

    fragment->timestamp = stream->segment_index * fragment->duration;
  }

  base_url = gst_uri_from_string (stream->baseURL);
  frag_url = gst_uri_from_string_with_base (base_url, mediaURL);
  g_free (mediaURL);
  if (stream->queryURL) {
    frag_url = gst_uri_make_writable (frag_url);
    gst_uri_set_query_string (frag_url, stream->queryURL);
  }
  fragment->uri = gst_uri_to_string (frag_url);
  gst_uri_unref (frag_url);

  if (indexURL != NULL) {
    frag_url = gst_uri_make_writable (gst_uri_from_string_with_base (base_url,
            indexURL));
    gst_uri_set_query_string (frag_url, stream->queryURL);
    fragment->index_uri = gst_uri_to_string (frag_url);
    gst_uri_unref (frag_url);
    g_free (indexURL);
  } else if (indexURL == NULL && (fragment->index_range_start
          || fragment->index_range_end != -1)) {
    /* index has no specific URL but has a range, we should only use this if
     * the media also has a range, otherwise we are serving some data twice
     * (in the media fragment and again in the index) */
    if (!(fragment->range_start || fragment->range_end != -1)) {
      GST_WARNING ("Ignoring index ranges because there isn't a media range "
          "and URIs would be the same");
      /* removing index information */
      fragment->index_range_start = 0;
      fragment->index_range_end = -1;
    }
  }

  gst_uri_unref (base_url);

  GST_DEBUG ("Loading chunk with URL %s", fragment->uri);

  return TRUE;
}

gboolean
gst_mpd_client_has_next_segment (GstMPDClient * client,
    GstActiveStream * stream, gboolean forward)
{
  if (forward) {
    guint segments_count = gst_mpd_client_get_segments_counts (client, stream);

    if (segments_count > 0 && stream->segments
        && stream->segment_index + 1 == segments_count) {
      GstMediaSegment *segment;

      segment = g_ptr_array_index (stream->segments, stream->segment_index);
      if (segment->repeat >= 0
          && stream->segment_repeat_index >= segment->repeat)
        return FALSE;
    } else if (segments_count > 0
        && stream->segment_index + 1 >= segments_count) {
      return FALSE;
    }
  } else {
    if (stream->segment_index < 0)
      return FALSE;
  }

  return TRUE;
}

GstFlowReturn
gst_mpd_client_advance_segment (GstMPDClient * client, GstActiveStream * stream,
    gboolean forward)
{
  GstMediaSegment *segment;
  GstFlowReturn ret = GST_FLOW_OK;
  guint segments_count = gst_mpd_client_get_segments_counts (client, stream);

  GST_DEBUG ("Advancing segment. Current: %d / %d r:%d", stream->segment_index,
      segments_count, stream->segment_repeat_index);

  /* handle special cases first */
  if (forward) {
    if (segments_count > 0 && stream->segment_index >= segments_count) {
      ret = GST_FLOW_EOS;
      goto done;
    }

    if (stream->segments == NULL) {
      if (stream->segment_index < 0) {
        stream->segment_index = 0;
      } else {
        stream->segment_index++;
        if (segments_count > 0 && stream->segment_index >= segments_count) {
          ret = GST_FLOW_EOS;
        }
      }
      goto done;
    }

    /* special case for when playback direction is reverted right at *
     * the end of the segment list */
    if (stream->segment_index < 0) {
      stream->segment_index = 0;
      goto done;
    }
  } else {
    if (stream->segments == NULL)
      stream->segment_index--;
    if (stream->segment_index < 0) {
      stream->segment_index = -1;
      ret = GST_FLOW_EOS;
      goto done;
    }
    if (stream->segments == NULL)
      goto done;

    /* special case for when playback direction is reverted right at *
     * the end of the segment list */
    if (stream->segment_index >= segments_count) {
      stream->segment_index = segments_count - 1;
      segment = g_ptr_array_index (stream->segments, stream->segment_index);
      if (segment->repeat >= 0) {
        stream->segment_repeat_index = segment->repeat;
      } else {
        GstClockTime start = segment->start;
        GstClockTime end =
            gst_mpd_client_get_segment_end_time (client, stream->segments,
            segment,
            stream->segment_index);
        stream->segment_repeat_index =
            (guint) (end - start) / segment->duration;
      }
      goto done;
    }
  }

  /* for the normal cases we can get the segment safely here */
  segment = g_ptr_array_index (stream->segments, stream->segment_index);
  if (forward) {
    if (segment->repeat >= 0 && stream->segment_repeat_index >= segment->repeat) {
      stream->segment_repeat_index = 0;
      stream->segment_index++;
      if (segments_count > 0 && stream->segment_index >= segments_count) {
        ret = GST_FLOW_EOS;
        goto done;
      }
    } else {
      stream->segment_repeat_index++;
    }
  } else {
    if (stream->segment_repeat_index == 0) {
      stream->segment_index--;
      if (stream->segment_index < 0) {
        ret = GST_FLOW_EOS;
        goto done;
      }

      segment = g_ptr_array_index (stream->segments, stream->segment_index);
      /* negative repeats only seem to make sense at the end of a list,
       * so this one will probably not be. Needs some sanity checking
       * when loading the XML data. */
      if (segment->repeat >= 0) {
        stream->segment_repeat_index = segment->repeat;
      } else {
        GstClockTime start = segment->start;
        GstClockTime end =
            gst_mpd_client_get_segment_end_time (client, stream->segments,
            segment,
            stream->segment_index);
        stream->segment_repeat_index =
            (guint) (end - start) / segment->duration;
      }
    } else {
      stream->segment_repeat_index--;
    }
  }

done:
  GST_DEBUG ("Advanced to segment: %d / %d r:%d (ret: %s)",
      stream->segment_index, segments_count,
      stream->segment_repeat_index, gst_flow_get_name (ret));
  return ret;
}

gboolean
gst_mpd_client_get_next_header (GstMPDClient * client, gchar ** uri,
    guint stream_idx, gint64 * range_start, gint64 * range_end)
{
  GstActiveStream *stream;

  stream = gst_mpd_client_get_active_stream_by_index (client, stream_idx);
  g_return_val_if_fail (stream != NULL, FALSE);
  g_return_val_if_fail (stream->cur_representation != NULL, FALSE);
#ifndef G_DISABLE_CHECKS
  GstStreamPeriod *stream_period = gst_mpd_client_get_stream_period (client);
  g_return_val_if_fail (stream_period != NULL, FALSE);
  g_return_val_if_fail (stream_period->period != NULL, FALSE);
#endif

  *range_start = 0;
  *range_end = -1;

  GST_DEBUG ("Looking for current representation header");
  *uri = NULL;
  if (stream->cur_segment_base) {
    if (stream->cur_segment_base->Initialization) {
      *uri = gst_mpdparser_get_initializationURL (stream,
          stream->cur_segment_base->Initialization);
      if (stream->cur_segment_base->Initialization->range) {
        *range_start =
            stream->cur_segment_base->Initialization->range->first_byte_pos;
        *range_end =
            stream->cur_segment_base->Initialization->range->last_byte_pos;
      }
    } else if (stream->cur_segment_base->indexRange) {
      *uri = gst_mpdparser_get_initializationURL (stream,
          stream->cur_segment_base->Initialization);
      *range_start = 0;
      *range_end = stream->cur_segment_base->indexRange->first_byte_pos - 1;
    }
  } else if (stream->cur_seg_template
      && stream->cur_seg_template->initialization) {
    *uri =
        gst_mpdparser_build_URL_from_template (stream->cur_seg_template->
        initialization, stream->cur_representation->id, 0,
        stream->cur_representation->bandwidth, 0);
  }

  return *uri == NULL ? FALSE : TRUE;
}

gboolean
gst_mpd_client_get_next_header_index (GstMPDClient * client, gchar ** uri,
    guint stream_idx, gint64 * range_start, gint64 * range_end)
{
  GstActiveStream *stream;

  stream = gst_mpd_client_get_active_stream_by_index (client, stream_idx);
  g_return_val_if_fail (stream != NULL, FALSE);
  g_return_val_if_fail (stream->cur_representation != NULL, FALSE);
#ifndef G_DISABLE_CHECKS
  GstStreamPeriod *stream_period = gst_mpd_client_get_stream_period (client);
  g_return_val_if_fail (stream_period != NULL, FALSE);
  g_return_val_if_fail (stream_period->period != NULL, FALSE);
#endif

  *range_start = 0;
  *range_end = -1;

  GST_DEBUG ("Looking for current representation index");
  *uri = NULL;
  if (stream->cur_segment_base && stream->cur_segment_base->indexRange) {
    *uri = gst_mpdparser_get_initializationURL (stream,
        stream->cur_segment_base->RepresentationIndex);
    *range_start = stream->cur_segment_base->indexRange->first_byte_pos;
    *range_end = stream->cur_segment_base->indexRange->last_byte_pos;
  } else if (stream->cur_seg_template && stream->cur_seg_template->index) {
    *uri =
        gst_mpdparser_build_URL_from_template (stream->cur_seg_template->index,
        stream->cur_representation->id, 0,
        stream->cur_representation->bandwidth, 0);
  }

  return *uri == NULL ? FALSE : TRUE;
}

GstClockTime
gst_mpd_client_get_next_fragment_duration (GstMPDClient * client,
    GstActiveStream * stream)
{
  GstMediaSegment *media_segment = NULL;
  gint seg_idx;

  g_return_val_if_fail (stream != NULL, 0);

  seg_idx = stream->segment_index;

  if (stream->segments) {
    if (seg_idx < stream->segments->len && seg_idx >= 0)
      media_segment = g_ptr_array_index (stream->segments, seg_idx);

    return media_segment == NULL ? 0 : media_segment->duration;
  } else {
    GstClockTime duration =
        gst_mpd_client_get_segment_duration (client, stream, NULL);
    guint segments_count = gst_mpd_client_get_segments_counts (client, stream);

    g_return_val_if_fail (GST_MPD_MULT_SEGMENT_BASE_NODE
        (stream->cur_seg_template)->SegmentTimeline == NULL, 0);

    if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0
            && seg_idx >= segments_count)) {
      return 0;
    }
    return duration;
  }
}

GstClockTime
gst_mpd_client_get_media_presentation_duration (GstMPDClient * client)
{
  GstClockTime duration;

  g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);

  if (client->mpd_root_node->mediaPresentationDuration != -1) {
    duration = client->mpd_root_node->mediaPresentationDuration * GST_MSECOND;
  } else {
    /* We can only get the duration for on-demand streams */
    duration = GST_CLOCK_TIME_NONE;
  }

  return duration;
}

gboolean
gst_mpd_client_set_period_id (GstMPDClient * client, const gchar * period_id)
{
  GstStreamPeriod *next_stream_period;
  gboolean ret = FALSE;
  GList *iter;
  guint period_idx;

  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->periods != NULL, FALSE);
  g_return_val_if_fail (period_id != NULL, FALSE);

  if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE, -1,
          period_id))
    return FALSE;

  for (period_idx = 0, iter = client->periods; iter;
      period_idx++, iter = g_list_next (iter)) {
    next_stream_period = iter->data;

    if (next_stream_period->period->id
        && strcmp (next_stream_period->period->id, period_id) == 0) {
      ret = TRUE;
      client->period_idx = period_idx;
      break;
    }
  }

  return ret;
}

gboolean
gst_mpd_client_set_period_index (GstMPDClient * client, guint period_idx)
{
  GstStreamPeriod *next_stream_period;
  gboolean ret = FALSE;

  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->periods != NULL, FALSE);

  if (!gst_mpd_client_setup_media_presentation (client, -1, period_idx, NULL))
    return FALSE;

  next_stream_period = g_list_nth_data (client->periods, period_idx);
  if (next_stream_period != NULL) {
    client->period_idx = period_idx;
    ret = TRUE;
  }

  return ret;
}

guint
gst_mpd_client_get_period_index (GstMPDClient * client)
{
  guint period_idx;

  g_return_val_if_fail (client != NULL, 0);
  period_idx = client->period_idx;

  return period_idx;
}

const gchar *
gst_mpd_client_get_period_id (GstMPDClient * client)
{
  GstStreamPeriod *period;
  gchar *period_id = NULL;

  g_return_val_if_fail (client != NULL, 0);
  period = g_list_nth_data (client->periods, client->period_idx);
  if (period && period->period)
    period_id = period->period->id;

  return period_id;
}

gboolean
gst_mpd_client_has_next_period (GstMPDClient * client)
{
  GList *next_stream_period;
  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->periods != NULL, FALSE);

  if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE,
          client->period_idx + 1, NULL))
    return FALSE;

  next_stream_period =
      g_list_nth_data (client->periods, client->period_idx + 1);
  return next_stream_period != NULL;
}

gboolean
gst_mpd_client_has_previous_period (GstMPDClient * client)
{
  GList *next_stream_period;
  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->periods != NULL, FALSE);

  if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE,
          client->period_idx - 1, NULL))
    return FALSE;

  next_stream_period =
      g_list_nth_data (client->periods, client->period_idx - 1);

  return next_stream_period != NULL;
}

gint
gst_mpd_client_get_rep_idx_with_min_bandwidth (GList * Representations)
{
  GList *list = NULL, *lowest = NULL;
  GstMPDRepresentationNode *rep = NULL;
  gint lowest_bandwidth = -1;

  if (Representations == NULL)
    return -1;

  for (list = g_list_first (Representations); list; list = g_list_next (list)) {
    rep = (GstMPDRepresentationNode *) list->data;
    if (rep && (!lowest || rep->bandwidth < lowest_bandwidth)) {
      lowest = list;
      lowest_bandwidth = rep->bandwidth;
    }
  }

  return lowest ? g_list_position (Representations, lowest) : -1;
}

gint
gst_mpd_client_get_rep_idx_with_max_bandwidth (GList * Representations,
    gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint
    max_video_framerate_n, gint max_video_framerate_d)
{
  GList *list = NULL, *best = NULL;
  GstMPDRepresentationNode *representation;
  gint best_bandwidth = 0;

  GST_DEBUG ("max_bandwidth = %" G_GINT64_FORMAT, max_bandwidth);

  if (Representations == NULL)
    return -1;

  if (max_bandwidth <= 0)       /* 0 => get lowest representation available */
    return gst_mpd_client_get_rep_idx_with_min_bandwidth (Representations);

  for (list = g_list_first (Representations); list; list = g_list_next (list)) {
    GstXMLFrameRate *framerate = NULL;

    representation = (GstMPDRepresentationNode *) list->data;

    /* FIXME: Really? */
    if (!representation)
      continue;

    framerate = GST_MPD_REPRESENTATION_BASE_NODE (representation)->frameRate;
    if (!framerate)
      framerate =
          GST_MPD_REPRESENTATION_BASE_NODE (representation)->maxFrameRate;

    if (framerate && max_video_framerate_n > 0) {
      if (gst_util_fraction_compare (framerate->num, framerate->den,
              max_video_framerate_n, max_video_framerate_d) > 0)
        continue;
    }

    if (max_video_width > 0
        && GST_MPD_REPRESENTATION_BASE_NODE (representation)->width >
        max_video_width)
      continue;
    if (max_video_height > 0
        && GST_MPD_REPRESENTATION_BASE_NODE (representation)->height >
        max_video_height)
      continue;

    if (representation->bandwidth <= max_bandwidth &&
        representation->bandwidth > best_bandwidth) {
      best = list;
      best_bandwidth = representation->bandwidth;
    }
  }

  return best ? g_list_position (Representations, best) : -1;
}

void
gst_mpd_client_seek_to_first_segment (GstMPDClient * client)
{
  GList *list;

  g_return_if_fail (client != NULL);
  g_return_if_fail (client->active_streams != NULL);

  for (list = g_list_first (client->active_streams); list;
      list = g_list_next (list)) {
    GstActiveStream *stream = (GstActiveStream *) list->data;
    if (stream) {
      stream->segment_index = 0;
      stream->segment_repeat_index = 0;
    }
  }
}

static guint
gst_mpd_client_get_segments_counts (GstMPDClient * client,
    GstActiveStream * stream)
{
  GstStreamPeriod *stream_period;

  g_return_val_if_fail (stream != NULL, 0);

  if (stream->segments)
    return stream->segments->len;
  g_return_val_if_fail (GST_MPD_MULT_SEGMENT_BASE_NODE
      (stream->cur_seg_template)->SegmentTimeline == NULL, 0);

  stream_period = gst_mpd_client_get_stream_period (client);
  if (stream_period->duration != -1)
    return gst_util_uint64_scale_ceil (stream_period->duration, 1,
        gst_mpd_client_get_segment_duration (client, stream, NULL));

  return 0;
}

gboolean
gst_mpd_client_is_live (GstMPDClient * client)
{
  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->mpd_root_node != NULL, FALSE);

  return client->mpd_root_node->type == GST_MPD_FILE_TYPE_DYNAMIC;
}

guint
gst_mpd_client_get_nb_active_stream (GstMPDClient * client)
{
  g_return_val_if_fail (client != NULL, 0);

  return g_list_length (client->active_streams);
}

guint
gst_mpd_client_get_nb_adaptationSet (GstMPDClient * client)
{
  GstStreamPeriod *stream_period;

  stream_period = gst_mpd_client_get_stream_period (client);
  g_return_val_if_fail (stream_period != NULL, 0);
  g_return_val_if_fail (stream_period->period != NULL, 0);

  return g_list_length (stream_period->period->AdaptationSets);
}

GstActiveStream *
gst_mpd_client_get_active_stream_by_index (GstMPDClient * client,
    guint stream_idx)
{
  g_return_val_if_fail (client != NULL, NULL);
  g_return_val_if_fail (client->active_streams != NULL, NULL);

  return g_list_nth_data (client->active_streams, stream_idx);
}

gboolean
gst_mpd_client_active_stream_contains_subtitles (GstActiveStream * stream)
{
  const gchar *mimeType;
  const gchar *adapt_set_codecs;
  const gchar *rep_codecs;

  mimeType =
      GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_representation)->mimeType;
  if (!mimeType)
    mimeType =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->mimeType;

  if (g_strcmp0 (mimeType, "application/ttml+xml") == 0 ||
      g_strcmp0 (mimeType, "text/vtt") == 0)
    return TRUE;

  adapt_set_codecs =
      GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->codecs;
  rep_codecs =
      GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_representation)->codecs;

  return (adapt_set_codecs && g_str_has_prefix (adapt_set_codecs, "stpp"))
      || (rep_codecs && g_str_has_prefix (rep_codecs, "stpp"));
}

GstCaps *
gst_mpd_client_get_stream_caps (GstActiveStream * stream)
{
  const gchar *mimeType, *caps_string;
  GstCaps *ret = NULL;

  if (stream == NULL || stream->cur_adapt_set == NULL
      || stream->cur_representation == NULL)
    return NULL;

  mimeType =
      GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_representation)->mimeType;
  if (mimeType == NULL) {
    mimeType =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->mimeType;
  }

  caps_string = gst_mpd_helper_mimetype_to_caps (mimeType);

  if ((g_strcmp0 (caps_string, "application/mp4") == 0)
      && gst_mpd_client_active_stream_contains_subtitles (stream))
    caps_string = "video/quicktime";

  if (caps_string)
    ret = gst_caps_from_string (caps_string);

  return ret;
}

gboolean
gst_mpd_client_get_bitstream_switching_flag (GstActiveStream * stream)
{
  if (stream == NULL || stream->cur_adapt_set == NULL)
    return FALSE;

  return stream->cur_adapt_set->bitstreamSwitching;
}

guint
gst_mpd_client_get_video_stream_width (GstActiveStream * stream)
{
  guint width;

  if (stream == NULL || stream->cur_adapt_set == NULL
      || stream->cur_representation == NULL)
    return 0;

  width = GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_representation)->width;
  if (width == 0) {
    width = GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->width;
  }

  return width;
}

guint
gst_mpd_client_get_video_stream_height (GstActiveStream * stream)
{
  guint height;

  if (stream == NULL || stream->cur_adapt_set == NULL
      || stream->cur_representation == NULL)
    return 0;

  height =
      GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_representation)->height;
  if (height == 0) {
    height = GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->height;
  }

  return height;
}

gboolean
gst_mpd_client_get_video_stream_framerate (GstActiveStream * stream,
    gint * fps_num, gint * fps_den)
{
  if (stream == NULL)
    return FALSE;

  if (stream->cur_adapt_set &&
      GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->frameRate !=
      NULL) {
    *fps_num =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->
        frameRate->num;
    *fps_den =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->
        frameRate->den;
    return TRUE;
  }

  if (stream->cur_adapt_set &&
      GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->maxFrameRate !=
      NULL) {
    *fps_num =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->
        maxFrameRate->num;
    *fps_den =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->cur_adapt_set)->
        maxFrameRate->den;
    return TRUE;
  }

  if (stream->cur_representation &&
      GST_MPD_REPRESENTATION_BASE_NODE (stream->
          cur_representation)->frameRate != NULL) {
    *fps_num =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->
        cur_representation)->frameRate->num;
    *fps_den =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->
        cur_representation)->frameRate->den;
    return TRUE;
  }

  if (stream->cur_representation &&
      GST_MPD_REPRESENTATION_BASE_NODE (stream->
          cur_representation)->maxFrameRate != NULL) {
    *fps_num =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->
        cur_representation)->maxFrameRate->num;
    *fps_den =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->
        cur_representation)->maxFrameRate->den;
    return TRUE;
  }

  return FALSE;
}

guint
gst_mpd_client_get_audio_stream_rate (GstActiveStream * stream)
{
  const gchar *rate;

  if (stream == NULL || stream->cur_adapt_set == NULL
      || stream->cur_representation == NULL)
    return 0;

  rate =
      GST_MPD_REPRESENTATION_BASE_NODE (stream->
      cur_representation)->audioSamplingRate;
  if (rate == NULL) {
    rate =
        GST_MPD_REPRESENTATION_BASE_NODE (stream->
        cur_adapt_set)->audioSamplingRate;
  }

  return rate ? atoi (rate) : 0;
}

guint
gst_mpd_client_get_audio_stream_num_channels (GstActiveStream * stream)
{
  if (stream == NULL || stream->cur_adapt_set == NULL
      || stream->cur_representation == NULL)
    return 0;
  /* TODO: here we have to parse the AudioChannelConfiguration descriptors */
  return 0;
}

guint
gst_mpd_client_get_list_and_nb_of_audio_language (GstMPDClient * client,
    GList ** lang)
{
  GstStreamPeriod *stream_period;
  GstMPDAdaptationSetNode *adapt_set;
  GList *adaptation_sets, *list;
  const gchar *this_mimeType = "audio";
  gchar *mimeType = NULL;
  guint nb_adaptation_set = 0;

  stream_period = gst_mpd_client_get_stream_period (client);
  g_return_val_if_fail (stream_period != NULL, 0);
  g_return_val_if_fail (stream_period->period != NULL, 0);

  adaptation_sets =
      gst_mpd_client_get_adaptation_sets_for_period (client, stream_period);
  for (list = adaptation_sets; list; list = g_list_next (list)) {
    adapt_set = (GstMPDAdaptationSetNode *) list->data;
    if (adapt_set && adapt_set->lang) {
      gchar *this_lang = adapt_set->lang;
      GstMPDRepresentationNode *rep;
      rep =
          gst_mpd_client_get_lowest_representation (adapt_set->Representations);
      mimeType = NULL;
      if (GST_MPD_REPRESENTATION_BASE_NODE (rep))
        mimeType = GST_MPD_REPRESENTATION_BASE_NODE (rep)->mimeType;
      if (!mimeType && GST_MPD_REPRESENTATION_BASE_NODE (adapt_set)) {
        mimeType = GST_MPD_REPRESENTATION_BASE_NODE (adapt_set)->mimeType;
      }

      if (gst_mpd_helper_strncmp_ext (mimeType, this_mimeType) == 0) {
        nb_adaptation_set++;
        *lang = g_list_append (*lang, this_lang);
      }
    }
  }

  return nb_adaptation_set;
}


GstDateTime *
gst_mpd_client_get_next_segment_availability_start_time (GstMPDClient * client,
    GstActiveStream * stream)
{
  GstDateTime *availability_start_time, *rv;
  gint seg_idx;
  GstMediaSegment *segment;
  GstClockTime segmentEndTime;
  const GstStreamPeriod *stream_period;
  GstClockTime period_start = 0;

  g_return_val_if_fail (client != NULL, NULL);
  g_return_val_if_fail (stream != NULL, NULL);

  stream_period = gst_mpd_client_get_stream_period (client);
  if (stream_period && stream_period->period) {
    period_start = stream_period->start;
  }

  seg_idx = stream->segment_index;

  if (stream->segments) {
    segment = g_ptr_array_index (stream->segments, seg_idx);

    if (segment->repeat >= 0) {
      segmentEndTime = segment->start + (stream->segment_repeat_index + 1) *
          segment->duration;
    } else if (seg_idx < stream->segments->len - 1) {
      const GstMediaSegment *next_segment =
          g_ptr_array_index (stream->segments, seg_idx + 1);
      segmentEndTime = next_segment->start;
    } else {
      g_return_val_if_fail (stream_period != NULL, NULL);
      segmentEndTime = period_start + stream_period->duration;
    }
  } else {
    GstClockTime seg_duration;
    seg_duration = gst_mpd_client_get_segment_duration (client, stream, NULL);
    if (seg_duration == 0)
      return NULL;
    segmentEndTime = period_start + (1 + seg_idx) * seg_duration;
  }

  availability_start_time = gst_mpd_client_get_availability_start_time (client);
  if (availability_start_time == NULL) {
    GST_WARNING_OBJECT (client, "Failed to get availability_start_time");
    return NULL;
  }

  rv = gst_mpd_client_add_time_difference (availability_start_time,
      segmentEndTime / GST_USECOND);
  gst_date_time_unref (availability_start_time);
  if (rv == NULL) {
    GST_WARNING_OBJECT (client, "Failed to offset availability_start_time");
    return NULL;
  }

  return rv;
}

gboolean
gst_mpd_client_seek_to_time (GstMPDClient * client, GDateTime * time)
{
  GDateTime *start;
  GTimeSpan ts_microseconds;
  GstClockTime ts;
  gboolean ret = TRUE;
  GList *stream;

  g_return_val_if_fail (gst_mpd_client_is_live (client), FALSE);
  g_return_val_if_fail (client->mpd_root_node->availabilityStartTime != NULL,
      FALSE);

  start =
      gst_date_time_to_g_date_time (client->mpd_root_node->
      availabilityStartTime);

  ts_microseconds = g_date_time_difference (time, start);
  g_date_time_unref (start);

  /* Clamp to availability start time, otherwise calculations wrap around */
  if (ts_microseconds < 0)
    ts_microseconds = 0;

  ts = ts_microseconds * GST_USECOND;
  for (stream = client->active_streams; stream; stream = g_list_next (stream)) {
    ret =
        ret & gst_mpd_client_stream_seek (client, stream->data, TRUE, 0, ts,
        NULL);
  }
  return ret;
}

gboolean
gst_mpd_client_has_isoff_ondemand_profile (GstMPDClient * client)
{
  return client->profile_isoff_ondemand;
}

/**
 * gst_mpd_client_parse_default_presentation_delay:
 * @client: #GstMPDClient that has a parsed manifest
 * @default_presentation_delay: A string that specifies a time period
 * in fragments (e.g. "5 f"), seconds ("12 s") or milliseconds
 * ("12000 ms")
 * Returns: the parsed string in milliseconds
 *
 * Since: 1.6
 */
gint64
gst_mpd_client_parse_default_presentation_delay (GstMPDClient * client,
    const gchar * default_presentation_delay)
{
  gint64 value;
  char *endptr = NULL;

  g_return_val_if_fail (client != NULL, 0);
  g_return_val_if_fail (default_presentation_delay != NULL, 0);
  value = strtol (default_presentation_delay, &endptr, 10);
  if (endptr == default_presentation_delay || value == 0) {
    return 0;
  }
  while (*endptr == ' ')
    endptr++;
  if (*endptr == 's' || *endptr == 'S') {
    value *= 1000;              /* convert to ms */
  } else if (*endptr == 'f' || *endptr == 'F') {
    gint64 segment_duration;
    g_assert (client->mpd_root_node != NULL);
    segment_duration = client->mpd_root_node->maxSegmentDuration;
    value *= segment_duration;
  } else if (*endptr != 'm' && *endptr != 'M') {
    GST_ERROR ("Unable to parse default presentation delay: %s",
        default_presentation_delay);
    value = 0;
  }
  return value;
}

GstClockTime
gst_mpd_client_get_maximum_segment_duration (GstMPDClient * client)
{
  GstClockTime ret = GST_CLOCK_TIME_NONE, dur;
  GList *stream;

  g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
  g_return_val_if_fail (client->mpd_root_node != NULL, GST_CLOCK_TIME_NONE);

  if (client->mpd_root_node->maxSegmentDuration != GST_MPD_DURATION_NONE) {
    return client->mpd_root_node->maxSegmentDuration * GST_MSECOND;
  }

  /* According to the DASH specification, if maxSegmentDuration is not present:
     "If not present, then the maximum Segment duration shall be the maximum
     duration of any Segment documented in this MPD"
   */
  for (stream = client->active_streams; stream; stream = g_list_next (stream)) {
    dur = gst_mpd_client_get_segment_duration (client, stream->data, NULL);
    if (dur != GST_CLOCK_TIME_NONE && (dur > ret || ret == GST_CLOCK_TIME_NONE)) {
      ret = dur;
    }
  }
  return ret;
}

guint
gst_mpd_client_get_period_index_at_time (GstMPDClient * client,
    GstDateTime * time)
{
  GList *iter;
  guint period_idx = G_MAXUINT;
  guint idx;
  gint64 time_offset;
  GstDateTime *avail_start =
      gst_mpd_client_get_availability_start_time (client);
  GstStreamPeriod *stream_period;

  if (avail_start == NULL)
    return 0;

  time_offset = gst_mpd_client_calculate_time_difference (avail_start, time);
  gst_date_time_unref (avail_start);

  if (time_offset < 0)
    return 0;

  if (!gst_mpd_client_setup_media_presentation (client, time_offset, -1, NULL))
    return 0;

  for (idx = 0, iter = client->periods; iter; idx++, iter = g_list_next (iter)) {
    stream_period = iter->data;
    if (stream_period->start <= time_offset
        && (!GST_CLOCK_TIME_IS_VALID (stream_period->duration)
            || stream_period->start + stream_period->duration > time_offset)) {
      period_idx = idx;
      break;
    }
  }

  return period_idx;
}

/* add or set node methods */

gboolean
gst_mpd_client_set_root_node (GstMPDClient * client,
    const gchar * property_name, ...)
{
  va_list myargs;
  g_return_val_if_fail (client != NULL, FALSE);

  if (!client->mpd_root_node)
    client->mpd_root_node = gst_mpd_root_node_new ();

  va_start (myargs, property_name);
  g_object_set_valist (G_OBJECT (client->mpd_root_node), property_name, myargs);
  va_end (myargs);

  return TRUE;
}

gboolean
gst_mpd_client_add_baseurl_node (GstMPDClient * client,
    const gchar * property_name, ...)
{
  GstMPDBaseURLNode *baseurl_node = NULL;
  va_list myargs;

  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->mpd_root_node != NULL, FALSE);

  va_start (myargs, property_name);

  baseurl_node = gst_mpd_baseurl_node_new ();
  g_object_set_valist (G_OBJECT (baseurl_node), property_name, myargs);
  client->mpd_root_node->BaseURLs =
      g_list_append (client->mpd_root_node->BaseURLs, baseurl_node);

  va_end (myargs);
  return TRUE;
}

/* returns a period id */
gchar *
gst_mpd_client_set_period_node (GstMPDClient * client,
    gchar * period_id, const gchar * property_name, ...)
{
  GstMPDPeriodNode *period_node = NULL;
  va_list myargs;

  g_return_val_if_fail (client != NULL, NULL);
  g_return_val_if_fail (client->mpd_root_node != NULL, NULL);

  period_node =
      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
      (client->mpd_root_node->Periods, period_id));
  if (!period_node) {
    period_node = gst_mpd_period_node_new ();
    if (period_id)
      period_node->id = g_strdup (period_id);
    else
      period_node->id =
          _generate_new_string_id (client->mpd_root_node->Periods,
          "period_%.2d", gst_mpd_client_get_period_with_id);
    client->mpd_root_node->Periods =
        g_list_append (client->mpd_root_node->Periods, period_node);
  }

  va_start (myargs, property_name);
  g_object_set_valist (G_OBJECT (period_node), property_name, myargs);
  va_end (myargs);

  return period_node->id;
}

/* returns an adaptation set id */
guint
gst_mpd_client_set_adaptation_set_node (GstMPDClient * client,
    gchar * period_id, guint adaptation_set_id, const gchar * property_name,
    ...)
{
  GstMPDAdaptationSetNode *adap_node = NULL;
  GstMPDPeriodNode *period_node = NULL;
  va_list myargs;

  g_return_val_if_fail (client != NULL, 0);
  g_return_val_if_fail (client->mpd_root_node != NULL, 0);

  period_node =
      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
      (client->mpd_root_node->Periods, period_id));
  g_return_val_if_fail (period_node != NULL, 0);
  adap_node =
      GST_MPD_ADAPTATION_SET_NODE (gst_mpd_client_get_adaptation_set_with_id
      (period_node->AdaptationSets, adaptation_set_id));
  if (!adap_node) {
    adap_node = gst_mpd_adaptation_set_node_new ();
    if (adaptation_set_id)
      adap_node->id = adaptation_set_id;
    else
      adap_node->id =
          _generate_new_id (period_node->AdaptationSets,
          gst_mpd_client_get_adaptation_set_with_id);
    GST_DEBUG_OBJECT (client, "Add a new adaptation set with id %d",
        adap_node->id);
    period_node->AdaptationSets =
        g_list_append (period_node->AdaptationSets, adap_node);
  }

  va_start (myargs, property_name);
  g_object_set_valist (G_OBJECT (adap_node), property_name, myargs);
  va_end (myargs);

  return adap_node->id;
}

/* returns a representation id */
gchar *
gst_mpd_client_set_representation_node (GstMPDClient * client,
    gchar * period_id, guint adaptation_set_id, gchar * representation_id,
    const gchar * property_name, ...)
{
  GstMPDRepresentationNode *rep_node = NULL;
  GstMPDAdaptationSetNode *adap_set_node = NULL;
  GstMPDPeriodNode *period_node = NULL;
  va_list myargs;

  g_return_val_if_fail (client != NULL, NULL);
  g_return_val_if_fail (client->mpd_root_node != NULL, NULL);

  period_node =
      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
      (client->mpd_root_node->Periods, period_id));
  adap_set_node =
      GST_MPD_ADAPTATION_SET_NODE (gst_mpd_client_get_adaptation_set_with_id
      (period_node->AdaptationSets, adaptation_set_id));
  g_return_val_if_fail (adap_set_node != NULL, NULL);
  rep_node =
      gst_mpd_client_get_representation_with_id (adap_set_node->Representations,
      representation_id);
  if (!rep_node) {
    rep_node = gst_mpd_representation_node_new ();
    if (representation_id)
      rep_node->id = g_strdup (representation_id);
    else
      rep_node->id =
          _generate_new_string_id (adap_set_node->Representations,
          "representation_%.2d",
          gst_mpd_client_get_representation_with_id_filter);
    GST_DEBUG_OBJECT (client, "Add a new representation with id %s",
        rep_node->id);
    adap_set_node->Representations =
        g_list_append (adap_set_node->Representations, rep_node);
  }

  va_start (myargs, property_name);
  g_object_set_valist (G_OBJECT (rep_node), property_name, myargs);
  va_end (myargs);

  return rep_node->id;
}

/* add/set a segment list node */
gboolean
gst_mpd_client_set_segment_list (GstMPDClient * client,
    gchar * period_id, guint adap_set_id, gchar * rep_id,
    const gchar * property_name, ...)
{
  GstMPDRepresentationNode *representation = NULL;
  GstMPDAdaptationSetNode *adaptation_set = NULL;
  GstMPDPeriodNode *period = NULL;
  va_list myargs;

  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->mpd_root_node != NULL, FALSE);

  period =
      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
      (client->mpd_root_node->Periods, period_id));
  adaptation_set =
      GST_MPD_ADAPTATION_SET_NODE (gst_mpd_client_get_adaptation_set_with_id
      (period->AdaptationSets, adap_set_id));
  g_return_val_if_fail (adaptation_set != NULL, FALSE);

  representation =
      gst_mpd_client_get_representation_with_id
      (adaptation_set->Representations, rep_id);
  if (!representation->SegmentList) {
    representation->SegmentList = gst_mpd_segment_list_node_new ();
  }

  va_start (myargs, property_name);
  g_object_set_valist (G_OBJECT (representation->SegmentList), property_name,
      myargs);
  va_end (myargs);

  return TRUE;
}

/* add/set a segment template node */
gboolean
gst_mpd_client_set_segment_template (GstMPDClient * client,
    gchar * period_id, guint adap_set_id, gchar * rep_id,
    const gchar * property_name, ...)
{
  GstMPDRepresentationNode *representation = NULL;
  GstMPDAdaptationSetNode *adaptation_set = NULL;
  GstMPDPeriodNode *period = NULL;
  va_list myargs;

  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->mpd_root_node != NULL, FALSE);

  period =
      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
      (client->mpd_root_node->Periods, period_id));
  adaptation_set =
      GST_MPD_ADAPTATION_SET_NODE (gst_mpd_client_get_adaptation_set_with_id
      (period->AdaptationSets, adap_set_id));
  g_return_val_if_fail (adaptation_set != NULL, FALSE);

  representation =
      gst_mpd_client_get_representation_with_id
      (adaptation_set->Representations, rep_id);
  if (!representation->SegmentTemplate) {
    representation->SegmentTemplate = gst_mpd_segment_template_node_new ();
  }

  va_start (myargs, property_name);
  g_object_set_valist (G_OBJECT (representation->SegmentTemplate),
      property_name, myargs);
  va_end (myargs);

  return TRUE;
}

/* add a segmentURL node with to a SegmentList node */
gboolean
gst_mpd_client_add_segment_url (GstMPDClient * client,
    gchar * period_id, guint adap_set_id, gchar * rep_id,
    const gchar * property_name, ...)
{
  GstMPDRepresentationNode *representation = NULL;
  GstMPDAdaptationSetNode *adaptation_set = NULL;
  GstMPDPeriodNode *period = NULL;
  GstMPDSegmentURLNode *segment_url = NULL;
  guint64 media_presentation_duration = 0;
  va_list myargs;

  g_return_val_if_fail (client != NULL, FALSE);
  g_return_val_if_fail (client->mpd_root_node != NULL, FALSE);

  period =
      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
      (client->mpd_root_node->Periods, period_id));
  adaptation_set =
      GST_MPD_ADAPTATION_SET_NODE (gst_mpd_client_get_adaptation_set_with_id
      (period->AdaptationSets, adap_set_id));
  g_return_val_if_fail (adaptation_set != NULL, FALSE);

  representation =
      gst_mpd_client_get_representation_with_id
      (adaptation_set->Representations, rep_id);

  if (!representation->SegmentList) {
    representation->SegmentList = gst_mpd_segment_list_node_new ();
  }

  segment_url = gst_mpd_segment_url_node_new ();

  va_start (myargs, property_name);
  g_object_set_valist (G_OBJECT (segment_url), property_name, myargs);
  va_end (myargs);

  gst_mpd_segment_list_node_add_segment (representation->SegmentList,
      segment_url);

  /* Set the media presentation time according to the new segment duration added */
  g_object_get (client->mpd_root_node, "media-presentation-duration",
      &media_presentation_duration, NULL);
  media_presentation_duration +=
      GST_MPD_MULT_SEGMENT_BASE_NODE (representation->SegmentList)->duration;
  g_object_set (client->mpd_root_node, "media-presentation-duration",
      media_presentation_duration, NULL);

  return TRUE;
}
