diff --git a/ext/hls/gsthlsdemux.c b/ext/hls/gsthlsdemux.c index 69d8761929..02464000d5 100644 --- a/ext/hls/gsthlsdemux.c +++ b/ext/hls/gsthlsdemux.c @@ -5,6 +5,7 @@ * Author: Youness Alaoui , Collabora Ltd. * Author: Sebastian Dröge , Collabora Ltd. * Copyright (C) 2014 Sebastian Dröge + * Copyright (C) 2015 Tim-Philipp Müller * * Gsthlsdemux.c: * @@ -108,6 +109,9 @@ static gboolean gst_hls_demux_select_bitrate (GstAdaptiveDemuxStream * stream, static void gst_hls_demux_reset (GstAdaptiveDemux * demux); static gboolean gst_hls_demux_get_live_seek_range (GstAdaptiveDemux * demux, gint64 * start, gint64 * stop); +static GstM3U8 *gst_hls_demux_stream_get_m3u8 (GstHLSDemuxStream * hls_stream); +static void gst_hls_demux_set_current_variant (GstHLSDemux * hlsdemux, + GstHLSVariantStream * variant); #define gst_hls_demux_parent_class parent_class G_DEFINE_TYPE (GstHLSDemux, gst_hls_demux, GST_TYPE_ADAPTIVE_DEMUX); @@ -260,6 +264,7 @@ gst_hls_demux_clear_all_pending_data (GstHLSDemux * hlsdemux) } } +#if 0 static void gst_hls_demux_set_current (GstHLSDemux * self, GstM3U8 * m3u8) { @@ -282,6 +287,7 @@ gst_hls_demux_set_current (GstHLSDemux * self, GstM3U8 * m3u8) } GST_M3U8_CLIENT_UNLOCK (self); } +#endif static gboolean gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) @@ -291,7 +297,7 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) GstSeekFlags flags; GstSeekType start_type, stop_type; gint64 start, stop; - gdouble rate; + gdouble rate, old_rate; GList *walk, *current_file = NULL; GstClockTime current_pos, target_pos; gint64 current_sequence; @@ -300,6 +306,8 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) gboolean snap_before, snap_after, snap_nearest, keyunit; gboolean reverse; + old_rate = demux->segment.rate; + gst_event_parse_seek (seek, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); @@ -311,15 +319,13 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) } /* Use I-frame variants for trick modes */ - if (hlsdemux->main->iframe_lists && rate < -1.0 - && demux->segment.rate >= -1.0 && demux->segment.rate <= 1.0) { + if (hlsdemux->master->iframe_variants != NULL + && rate < -1.0 && old_rate >= -1.0 && old_rate <= 1.0) { GError *err = NULL; - GST_M3U8_CLIENT_LOCK (hlsdemux->client); /* Switch to I-frame variant */ - hlsdemux->main->current_variant = hlsdemux->main->iframe_lists; - GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); - gst_hls_demux_set_current (hlsdemux, hlsdemux->main->iframe_lists->data); + gst_hls_demux_set_current_variant (hlsdemux, + hlsdemux->master->iframe_variants->data); gst_uri_downloader_reset (demux->downloader); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); @@ -328,14 +334,11 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) //hlsdemux->discont = TRUE; gst_hls_demux_change_playlist (hlsdemux, bitrate / ABS (rate), NULL); - } else if (rate > -1.0 && rate <= 1.0 && (demux->segment.rate < -1.0 - || demux->segment.rate > 1.0)) { + } else if (rate > -1.0 && rate <= 1.0 && (old_rate < -1.0 || old_rate > 1.0)) { GError *err = NULL; - GST_M3U8_CLIENT_LOCK (hlsdemux->client); /* Switch to normal variant */ - hlsdemux->main->current_variant = hlsdemux->main->lists; - GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); - gst_hls_demux_set_current (hlsdemux, hlsdemux->main->lists->data); + gst_hls_demux_set_current_variant (hlsdemux, + hlsdemux->master->variants->data); gst_uri_downloader_reset (demux->downloader); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); @@ -346,7 +349,7 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) gst_hls_demux_change_playlist (hlsdemux, bitrate, NULL); } GST_M3U8_CLIENT_LOCK (hlsdemux->client); - file = GST_M3U8_MEDIA_FILE (hlsdemux->current->files->data); + file = GST_M3U8_MEDIA_FILE (hlsdemux->current_variant->m3u8->files->data); current_sequence = file->sequence; current_pos = 0; reverse = rate < 0; @@ -360,7 +363,7 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) snap_after = ! !(flags & GST_SEEK_FLAG_SNAP_AFTER); /* FIXME: Here we need proper discont handling */ - for (walk = hlsdemux->current->files; walk; walk = walk->next) { + for (walk = hlsdemux->current_variant->m3u8->files; walk; walk = walk->next) { file = walk->data; current_sequence = file->sequence; @@ -393,10 +396,10 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) GST_DEBUG_OBJECT (demux, "seeking to sequence %u", (guint) current_sequence); for (walk = demux->streams; walk != NULL; walk = walk->next) GST_HLS_DEMUX_STREAM_CAST (walk->data)->reset_pts = TRUE; - hlsdemux->current->sequence = current_sequence; - hlsdemux->current->current_file = - current_file ? current_file : hlsdemux->current->files; - hlsdemux->current->sequence_position = current_pos; + hlsdemux->current_variant->m3u8->sequence = current_sequence; + hlsdemux->current_variant->m3u8->current_file = + current_file ? current_file : hlsdemux->current_variant->m3u8->files; + hlsdemux->current_variant->m3u8->sequence_position = current_pos; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); /* Play from the end of the current selected segment */ @@ -445,63 +448,90 @@ gst_hls_demux_setup_streams (GstAdaptiveDemux * demux) return TRUE; } +static const gchar * +gst_adaptive_demux_get_manifest_ref_uri (GstAdaptiveDemux * d) +{ + return d->manifest_base_uri ? d->manifest_base_uri : d->manifest_uri; +} + +static void +gst_hls_demux_set_current_variant (GstHLSDemux * hlsdemux, + GstHLSVariantStream * variant) +{ + if (hlsdemux->current_variant == variant || variant == NULL) + return; + + if (hlsdemux->current_variant != NULL) { + //#warning FIXME: Synching fragments across variants + // should be done based on media timestamps, and + // discont-sequence-numbers not sequence numbers. + variant->m3u8->sequence_position = + hlsdemux->current_variant->m3u8->sequence_position; + variant->m3u8->sequence = hlsdemux->current_variant->m3u8->sequence; + variant->m3u8->highest_sequence_number = + hlsdemux->current_variant->m3u8->highest_sequence_number; + + GST_DEBUG_OBJECT (hlsdemux, + "Switching Variant. Copying over sequence %" G_GINT64_FORMAT + " and sequence_pos %" GST_TIME_FORMAT, variant->m3u8->sequence, + GST_TIME_ARGS (variant->m3u8->sequence_position)); + + gst_hls_variant_stream_unref (hlsdemux->current_variant); + } + + hlsdemux->current_variant = gst_hls_variant_stream_ref (variant); + +} static gboolean gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf) { + GstHLSVariantStream *variant; GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); gchar *playlist = NULL; - hlsdemux->main = gst_m3u8_new (); - gst_m3u8_set_uri (hlsdemux->main, demux->manifest_uri, - demux->manifest_base_uri, NULL); - hlsdemux->current = NULL; - - GST_INFO_OBJECT (demux, "Changed location: %s (base uri: %s)", - demux->manifest_uri, GST_STR_NULL (demux->manifest_base_uri)); + GST_INFO_OBJECT (demux, "Initial playlist location: %s (base uri: %s)", + demux->manifest_uri, demux->manifest_base_uri); playlist = gst_hls_src_buf_to_utf8_playlist (buf); if (playlist == NULL) { - GST_WARNING_OBJECT (demux, "Error validating first playlist."); + GST_WARNING_OBJECT (demux, "Error validating initial playlist"); return FALSE; } - if (!gst_m3u8_update (hlsdemux->main, playlist)) { + hlsdemux->master = gst_hls_master_playlist_new_from_data (playlist, + gst_adaptive_demux_get_manifest_ref_uri (demux)); + + if (hlsdemux->master == NULL || hlsdemux->master->variants == NULL) { /* In most cases, this will happen if we set a wrong url in the * source element and we have received the 404 HTML response instead of * the playlist */ - GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid playlist."), (NULL)); + GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid playlist."), + ("Could not parse playlist. Check if the URL is correct.")); return FALSE; } - /* If this playlist is a variant playlist, select the first one - * and update it */ - if (gst_m3u8_has_variant_playlist (hlsdemux->main)) { - GstM3U8 *child = NULL; + /* select the initial variant stream */ + if (demux->connection_speed == 0) { + variant = hlsdemux->master->variants->data; + } else { + variant = + gst_hls_master_playlist_get_variant_for_bitrate (hlsdemux->master, + NULL, demux->connection_speed); + } + + GST_INFO_OBJECT (hlsdemux, "selected %s", variant->name); + gst_hls_demux_set_current_variant (hlsdemux, variant); // FIXME: inline? + + /* get the selected media playlist (unless the inital list was one already) */ + if (!hlsdemux->master->is_simple) { GError *err = NULL; - if (demux->connection_speed == 0) { - GST_M3U8_CLIENT_LOCK (hlsdemux->client); - child = hlsdemux->main->current_variant->data; - GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); - } else { - GList *tmp = gst_m3u8_get_playlist_for_bitrate (hlsdemux->main, - demux->connection_speed); - GST_M3U8_CLIENT_LOCK (hlsdemux->client); - hlsdemux->main->current_variant = tmp; - GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); - - child = GST_M3U8 (tmp->data); - } - - gst_hls_demux_set_current (hlsdemux, child); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { - GST_ELEMENT_ERROR_FROM_ERROR (demux, "Could not fetch the child playlist", + GST_ELEMENT_ERROR_FROM_ERROR (demux, "Could not fetch media playlist", err); return FALSE; } - } else { - gst_hls_demux_set_current (hlsdemux, hlsdemux->main); } return gst_hls_demux_setup_streams (demux); @@ -513,8 +543,8 @@ gst_hls_demux_get_duration (GstAdaptiveDemux * demux) GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); GstClockTime duration = GST_CLOCK_TIME_NONE; - if (hlsdemux->current != NULL) - duration = gst_m3u8_get_duration (hlsdemux->current); + if (hlsdemux->current_variant != NULL) + duration = gst_m3u8_get_duration (hlsdemux->current_variant->m3u8); return duration; } @@ -525,8 +555,8 @@ gst_hls_demux_is_live (GstAdaptiveDemux * demux) GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); gboolean is_live = FALSE; - if (hlsdemux->current) - is_live = gst_m3u8_is_live (hlsdemux->current); + if (hlsdemux->current_variant) + is_live = gst_hls_variant_stream_is_live (hlsdemux->current_variant); return is_live; } @@ -592,6 +622,7 @@ gst_hls_demux_start_fragment (GstAdaptiveDemux * demux, GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream); GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); const GstHLSKey *key; + GstM3U8 *m3u8; gst_hls_demux_stream_clear_pending_data (hls_stream); @@ -599,9 +630,10 @@ gst_hls_demux_start_fragment (GstAdaptiveDemux * demux, if (hls_stream->current_key == NULL) return TRUE; + m3u8 = gst_hls_demux_stream_get_m3u8 (hls_stream); + key = gst_hls_demux_get_key (hlsdemux, hls_stream->current_key, - hlsdemux->main ? hlsdemux->main->uri : NULL, - hlsdemux->current ? hlsdemux->current->allowcache : TRUE); + m3u8->uri, m3u8->allowcache); if (key == NULL) goto key_failed; @@ -795,14 +827,31 @@ gst_hls_demux_stream_free (GstAdaptiveDemuxStream * stream) gst_hls_demux_stream_decrypt_end (hls_stream); } +static GstM3U8 * +gst_hls_demux_stream_get_m3u8 (GstHLSDemuxStream * hlsdemux_stream) +{ + GstAdaptiveDemuxStream *stream = (GstAdaptiveDemuxStream *) hlsdemux_stream; + GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux); + GstM3U8 *m3u8; + + g_assert (hlsdemux->current_variant != NULL); + + // FIXME: what about locking? should always be called with lock + // that makes sure playlist aren't changed while we do things + m3u8 = hlsdemux->current_variant->m3u8; + + return m3u8; +} + static gboolean gst_hls_demux_stream_has_next_fragment (GstAdaptiveDemuxStream * stream) { - GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux); gboolean has_next; + GstM3U8 *m3u8; - has_next = gst_m3u8_has_next_fragment (hlsdemux->current, - stream->demux->segment.rate > 0); + m3u8 = gst_hls_demux_stream_get_m3u8 (GST_HLS_DEMUX_STREAM_CAST (stream)); + + has_next = gst_m3u8_has_next_fragment (m3u8, stream->demux->segment.rate > 0); return has_next; } @@ -811,11 +860,13 @@ static GstFlowReturn gst_hls_demux_advance_fragment (GstAdaptiveDemuxStream * stream) { GstHLSDemuxStream *hlsdemux_stream = GST_HLS_DEMUX_STREAM_CAST (stream); - GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux); + GstM3U8 *m3u8; - gst_m3u8_advance_fragment (hlsdemux->current, - stream->demux->segment.rate > 0); + m3u8 = gst_hls_demux_stream_get_m3u8 (hlsdemux_stream); + + gst_m3u8_advance_fragment (m3u8, stream->demux->segment.rate > 0); hlsdemux_stream->reset_pts = FALSE; + return GST_FLOW_OK; } @@ -826,10 +877,13 @@ gst_hls_demux_update_fragment_info (GstAdaptiveDemuxStream * stream) GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux); GstM3U8MediaFile *file; GstClockTime sequence_pos; - gboolean discont; + gboolean discont, forward; + GstM3U8 *m3u8; - file = gst_m3u8_get_next_fragment (hlsdemux->current, - stream->demux->segment.rate > 0, &sequence_pos, &discont); + m3u8 = gst_hls_demux_stream_get_m3u8 (hlsdemux_stream); + + forward = (stream->demux->segment.rate > 0); + file = gst_m3u8_get_next_fragment (m3u8, forward, &sequence_pos, &discont); if (file == NULL) { GST_INFO_OBJECT (hlsdemux, "This playlist doesn't contain more fragments"); @@ -876,15 +930,13 @@ gst_hls_demux_select_bitrate (GstAdaptiveDemuxStream * stream, guint64 bitrate) gboolean changed = FALSE; GST_M3U8_CLIENT_LOCK (hlsdemux->client); - if (!hlsdemux->main->lists) { + if (hlsdemux->master->is_simple) { GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); return FALSE; } GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); - /* FIXME: Currently several issues have be found when letting bitrate adaptation - * happen using trick modes (such as 'All streams finished without buffers') and - * the adaptive algorithm does not properly behave. */ + /* Bitrate adaptation during trick modes does not work well */ if (demux->segment.rate != 1.0) return FALSE; @@ -899,11 +951,14 @@ gst_hls_demux_reset (GstAdaptiveDemux * ademux) { GstHLSDemux *demux = GST_HLS_DEMUX_CAST (ademux); - if (demux->main) { - gst_m3u8_unref (demux->main); - demux->main = NULL; + if (demux->master) { + gst_hls_master_playlist_unref (demux->master); + demux->master = NULL; + } + if (demux->current_variant != NULL) { + gst_hls_variant_stream_unref (demux->current_variant); + demux->current_variant = NULL; } - demux->current = NULL; demux->srcpad_counter = 0; gst_hls_demux_clear_all_pending_data (demux); @@ -935,7 +990,8 @@ map_error: } static gint -gst_hls_demux_find_m3u8_list_match (const GstM3U8 * a, const GstM3U8 * b) +gst_hls_demux_find_variant_match (const GstHLSVariantStream * a, + const GstHLSVariantStream * b) { if (g_strcmp0 (a->name, b->name) == 0 && a->bandwidth == b->bandwidth && @@ -953,33 +1009,35 @@ static gboolean gst_hls_demux_update_variant_playlist (GstHLSDemux * hlsdemux, gchar * data, const gchar * uri, const gchar * base_uri) { - GstM3U8 *new_main, *old; + GstHLSMasterPlaylist *new_master, *old; gboolean ret = FALSE; GList *l, *unmatched_lists; - new_main = gst_m3u8_new (); - gst_m3u8_set_uri (new_main, uri, base_uri, NULL); - if (gst_m3u8_update (new_main, data)) { - if (!new_main->lists) { + new_master = gst_hls_master_playlist_new_from_data (data, base_uri ? base_uri : uri); // FIXME: check which uri to use here + + if (new_master != NULL) { + if (new_master->is_simple) { + // FIXME: we should be able to support this though, in the unlikely + // case that it changed? GST_ERROR ("Cannot update variant playlist: New playlist is not a variant playlist"); - gst_m3u8_unref (new_main); + gst_hls_master_playlist_unref (new_master); return FALSE; } GST_M3U8_CLIENT_LOCK (self); - if (!hlsdemux->main->lists) { + if (hlsdemux->master->is_simple) { GST_ERROR ("Cannot update variant playlist: Current playlist is not a variant playlist"); goto out; } /* Now see if the variant playlist still has the same lists */ - unmatched_lists = g_list_copy (hlsdemux->main->lists); - for (l = new_main->lists; l != NULL; l = l->next) { + unmatched_lists = g_list_copy (hlsdemux->master->variants); + for (l = new_master->variants; l != NULL; l = l->next) { GList *match = g_list_find_custom (unmatched_lists, l->data, - (GCompareFunc) gst_hls_demux_find_m3u8_list_match); + (GCompareFunc) gst_hls_demux_find_variant_match); if (match) { unmatched_lists = g_list_delete_link (unmatched_lists, match); // FIXME: copy over state variables of playlist, or keep old instance @@ -991,7 +1049,7 @@ gst_hls_demux_update_variant_playlist (GstHLSDemux * hlsdemux, gchar * data, GST_WARNING ("Unable to match all playlists"); for (l = unmatched_lists; l != NULL; l = l->next) { - if (l->data == hlsdemux->current) { + if (l->data == hlsdemux->current_variant) { GST_WARNING ("Unable to match current playlist"); } } @@ -1000,16 +1058,14 @@ gst_hls_demux_update_variant_playlist (GstHLSDemux * hlsdemux, gchar * data, } /* Switch out the variant playlist, steal it from new_client */ - old = hlsdemux->main; + old = hlsdemux->master; - hlsdemux->main = new_main; + // FIXME: check all this and also switch of variants, if anything needs updating + hlsdemux->master = new_master; - if (hlsdemux->main->lists) - hlsdemux->current = hlsdemux->main->current_variant->data; - else - hlsdemux->current = hlsdemux->main; + hlsdemux->current_variant = hlsdemux->master->default_variant; - gst_m3u8_unref (old); + gst_hls_master_playlist_unref (old); ret = TRUE; @@ -1029,31 +1085,30 @@ gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update, GstBuffer *buf; gchar *playlist; gboolean main_checked = FALSE; - gchar *uri, *main_uri; + const gchar *main_uri; + GstM3U8 *m3u8; + gchar *uri; retry: - uri = gst_m3u8_get_uri (demux->current); - main_uri = gst_m3u8_get_uri (demux->main); + uri = gst_m3u8_get_uri (demux->current_variant->m3u8); + main_uri = gst_adaptive_demux_get_manifest_ref_uri (adaptive_demux); download = gst_uri_downloader_fetch_uri (adaptive_demux->downloader, uri, main_uri, TRUE, TRUE, TRUE, err); - g_free (main_uri); if (download == NULL) { gchar *base_uri; - if (!update || main_checked || !gst_m3u8_has_variant_playlist (demux->main)) { + if (!update || main_checked || demux->master->is_simple) { g_free (uri); return FALSE; } g_clear_error (err); - main_uri = gst_m3u8_get_uri (demux->main); GST_INFO_OBJECT (demux, "Updating playlist %s failed, attempt to refresh variant playlist %s", uri, main_uri); download = gst_uri_downloader_fetch_uri (adaptive_demux->downloader, main_uri, NULL, TRUE, TRUE, TRUE, err); - g_free (main_uri); if (download == NULL) { g_free (uri); return FALSE; @@ -1097,18 +1152,16 @@ retry: } g_free (uri); + m3u8 = demux->current_variant->m3u8; + /* Set the base URI of the playlist to the redirect target if any */ - GST_M3U8_CLIENT_LOCK (demux->client); - g_free (demux->current->uri); - g_free (demux->current->base_uri); if (download->redirect_permanent && download->redirect_uri) { - demux->current->uri = g_strdup (download->redirect_uri); - demux->current->base_uri = NULL; + gst_m3u8_set_uri (m3u8, download->redirect_uri, NULL, + demux->current_variant->name); } else { - demux->current->uri = g_strdup (download->uri); - demux->current->base_uri = g_strdup (download->redirect_uri); + gst_m3u8_set_uri (m3u8, download->uri, download->redirect_uri, + demux->current_variant->name); } - GST_M3U8_CLIENT_UNLOCK (demux->client); buf = gst_fragment_get_buffer (download); playlist = gst_hls_src_buf_to_utf8_playlist (buf); @@ -1122,7 +1175,7 @@ retry: return FALSE; } - if (!gst_m3u8_update (demux->current, playlist)) { + if (!gst_m3u8_update (m3u8, playlist)) { GST_WARNING_OBJECT (demux, "Couldn't update playlist"); g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_FAILED, "Couldn't update playlist"); @@ -1131,30 +1184,29 @@ retry: /* If it's a live source, do not let the sequence number go beyond * three fragments before the end of the list */ - if (update == FALSE && demux->current && gst_m3u8_is_live (demux->current)) { + if (update == FALSE && gst_m3u8_is_live (m3u8)) { gint64 last_sequence, first_sequence; GST_M3U8_CLIENT_LOCK (demux->client); last_sequence = - GST_M3U8_MEDIA_FILE (g_list_last (demux->current->files)-> - data)->sequence; + GST_M3U8_MEDIA_FILE (g_list_last (m3u8->files)->data)->sequence; first_sequence = - GST_M3U8_MEDIA_FILE (demux->current->files->data)->sequence; + GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence; GST_DEBUG_OBJECT (demux, "sequence:%" G_GINT64_FORMAT " , first_sequence:%" G_GINT64_FORMAT - " , last_sequence:%" G_GINT64_FORMAT, demux->current->sequence, + " , last_sequence:%" G_GINT64_FORMAT, m3u8->sequence, first_sequence, last_sequence); - if (demux->current->sequence >= last_sequence - 3) { + if (m3u8->sequence >= last_sequence - 3) { //demux->need_segment = TRUE; /* Make sure we never go below the minimum sequence number */ - demux->current->sequence = MAX (first_sequence, last_sequence - 3); + m3u8->sequence = MAX (first_sequence, last_sequence - 3); GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %" G_GINT64_FORMAT, - demux->current->sequence); + m3u8->sequence); } GST_M3U8_CLIENT_UNLOCK (demux->client); - } else if (demux->current && !gst_m3u8_is_live (demux->current)) { + } else if (!gst_m3u8_is_live (m3u8)) { GstClockTime current_pos, target_pos; guint sequence = 0; GList *walk; @@ -1173,15 +1225,15 @@ retry: } else { target_pos = 0; } - if (GST_CLOCK_TIME_IS_VALID (demux->current->sequence_position)) { - target_pos = MAX (target_pos, demux->current->sequence_position); + if (GST_CLOCK_TIME_IS_VALID (m3u8->sequence_position)) { + target_pos = MAX (target_pos, m3u8->sequence_position); } GST_LOG_OBJECT (demux, "Looking for sequence position %" GST_TIME_FORMAT " in updated playlist", GST_TIME_ARGS (target_pos)); current_pos = 0; - for (walk = demux->current->files; walk; walk = walk->next) { + for (walk = m3u8->files; walk; walk = walk->next) { GstM3U8MediaFile *file = walk->data; sequence = file->sequence; @@ -1194,8 +1246,8 @@ retry: /* End of playlist */ if (!walk) sequence++; - demux->current->sequence = sequence; - demux->current->sequence_position = current_pos; + m3u8->sequence = sequence; + m3u8->sequence_position = current_pos; GST_M3U8_CLIENT_UNLOCK (demux->client); } @@ -1206,7 +1258,8 @@ static gboolean gst_hls_demux_change_playlist (GstHLSDemux * demux, guint max_bitrate, gboolean * changed) { - GList *previous_variant, *current_variant; + GstHLSVariantStream *lowest_variant, *lowest_ivariant; + GstHLSVariantStream *previous_variant, *new_variant; gint old_bandwidth, new_bandwidth; GstAdaptiveDemux *adaptive_demux = GST_ADAPTIVE_DEMUX_CAST (demux); GstAdaptiveDemuxStream *stream; @@ -1215,15 +1268,16 @@ gst_hls_demux_change_playlist (GstHLSDemux * demux, guint max_bitrate, stream = adaptive_demux->streams->data; - previous_variant = demux->main->current_variant; - current_variant = - gst_m3u8_get_playlist_for_bitrate (demux->main, max_bitrate); + previous_variant = demux->current_variant; + new_variant = + gst_hls_master_playlist_get_variant_for_bitrate (demux->master, + demux->current_variant, max_bitrate); GST_M3U8_CLIENT_LOCK (demux->client); retry_failover_protection: - old_bandwidth = GST_M3U8 (previous_variant->data)->bandwidth; - new_bandwidth = GST_M3U8 (current_variant->data)->bandwidth; + old_bandwidth = previous_variant->bandwidth; + new_bandwidth = new_variant->bandwidth; /* Don't do anything else if the playlist is the same */ if (new_bandwidth == old_bandwidth) { @@ -1231,19 +1285,19 @@ retry_failover_protection: return TRUE; } - demux->main->current_variant = current_variant; GST_M3U8_CLIENT_UNLOCK (demux->client); - gst_hls_demux_set_current (demux, current_variant->data); + gst_hls_demux_set_current_variant (demux, new_variant); GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching" " to bitrate %dbps", old_bandwidth, max_bitrate, new_bandwidth); if (gst_hls_demux_update_playlist (demux, TRUE, NULL)) { + const gchar *main_uri; gchar *uri; - gchar *main_uri; - uri = gst_m3u8_get_uri (demux->current); - main_uri = gst_m3u8_get_uri (demux->main); + + uri = gst_m3u8_get_uri (new_variant->m3u8); + main_uri = gst_adaptive_demux_get_manifest_ref_uri (adaptive_demux); gst_element_post_message (GST_ELEMENT_CAST (demux), gst_message_new_element (GST_OBJECT_CAST (demux), gst_structure_new (GST_ADAPTIVE_DEMUX_STATISTICS_MESSAGE_NAME, @@ -1251,31 +1305,37 @@ retry_failover_protection: main_uri, "uri", G_TYPE_STRING, uri, "bitrate", G_TYPE_INT, new_bandwidth, NULL))); g_free (uri); - g_free (main_uri); if (changed) *changed = TRUE; stream->discont = TRUE; } else { - GList *failover = NULL; + GstHLSVariantStream *failover_variant = NULL; + GList *failover; GST_INFO_OBJECT (demux, "Unable to update playlist. Switching back"); GST_M3U8_CLIENT_LOCK (demux->client); - failover = g_list_previous (current_variant); - if (failover && new_bandwidth == GST_M3U8 (failover->data)->bandwidth) { - current_variant = failover; + /* we find variants by bitrate by going from highest to lowest, so it's + * possible that there's another variant with the same bitrate before the + * one selected which we can use as failover */ + failover = g_list_find (demux->master->variants, new_variant); + if (failover != NULL) + failover = failover->prev; + if (failover != NULL) + failover_variant = failover->data; + if (failover_variant && new_bandwidth == failover_variant->bandwidth) { + new_variant = failover_variant; goto retry_failover_protection; } - demux->main->current_variant = previous_variant; GST_M3U8_CLIENT_UNLOCK (demux->client); - gst_hls_demux_set_current (demux, previous_variant->data); + gst_hls_demux_set_current_variant (demux, previous_variant); /* Try a lower bitrate (or stop if we just tried the lowest) */ - if (GST_M3U8 (previous_variant->data)->iframe && new_bandwidth == - GST_M3U8 (g_list_first (demux->main->iframe_lists)->data)->bandwidth) + lowest_variant = demux->master->variants->data; + lowest_ivariant = demux->master->iframe_variants->data; + if (previous_variant->iframe && new_bandwidth == lowest_ivariant->bandwidth) return FALSE; - else if (!GST_M3U8 (previous_variant->data)->iframe && new_bandwidth == - GST_M3U8 (g_list_first (demux->main->lists)->data)->bandwidth) + if (!previous_variant->iframe && new_bandwidth == lowest_variant->bandwidth) return FALSE; else return gst_hls_demux_change_playlist (demux, new_bandwidth - 1, changed); @@ -1447,7 +1507,12 @@ gst_hls_demux_get_manifest_update_interval (GstAdaptiveDemux * demux) GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); GstClockTime target_duration; - target_duration = gst_m3u8_get_target_duration (hlsdemux->current); + if (hlsdemux->current_variant) { + target_duration = + gst_m3u8_get_target_duration (hlsdemux->current_variant->m3u8); + } else { + target_duration = 5 * GST_SECOND; + } return gst_util_uint64_scale (target_duration, G_USEC_PER_SEC, GST_SECOND); } @@ -1459,8 +1524,10 @@ gst_hls_demux_get_live_seek_range (GstAdaptiveDemux * demux, gint64 * start, GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); gboolean ret = FALSE; - if (hlsdemux->current) - ret = gst_m3u8_get_seek_range (hlsdemux->current, start, stop); + if (hlsdemux->current_variant) { + ret = + gst_m3u8_get_seek_range (hlsdemux->current_variant->m3u8, start, stop); + } return ret; } diff --git a/ext/hls/gsthlsdemux.h b/ext/hls/gsthlsdemux.h index a762bf065f..838e608567 100644 --- a/ext/hls/gsthlsdemux.h +++ b/ext/hls/gsthlsdemux.h @@ -1,6 +1,7 @@ /* GStreamer * Copyright (C) 2010 Marc-Andre Lureau * Copyright (C) 2010 Andoni Morales Alastruey + * Copyright (C) 2015 Tim-Philipp Müller * * gsthlsdemux.h: * @@ -111,9 +112,10 @@ struct _GstHLSDemux GMutex keys_lock; /* FIXME: check locking, protected automatically by manifest_lock already? */ - /* playlists */ - GstM3U8 *main; /* main playlist */ - GstM3U8 *current; + /* The master playlist with the available variant streams */ + GstHLSMasterPlaylist *master; + + GstHLSVariantStream *current_variant; }; struct _GstHLSDemuxClass diff --git a/ext/hls/m3u8.c b/ext/hls/m3u8.c index d832ea0d8b..0f327adc1f 100644 --- a/ext/hls/m3u8.c +++ b/ext/hls/m3u8.c @@ -1,5 +1,6 @@ /* GStreamer * Copyright (C) 2010 Marc-Andre Lureau + * Copyright (C) 2015 Tim-Philipp Müller * * m3u8.c: * @@ -33,7 +34,6 @@ static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration, guint sequence); gchar *uri_join (const gchar * uri, const gchar * path); -static gboolean gst_m3u8_update_master_playlist (GstM3U8 * self, gchar * data); GstM3U8 * gst_m3u8_new (void) @@ -103,17 +103,11 @@ gst_m3u8_unref (GstM3U8 * self) g_free (self->uri); g_free (self->base_uri); g_free (self->name); - g_free (self->codecs); g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_unref, NULL); g_list_free (self->files); g_free (self->last_data); - g_list_foreach (self->lists, (GFunc) gst_m3u8_unref, NULL); - g_list_free (self->lists); - g_list_foreach (self->iframe_lists, (GFunc) gst_m3u8_unref, NULL); - g_list_free (self->iframe_lists); - g_free (self); } } @@ -301,18 +295,15 @@ parse_attributes (gchar ** ptr, gchar ** a, gchar ** v) } static gint -_m3u8_compare_uri (GstM3U8 * a, gchar * uri) +gst_hls_variant_stream_compare_by_bitrate (gconstpointer a, gconstpointer b) { - g_return_val_if_fail (a != NULL, 0); - g_return_val_if_fail (uri != NULL, 0); + const GstHLSVariantStream *vs_a = (const GstHLSVariantStream *) a; + const GstHLSVariantStream *vs_b = (const GstHLSVariantStream *) b; - return g_strcmp0 (a->uri, uri); -} + if (vs_a->bandwidth == vs_b->bandwidth) + return g_strcmp0 (vs_a->name, vs_b->name); -static gint -gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b) -{ - return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth; + return vs_a->bandwidth - vs_b->bandwidth; } /* @@ -352,9 +343,9 @@ gst_m3u8_update (GstM3U8 * self, gchar * data) } if (g_strrstr (data, "\n#EXT-X-STREAM-INF:") != NULL) { - GST_DEBUG ("Not a media playlist, but a master playlist!"); + GST_WARNING ("Not a media playlist, but a master playlist!"); GST_M3U8_UNLOCK (self); - return gst_m3u8_update_master_playlist (self, data); + return FALSE; } GST_TRACE ("data:\n%s", data); @@ -605,8 +596,11 @@ gst_m3u8_update (GstM3U8 * self, gchar * data) file = g_list_last (self->files); /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from - * the end of the playlist. See section 6.3.3 of HLS draft */ - for (i = 0; i < GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE && file->prev; ++i) + * the end of the playlist. See section 6.3.3 of HLS draft. Note + * the -1, because GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE = 1 means + * start 1 target-duration from the end */ + for (i = 0; i < GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE - 1 && file->prev; + ++i) file = file->prev; } else { file = g_list_first (self->files); @@ -683,8 +677,10 @@ gst_m3u8_get_next_fragment (GstM3U8 * m3u8, gboolean forward, GST_DEBUG ("Got fragment with sequence %u (current sequence %u)", (guint) file->sequence, (guint) m3u8->sequence); - *sequence_position = m3u8->sequence_position; - *discont = file->discont || (m3u8->sequence != file->sequence); + if (sequence_position) + *sequence_position = m3u8->sequence_position; + if (discont) + *discont = file->discont || (m3u8->sequence != file->sequence); m3u8->current_file_duration = file->duration; m3u8->sequence = file->sequence; @@ -888,19 +884,6 @@ gst_m3u8_get_uri (GstM3U8 * m3u8) return uri; } -gboolean -gst_m3u8_has_variant_playlist (GstM3U8 * m3u8) -{ - gboolean ret; - - g_return_val_if_fail (m3u8 != NULL, FALSE); - - GST_M3U8_LOCK (m3u8); - ret = (m3u8->lists != NULL); - GST_M3U8_UNLOCK (m3u8); - return ret; -} - gboolean gst_m3u8_is_live (GstM3U8 * m3u8) { @@ -915,33 +898,6 @@ gst_m3u8_is_live (GstM3U8 * m3u8) return is_live; } -GList * -gst_m3u8_get_playlist_for_bitrate (GstM3U8 * main, guint bitrate) -{ - GList *list, *current_variant; - - GST_M3U8_LOCK (main); - current_variant = main->current_variant; - - /* Go to the highest possible bandwidth allowed */ - while (GST_M3U8 (current_variant->data)->bandwidth <= bitrate) { - list = g_list_next (current_variant); - if (!list) - break; - current_variant = list; - } - - while (GST_M3U8 (current_variant->data)->bandwidth > bitrate) { - list = g_list_previous (current_variant); - if (!list) - break; - current_variant = list; - } - GST_M3U8_UNLOCK (main); - - return current_variant; -} - gchar * uri_join (const gchar * uri1, const gchar * uri2) { @@ -1040,48 +996,302 @@ out: return (duration > 0); } -static gboolean -gst_m3u8_update_master_playlist (GstM3U8 * self, gchar * data) +GstHLSMedia * +gst_hls_media_ref (GstHLSMedia * media) { - GstM3U8 *list; - gchar *end; - gint val; + g_assert (media != NULL && media->ref_count > 0); + g_atomic_int_add (&media->ref_count, 1); + return media; +} - g_return_val_if_fail (self != NULL, FALSE); - g_return_val_if_fail (data != NULL, FALSE); - - GST_M3U8_LOCK (self); - - /* check if the data changed since last update */ - if (self->last_data && g_str_equal (self->last_data, data)) { - GST_DEBUG ("Playlist is the same as previous one"); - g_free (data); - GST_M3U8_UNLOCK (self); - return TRUE; +void +gst_hls_media_unref (GstHLSMedia * media) +{ + g_assert (media != NULL && media->ref_count > 0); + if (g_atomic_int_dec_and_test (&media->ref_count)) { + g_free (media->group_id); + g_free (media->name); + g_free (media->uri); + g_free (media); } +} + +static GstHLSMediaType +gst_m3u8_get_hls_media_type_from_string (const gchar * type_name) +{ + if (strcmp (type_name, "AUDIO") == 0) + return GST_HLS_MEDIA_TYPE_AUDIO; + if (strcmp (type_name, "VIDEO") == 0) + return GST_HLS_MEDIA_TYPE_VIDEO; + if (strcmp (type_name, "SUBTITLES") == 0) + return GST_HLS_MEDIA_TYPE_SUBTITLES; + if (strcmp (type_name, "CLOSED_CAPTIONS") == 0) + return GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS; + + return GST_HLS_MEDIA_TYPE_INVALID; +} + +#define GST_HLS_MEDIA_TYPE_NAME(mtype) gst_m3u8_hls_media_type_get_nick(mtype) +static inline const gchar * +gst_m3u8_hls_media_type_get_nick (GstHLSMediaType mtype) +{ + static const gchar *nicks[GST_HLS_N_MEDIA_TYPES] = { "audio", "video", + "subtitle", "closed-captions" + }; + + if (mtype < 0 || mtype > GST_HLS_N_MEDIA_TYPES) + return "invalid"; + + return nicks[mtype]; +} + +/* returns unquoted copy of string */ +static gchar * +gst_m3u8_unquote (const gchar * str) +{ + const gchar *start, *end; + + start = strchr (str, '"'); + if (start == NULL) + return g_strdup (str); + end = strchr (start + 1, '"'); + if (end == NULL) { + GST_WARNING ("Broken quoted string [%s] - can't find end quote", str); + return g_strdup (start + 1); + } + return g_strndup (start + 1, (gsize) (end - (start + 1))); +} + +static GstHLSMedia * +gst_m3u8_parse_media (gchar * desc, const gchar * base_uri) +{ + GstHLSMediaType mtype = GST_HLS_MEDIA_TYPE_INVALID; + GstHLSMedia *media; + gchar *a, *v; + + media = g_new0 (GstHLSMedia, 1); + media->ref_count = 1; + media->playlist = gst_m3u8_new (); + + GST_LOG ("parsing %s", desc); + while (desc != NULL && parse_attributes (&desc, &a, &v)) { + if (strcmp (a, "TYPE") == 0) { + media->mtype = gst_m3u8_get_hls_media_type_from_string (v); + } else if (strcmp (a, "GROUP-ID") == 0) { + g_free (media->group_id); + media->group_id = gst_m3u8_unquote (v); + } else if (strcmp (a, "NAME") == 0) { + g_free (media->name); + media->name = gst_m3u8_unquote (v); + } else if (strcmp (a, "URI") == 0) { + gchar *uri; + + g_free (media->uri); + uri = gst_m3u8_unquote (v); + media->uri = uri_join (base_uri, uri); + g_free (uri); + } else if (strcmp (a, "LANGUAGE") == 0) { + g_free (media->lang); + media->lang = gst_m3u8_unquote (v); + } else if (strcmp (a, "DEFAULT") == 0) { + media->is_default = g_ascii_strcasecmp (v, "yes") == 0; + } else if (strcmp (a, "FORCED") == 0) { + media->forced = g_ascii_strcasecmp (v, "yes") == 0; + } else if (strcmp (a, "AUTOSELECT") == 0) { + media->autoselect = g_ascii_strcasecmp (v, "yes") == 0; + } else { + /* unhandled: ASSOC-LANGUAGE, INSTREAM-ID, CHARACTERISTICS */ + GST_FIXME ("EXT-X-MEDIA: unhandled attribute: %s = %s", a, v); + } + } + + if (media->mtype == GST_HLS_MEDIA_TYPE_INVALID) + goto required_attributes_missing; + + if (media->uri == NULL) + goto existing_stream; + + if (media->group_id == NULL || media->name == NULL) + goto required_attributes_missing; + + if (mtype == GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS && media->uri != NULL) + goto uri_with_cc; + + if (mtype == GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS) + goto cc_unsupported; + + GST_DEBUG ("media: %s, group '%s', name '%s', uri '%s', %s %s %s, lang=%s", + GST_HLS_MEDIA_TYPE_NAME (media->mtype), media->group_id, media->name, + media->uri, media->is_default ? "default" : "-", + media->autoselect ? "autoselect" : "-", + media->forced ? "forced" : "-", media->lang ? media->lang : "??"); + + return media; + +cc_unsupported: + { + GST_FIXME ("closed captions EXT-X-MEDIA are not yet supported"); + goto out_error; + } +uri_with_cc: + { + GST_WARNING ("closed captions EXT-X-MEDIA should not have URI specified"); + goto out_error; + } +required_attributes_missing: + { + GST_WARNING ("EXT-X-MEDIA description is missing required attributes"); + goto out_error; + /* fall through */ + } +existing_stream: + { + GST_DEBUG ("EXT-X-MEDIA without URI, describes embedded stream, skipping"); + /* fall through */ + } + +out_error: + { + gst_hls_media_unref (media); + return NULL; + } +} + +static GstHLSVariantStream * +gst_hls_variant_stream_new (void) +{ + GstHLSVariantStream *stream; + + stream = g_new0 (GstHLSVariantStream, 1); + stream->m3u8 = gst_m3u8_new (); + stream->refcount = 1; + return stream; +} + +GstHLSVariantStream * +gst_hls_variant_stream_ref (GstHLSVariantStream * stream) +{ + g_atomic_int_inc (&stream->refcount); + return stream; +} + +void +gst_hls_variant_stream_unref (GstHLSVariantStream * stream) +{ + if (g_atomic_int_dec_and_test (&stream->refcount)) { + gint i; + + g_free (stream->name); + g_free (stream->uri); + g_free (stream->codecs); + gst_m3u8_unref (stream->m3u8); + for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) { + g_free (stream->media_groups[i]); + g_list_free_full (stream->media[i], (GDestroyNotify) gst_hls_media_unref); + } + g_free (stream); + } +} + +static GstHLSVariantStream * +find_variant_stream_by_name (GList * list, const gchar * name) +{ + for (; list != NULL; list = list->next) { + GstHLSVariantStream *variant_stream = list->data; + + if (variant_stream->name != NULL && !strcmp (variant_stream->name, name)) + return variant_stream; + } + return NULL; +} + +static GstHLSVariantStream * +find_variant_stream_by_uri (GList * list, const gchar * uri) +{ + for (; list != NULL; list = list->next) { + GstHLSVariantStream *variant_stream = list->data; + + if (variant_stream->uri != NULL && !strcmp (variant_stream->uri, uri)) + return variant_stream; + } + return NULL; +} + +static GstHLSMasterPlaylist * +gst_hls_master_playlist_new (void) +{ + GstHLSMasterPlaylist *playlist; + + playlist = g_new0 (GstHLSMasterPlaylist, 1); + playlist->refcount = 1; + playlist->is_simple = FALSE; + + return playlist; +} + +void +gst_hls_master_playlist_unref (GstHLSMasterPlaylist * playlist) +{ + if (g_atomic_int_dec_and_test (&playlist->refcount)) { + g_list_free_full (playlist->variants, + (GDestroyNotify) gst_hls_variant_stream_unref); + g_list_free_full (playlist->iframe_variants, + (GDestroyNotify) gst_hls_variant_stream_unref); + g_free (playlist->last_data); + g_free (playlist); + } +} + +static gint +hls_media_name_compare_func (gconstpointer media, gconstpointer name) +{ + return strcmp (((GstHLSMedia *) media)->name, (const gchar *) name); +} + +/* Takes ownership of @data */ +GstHLSMasterPlaylist * +gst_hls_master_playlist_new_from_data (gchar * data, const gchar * base_uri) +{ + GHashTable *media_groups[GST_HLS_N_MEDIA_TYPES] = { NULL, }; + GstHLSMasterPlaylist *playlist; + GstHLSVariantStream *pending_stream; + gchar *end, *free_data = data; + gint val, i; + GList *l; if (!g_str_has_prefix (data, "#EXTM3U")) { GST_WARNING ("Data doesn't start with #EXTM3U"); - g_free (data); - GST_M3U8_UNLOCK (self); - return FALSE; + g_free (free_data); + return NULL; } - if (strstr (data, "\n#EXTINF:") != NULL) { - GST_WARNING ("This is a media playlist, not a master playlist!"); - g_free (data); - GST_M3U8_UNLOCK (self); - return FALSE; - } + playlist = gst_hls_master_playlist_new (); + + /* store data before we modify it for parsing */ + playlist->last_data = g_strdup (data); GST_TRACE ("data:\n%s", data); - g_free (self->last_data); - self->last_data = data; + if (strstr (data, "\n#EXTINF:") != NULL) { + GST_INFO ("This is a simple media playlist, not a master playlist"); - self->duration = GST_CLOCK_TIME_NONE; + pending_stream = gst_hls_variant_stream_new (); + pending_stream->name = g_strdup (base_uri); + pending_stream->uri = g_strdup (base_uri); + gst_m3u8_set_uri (pending_stream->m3u8, base_uri, NULL, base_uri); + playlist->variants = g_list_append (playlist->variants, pending_stream); + playlist->default_variant = gst_hls_variant_stream_ref (pending_stream); + playlist->is_simple = TRUE; - list = NULL; + if (!gst_m3u8_update (pending_stream->m3u8, data)) { + GST_WARNING ("Failed to parse media playlist"); + gst_hls_master_playlist_unref (playlist); + playlist = NULL; + } + return playlist; + } + + pending_stream = NULL; data += 7; while (TRUE) { gchar *r; @@ -1095,96 +1305,130 @@ gst_m3u8_update_master_playlist (GstM3U8 * self, gchar * data) *r = '\0'; if (data[0] != '#' && data[0] != '\0') { - gchar *name = data; + gchar *name, *uri; - if (list == NULL) { + if (pending_stream == NULL) { GST_LOG ("%s: got line without EXT-STREAM-INF, dropping", data); goto next_line; } - data = uri_join (self->base_uri ? self->base_uri : self->uri, data); - if (data == NULL) + name = data; + uri = uri_join (base_uri, name); + if (uri == NULL) goto next_line; - if (g_list_find_custom (self->lists, data, - (GCompareFunc) _m3u8_compare_uri)) { - GST_DEBUG ("Already have a list with this URI"); - gst_m3u8_unref (list); - g_free (data); + pending_stream->name = g_strdup (name); + pending_stream->uri = uri; + + if (find_variant_stream_by_name (playlist->variants, name) + || find_variant_stream_by_uri (playlist->variants, uri)) { + GST_DEBUG ("Already have a list with this name or URI: %s", name); + gst_hls_variant_stream_unref (pending_stream); } else { - gst_m3u8_take_uri (list, data, NULL, g_strdup (name)); - self->lists = g_list_append (self->lists, list); + GST_INFO ("stream %s @ %u: %s", name, pending_stream->bandwidth, uri); + gst_m3u8_set_uri (pending_stream->m3u8, uri, NULL, name); + playlist->variants = g_list_append (playlist->variants, pending_stream); + /* use first stream in the playlist as default */ + if (playlist->default_variant == NULL) { + playlist->default_variant = + gst_hls_variant_stream_ref (pending_stream); + } } - list = NULL; + pending_stream = NULL; } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) { if (int_from_string (data + 15, &data, &val)) - self->version = val; + playlist->version = val; } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:") || g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:")) { - gboolean iframe = g_str_has_prefix (data + 7, "I-FRAME"); - GstM3U8 *new_list; + GstHLSVariantStream *stream; gchar *v, *a; - new_list = gst_m3u8_new (); - new_list->iframe = iframe; - data = data + (iframe ? 26 : 18); + stream = gst_hls_variant_stream_new (); + stream->iframe = g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:"); + data += stream->iframe ? 26 : 18; while (data && parse_attributes (&data, &a, &v)) { if (g_str_equal (a, "BANDWIDTH")) { - if (!int_from_string (v, NULL, &new_list->bandwidth)) + if (!int_from_string (v, NULL, &stream->bandwidth)) GST_WARNING ("Error while reading BANDWIDTH"); } else if (g_str_equal (a, "PROGRAM-ID")) { - if (!int_from_string (v, NULL, &new_list->program_id)) + if (!int_from_string (v, NULL, &stream->program_id)) GST_WARNING ("Error while reading PROGRAM-ID"); } else if (g_str_equal (a, "CODECS")) { - g_free (new_list->codecs); - new_list->codecs = g_strdup (v); + g_free (stream->codecs); + stream->codecs = g_strdup (v); } else if (g_str_equal (a, "RESOLUTION")) { - if (!int_from_string (v, &v, &new_list->width)) + if (!int_from_string (v, &v, &stream->width)) GST_WARNING ("Error while reading RESOLUTION width"); if (!v || *v != 'x') { GST_WARNING ("Missing height"); } else { v = g_utf8_next_char (v); - if (!int_from_string (v, NULL, &new_list->height)) + if (!int_from_string (v, NULL, &stream->height)) GST_WARNING ("Error while reading RESOLUTION height"); } - } else if (iframe && g_str_equal (a, "URI")) { - gchar *name; - gchar *uri = g_strdup (v); - gchar *urip = uri; - - uri = unquote_string (uri); - if (uri) { - uri = uri_join (self->base_uri ? self->base_uri : self->uri, uri); - if (uri == NULL) { - g_free (urip); - continue; - } - name = g_strdup (uri); - - gst_m3u8_take_uri (new_list, uri, NULL, name); + } else if (stream->iframe && g_str_equal (a, "URI")) { + stream->uri = uri_join (base_uri, v); + if (stream->uri != NULL) { + stream->name = g_strdup (stream->uri); + gst_m3u8_set_uri (stream->m3u8, stream->uri, NULL, stream->name); } else { - GST_WARNING - ("Cannot remove quotation marks from i-frame-stream URI"); + gst_hls_variant_stream_unref (stream); } - g_free (urip); + } else if (g_str_equal (a, "AUDIO")) { + g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO]); + stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO] = gst_m3u8_unquote (v); + } else if (g_str_equal (a, "SUBTITLES")) { + g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES]); + stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES] = + gst_m3u8_unquote (v); + } else if (g_str_equal (a, "VIDEO")) { + g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO]); + stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO] = gst_m3u8_unquote (v); + } else if (g_str_equal (a, "CLOSED-CAPTIONS")) { + /* closed captions will be embedded inside the video stream, ignore */ } } - if (iframe) { - if (g_list_find_custom (self->iframe_lists, new_list->uri, - (GCompareFunc) _m3u8_compare_uri)) { + if (stream->iframe) { + if (find_variant_stream_by_uri (playlist->iframe_variants, stream->uri)) { GST_DEBUG ("Already have a list with this URI"); - gst_m3u8_unref (new_list); + gst_hls_variant_stream_unref (stream); } else { - self->iframe_lists = g_list_append (self->iframe_lists, new_list); + playlist->iframe_variants = + g_list_append (playlist->iframe_variants, stream); } } else { - if (list != NULL) { - GST_WARNING ("Found a list without a uri..., dropping"); - gst_m3u8_unref (list); + if (pending_stream != NULL) { + GST_WARNING ("variant stream without uri, dropping"); + gst_hls_variant_stream_unref (pending_stream); } - list = new_list; + pending_stream = stream; + } + } else if (g_str_has_prefix (data, "#EXT-X-MEDIA:")) { + GstHLSMedia *media; + GList *list; + + media = gst_m3u8_parse_media (data + strlen ("#EXT-X-MEDIA:"), base_uri); + + if (media == NULL) + goto next_line; + + if (media_groups[media->mtype] == NULL) { + media_groups[media->mtype] = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + } + + list = g_hash_table_lookup (media_groups[media->mtype], media->group_id); + + /* make sure there isn't already a media with the same name */ + if (!g_list_find_custom (list, media->name, hls_media_name_compare_func)) { + g_hash_table_replace (media_groups[media->mtype], + g_strdup (media->group_id), g_list_append (list, media)); + GST_INFO ("Added media %s to group %s", media->name, media->group_id); + } else { + GST_WARNING (" media with name '%s' already exists in group '%s'!", + media->name, media->group_id); + gst_hls_media_unref (media); } } else if (*data != '\0') { GST_LOG ("Ignored line: %s", data); @@ -1196,8 +1440,76 @@ gst_m3u8_update_master_playlist (GstM3U8 * self, gchar * data) data = g_utf8_next_char (end); /* skip \n */ } - /* reorder playlists by bitrate */ - if (self->lists) { + if (pending_stream != NULL) { + GST_WARNING ("#EXT-X-STREAM-INF without uri, dropping"); + gst_hls_variant_stream_unref (pending_stream); + } + + g_free (free_data); + + /* Add alternative renditions media to variant streams */ + for (l = playlist->variants; l != NULL; l = l->next) { + GstHLSVariantStream *stream = l->data; + GList *mlist; + + for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) { + if (stream->media_groups[i] != NULL && media_groups[i] != NULL) { + GST_INFO ("Adding %s group '%s' to stream '%s'", + GST_HLS_MEDIA_TYPE_NAME (i), stream->media_groups[i], stream->name); + + mlist = g_hash_table_lookup (media_groups[i], stream->media_groups[i]); + + if (mlist == NULL) + GST_WARNING ("Group '%s' does not exist!", stream->media_groups[i]); + + while (mlist != NULL) { + GstHLSMedia *media = mlist->data; + + GST_DEBUG (" %s media %s, uri: %s", GST_HLS_MEDIA_TYPE_NAME (i), + media->name, media->uri); + + stream->media[i] = + g_list_append (stream->media[i], gst_hls_media_ref (media)); + mlist = mlist->next; + } + } + } + } + + /* clean up our temporary alternative rendition groups hash tables */ + for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) { + if (media_groups[i] != NULL) { + GList *groups, *mlist; + + groups = g_hash_table_get_keys (media_groups[i]); + for (l = groups; l != NULL; l = l->next) { + mlist = g_hash_table_lookup (media_groups[i], l->data); + g_list_free_full (mlist, (GDestroyNotify) gst_hls_media_unref); + } + g_list_free (groups); + g_hash_table_unref (media_groups[i]); + } + } + + if (playlist->variants == NULL) { + GST_WARNING ("Master playlist without any media playlists!"); + gst_hls_master_playlist_unref (playlist); + return NULL; + } + + /* reorder variants by bitrate */ + playlist->variants = + g_list_sort (playlist->variants, + (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate); + + playlist->iframe_variants = + g_list_sort (playlist->iframe_variants, + (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate); + + /* FIXME: restore old current_variant after master playlist update + * (move into code that does that update) */ +#if 0 + { gchar *top_variant_uri = NULL; gboolean iframe = FALSE; @@ -1208,33 +1520,57 @@ gst_m3u8_update_master_playlist (GstM3U8 * self, gchar * data) iframe = GST_M3U8 (self->current_variant->data)->iframe; } - self->lists = - g_list_sort (self->lists, - (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate); - - self->iframe_lists = - g_list_sort (self->iframe_lists, - (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate); + /* here we sorted the lists */ if (iframe) - self->current_variant = - g_list_find_custom (self->iframe_lists, top_variant_uri, - (GCompareFunc) _m3u8_compare_uri); + playlist->current_variant = + find_variant_stream_by_uri (playlist->iframe_variants, + top_variant_uri); else - self->current_variant = g_list_find_custom (self->lists, top_variant_uri, - (GCompareFunc) _m3u8_compare_uri); - } - - if (self->lists == NULL) { - GST_ERROR ("Invalid master playlist, it does not contain any streams"); - GST_M3U8_UNLOCK (self); - return FALSE; + playlist->current_variant = + find_variant_stream_by_uri (playlist->variants, top_variant_uri); } +#endif GST_DEBUG ("parsed master playlist with %d streams and %d I-frame streams", - g_list_length (self->lists), g_list_length (self->iframe_lists)); + g_list_length (playlist->variants), + g_list_length (playlist->iframe_variants)); - GST_M3U8_UNLOCK (self); - return TRUE; + return playlist; +} + +gboolean +gst_hls_variant_stream_is_live (GstHLSVariantStream * variant) +{ + gboolean is_live; + + g_return_val_if_fail (variant != NULL, FALSE); + + is_live = gst_m3u8_is_live (variant->m3u8); + + return is_live; +} + +GstHLSVariantStream * +gst_hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist * + playlist, GstHLSVariantStream * current_variant, guint bitrate) +{ + GstHLSVariantStream *variant = current_variant; + GList *l; + + /* variant lists are sorted low to high, so iterate from highest to lowest */ + if (current_variant == NULL || !current_variant->iframe) + l = g_list_last (playlist->variants); + else + l = g_list_last (playlist->iframe_variants); + + while (l != NULL) { + variant = l->data; + if (variant->bandwidth <= bitrate) + break; + l = l->prev; + } + + return variant; } diff --git a/ext/hls/m3u8.h b/ext/hls/m3u8.h index 01bcf6aa87..9bdb7a6fdb 100644 --- a/ext/hls/m3u8.h +++ b/ext/hls/m3u8.h @@ -1,6 +1,7 @@ /* GStreamer * Copyright (C) 2010 Marc-Andre Lureau * Copyright (C) 2010 Andoni Morales Alastruey + * Copyright (C) 2015 Tim-Philipp Müller * * m3u8.h: * @@ -29,7 +30,10 @@ G_BEGIN_DECLS typedef struct _GstM3U8 GstM3U8; typedef struct _GstM3U8MediaFile GstM3U8MediaFile; +typedef struct _GstHLSMedia GstHLSMedia; typedef struct _GstM3U8Client GstM3U8Client; +typedef struct _GstHLSVariantStream GstHLSVariantStream; +typedef struct _GstHLSMasterPlaylist GstHLSMasterPlaylist; #define GST_M3U8(m) ((GstM3U8*)m) #define GST_M3U8_MEDIA_FILE(f) ((GstM3U8MediaFile*)f) @@ -59,12 +63,6 @@ struct _GstM3U8 GstClockTime targetduration; /* last EXT-X-TARGETDURATION */ gboolean allowcache; /* last EXT-X-ALLOWCACHE */ - gint bandwidth; - gint program_id; - gchar *codecs; - gint width; - gint height; - gboolean iframe; GList *files; /* state */ @@ -79,9 +77,6 @@ struct _GstM3U8 /*< private > */ gchar *last_data; - GList *lists; /* list of GstM3U8 from the main playlist */ - GList *iframe_lists; /* I-frame lists from the main playlist */ - GList *current_variant; /* Current variant playlist used */ GMutex lock; gint ref_count; /* ATOMIC */ @@ -136,16 +131,97 @@ GstClockTime gst_m3u8_get_target_duration (GstM3U8 * m3u8); gchar * gst_m3u8_get_uri (GstM3U8 * m3u8); -gboolean gst_m3u8_has_variant_playlist (GstM3U8 * m3u8); - gboolean gst_m3u8_is_live (GstM3U8 * m3u8); gboolean gst_m3u8_get_seek_range (GstM3U8 * m3u8, gint64 * start, gint64 * stop); -GList * gst_m3u8_get_playlist_for_bitrate (GstM3U8 * main, - guint bitrate); +typedef enum +{ + GST_HLS_MEDIA_TYPE_INVALID = -1, + GST_HLS_MEDIA_TYPE_AUDIO, + GST_HLS_MEDIA_TYPE_VIDEO, + GST_HLS_MEDIA_TYPE_SUBTITLES, + GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS, + GST_HLS_N_MEDIA_TYPES +} GstHLSMediaType; + +struct _GstHLSMedia { + GstHLSMediaType mtype; + gchar *group_id; + gchar *name; + gchar *lang; + gchar *uri; + gboolean is_default; + gboolean autoselect; + gboolean forced; + + GstM3U8 *playlist; /* media playlist */ + + gint ref_count; /* ATOMIC */ +}; + +GstHLSMedia * gst_hls_media_ref (GstHLSMedia * media); + +void gst_hls_media_unref (GstHLSMedia * media); + + +struct _GstHLSVariantStream { + gchar *name; /* This will be the "name" of the playlist, the original + * relative/absolute uri in a variant playlist */ + gchar *uri; + gchar *codecs; + gint bandwidth; + gint program_id; + gint width; + gint height; + gboolean iframe; + + gint refcount; /* ATOMIC */ + + GstM3U8 *m3u8; /* media playlist */ + + /* alternative renditions */ + gchar *media_groups[GST_HLS_N_MEDIA_TYPES]; + GList *media[GST_HLS_N_MEDIA_TYPES]; +}; + +GstHLSVariantStream * gst_hls_variant_stream_ref (GstHLSVariantStream * stream); + +void gst_hls_variant_stream_unref (GstHLSVariantStream * stream); + +gboolean gst_hls_variant_stream_is_live (GstHLSVariantStream * stream); + + +struct _GstHLSMasterPlaylist +{ + /* Available variant streams, sorted by bitrate (low -> high) */ + GList *variants; + GList *iframe_variants; + + GstHLSVariantStream *default_variant; /* first in the list */ + + gint version; /* EXT-X-VERSION */ + + gint refcount; /* ATOMIC */ + + gboolean is_simple; /* TRUE if simple main media playlist, + * FALSE if variant playlist (either + * way the variants list will be set) */ + + /*< private > */ + gchar *last_data; +}; + +GstHLSMasterPlaylist * gst_hls_master_playlist_new_from_data (gchar * data, + const gchar * base_uri); + +GstHLSVariantStream * gst_hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist * playlist, + GstHLSVariantStream * current_variant, + guint bitrate); + +void gst_hls_master_playlist_unref (GstHLSMasterPlaylist * playlist); G_END_DECLS diff --git a/tests/check/elements/hlsdemux_m3u8.c b/tests/check/elements/hlsdemux_m3u8.c index f49e90184b..02f50f1a6e 100644 --- a/tests/check/elements/hlsdemux_m3u8.c +++ b/tests/check/elements/hlsdemux_m3u8.c @@ -322,44 +322,41 @@ http://example.com/hi.m3u8\r\n\ #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\r\n\ http://example.com/audio-only.m3u8"; -static GstM3U8Client * +static GstHLSMasterPlaylist * load_playlist (const gchar * data) { - gboolean ret; - GstM3U8Client *client; + GstHLSMasterPlaylist *master; - client = gst_m3u8_client_new ("http://localhost/test.m3u8", NULL); - ret = gst_m3u8_client_update (client, g_strdup (data)); - assert_equals_int (ret, TRUE); + master = gst_hls_master_playlist_new_from_data (g_strdup (data), + "http://localhost/test.m3u8"); + fail_unless (master != NULL); - return client; + return master; } GST_START_TEST (test_load_main_playlist_invalid) { - gboolean ret; - GstM3U8Client *client = - gst_m3u8_client_new ("http://localhost/test.m3u8", NULL); + GstHLSMasterPlaylist *master; - ret = gst_m3u8_client_update (client, g_strdup (INVALID_PLAYLIST)); - assert_equals_int (ret, FALSE); - - gst_m3u8_client_free (client); + master = + gst_hls_master_playlist_new_from_data (g_strdup (INVALID_PLAYLIST), NULL); + fail_unless (master == NULL); } GST_END_TEST; GST_START_TEST (test_load_main_playlist_rendition) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; + GstHLSVariantStream *variant; - client = load_playlist (ON_DEMAND_PLAYLIST); + master = load_playlist (ON_DEMAND_PLAYLIST); + variant = master->default_variant; - assert_equals_int (g_list_length (client->main->files), 4); - assert_equals_int (g_list_length (client->current->files), 4); - assert_equals_int (client->sequence, 0); + assert_equals_int (g_list_length (variant->m3u8->files), 4); + assert_equals_int (master->version, 0); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -367,17 +364,18 @@ GST_END_TEST; static void do_test_load_main_playlist_variant (const gchar * playlist) { - GstM3U8Client *client; - GstM3U8 *stream; + GstHLSMasterPlaylist *master; + GstHLSVariantStream *stream; GList *tmp; - client = load_playlist (playlist); + master = gst_hls_master_playlist_new_from_data (g_strdup (playlist), NULL); + fail_unless (master != NULL); - assert_equals_int (g_list_length (client->main->lists), 4); + assert_equals_int (g_list_length (master->variants), 4); /* Audio-Only */ - tmp = g_list_first (client->main->lists); - stream = GST_M3U8 (tmp->data); + tmp = g_list_first (master->variants); + stream = tmp->data; assert_equals_int (stream->bandwidth, 65000); assert_equals_int (stream->program_id, 1); assert_equals_string (stream->uri, "http://example.com/audio-only.m3u8"); @@ -385,30 +383,30 @@ do_test_load_main_playlist_variant (const gchar * playlist) /* Low */ tmp = g_list_next (tmp); - stream = GST_M3U8 (tmp->data); + stream = tmp->data; assert_equals_int (stream->bandwidth, 128000); assert_equals_int (stream->program_id, 1); assert_equals_string (stream->uri, "http://example.com/low.m3u8"); /* Mid */ tmp = g_list_next (tmp); - stream = GST_M3U8 (tmp->data); + stream = tmp->data; assert_equals_int (stream->bandwidth, 256000); assert_equals_int (stream->program_id, 1); assert_equals_string (stream->uri, "http://example.com/mid.m3u8"); /* High */ tmp = g_list_next (tmp); - stream = GST_M3U8 (tmp->data); + stream = tmp->data; assert_equals_int (stream->bandwidth, 768000); assert_equals_int (stream->program_id, 1); assert_equals_string (stream->uri, "http://example.com/hi.m3u8"); /* Check the first playlist is selected */ - assert_equals_int (client->current != NULL, TRUE); - assert_equals_int (client->current->bandwidth, 128000); + assert_equals_int (master->default_variant != NULL, TRUE); + assert_equals_int (master->default_variant->bandwidth, 128000); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_START_TEST (test_load_main_playlist_variant) @@ -420,11 +418,11 @@ GST_END_TEST; GST_START_TEST (test_load_main_playlist_variant_with_missing_uri) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; - client = load_playlist (VARIANT_PLAYLIST_WITH_URI_MISSING); - assert_equals_int (g_list_length (client->main->lists), 3); - gst_m3u8_client_free (client); + master = load_playlist (VARIANT_PLAYLIST_WITH_URI_MISSING); + assert_equals_int (g_list_length (master->variants), 3); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -453,17 +451,17 @@ GST_END_TEST; static void check_on_demand_playlist (const gchar * data) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; GstM3U8 *pl; GstM3U8MediaFile *file; - client = load_playlist (data); - pl = client->current; + master = load_playlist (data); + pl = master->default_variant->m3u8; /* Sequence should be 0 as it's an ondemand playlist */ - assert_equals_int (client->sequence, 0); + assert_equals_int (pl->sequence, 0); /* Check that we are not live */ - assert_equals_int (gst_m3u8_client_is_live (client), FALSE); + assert_equals_int (gst_m3u8_is_live (pl), FALSE); /* Check number of entries */ assert_equals_int (g_list_length (pl->files), 4); /* Check first media segments */ @@ -475,7 +473,7 @@ check_on_demand_playlist (const gchar * data) assert_equals_string (file->uri, "http://media.example.com/004.ts"); assert_equals_int (file->sequence, 3); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_START_TEST (test_on_demand_playlist) @@ -508,18 +506,18 @@ GST_END_TEST; GST_START_TEST (test_live_playlist) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; GstM3U8 *pl; GstM3U8MediaFile *file; gint64 start = -1; gint64 stop = -1; - client = load_playlist (LIVE_PLAYLIST); + master = load_playlist (LIVE_PLAYLIST); - pl = client->current; + pl = master->default_variant->m3u8; /* Check that we are live */ - assert_equals_int (gst_m3u8_client_is_live (client), TRUE); - assert_equals_int (client->sequence, 2681); + assert_equals_int (gst_m3u8_is_live (pl), TRUE); + assert_equals_int (pl->sequence, 2681); /* Check number of entries */ assert_equals_int (g_list_length (pl->files), 4); /* Check first media segments */ @@ -532,11 +530,11 @@ GST_START_TEST (test_live_playlist) assert_equals_string (file->uri, "https://priv.example.com/fileSequence2683.ts"); assert_equals_int (file->sequence, 2683); - fail_unless (gst_m3u8_client_get_seek_range (client, &start, &stop)); + fail_unless (gst_m3u8_get_seek_range (pl, &start, &stop)); assert_equals_int64 (start, 0); assert_equals_float (stop / (double) GST_SECOND, 16.0); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -546,44 +544,45 @@ GST_END_TEST; * there is a jump in the media sequence that must be handled correctly. */ GST_START_TEST (test_live_playlist_rotated) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; GstM3U8 *pl; GstM3U8MediaFile *file; gboolean ret; - client = load_playlist (LIVE_PLAYLIST); - pl = client->current; - assert_equals_int (client->sequence, 2681); + master = load_playlist (LIVE_PLAYLIST); + pl = master->default_variant->m3u8; + + assert_equals_int (pl->sequence, 2681); /* Check first media segments */ file = GST_M3U8_MEDIA_FILE (g_list_first (pl->files)->data); assert_equals_int (file->sequence, 2680); - ret = gst_m3u8_client_update (client, g_strdup (LIVE_ROTATED_PLAYLIST)); + ret = gst_m3u8_update (pl, g_strdup (LIVE_ROTATED_PLAYLIST)); assert_equals_int (ret, TRUE); - gst_m3u8_client_get_next_fragment (client, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, TRUE); + gst_m3u8_get_next_fragment (pl, TRUE, NULL, NULL); + /* FIXME: Sequence should last - 3. Should it? */ - assert_equals_int (client->sequence, 3001); + assert_equals_int (pl->sequence, 3001); /* Check first media segments */ file = GST_M3U8_MEDIA_FILE (g_list_first (pl->files)->data); assert_equals_int (file->sequence, 3001); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; GST_START_TEST (test_playlist_with_doubles_duration) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; GstM3U8 *pl; GstM3U8MediaFile *file; gint64 start = -1; gint64 stop = -1; - client = load_playlist (DOUBLES_PLAYLIST); + master = load_playlist (DOUBLES_PLAYLIST); + pl = master->default_variant->m3u8; - pl = client->current; /* Check first media segments */ file = GST_M3U8_MEDIA_FILE (g_list_nth_data (pl->files, 0)); assert_equals_float (file->duration / (double) GST_SECOND, 10.321); @@ -593,18 +592,19 @@ GST_START_TEST (test_playlist_with_doubles_duration) assert_equals_float (file->duration / (double) GST_SECOND, 10.2344); file = GST_M3U8_MEDIA_FILE (g_list_nth_data (pl->files, 3)); assert_equals_float (file->duration / (double) GST_SECOND, 9.92); - fail_unless (gst_m3u8_client_get_seek_range (client, &start, &stop)); + fail_unless (gst_m3u8_get_seek_range (pl, &start, &stop)); assert_equals_int64 (start, 0); assert_equals_float (stop / (double) GST_SECOND, 10.321 + 9.6789 + 10.2344 + 9.92); - gst_m3u8_client_free (client); + + gst_hls_master_playlist_unref (master); } GST_END_TEST; GST_START_TEST (test_playlist_with_encryption) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; GstM3U8 *pl; GstM3U8MediaFile *file; guint8 iv1[16] = { 0, }; @@ -613,9 +613,9 @@ GST_START_TEST (test_playlist_with_encryption) iv1[15] = 1; iv2[15] = 2; - client = load_playlist (AES_128_ENCRYPTED_PLAYLIST); + master = load_playlist (AES_128_ENCRYPTED_PLAYLIST); + pl = master->default_variant->m3u8; - pl = client->current; assert_equals_int (g_list_length (pl->files), 5); /* Check all media segments */ @@ -640,72 +640,73 @@ GST_START_TEST (test_playlist_with_encryption) assert_equals_string (file->key, "https://priv.example.com/key2.bin"); fail_unless (memcmp (&file->iv, iv1, 16) == 0); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; + GST_START_TEST (test_update_invalid_playlist) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; GstM3U8 *pl; gboolean ret; /* Test updates in on-demand playlists */ - client = load_playlist (ON_DEMAND_PLAYLIST); - pl = client->current; + master = load_playlist (ON_DEMAND_PLAYLIST); + pl = master->default_variant->m3u8; assert_equals_int (g_list_length (pl->files), 4); - ret = gst_m3u8_client_update (client, g_strdup ("#INVALID")); + ret = gst_m3u8_update (pl, g_strdup ("#INVALID")); assert_equals_int (ret, FALSE); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; GST_START_TEST (test_update_playlist) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; GstM3U8 *pl; gchar *live_pl; gboolean ret; /* Test updates in on-demand playlists */ - client = load_playlist (ON_DEMAND_PLAYLIST); - pl = client->current; + master = load_playlist (ON_DEMAND_PLAYLIST); + pl = master->default_variant->m3u8; assert_equals_int (g_list_length (pl->files), 4); - ret = gst_m3u8_client_update (client, g_strdup (ON_DEMAND_PLAYLIST)); + ret = gst_m3u8_update (pl, g_strdup (ON_DEMAND_PLAYLIST)); assert_equals_int (ret, TRUE); assert_equals_int (g_list_length (pl->files), 4); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); /* Test updates in live playlists */ - client = load_playlist (LIVE_PLAYLIST); - pl = client->current; + master = load_playlist (LIVE_PLAYLIST); + pl = master->default_variant->m3u8; assert_equals_int (g_list_length (pl->files), 4); /* Add a new entry to the playlist and check the update */ live_pl = g_strdup_printf ("%s\n%s\n%s", LIVE_PLAYLIST, "#EXTINF:8", "https://priv.example.com/fileSequence2683.ts"); - ret = gst_m3u8_client_update (client, live_pl); + ret = gst_m3u8_update (pl, live_pl); assert_equals_int (ret, TRUE); assert_equals_int (g_list_length (pl->files), 5); /* Test sliding window */ - ret = gst_m3u8_client_update (client, g_strdup (LIVE_PLAYLIST)); + ret = gst_m3u8_update (pl, g_strdup (LIVE_PLAYLIST)); assert_equals_int (ret, TRUE); assert_equals_int (g_list_length (pl->files), 4); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; GST_START_TEST (test_playlist_media_files) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; GstM3U8 *pl; GstM3U8MediaFile *file; - client = load_playlist (ON_DEMAND_PLAYLIST); - pl = client->current; + master = load_playlist (ON_DEMAND_PLAYLIST); + pl = master->default_variant->m3u8; /* Check number of entries */ assert_equals_int (g_list_length (pl->files), 4); @@ -718,19 +719,19 @@ GST_START_TEST (test_playlist_media_files) assert_equals_int (file->size, -1); assert_equals_string (file->title, "Test"); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; GST_START_TEST (test_playlist_byte_range_media_files) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; GstM3U8 *pl; GstM3U8MediaFile *file; - client = load_playlist (BYTE_RANGES_PLAYLIST); - pl = client->current; + master = load_playlist (BYTE_RANGES_PLAYLIST); + pl = master->default_variant->m3u8; /* Check number of entries */ assert_equals_int (g_list_length (pl->files), 4); @@ -749,11 +750,11 @@ GST_START_TEST (test_playlist_byte_range_media_files) assert_equals_int (file->offset, 3000); assert_equals_int (file->size, 1000); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); - client = load_playlist (BYTE_RANGES_ACC_OFFSET_PLAYLIST); - pl = client->current; + master = load_playlist (BYTE_RANGES_ACC_OFFSET_PLAYLIST); + pl = master->default_variant->m3u8; /* Check number of entries */ assert_equals_int (g_list_length (pl->files), 4); @@ -772,90 +773,93 @@ GST_START_TEST (test_playlist_byte_range_media_files) assert_equals_int (file->offset, 3000); assert_equals_int (file->size, 1000); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; GST_START_TEST (test_get_next_fragment) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; + GstM3U8 *pl; + GstM3U8MediaFile *mf; gboolean discontinous; - gchar *uri; - GstClockTime duration, timestamp; - gint64 range_start, range_end; + GstClockTime timestamp; - client = load_playlist (BYTE_RANGES_PLAYLIST); + master = load_playlist (BYTE_RANGES_PLAYLIST); + pl = master->default_variant->m3u8; /* Check the next fragment */ - gst_m3u8_client_get_next_fragment (client, &discontinous, &uri, &duration, - ×tamp, &range_start, &range_end, NULL, NULL, TRUE); + mf = gst_m3u8_get_next_fragment (pl, TRUE, ×tamp, &discontinous); + fail_unless (mf != NULL); assert_equals_int (discontinous, FALSE); - assert_equals_string (uri, "http://media.example.com/all.ts"); + assert_equals_string (mf->uri, "http://media.example.com/all.ts"); assert_equals_uint64 (timestamp, 0); - assert_equals_uint64 (duration, 10 * GST_SECOND); - assert_equals_uint64 (range_start, 100); - assert_equals_uint64 (range_end, 1099); - g_free (uri); + assert_equals_uint64 (mf->duration, 10 * GST_SECOND); + assert_equals_uint64 (mf->offset, 100); + assert_equals_uint64 (mf->offset + mf->size, 1100); - gst_m3u8_client_advance_fragment (client, TRUE); + gst_m3u8_advance_fragment (pl, TRUE); /* Check next media segments */ - gst_m3u8_client_get_next_fragment (client, &discontinous, &uri, &duration, - ×tamp, &range_start, &range_end, NULL, NULL, TRUE); + mf = gst_m3u8_get_next_fragment (pl, TRUE, ×tamp, &discontinous); + fail_unless (mf != NULL); assert_equals_int (discontinous, FALSE); - assert_equals_string (uri, "http://media.example.com/all.ts"); + assert_equals_string (mf->uri, "http://media.example.com/all.ts"); assert_equals_uint64 (timestamp, 10 * GST_SECOND); - assert_equals_uint64 (duration, 10 * GST_SECOND); - assert_equals_uint64 (range_start, 1000); - assert_equals_uint64 (range_end, 1999); - g_free (uri); + assert_equals_uint64 (mf->duration, 10 * GST_SECOND); + assert_equals_uint64 (mf->offset, 1000); + assert_equals_uint64 (mf->offset + mf->size, 2000); - gst_m3u8_client_advance_fragment (client, TRUE); + gst_m3u8_advance_fragment (pl, TRUE); /* Check next media segments */ - gst_m3u8_client_get_next_fragment (client, &discontinous, &uri, &duration, - ×tamp, &range_start, &range_end, NULL, NULL, TRUE); + mf = gst_m3u8_get_next_fragment (pl, TRUE, ×tamp, &discontinous); assert_equals_int (discontinous, FALSE); - assert_equals_string (uri, "http://media.example.com/all.ts"); + assert_equals_string (mf->uri, "http://media.example.com/all.ts"); assert_equals_uint64 (timestamp, 20 * GST_SECOND); - assert_equals_uint64 (duration, 10 * GST_SECOND); - assert_equals_uint64 (range_start, 2000); - assert_equals_uint64 (range_end, 2999); - g_free (uri); + assert_equals_uint64 (mf->duration, 10 * GST_SECOND); + assert_equals_uint64 (mf->offset, 2000); + assert_equals_uint64 (mf->offset + mf->size, 3000); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; GST_START_TEST (test_get_duration) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; + GstM3U8 *pl; /* Test duration for on-demand playlists */ - client = load_playlist (ON_DEMAND_PLAYLIST); - assert_equals_uint64 (gst_m3u8_client_get_duration (client), 40 * GST_SECOND); - gst_m3u8_client_free (client); + master = load_playlist (ON_DEMAND_PLAYLIST); + pl = master->default_variant->m3u8; + + assert_equals_uint64 (gst_m3u8_get_duration (pl), 40 * GST_SECOND); + gst_hls_master_playlist_unref (master); /* Test duration for live playlists */ - client = load_playlist (LIVE_PLAYLIST); - assert_equals_uint64 (gst_m3u8_client_get_duration (client), - GST_CLOCK_TIME_NONE); - gst_m3u8_client_free (client); + master = load_playlist (LIVE_PLAYLIST); + pl = master->default_variant->m3u8; + assert_equals_uint64 (gst_m3u8_get_duration (pl), GST_CLOCK_TIME_NONE); + + gst_hls_master_playlist_unref (master); } GST_END_TEST; GST_START_TEST (test_get_target_duration) { - GstM3U8Client *client; + GstHLSMasterPlaylist *master; + GstM3U8 *pl; - client = load_playlist (ON_DEMAND_PLAYLIST); - assert_equals_uint64 (gst_m3u8_client_get_target_duration (client), - 10 * GST_SECOND); + master = load_playlist (ON_DEMAND_PLAYLIST); + pl = master->default_variant->m3u8; - gst_m3u8_client_free (client); + assert_equals_uint64 (gst_m3u8_get_target_duration (pl), 10 * GST_SECOND); + + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -863,23 +867,29 @@ GST_END_TEST; GST_START_TEST (test_get_stream_for_bitrate) { - GstM3U8Client *client; - GstM3U8 *stream; + GstHLSMasterPlaylist *master; + GstHLSVariantStream *stream; + + master = load_playlist (VARIANT_PLAYLIST); + stream = gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 0); - client = load_playlist (VARIANT_PLAYLIST); - stream = gst_m3u8_client_get_playlist_for_bitrate (client, 0)->data; assert_equals_int (stream->bandwidth, 65000); - stream = gst_m3u8_client_get_playlist_for_bitrate (client, G_MAXINT32)->data; + + stream = + gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, + G_MAXINT32); assert_equals_int (stream->bandwidth, 768000); - stream = gst_m3u8_client_get_playlist_for_bitrate (client, 300000)->data; + stream = + gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 300000); assert_equals_int (stream->bandwidth, 256000); - stream = gst_m3u8_client_get_playlist_for_bitrate (client, 500000)->data; + stream = + gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 500000); assert_equals_int (stream->bandwidth, 256000); - stream = gst_m3u8_client_get_playlist_for_bitrate (client, 255000)->data; + stream = + gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 255000); assert_equals_int (stream->bandwidth, 128000); - - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -905,7 +915,7 @@ GST_START_TEST (test_seek) { GstM3U8Client *client; - client = load_playlist (ON_DEMAND_PLAYLIST); + master = load_playlist (ON_DEMAND_PLAYLIST); /* Test seek in the middle of a fragment */ do_test_seek (client, 1, 0); @@ -922,10 +932,10 @@ GST_START_TEST (test_seek) /* Test invalid seeks (end if list should be 30 + 10) */ do_test_seek (client, 39, 30); do_test_seek (client, 40, -1); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); /* Test seeks on a live playlist */ - client = load_playlist (LIVE_PLAYLIST); + master = load_playlist (LIVE_PLAYLIST); do_test_seek (client, 0, 0); do_test_seek (client, 8, 8); @@ -933,7 +943,7 @@ GST_START_TEST (test_seek) do_test_seek (client, 30, 24); do_test_seek (client, 3000, -1); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -944,7 +954,7 @@ GST_START_TEST (test_alternate_audio_playlist) GstM3U8Media *media; GList *alternates; - client = load_playlist (ALTERNATE_AUDIO_PLAYLIST); + master = load_playlist (ALTERNATE_AUDIO_PLAYLIST); assert_equals_int (g_list_length (client->main->streams), 4); assert_equals_int (g_hash_table_size (client->main->video_rendition_groups), @@ -980,7 +990,7 @@ GST_START_TEST (test_alternate_audio_playlist) assert_equals_string (g_list_nth_data (alternates, 1), "Commentary"); assert_equals_string (g_list_nth_data (alternates, 2), "Deutsche"); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -991,7 +1001,7 @@ GST_START_TEST (test_subtitles_playlist) GstM3U8Media *media; GList *alternates; - client = load_playlist (SUBTITLES_PLAYLIST); + master = load_playlist (SUBTITLES_PLAYLIST); assert_equals_int (g_list_length (client->main->streams), 3); assert_equals_int (g_hash_table_size (client->main->video_rendition_groups), @@ -1028,7 +1038,7 @@ GST_START_TEST (test_subtitles_playlist) assert_equals_string (g_list_nth_data (alternates, 1), "Spanish"); assert_equals_string (g_list_nth_data (alternates, 2), "English"); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -1041,7 +1051,7 @@ GST_START_TEST (test_select_subs_alternate) /* Check with a playlist with alternative audio renditions where the video * stream is video-only and therefor we always have 2 playlists, one for * video and another one for audio */ - client = load_playlist (SUBTITLES_PLAYLIST); + master = load_playlist (SUBTITLES_PLAYLIST); gst_m3u8_client_get_current_uri (client, &v_uri, &a_uri, &s_uri); assert_equals_int (a_uri == NULL, TRUE); assert_equals_int (s_uri != NULL, TRUE); @@ -1081,7 +1091,7 @@ GST_START_TEST (test_select_subs_alternate) assert_equals_string (v_uri, "http://localhost/low/video-audio.m3u8"); assert_equals_int (s_uri == NULL, TRUE); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -1094,7 +1104,7 @@ GST_START_TEST (test_select_alternate) /* Check with a playlist with alternative audio renditions where the video * stream is video-only and therefor we always have 2 playlists, one for * video and another one for audio */ - client = load_playlist (ALTERNATE_AUDIO_PLAYLIST); + master = load_playlist (ALTERNATE_AUDIO_PLAYLIST); gst_m3u8_client_get_current_uri (client, &v_uri, &a_uri, &s_uri); assert_equals_int (a_uri != NULL, TRUE); assert_equals_string (a_uri, "http://localhost/main/english-audio.m3u8"); @@ -1123,13 +1133,13 @@ GST_START_TEST (test_select_alternate) assert_equals_int (v_uri == NULL, TRUE); assert_equals_int (s_uri == NULL, TRUE); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); /* Now check with a playlist with alternative audio renditions where the * video * stream has the default audio rendition muxed and therefore we * only have 2 playlists when the audio alternative rendition is not the * default one */ - client = load_playlist (ALT_AUDIO_PLAYLIST_WITH_VIDEO_AUDIO); + master = load_playlist (ALT_AUDIO_PLAYLIST_WITH_VIDEO_AUDIO); gst_m3u8_client_get_current_uri (client, &v_uri, &a_uri, &s_uri); assert_equals_int (a_uri == NULL, TRUE); assert_equals_int (v_uri != NULL, TRUE); @@ -1161,7 +1171,7 @@ GST_START_TEST (test_select_alternate) assert_equals_string (v_uri, "http://localhost/low/video-audio.m3u8"); assert_equals_int (s_uri == NULL, TRUE); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -1173,7 +1183,7 @@ GST_START_TEST (test_simulation) GstFragment *a_frag, *v_frag, *s_frag; gboolean ret; - client = load_playlist (ALTERNATE_AUDIO_PLAYLIST); + master = load_playlist (ALTERNATE_AUDIO_PLAYLIST); /* The default selection should be audio-only, which only has audio and not * video */ gst_m3u8_client_get_current_uri (client, &v_uri, &a_uri, &s_uri); @@ -1184,7 +1194,7 @@ GST_START_TEST (test_simulation) assert_equals_int (s_uri == NULL, TRUE); /* Update the playlists */ - ret = gst_m3u8_client_update (client, + ret = gst_m3u8_update (client, g_strdup (ON_DEMAND_LOW_VIDEO_ONLY_PLAYLIST), g_strdup (ON_DEMAND_ENGLISH_PLAYLIST), NULL); assert_equals_int (ret, TRUE); @@ -1229,7 +1239,7 @@ GST_START_TEST (test_simulation) assert_equals_int (s_uri == NULL, TRUE); /* Update the new uri's */ ret = - gst_m3u8_client_update (client, + gst_m3u8_update (client, g_strdup (ON_DEMAND_LOW_VIDEO_ONLY_PLAYLIST), g_strdup (ON_DEMAND_GERMAN_PLAYLIST), NULL); assert_equals_int (ret, TRUE); @@ -1254,7 +1264,7 @@ GST_START_TEST (test_simulation) assert_equals_string (v_uri, "http://localhost/mid/video-only.m3u8"); assert_equals_int (s_uri == NULL, TRUE); ret = - gst_m3u8_client_update (client, + gst_m3u8_update (client, g_strdup (ON_DEMAND_MID_VIDEO_ONLY_PLAYLIST), g_strdup (ON_DEMAND_GERMAN_PLAYLIST), NULL); assert_equals_int (ret, TRUE); @@ -1327,7 +1337,7 @@ GST_START_TEST (test_simulation) ret = gst_m3u8_client_get_next_fragment (client, &v_frag, &a_frag, &s_frag); assert_equals_int (ret, FALSE); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -1339,16 +1349,19 @@ GST_START_TEST (test_url_with_slash_query_param) "#EXT-X-VERSION:4\n" "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f, mp4a.40.2\", RESOLUTION=640x352\n" "1251/media.m3u8?acl=/*1054559_h264_1500k.mp4\n"; - GstM3U8Client *client; + GstHLSMasterPlaylist *master; + GstHLSVariantStream *stream; GstM3U8 *media; - client = load_playlist (MASTER_PLAYLIST); + master = load_playlist (MASTER_PLAYLIST); + + assert_equals_int (g_list_length (master->variants), 1); + stream = g_list_nth_data (master->variants, 0); + media = stream->m3u8; - assert_equals_int (g_list_length (client->main->lists), 1); - media = g_list_nth_data (client->main->lists, 0); assert_equals_string (media->uri, "http://localhost/1251/media.m3u8?acl=/*1054559_h264_1500k.mp4"); - gst_m3u8_client_free (client); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -1359,19 +1372,20 @@ GST_START_TEST (test_stream_inf_tag) "#EXT-X-VERSION:4\n" "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f, mp4a.40.2\", RESOLUTION=640x352\n" "media.m3u8\n"; - GstM3U8Client *client; - GstM3U8 *media; + GstHLSMasterPlaylist *master; + GstHLSVariantStream *stream; - client = load_playlist (MASTER_PLAYLIST); + master = load_playlist (MASTER_PLAYLIST); - assert_equals_int (g_list_length (client->main->lists), 1); - media = g_list_nth_data (client->main->lists, 0); - assert_equals_int64 (media->program_id, 1); - assert_equals_int64 (media->width, 640); - assert_equals_int64 (media->height, 352); - assert_equals_int64 (media->bandwidth, 1251135); - assert_equals_string (media->codecs, "avc1.42001f, mp4a.40.2"); - gst_m3u8_client_free (client); + assert_equals_int (g_list_length (master->variants), 1); + stream = g_list_nth_data (master->variants, 0); + + assert_equals_int64 (stream->program_id, 1); + assert_equals_int64 (stream->width, 640); + assert_equals_int64 (stream->height, 352); + assert_equals_int64 (stream->bandwidth, 1251135); + assert_equals_string (stream->codecs, "avc1.42001f, mp4a.40.2"); + gst_hls_master_playlist_unref (master); } GST_END_TEST; @@ -1398,6 +1412,8 @@ hlsdemux_suite (void) tcase_add_test (tc_m3u8, test_empty_lines_playlist); tcase_add_test (tc_m3u8, test_live_playlist); tcase_add_test (tc_m3u8, test_live_playlist_rotated); + tcase_add_test (tc_m3u8, test_playlist_with_doubles_duration); + tcase_add_test (tc_m3u8, test_playlist_with_encryption); tcase_add_test (tc_m3u8, test_update_invalid_playlist); tcase_add_test (tc_m3u8, test_update_playlist); tcase_add_test (tc_m3u8, test_playlist_media_files); @@ -1414,8 +1430,6 @@ hlsdemux_suite (void) tcase_add_test (tc_m3u8, test_select_subs_alternate); tcase_add_test (tc_m3u8, test_simulation); #endif - tcase_add_test (tc_m3u8, test_playlist_with_doubles_duration); - tcase_add_test (tc_m3u8, test_playlist_with_encryption); tcase_add_test (tc_m3u8, test_url_with_slash_query_param); tcase_add_test (tc_m3u8, test_stream_inf_tag); return s;