hlsdemux: Reload the variant playlist if refreshing a playlist or downloading a fragm...
authorThomas Bluemel <tbluemel@control4.com>
Fri, 30 May 2014 22:34:18 +0000 (16:34 -0600)
committerSebastian Dröge <sebastian@centricular.com>
Fri, 6 Jun 2014 10:02:47 +0000 (13:02 +0300)
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

ext/hls/gsthlsdemux.c
ext/hls/m3u8.c
ext/hls/m3u8.h

index 3476e27..5ad3646 100644 (file)
@@ -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)
index d66da2d..40ce090 100644 (file)
@@ -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)
 {
index d49287a..9ae6c07 100644 (file)
@@ -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,