mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-17 03:35:21 +00:00
hlsdemux: move variant list handling over to new master playlist code
Adapt hlsdemux for the m3u8 playlist changes.
This commit is contained in:
parent
f0fcf1d718
commit
4df6f1ee93
5 changed files with 1006 additions and 511 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
666
ext/hls/m3u8.c
666
ext/hls/m3u8.c
|
@ -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;
|
||||
}
|
||||
|
|
102
ext/hls/m3u8.h
102
ext/hls/m3u8.h
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue