/* GStreamer * * Copyright (C) 2019 Collabora Ltd. * Author: Stéphane Cerveau * * 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 : ""); 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[] = ""; 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 "" #define CUSTOM_WRAPPER_END "" static GList * gst_mpd_client_fetch_external_periods (GstMPDClient * client, GstMPDPeriodNode * period_node) { GstFragment *download; GstAdapter *adapter; GstBuffer *period_buffer; GError *err = NULL; GstUri *base_uri, *uri; gchar *query = NULL; gchar *uri_string, *wrapper; GList *new_periods = NULL; const gchar *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 */ adapter = gst_adapter_new (); wrapper = g_new (gchar, strlen (CUSTOM_WRAPPER_START)); memcpy (wrapper, CUSTOM_WRAPPER_START, strlen (CUSTOM_WRAPPER_START)); gst_adapter_push (adapter, gst_buffer_new_wrapped (wrapper, strlen (CUSTOM_WRAPPER_START))); gst_adapter_push (adapter, period_buffer); wrapper = g_strdup (CUSTOM_WRAPPER_END); gst_adapter_push (adapter, gst_buffer_new_wrapped (wrapper, strlen (CUSTOM_WRAPPER_END) + 1)); data = gst_adapter_map (adapter, gst_adapter_available (adapter)); new_periods = gst_mpdparser_get_external_periods (data, gst_adapter_available (adapter)); gst_adapter_unmap (adapter); gst_adapter_clear (adapter); gst_object_unref (adapter); 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; GstStreamPeriod *stream_period; 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); 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); *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; GstStreamPeriod *stream_period; 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); 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); *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; }