From b1029d767e614db6ef773945951cd6b228d60cae Mon Sep 17 00:00:00 2001 From: Alessandro Decina Date: Sun, 21 Aug 2011 11:00:51 +0200 Subject: [PATCH] hls: add hlssink element --- configure.ac | 2 +- gst/hls/Makefile.am | 16 +- gst/hls/gstfragmentedplugin.c | 5 + gst/hls/gsthlssink.c | 378 ++++++++++++++++++++++++++++++++++ gst/hls/gsthlssink.h | 65 ++++++ gst/hls/gstm3u8playlist.c | 228 ++++++++++++++++++++ gst/hls/gstm3u8playlist.h | 74 +++++++ 7 files changed, 761 insertions(+), 7 deletions(-) create mode 100644 gst/hls/gsthlssink.c create mode 100644 gst/hls/gsthlssink.h create mode 100644 gst/hls/gstm3u8playlist.c create mode 100644 gst/hls/gstm3u8playlist.h diff --git a/configure.ac b/configure.ac index 47b93d99cc..626cf48e97 100644 --- a/configure.ac +++ b/configure.ac @@ -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 \ diff --git a/gst/hls/Makefile.am b/gst/hls/Makefile.am index 05f0ac89f1..3d00b244c1 100644 --- a/gst/hls/Makefile.am +++ b/gst/hls/Makefile.am @@ -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 \ diff --git a/gst/hls/gstfragmentedplugin.c b/gst/hls/gstfragmentedplugin.c index a067a9e505..89a77412ad 100644 --- a/gst/hls/gstfragmentedplugin.c +++ b/gst/hls/gstfragmentedplugin.c @@ -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 index 0000000000..8a67f40432 --- /dev/null +++ b/gst/hls/gsthlssink.c @@ -0,0 +1,378 @@ +/* GStreamer + * Copyright (C) 2011 Alessandro Decina + * + * 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 +#include +#include +#include +#include + + +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 "); +} + +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, + ×tamp, &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 index 0000000000..44c60da845 --- /dev/null +++ b/gst/hls/gsthlssink.h @@ -0,0 +1,65 @@ +/* GStreamer + * Copyright (C) 2011 Alessandro Decina + * + * 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 + +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 index 0000000000..531c14899b --- /dev/null +++ b/gst/hls/gstm3u8playlist.c @@ -0,0 +1,228 @@ +/* GStreamer + * Copyright (C) 2011 Andoni Morales Alastruey + * + * 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 + +#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 index 0000000000..f8227fe6ea --- /dev/null +++ b/gst/hls/gstm3u8playlist.h @@ -0,0 +1,74 @@ +/* GStreamer + * Copyright (C) 2011 Andoni Morales Alastruey + * + * 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 +#include + +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__ */