hlsdemux: move variant list handling over to new master playlist code

Adapt hlsdemux for the m3u8 playlist changes.
This commit is contained in:
Tim-Philipp Müller 2015-12-05 11:12:33 +00:00 committed by Jan Schmidt
parent f0fcf1d718
commit 4df6f1ee93
5 changed files with 1006 additions and 511 deletions

View file

@ -5,6 +5,7 @@
* Author: Youness Alaoui <youness.alaoui@collabora.co.uk>, Collabora Ltd.
* Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, Collabora Ltd.
* Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
* Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
*
* 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;
}

View file

@ -1,6 +1,7 @@
/* GStreamer
* Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
* Copyright (C) 2010 Andoni Morales Alastruey <ylatuya@gmail.com>
* Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
*
* 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

View file

@ -1,5 +1,6 @@
/* GStreamer
* Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
* Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
*
* m3u8.c:
*
@ -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;
}

View file

@ -1,6 +1,7 @@
/* GStreamer
* Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
* Copyright (C) 2010 Andoni Morales Alastruey <ylatuya@gmail.com>
* Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
*
* 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

View file

@ -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,
&timestamp, &range_start, &range_end, NULL, NULL, TRUE);
mf = gst_m3u8_get_next_fragment (pl, TRUE, &timestamp, &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,
&timestamp, &range_start, &range_end, NULL, NULL, TRUE);
mf = gst_m3u8_get_next_fragment (pl, TRUE, &timestamp, &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,
&timestamp, &range_start, &range_end, NULL, NULL, TRUE);
mf = gst_m3u8_get_next_fragment (pl, TRUE, &timestamp, &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;