hlsdemux: Reload the variant playlist if refreshing a playlist or downloading a fragment fails

This can happen if the playlists have moved due to the variant playlist
now being redirected to another target. This currently only works as long
as the referenced playlists don't change in relation to the variant
playlist, and the new location is purely due to a new path triggered by a
new redirection target of the variant playlist, or a new redirection
target of the playlist itself.

https://bugzilla.gnome.org/show_bug.cgi?id=731164
This commit is contained in:
Thomas Bluemel 2014-05-30 16:34:18 -06:00 committed by Sebastian Dröge
parent e259557c5a
commit babd8969f2
3 changed files with 241 additions and 13 deletions

View file

@ -1490,15 +1490,65 @@ gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update,
GstFragment *download; GstFragment *download;
GstBuffer *buf; GstBuffer *buf;
gchar *playlist; gchar *playlist;
gboolean updated = FALSE; gboolean main_checked = FALSE, updated = FALSE;
const gchar *uri = gst_m3u8_client_get_current_uri (demux->client); const gchar *uri;
retry:
uri = gst_m3u8_client_get_current_uri (demux->client);
download = download =
gst_uri_downloader_fetch_uri (demux->downloader, uri, gst_uri_downloader_fetch_uri (demux->downloader, uri,
demux->client->main ? demux->client->main->uri : NULL, TRUE, TRUE, TRUE, demux->client->main ? demux->client->main->uri : NULL, TRUE, TRUE, TRUE,
err); err);
if (download == NULL) if (download == NULL) {
return FALSE; if (update && !main_checked
&& gst_m3u8_client_has_variant_playlist (demux->client)
&& demux->client->main) {
GError *err2 = NULL;
GST_INFO_OBJECT (demux,
"Updating playlist %s failed, attempt to refresh variant playlist %s",
uri, demux->client->main->uri);
download =
gst_uri_downloader_fetch_uri (demux->downloader,
demux->client->main->uri, NULL, TRUE, TRUE, TRUE, &err2);
g_clear_error (&err2);
if (download != NULL) {
gchar *base_uri;
buf = gst_fragment_get_buffer (download);
playlist = gst_hls_src_buf_to_utf8_playlist (buf);
if (playlist == NULL) {
GST_WARNING_OBJECT (demux,
"Failed to validate variant playlist encoding");
return FALSE;
}
if (download->redirect_permanent && download->redirect_uri) {
uri = download->redirect_uri;
base_uri = NULL;
} else {
uri = download->uri;
base_uri = download->redirect_uri;
}
if (!gst_m3u8_client_update_variant_playlist (demux->client, playlist,
uri, base_uri)) {
GST_WARNING_OBJECT (demux, "Failed to update the variant playlist");
return FALSE;
}
g_object_unref (download);
g_clear_error (err);
main_checked = TRUE;
goto retry;
} else {
return FALSE;
}
} else {
return FALSE;
}
}
/* Set the base URI of the playlist to the redirect target if any */ /* Set the base URI of the playlist to the redirect target if any */
GST_M3U8_CLIENT_LOCK (demux->client); GST_M3U8_CLIENT_LOCK (demux->client);
@ -1540,8 +1590,8 @@ gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update,
GST_M3U8_CLIENT_LOCK (demux->client); GST_M3U8_CLIENT_LOCK (demux->client);
last_sequence = last_sequence =
GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current->files)-> GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current->
data)->sequence; files)->data)->sequence;
if (demux->client->sequence >= last_sequence - 3) { if (demux->client->sequence >= last_sequence - 3) {
GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %u", GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %u",
@ -1642,8 +1692,8 @@ retry_failover_protection:
gst_m3u8_client_set_current (demux->client, previous_variant->data); gst_m3u8_client_set_current (demux->client, previous_variant->data);
/* Try a lower bitrate (or stop if we just tried the lowest) */ /* Try a lower bitrate (or stop if we just tried the lowest) */
if (GST_M3U8 (previous_variant->data)->iframe && new_bandwidth == if (GST_M3U8 (previous_variant->data)->iframe && new_bandwidth ==
GST_M3U8 (g_list_first (demux->client->main->iframe_lists)-> GST_M3U8 (g_list_first (demux->client->main->iframe_lists)->data)->
data)->bandwidth) bandwidth)
return FALSE; return FALSE;
else if (!GST_M3U8 (previous_variant->data)->iframe && new_bandwidth == else if (!GST_M3U8 (previous_variant->data)->iframe && new_bandwidth ==
GST_M3U8 (g_list_first (demux->client->main->lists)->data)->bandwidth) GST_M3U8 (g_list_first (demux->client->main->lists)->data)->bandwidth)

View file

@ -50,7 +50,7 @@ gst_m3u8_new (void)
} }
static void static void
gst_m3u8_set_uri (GstM3U8 * self, gchar * uri, gchar * base_uri) gst_m3u8_set_uri (GstM3U8 * self, gchar * uri, gchar * base_uri, gchar * name)
{ {
g_return_if_fail (self != NULL); g_return_if_fail (self != NULL);
@ -59,6 +59,9 @@ gst_m3u8_set_uri (GstM3U8 * self, gchar * uri, gchar * base_uri)
g_free (self->base_uri); g_free (self->base_uri);
self->base_uri = base_uri; self->base_uri = base_uri;
g_free (self->name);
self->name = name;
} }
static void static void
@ -68,6 +71,7 @@ gst_m3u8_free (GstM3U8 * self)
g_free (self->uri); g_free (self->uri);
g_free (self->base_uri); g_free (self->base_uri);
g_free (self->name);
g_free (self->codecs); g_free (self->codecs);
g_free (self->key); g_free (self->key);
@ -109,6 +113,85 @@ gst_m3u8_media_file_free (GstM3U8MediaFile * self)
g_free (self); g_free (self);
} }
static GstM3U8MediaFile *
gst_m3u8_media_file_copy (const GstM3U8MediaFile * self, gpointer user_data)
{
g_return_if_fail (self != NULL);
return gst_m3u8_media_file_new (g_strdup (self->uri), g_strdup (self->title),
self->duration, self->sequence);
}
static GstM3U8 *
_m3u8_copy (const GstM3U8 * self, GstM3U8 * parent)
{
GstM3U8 *dup;
g_return_if_fail (self != NULL);
dup = gst_m3u8_new ();
dup->uri = g_strdup (self->uri);
dup->base_uri = g_strdup (self->base_uri);
dup->name = g_strdup (self->name);
dup->endlist = self->endlist;
dup->version = self->version;
dup->targetduration = self->targetduration;
dup->allowcache = self->allowcache;
dup->key = g_strdup (self->key);
dup->bandwidth = self->bandwidth;
dup->program_id = self->program_id;
dup->codecs = g_strdup (self->codecs);
dup->width = self->width;
dup->height = self->height;
dup->iframe = self->iframe;
dup->files =
g_list_copy_deep (self->files, (GCopyFunc) gst_m3u8_media_file_copy,
NULL);
/* private */
dup->last_data = g_strdup (self->last_data);
dup->lists = g_list_copy_deep (self->lists, (GCopyFunc) _m3u8_copy, dup);
dup->iframe_lists =
g_list_copy_deep (self->iframe_lists, (GCopyFunc) _m3u8_copy, dup);
/* NOTE: current_variant will get set in gst_m3u8_copy () */
dup->parent = parent;
dup->mediasequence = self->mediasequence;
return dup;
}
static GstM3U8 *
gst_m3u8_copy (const GstM3U8 * self)
{
GList *entry;
guint n;
GstM3U8 *dup = _m3u8_copy (self, NULL);
if (self->current_variant != NULL) {
for (n = 0, entry = self->lists; entry; entry = entry->next, n++) {
if (entry == self->current_variant) {
dup->current_variant = g_list_nth (dup->lists, n);
break;
}
}
if (!dup->current_variant) {
for (n = 0, entry = self->iframe_lists; entry; entry = entry->next, n++) {
if (entry == self->current_variant) {
dup->current_variant = g_list_nth (dup->iframe_lists, n);
break;
}
}
if (!dup->current_variant) {
GST_ERROR ("Failed to determine current playlist");
}
}
}
return dup;
}
static gboolean static gboolean
int_from_string (gchar * ptr, gchar ** endptr, gint * val) int_from_string (gchar * ptr, gchar ** endptr, gint * val)
{ {
@ -320,6 +403,7 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
*r = '\0'; *r = '\0';
if (data[0] != '#' && data[0] != '\0') { if (data[0] != '#' && data[0] != '\0') {
gchar *name = data;
if (duration <= 0 && list == NULL) { if (duration <= 0 && list == NULL) {
GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data); GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data);
goto next_line; goto next_line;
@ -336,7 +420,7 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
gst_m3u8_free (list); gst_m3u8_free (list);
g_free (data); g_free (data);
} else { } else {
gst_m3u8_set_uri (list, data, NULL); gst_m3u8_set_uri (list, data, NULL, g_strdup (name));
self->lists = g_list_append (self->lists, list); self->lists = g_list_append (self->lists, list);
} }
list = NULL; list = NULL;
@ -397,6 +481,7 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
GstM3U8 *new_list; GstM3U8 *new_list;
new_list = gst_m3u8_new (); new_list = gst_m3u8_new ();
new_list->parent = self;
new_list->iframe = iframe; new_list->iframe = iframe;
data = data + (iframe ? 26 : 18); data = data + (iframe ? 26 : 18);
while (data && parse_attributes (&data, &a, &v)) { while (data && parse_attributes (&data, &a, &v)) {
@ -420,6 +505,7 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
GST_WARNING ("Error while reading RESOLUTION height"); GST_WARNING ("Error while reading RESOLUTION height");
} }
} else if (iframe && g_str_equal (a, "URI")) { } else if (iframe && g_str_equal (a, "URI")) {
gchar *name;
gchar *uri = g_strdup (v); gchar *uri = g_strdup (v);
gchar *urip = uri; gchar *urip = uri;
int len = strlen (uri); int len = strlen (uri);
@ -430,12 +516,15 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
if (uri[0] == '"') if (uri[0] == '"')
uri += 1; uri += 1;
name = g_strdup (uri);
uri = uri_join (self->base_uri ? self->base_uri : self->uri, uri); uri = uri_join (self->base_uri ? self->base_uri : self->uri, uri);
g_free (urip); g_free (urip);
if (uri == NULL) if (uri == NULL) {
g_free (name);
continue; continue;
gst_m3u8_set_uri (new_list, uri, NULL); }
gst_m3u8_set_uri (new_list, uri, NULL, name);
} }
} }
@ -620,7 +709,7 @@ gst_m3u8_client_new (const gchar * uri, const gchar * base_uri)
client->sequence_position = 0; client->sequence_position = 0;
client->update_failed_count = 0; client->update_failed_count = 0;
g_mutex_init (&client->lock); g_mutex_init (&client->lock);
gst_m3u8_set_uri (client->main, g_strdup (uri), g_strdup (base_uri)); gst_m3u8_set_uri (client->main, g_strdup (uri), g_strdup (base_uri), NULL);
return client; return client;
} }
@ -695,6 +784,92 @@ out:
return ret; return ret;
} }
static gint
_find_m3u8_list_match (const GstM3U8 * a, const GstM3U8 * b)
{
if (g_strcmp0 (a->name, b->name) == 0 &&
a->bandwidth == b->bandwidth &&
a->program_id == b->program_id &&
g_strcmp0 (a->codecs, b->codecs) == 0 &&
a->width == b->width &&
a->height == b->height && a->iframe == b->iframe) {
return 0;
}
return 1;
}
gboolean
gst_m3u8_client_update_variant_playlist (GstM3U8Client * self, gchar * data,
const gchar * uri, const gchar * base_uri)
{
gboolean ret = FALSE;
GList *list_entry, *unmatched_lists;
GstM3U8Client *new_client;
GstM3U8 *old;
g_return_val_if_fail (self != NULL, FALSE);
new_client = gst_m3u8_client_new (uri, base_uri);
if (gst_m3u8_client_update (new_client, data)) {
if (!new_client->main->lists) {
GST_ERROR
("Cannot update variant playlist: New playlist is not a variant playlist");
gst_m3u8_client_free (new_client);
return FALSE;
}
GST_M3U8_CLIENT_LOCK (self);
if (!self->main->lists) {
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 (self->main->lists);
for (list_entry = new_client->main->lists; list_entry;
list_entry = list_entry->next) {
GList *match = g_list_find_custom (unmatched_lists, list_entry->data,
(GCompareFunc) _find_m3u8_list_match);
if (match)
unmatched_lists = g_list_remove_link (unmatched_lists, match);
}
if (unmatched_lists != NULL) {
g_list_free (unmatched_lists);
/* We should attempt to handle the case where playlists are dropped/replaced,
* and possibly switch over to a comparable (not neccessarily identical)
* playlist.
*/
GST_FIXME
("Cannot update variant playlist, unable to match all playlists");
goto out;
}
/* Switch out the variant playlist */
old = self->main;
self->main = gst_m3u8_copy (new_client->main);
if (self->main->lists)
self->current = self->main->current_variant->data;
else
self->current = self->main;
gst_m3u8_free (old);
ret = TRUE;
out:
GST_M3U8_CLIENT_UNLOCK (self);
}
gst_m3u8_client_free (new_client);
return ret;
}
static gboolean static gboolean
_find_current (GstM3U8MediaFile * file, GstM3U8Client * client) _find_current (GstM3U8MediaFile * file, GstM3U8Client * client)
{ {

View file

@ -40,6 +40,8 @@ struct _GstM3U8
gchar *uri; /* actually downloaded URI */ gchar *uri; /* actually downloaded URI */
gchar *base_uri; /* URI to use as base for resolving relative URIs. gchar *base_uri; /* URI to use as base for resolving relative URIs.
* This will be different to uri in case of redirects */ * This will be different to uri in case of redirects */
gchar *name; /* This will be the "name" of the playlist, the original
* relative/absolute uri in a variant playlist */
gboolean endlist; /* if ENDLIST has been reached */ gboolean endlist; /* if ENDLIST has been reached */
gint version; /* last EXT-X-VERSION */ gint version; /* last EXT-X-VERSION */
@ -90,6 +92,7 @@ struct _GstM3U8Client
GstM3U8Client *gst_m3u8_client_new (const gchar * uri, const gchar * base_uri); GstM3U8Client *gst_m3u8_client_new (const gchar * uri, const gchar * base_uri);
void gst_m3u8_client_free (GstM3U8Client * client); void gst_m3u8_client_free (GstM3U8Client * client);
gboolean gst_m3u8_client_update (GstM3U8Client * client, gchar * data); gboolean gst_m3u8_client_update (GstM3U8Client * client, gchar * data);
gboolean gst_m3u8_client_update_variant_playlist (GstM3U8Client * client, gchar * data, const gchar * uri, const gchar * base_uri);
void gst_m3u8_client_set_current (GstM3U8Client * client, GstM3U8 * m3u8); void gst_m3u8_client_set_current (GstM3U8Client * client, GstM3U8 * m3u8);
gboolean gst_m3u8_client_get_next_fragment (GstM3U8Client * client, gboolean gst_m3u8_client_get_next_fragment (GstM3U8Client * client,
gboolean * discontinuity, const gchar ** uri, GstClockTime * duration, gboolean * discontinuity, const gchar ** uri, GstClockTime * duration,