diff --git a/ext/hls/gsthlsdemux.c b/ext/hls/gsthlsdemux.c index 3476e271b9..5ad3646c60 100644 --- a/ext/hls/gsthlsdemux.c +++ b/ext/hls/gsthlsdemux.c @@ -1490,15 +1490,65 @@ gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update, GstFragment *download; GstBuffer *buf; gchar *playlist; - gboolean updated = FALSE; - const gchar *uri = gst_m3u8_client_get_current_uri (demux->client); + gboolean main_checked = FALSE, updated = FALSE; + const gchar *uri; +retry: + uri = gst_m3u8_client_get_current_uri (demux->client); download = gst_uri_downloader_fetch_uri (demux->downloader, uri, demux->client->main ? demux->client->main->uri : NULL, TRUE, TRUE, TRUE, err); - if (download == NULL) - return FALSE; + if (download == NULL) { + 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 */ 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); last_sequence = - GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current->files)-> - data)->sequence; + GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current-> + files)->data)->sequence; if (demux->client->sequence >= last_sequence - 3) { 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); /* 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->client->main->iframe_lists)-> - data)->bandwidth) + GST_M3U8 (g_list_first (demux->client->main->iframe_lists)->data)-> + bandwidth) return FALSE; else if (!GST_M3U8 (previous_variant->data)->iframe && new_bandwidth == GST_M3U8 (g_list_first (demux->client->main->lists)->data)->bandwidth) diff --git a/ext/hls/m3u8.c b/ext/hls/m3u8.c index d66da2d47d..40ce09027d 100644 --- a/ext/hls/m3u8.c +++ b/ext/hls/m3u8.c @@ -50,7 +50,7 @@ gst_m3u8_new (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); @@ -59,6 +59,9 @@ gst_m3u8_set_uri (GstM3U8 * self, gchar * uri, gchar * base_uri) g_free (self->base_uri); self->base_uri = base_uri; + + g_free (self->name); + self->name = name; } static void @@ -68,6 +71,7 @@ gst_m3u8_free (GstM3U8 * self) g_free (self->uri); g_free (self->base_uri); + g_free (self->name); g_free (self->codecs); g_free (self->key); @@ -109,6 +113,85 @@ gst_m3u8_media_file_free (GstM3U8MediaFile * 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 int_from_string (gchar * ptr, gchar ** endptr, gint * val) { @@ -320,6 +403,7 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated) *r = '\0'; if (data[0] != '#' && data[0] != '\0') { + gchar *name = data; if (duration <= 0 && list == NULL) { GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data); goto next_line; @@ -336,7 +420,7 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated) gst_m3u8_free (list); g_free (data); } 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); } list = NULL; @@ -397,6 +481,7 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated) GstM3U8 *new_list; new_list = gst_m3u8_new (); + new_list->parent = self; new_list->iframe = iframe; data = data + (iframe ? 26 : 18); 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"); } } else if (iframe && g_str_equal (a, "URI")) { + gchar *name; gchar *uri = g_strdup (v); gchar *urip = uri; int len = strlen (uri); @@ -430,12 +516,15 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated) if (uri[0] == '"') uri += 1; + name = g_strdup (uri); uri = uri_join (self->base_uri ? self->base_uri : self->uri, uri); g_free (urip); - if (uri == NULL) + if (uri == NULL) { + g_free (name); 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->update_failed_count = 0; 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; } @@ -695,6 +784,92 @@ out: 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 _find_current (GstM3U8MediaFile * file, GstM3U8Client * client) { diff --git a/ext/hls/m3u8.h b/ext/hls/m3u8.h index d49287a062..9ae6c079de 100644 --- a/ext/hls/m3u8.h +++ b/ext/hls/m3u8.h @@ -40,6 +40,8 @@ struct _GstM3U8 gchar *uri; /* actually downloaded URI */ gchar *base_uri; /* URI to use as base for resolving relative URIs. * 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 */ gint version; /* last EXT-X-VERSION */ @@ -90,6 +92,7 @@ struct _GstM3U8Client GstM3U8Client *gst_m3u8_client_new (const gchar * uri, const gchar * base_uri); void gst_m3u8_client_free (GstM3U8Client * client); 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); gboolean gst_m3u8_client_get_next_fragment (GstM3U8Client * client, gboolean * discontinuity, const gchar ** uri, GstClockTime * duration,