hls: add hlssink element
authorAlessandro Decina <alessandro.d@gmail.com>
Sun, 21 Aug 2011 09:00:51 +0000 (11:00 +0200)
committerRobert Swain <robert.swain@collabora.co.uk>
Fri, 7 Sep 2012 14:25:36 +0000 (16:25 +0200)
configure.ac
gst/hls/Makefile.am
gst/hls/gstfragmentedplugin.c
gst/hls/gsthlssink.c [new file with mode: 0644]
gst/hls/gsthlssink.h [new file with mode: 0644]
gst/hls/gstm3u8playlist.c [new file with mode: 0644]
gst/hls/gstm3u8playlist.h [new file with mode: 0644]

index 47b93d9..626cf48 100644 (file)
@@ -314,7 +314,7 @@ GST_PLUGINS_NONPORTED=" aiff \
  cdxaparse \
  dccp faceoverlay \
  fieldanalysis freeverb freeze frei0r \
- hdvparse inter ivfparse jpegformat jp2kdecimator \
+ hdvparse hlssink inter ivfparse jpegformat jp2kdecimator \
  kate liveadder librfb \
  mpegpsmux mve mxf mythtv nsf nuvdemux \
  patchdetect pnm real \
index 05f0ac8..3d00b24 100644 (file)
@@ -6,20 +6,24 @@ libgstfragmented_la_SOURCES =                 \
        gsthlsdemux.c                           \
        gstfragment.c                           \
        gsturidownloader.c                      \
+       gstm3u8playlist.c                       \
+       gsthlssink.c                            \
        gstfragmentedplugin.c
 
-libgstfragmented_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(SOUP_CFLAGS)
-libgstfragmented_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(SOUP_LIBS)
+libgstfragmented_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(SOUP_CFLAGS) $(GIO_CFLAGS)
+libgstfragmented_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(SOUP_LIBS) $(GIO_LIBS) -lgstpbutils-$(GST_MAJORMINOR) -lgstvideo-$(GST_MAJORMINOR)
 libgstfragmented_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) -no-undefined
 libgstfragmented_la_LIBTOOLFLAGS = --tag=disable-static
 
 # headers we need but don't want installed
 noinst_HEADERS =                       \
-       gstfragmented.h         \
-       gstfragment.h                           \
+       gstfragmented.h                 \
+       gstfragment.h                   \
        gsthlsdemux.h                   \
-       gsturidownloader.h                      \
-       m3u8.h
+       gsturidownloader.h              \
+       m3u8.h                          \
+       gstm3u8playlist.h               \
+       gsthlssink.h
 
 Android.mk: Makefile.am $(BUILT_SOURCES)
        androgenizer \
index a067a9e..89a7741 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "gstfragmented.h"
 #include "gsthlsdemux.h"
+#include "gsthlssink.h"
 
 GST_DEBUG_CATEGORY (fragmented_debug);
 
@@ -17,6 +18,10 @@ fragmented_init (GstPlugin * plugin)
   if (!gst_element_register (plugin, "hlsdemux", GST_RANK_PRIMARY,
           GST_TYPE_HLS_DEMUX) || FALSE)
     return FALSE;
+
+  if (!gst_hls_sink_plugin_init (plugin))
+    return FALSE;
+
   return TRUE;
 }
 
diff --git a/gst/hls/gsthlssink.c b/gst/hls/gsthlssink.c
new file mode 100644 (file)
index 0000000..8a67f40
--- /dev/null
@@ -0,0 +1,378 @@
+/* GStreamer
+ * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gsthlssink.h"
+#include <gst/pbutils/pbutils.h>
+#include <gst/video/video.h>
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+#include <memory.h>
+
+
+GST_DEBUG_CATEGORY_STATIC (gst_hls_sink_debug);
+#define GST_CAT_DEFAULT gst_hls_sink_debug
+
+#define DEFAULT_LOCATION "segment%05d.ts"
+#define DEFAULT_PLAYLIST_LOCATION "playlist.m3u8"
+#define DEFAULT_PLAYLIST_ROOT NULL
+#define DEFAULT_MAX_FILES 10
+
+enum
+{
+  PROP_0,
+  PROP_LOCATION,
+  PROP_PLAYLIST_LOCATION,
+  PROP_PLAYLIST_ROOT,
+  PROP_MAX_FILES
+};
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+GST_BOILERPLATE (GstHlsSink, gst_hls_sink, GstBin, GST_TYPE_BIN);
+
+static void gst_hls_sink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * spec);
+static void gst_hls_sink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * spec);
+static void gst_hls_sink_handle_message (GstBin * bin, GstMessage * message);
+static gboolean gst_hls_sink_ghost_event_probe (GstPad * pad,
+    GstEvent * event, gpointer data);
+
+static GstStateChangeReturn
+gst_hls_sink_change_state (GstElement * element, GstStateChange trans);
+
+static void
+gst_hls_sink_dispose (GObject * object)
+{
+  GstHlsSink *sink = GST_HLS_SINK_CAST (object);
+
+  if (sink->multifilesink)
+    g_object_unref (sink->multifilesink);
+
+  G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink);
+}
+
+static void
+gst_hls_sink_finalize (GObject * object)
+{
+  GstHlsSink *sink = GST_HLS_SINK_CAST (object);
+
+  gst_event_replace (&sink->force_key_unit_event, NULL);
+  g_free (sink->location);
+  g_free (sink->playlist_location);
+  g_free (sink->playlist_root);
+  gst_m3u8_playlist_free (sink->playlist);
+
+  G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
+}
+
+static void
+gst_hls_sink_base_init (gpointer g_class)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&sink_template));
+
+  gst_element_class_set_details_simple (element_class,
+      "HTTP Live Streaming sink", "Sink", "HTTP Live Streaming sink",
+      "Alessandro Decina <alessandro.decina@gmail.com>");
+}
+
+static void
+gst_hls_sink_class_init (GstHlsSinkClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *element_class;
+  GstBinClass *bin_class;
+
+  gobject_class = (GObjectClass *) klass;
+  element_class = GST_ELEMENT_CLASS (klass);
+  bin_class = GST_BIN_CLASS (klass);
+
+  bin_class->handle_message = gst_hls_sink_handle_message;
+
+  element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_sink_change_state);
+
+  gobject_class->dispose = gst_hls_sink_dispose;
+  gobject_class->finalize = gst_hls_sink_finalize;
+  gobject_class->set_property = gst_hls_sink_set_property;
+  gobject_class->get_property = gst_hls_sink_get_property;
+
+  g_object_class_install_property (gobject_class, PROP_LOCATION,
+      g_param_spec_string ("location", "File Location",
+          "Location of the file to write", DEFAULT_LOCATION,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_PLAYLIST_LOCATION,
+      g_param_spec_string ("playlist-location", "Playlist Location",
+          "Location of the playlist to write", DEFAULT_PLAYLIST_LOCATION,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_PLAYLIST_ROOT,
+      g_param_spec_string ("playlist-root", "Playlist Root",
+          "Location of the playlist to write", DEFAULT_PLAYLIST_ROOT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_MAX_FILES,
+      g_param_spec_uint ("max-files", "Max files",
+          "Maximum number of files to keep on disk. Once the maximum is reached,"
+          "old files start to be deleted to make room for new ones.",
+          0, G_MAXUINT, DEFAULT_MAX_FILES,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gst_hls_sink_init (GstHlsSink * sink, GstHlsSinkClass * sink_class)
+{
+  GstPadTemplate *templ = gst_static_pad_template_get (&sink_template);
+  sink->ghostpad = gst_ghost_pad_new_no_target_from_template ("sink", templ);
+  gst_object_unref (templ);
+  gst_element_add_pad (GST_ELEMENT_CAST (sink), sink->ghostpad);
+  gst_pad_add_event_probe (sink->ghostpad,
+      G_CALLBACK (gst_hls_sink_ghost_event_probe), sink);
+
+  sink->index = 0;
+  sink->multifilesink = NULL;
+  sink->last_stream_time = 0;
+  sink->location = g_strdup (DEFAULT_LOCATION);
+  sink->playlist_location = g_strdup (DEFAULT_PLAYLIST_LOCATION);
+  sink->playlist_root = g_strdup (DEFAULT_PLAYLIST_ROOT);
+  sink->playlist = gst_m3u8_playlist_new (6, 5, FALSE);
+  sink->max_files = DEFAULT_MAX_FILES;
+}
+
+static gboolean
+gst_hls_sink_create_elements (GstHlsSink * sink)
+{
+  GstPad *pad = NULL;
+
+  GST_DEBUG_OBJECT (sink, "Creating internal elements");
+
+  if (sink->elements_created)
+    return TRUE;
+
+  sink->multifilesink = gst_element_factory_make ("multifilesink", NULL);
+  if (sink->multifilesink == NULL)
+    goto missing_element;
+
+  g_object_set (sink->multifilesink, "location", sink->location,
+      "next-file", 3, "post-messages", TRUE, "max-files", sink->max_files,
+      NULL);
+
+  gst_bin_add (GST_BIN_CAST (sink), sink->multifilesink);
+
+  pad = gst_element_get_static_pad (sink->multifilesink, "sink");
+  gst_ghost_pad_set_target (GST_GHOST_PAD (sink->ghostpad), pad);
+  gst_object_unref (pad);
+
+  sink->elements_created = TRUE;
+  return TRUE;
+
+missing_element:
+  gst_element_post_message (GST_ELEMENT_CAST (sink),
+      gst_missing_element_message_new (GST_ELEMENT_CAST (sink),
+          "multifilesink"));
+  GST_ELEMENT_ERROR (sink, CORE, MISSING_PLUGIN,
+      (("Missing element '%s' - check your GStreamer installation."),
+          "multifilesink"), (NULL));
+  return FALSE;
+}
+
+static void
+gst_hls_sink_handle_message (GstBin * bin, GstMessage * message)
+{
+  GstHlsSink *sink = GST_HLS_SINK_CAST (bin);
+
+  switch (message->type) {
+    case GST_MESSAGE_ELEMENT:
+    {
+      GFile *file;
+      const char *filename, *title;
+      char *playlist_content;
+      GstClockTime stream_time, duration;
+      gboolean discont = FALSE;
+      GError *error = NULL;
+      gchar *entry_location;
+
+      if (strcmp (gst_structure_get_name (message->structure),
+              "GstMultiFileSink"))
+        break;
+
+      filename = gst_structure_get_string (message->structure, "filename");
+      gst_structure_get_clock_time (message->structure, "stream-time",
+          &stream_time);
+      duration = stream_time - sink->last_stream_time;
+      sink->last_stream_time = stream_time;
+      file = g_file_new_for_path (filename);
+      title = "ciao";
+      GST_INFO_OBJECT (sink, "COUNT %d", sink->index);
+      if (sink->playlist_root == NULL)
+        entry_location = g_strdup (filename);
+      else {
+        gchar *name = g_path_get_basename (filename);
+        entry_location = g_build_filename (sink->playlist_root, name, NULL);
+        g_free (name);
+      }
+      gst_m3u8_playlist_add_entry (sink->playlist, entry_location, file,
+          title, duration, sink->index, discont);
+      g_free (entry_location);
+      playlist_content = gst_m3u8_playlist_render (sink->playlist);
+      g_file_set_contents (sink->playlist_location,
+          playlist_content, -1, &error);
+      g_free (playlist_content);
+      break;
+    }
+    default:
+      break;
+  }
+
+  GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+}
+
+static GstStateChangeReturn
+gst_hls_sink_change_state (GstElement * element, GstStateChange trans)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+  GstHlsSink *sink = GST_HLS_SINK_CAST (element);
+
+  switch (trans) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      if (!gst_hls_sink_create_elements (sink)) {
+        return GST_STATE_CHANGE_FAILURE;
+      }
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
+
+  switch (trans) {
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static void
+gst_hls_sink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstHlsSink *sink = GST_HLS_SINK_CAST (object);
+
+  switch (prop_id) {
+    case PROP_LOCATION:
+      g_free (sink->location);
+      sink->location = g_value_dup_string (value);
+      if (sink->multifilesink)
+        g_object_set (sink->multifilesink, "location", sink->location, NULL);
+      break;
+    case PROP_PLAYLIST_LOCATION:
+      g_free (sink->playlist_location);
+      sink->playlist_location = g_value_dup_string (value);
+      break;
+    case PROP_PLAYLIST_ROOT:
+      g_free (sink->playlist_root);
+      sink->playlist_root = g_value_dup_string (value);
+      break;
+    case PROP_MAX_FILES:
+      sink->max_files = g_value_get_uint (value);
+      if (sink->multifilesink) {
+        g_object_set (sink->multifilesink, "location", sink->location,
+            "next-file", 3, "post-messages", TRUE, "max-files", sink->max_files,
+            NULL);
+      }
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_hls_sink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstHlsSink *sink = GST_HLS_SINK_CAST (object);
+
+  switch (prop_id) {
+    case PROP_LOCATION:
+      g_value_set_string (value, sink->location);
+      break;
+    case PROP_PLAYLIST_LOCATION:
+      g_value_set_string (value, sink->playlist_location);
+      break;
+    case PROP_PLAYLIST_ROOT:
+      g_value_set_string (value, sink->playlist_root);
+      break;
+    case PROP_MAX_FILES:
+      g_value_set_uint (value, sink->max_files);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static gboolean
+gst_hls_sink_ghost_event_probe (GstPad * pad, GstEvent * event, gpointer data)
+{
+  GstHlsSink *sink = GST_HLS_SINK_CAST (data);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_CUSTOM_DOWNSTREAM:
+    {
+      GstClockTime timestamp;
+      GstClockTime running_time, stream_time;
+      gboolean all_headers;
+      guint count;
+
+      if (!gst_video_event_is_force_key_unit (event))
+        break;
+
+      gst_event_replace (&sink->force_key_unit_event, event);
+      gst_video_event_parse_downstream_force_key_unit (event,
+          &timestamp, &stream_time, &running_time, &all_headers, &count);
+      GST_INFO_OBJECT (sink, "setting index %d", count);
+      sink->index = count;
+      break;
+    }
+    default:
+      break;
+  }
+
+  return TRUE;
+}
+
+
+gboolean
+gst_hls_sink_plugin_init (GstPlugin * plugin)
+{
+  GST_DEBUG_CATEGORY_INIT (gst_hls_sink_debug, "hlssink", 0, "HlsSink");
+  return gst_element_register (plugin, "hlssink", GST_RANK_NONE,
+      gst_hls_sink_get_type ());
+}
diff --git a/gst/hls/gsthlssink.h b/gst/hls/gsthlssink.h
new file mode 100644 (file)
index 0000000..44c60da
--- /dev/null
@@ -0,0 +1,65 @@
+/* GStreamer
+ * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef _GST_HLS_SINK_H_
+#define _GST_HLS_SINK_H_
+
+#include "gstm3u8playlist.h"
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_HLS_SINK   (gst_hls_sink_get_type())
+#define GST_HLS_SINK(obj)   (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_HLS_SINK,GstHlsSink))
+#define GST_HLS_SINK_CAST(obj)   ((GstHlsSink *) obj)
+#define GST_HLS_SINK_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_HLS_SINK,GstHlsSinkClass))
+#define GST_IS_HLS_SINK(obj)   (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_HLS_SINK))
+#define GST_IS_HLS_SINK_CLASS(obj)   (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_HLS_SINK))
+
+typedef struct _GstHlsSink GstHlsSink;
+typedef struct _GstHlsSinkClass GstHlsSinkClass;
+
+struct _GstHlsSink
+{
+  GstBin bin;
+
+  GstPad *ghostpad;
+  GstElement *multifilesink;
+  gboolean elements_created;
+  GstEvent *force_key_unit_event;
+
+  GstClockTime last_stream_time;
+  gchar *location;
+  gchar *playlist_location;
+  gchar *playlist_root;
+  GstM3U8Playlist *playlist;
+  guint index;
+  gint max_files;
+};
+
+struct _GstHlsSinkClass
+{
+  GstBinClass bin_class;
+};
+
+GType gst_hls_sink_get_type (void);
+gboolean gst_hls_sink_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+
+#endif
diff --git a/gst/hls/gstm3u8playlist.c b/gst/hls/gstm3u8playlist.c
new file mode 100644 (file)
index 0000000..531c148
--- /dev/null
@@ -0,0 +1,228 @@
+/* GStreamer
+ * Copyright (C) 2011 Andoni Morales Alastruey <ylatuya@gmail.com>
+ *
+ * gstm3u8playlist.c:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib.h>
+
+#include "gstfragmented.h"
+#include "gstm3u8playlist.h"
+
+#define GST_CAT_DEFAULT fragmented_debug
+
+#define M3U8_HEADER_TAG "#EXTM3U\n"
+#define M3U8_VERSION_TAG "#EXT-X-VERSION:%d\n"
+#define M3U8_TARGETDURATION_TAG "#EXT-X-TARGETDURATION:%d\n"
+#define M3U8_MEDIA_SEQUENCE_TAG "#EXT-X-MEDIA-SEQUENCE:%d\n"
+#define M3U8_DISCONTINUITY_TAG "#EXT-X-DISCONTINUITY\n"
+#define M3U8_INT_INF_TAG "#EXTINF:%d,%s\n%s\n"
+#define M3U8_FLOAT_INF_TAG "#EXTINF:%.2f,%s\n%s\n"
+#define M3U8_ENDLIST_TAG "#EXT-X-ENDLIST"
+
+enum
+{
+  GST_M3U8_PLAYLIST_TYPE_EVENT,
+  GST_M3U8_PLAYLIST_TYPE_VOD,
+};
+
+static GstM3U8Entry *
+gst_m3u8_entry_new (const gchar * url, GFile * file, const gchar * title,
+    gfloat duration, gboolean discontinuous)
+{
+  GstM3U8Entry *entry;
+
+  g_return_val_if_fail (url != NULL, NULL);
+  g_return_val_if_fail (title != NULL, NULL);
+
+  entry = g_new0 (GstM3U8Entry, 1);
+  entry->url = g_strdup (url);
+  entry->title = g_strdup (title);
+  entry->duration = duration;
+  entry->file = file;
+  entry->discontinuous = discontinuous;
+  return entry;
+}
+
+static void
+gst_m3u8_entry_free (GstM3U8Entry * entry)
+{
+  g_return_if_fail (entry != NULL);
+
+  g_free (entry->url);
+  g_free (entry->title);
+  if (entry->file != NULL)
+    g_object_unref (entry->file);
+  g_free (entry);
+}
+
+static gchar *
+gst_m3u8_entry_render (GstM3U8Entry * entry, guint version)
+{
+  g_return_val_if_fail (entry != NULL, NULL);
+
+  /* FIXME: Ensure the radix is always a '.' and not a ',' when printing
+   * floating point number, but for now only use integers*/
+  /* if (version < 3) */
+  if (TRUE)
+    return g_strdup_printf ("%s" M3U8_INT_INF_TAG,
+        entry->discontinuous ? M3U8_DISCONTINUITY_TAG : "",
+        (gint) (entry->duration / GST_SECOND), entry->title, entry->url);
+
+  return g_strdup_printf ("%s" M3U8_FLOAT_INF_TAG,
+      entry->discontinuous ? M3U8_DISCONTINUITY_TAG : "",
+      (entry->duration / GST_SECOND), entry->title, entry->url);
+}
+
+GstM3U8Playlist *
+gst_m3u8_playlist_new (guint version, guint window_size, gboolean allow_cache)
+{
+  GstM3U8Playlist *playlist;
+
+  playlist = g_new0 (GstM3U8Playlist, 1);
+  playlist->version = version;
+  playlist->window_size = window_size;
+  playlist->allow_cache = allow_cache;
+  playlist->type = GST_M3U8_PLAYLIST_TYPE_EVENT;
+  playlist->end_list = FALSE;
+  playlist->entries = g_queue_new ();
+
+  return playlist;
+}
+
+void
+gst_m3u8_playlist_free (GstM3U8Playlist * playlist)
+{
+  g_return_if_fail (playlist != NULL);
+
+  g_queue_foreach (playlist->entries, (GFunc) gst_m3u8_entry_free, NULL);
+  g_queue_free (playlist->entries);
+  g_free (playlist);
+}
+
+
+GList *
+gst_m3u8_playlist_add_entry (GstM3U8Playlist * playlist,
+    const gchar * url, GFile * file, const gchar * title,
+    gfloat duration, guint index, gboolean discontinuous)
+{
+  GstM3U8Entry *entry;
+  GList *old_files = NULL;
+
+  g_return_val_if_fail (playlist != NULL, FALSE);
+  g_return_val_if_fail (url != NULL, FALSE);
+  g_return_val_if_fail (title != NULL, FALSE);
+
+  if (playlist->type == GST_M3U8_PLAYLIST_TYPE_VOD)
+    return FALSE;
+
+  entry = gst_m3u8_entry_new (url, file, title, duration, discontinuous);
+
+  if (playlist->window_size != -1) {
+    /* Delete old entries from the playlist */
+    while (playlist->entries->length >= playlist->window_size) {
+      GstM3U8Entry *old_entry;
+
+      old_entry = g_queue_pop_head (playlist->entries);
+      g_object_ref (old_entry->file);
+      old_files = g_list_prepend (old_files, old_entry->file);
+      gst_m3u8_entry_free (old_entry);
+    }
+  }
+
+  playlist->sequence_number = index + 1;
+  g_queue_push_tail (playlist->entries, entry);
+
+  return old_files;
+}
+
+static guint
+gst_m3u8_playlist_target_duration (GstM3U8Playlist * playlist)
+{
+  gint i;
+  GstM3U8Entry *entry;
+  guint64 target_duration = 0;
+
+  for (i = 0; i < playlist->entries->length; i++) {
+    entry = (GstM3U8Entry *) g_queue_peek_nth (playlist->entries, i);
+    if (entry->duration > target_duration)
+      target_duration = entry->duration;
+  }
+
+  return (guint) (target_duration / GST_SECOND);
+}
+
+static void
+render_entry (GstM3U8Entry * entry, GstM3U8Playlist * playlist)
+{
+  gchar *entry_str;
+
+  entry_str = gst_m3u8_entry_render (entry, playlist->version);
+  g_string_append_printf (playlist->playlist_str, "%s", entry_str);
+  g_free (entry_str);
+}
+
+gchar *
+gst_m3u8_playlist_render (GstM3U8Playlist * playlist)
+{
+  gchar *pl;
+
+  g_return_val_if_fail (playlist != NULL, NULL);
+
+  playlist->playlist_str = g_string_new ("");
+
+  /* #EXTM3U */
+  g_string_append_printf (playlist->playlist_str, M3U8_HEADER_TAG);
+  /* #EXT-X-VERSION */
+//  g_string_append_printf (playlist->playlist_str, M3U8_VERSION_TAG,
+//      playlist->version);
+  /* #EXT-X-MEDIA-SEQUENCE */
+  g_string_append_printf (playlist->playlist_str, M3U8_MEDIA_SEQUENCE_TAG,
+      playlist->sequence_number - playlist->entries->length);
+  /* #EXT-X-TARGETDURATION */
+  g_string_append_printf (playlist->playlist_str, M3U8_TARGETDURATION_TAG,
+      gst_m3u8_playlist_target_duration (playlist));
+  g_string_append_printf (playlist->playlist_str, "\n");
+
+  /* Entries */
+  g_queue_foreach (playlist->entries, (GFunc) render_entry, playlist);
+
+  if (playlist->end_list)
+    g_string_append_printf (playlist->playlist_str, M3U8_ENDLIST_TAG);
+
+  pl = playlist->playlist_str->str;
+  g_string_free (playlist->playlist_str, FALSE);
+  return pl;
+}
+
+void
+gst_m3u8_playlist_clear (GstM3U8Playlist * playlist)
+{
+  g_return_if_fail (playlist != NULL);
+
+  g_queue_foreach (playlist->entries, (GFunc) gst_m3u8_entry_free, NULL);
+  g_queue_clear (playlist->entries);
+}
+
+guint
+gst_m3u8_playlist_n_entries (GstM3U8Playlist * playlist)
+{
+  g_return_val_if_fail (playlist != NULL, 0);
+
+  return playlist->entries->length;
+}
diff --git a/gst/hls/gstm3u8playlist.h b/gst/hls/gstm3u8playlist.h
new file mode 100644 (file)
index 0000000..f8227fe
--- /dev/null
@@ -0,0 +1,74 @@
+/* GStreamer
+ * Copyright (C) 2011 Andoni Morales Alastruey <ylatuya@gmail.com>
+ *
+ * gstm3u8playlist.h:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_M3U8_PLAYLIST_H__
+#define __GST_M3U8_PLAYLIST_H__
+
+#include <glib.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GstM3U8Playlist GstM3U8Playlist;
+typedef struct _GstM3U8Entry GstM3U8Entry;
+
+
+struct _GstM3U8Entry
+{
+  gfloat duration;
+  gchar *title;
+  gchar *url;
+  GFile *file;
+  gboolean discontinuous;
+};
+
+struct _GstM3U8Playlist
+{
+  guint version;
+  gboolean allow_cache;
+  gint window_size;
+  gint type;
+  gboolean end_list;
+  guint sequence_number;
+
+  /*< Private >*/
+  GQueue *entries;
+  GString *playlist_str;
+};
+
+
+GstM3U8Playlist * gst_m3u8_playlist_new (guint version, 
+                                        guint window_size,
+                                        gboolean allow_cache);
+void gst_m3u8_playlist_free (GstM3U8Playlist * playlist);
+GList * gst_m3u8_playlist_add_entry (GstM3U8Playlist * playlist,
+                                    const gchar * url,
+                                    GFile * file,
+                                    const gchar *title,
+                                    gfloat duration,
+                                    guint index,
+                                    gboolean discontinuous);
+gchar * gst_m3u8_playlist_render (GstM3U8Playlist * playlist); 
+void gst_m3u8_playlist_clear (GstM3U8Playlist * playlist); 
+guint gst_m3u8_playlist_n_entries (GstM3U8Playlist * playlist); 
+
+G_END_DECLS
+#endif /* __M3U8_H__ */