diff --git a/configure.ac b/configure.ac index fbd555fbf5..949f854cb1 100644 --- a/configure.ac +++ b/configure.ac @@ -1756,6 +1756,7 @@ gst/gaudieffects/Makefile gst/geometrictransform/Makefile gst/h264parse/Makefile gst/hdvparse/Makefile +gst/hls/Makefile gst/id3tag/Makefile gst/interlace/Makefile gst/invtelecine/Makefile diff --git a/gst/hls/Makefile.am b/gst/hls/Makefile.am new file mode 100644 index 0000000000..5587ffc4a2 --- /dev/null +++ b/gst/hls/Makefile.am @@ -0,0 +1,19 @@ +plugindir = $(GST_PLUGINS_DIR) + +plugin_LTLIBRARIES = libgstfragmented.la + +libgstfragmented_la_SOURCES = \ + m3u8.c \ + gsthlsdemux.c \ + gstfragmentedplugin.c + +libgstfragmented_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(SOUP_CFLAGS) +libgstfragmented_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(SOUP_LIBS) +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 \ + gsthlsdemux.h \ + m3u8.h diff --git a/gst/hls/gstfragmented.h b/gst/hls/gstfragmented.h new file mode 100644 index 0000000000..c1c7a2b0f0 --- /dev/null +++ b/gst/hls/gstfragmented.h @@ -0,0 +1,14 @@ +#ifndef __GST_FRAGMENTED_H__ +#define __GST_FRAGMENTED_H__ + +#include + +G_BEGIN_DECLS + +GST_DEBUG_CATEGORY_EXTERN (fragmented_debug); + +#define LOG_CAPS(obj, caps) GST_DEBUG_OBJECT (obj, "%s: %" GST_PTR_FORMAT, #caps, caps) + +G_END_DECLS + +#endif /* __GST_FRAGMENTED_H__ */ diff --git a/gst/hls/gstfragmentedplugin.c b/gst/hls/gstfragmentedplugin.c new file mode 100644 index 0000000000..84e3a4c5e7 --- /dev/null +++ b/gst/hls/gstfragmentedplugin.c @@ -0,0 +1,27 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "gstfragmented.h" +#include "gsthlsdemux.h" + +GST_DEBUG_CATEGORY (fragmented_debug); + +static gboolean +fragmented_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (fragmented_debug, "fragmented", 0, "fragmented"); + + if (!gst_element_register (plugin, "hlsdemux", GST_RANK_PRIMARY, + GST_TYPE_HLS_DEMUX) || FALSE) + return FALSE; + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "fragmented", + "Fragmented streaming plugins", + fragmented_init, VERSION, "LGPL", PACKAGE_NAME, "http://www.gstreamer.org/") diff --git a/gst/hls/gsthlsdemux.c b/gst/hls/gsthlsdemux.c new file mode 100644 index 0000000000..a8bae2b785 --- /dev/null +++ b/gst/hls/gsthlsdemux.c @@ -0,0 +1,878 @@ +/* GStreamer + * Copyright (C) 2010 Marc-Andre Lureau + * Copyright (C) 2010 Andoni Morales Alastruey + * + * Gsthlsdemux.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. + */ +/** + * SECTION:element-hlsdemux + * + * HTTP Live Streaming source element. + * + * + * Example launch line + * |[ + * gst-launch hlsdemux location=http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8 ! decodebin2 ! xvimagesink + * ]| + * + * + * Last reviewed on 2010-10-07 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include +#include "gsthlsdemux.h" + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/mpegts, systemstream=(boolean)true; " + "video/webm")); + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("playlist/m3u8")); + +static GstStaticPadTemplate fetchertemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (gst_hls_demux_debug); +#define GST_CAT_DEFAULT gst_hls_demux_debug + +enum +{ + PROP_0, + + PROP_FRAGMENTS_CACHE, + PROP_BITRATE_SWITCH_TOLERANCE, + PROP_LAST +}; + +static const float update_interval_factor[] = { 1, 0.5, 1.5, 3 }; + +#define DEFAULT_FRAGMENTS_CACHE 3 +#define DEFAULT_FAILED_COUNT 3 +#define DEFAULT_BITRATE_SWITCH_TOLERANCE 0.4 + +/* GObject */ +static void gst_hls_demux_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_hls_demux_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_hls_demux_dispose (GObject * obj); + +/* GstElement */ +static GstStateChangeReturn +gst_hls_demux_change_state (GstElement * element, GstStateChange transition); + +/* GstHLSDemux */ +static GstBusSyncReply gst_hls_demux_fetcher_bus_handler (GstBus * bus, + GstMessage * message, gpointer data); +static GstFlowReturn gst_hls_demux_chain (GstPad * pad, GstBuffer * buf); +static gboolean gst_hls_demux_sink_event (GstPad * pad, GstEvent * event); +static GstFlowReturn gst_hls_demux_fetcher_chain (GstPad * pad, GstBuffer * buf); +static gboolean gst_hls_demux_fetcher_sink_event (GstPad * pad, GstEvent * event); +static void gst_hls_demux_loop (GstHLSDemux * demux); +static void gst_hls_demux_stop (GstHLSDemux * demux); +static void gst_hls_demux_stop_fetcher (GstHLSDemux * demux, gboolean cancelled); +static gboolean gst_hls_demux_start_update (GstHLSDemux * demux); +static gboolean gst_hls_demux_cache_fragments (GstHLSDemux * demux); +static gboolean gst_hls_demux_schedule (GstHLSDemux * demux); +static gboolean gst_hls_demux_switch_playlist (GstHLSDemux * demux); +static gboolean gst_hls_demux_get_next_fragment (GstHLSDemux * demux, gboolean retry); +static gboolean gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean retry); +static void gst_hls_demux_reset (GstHLSDemux * demux); +static gboolean gst_hls_demux_set_location (GstHLSDemux * demux, const gchar * uri); + +static void +_do_init (GType type) +{ + GST_DEBUG_CATEGORY_INIT (gst_hls_demux_debug, "hlsdemux", 0, "hlsdemux element"); +} + +GST_BOILERPLATE_FULL (GstHLSDemux, gst_hls_demux, GstElement, + GST_TYPE_ELEMENT, _do_init); + +static void +gst_hls_demux_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 (&srctemplate)); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sinktemplate)); + + gst_element_class_set_details_simple (element_class, + "HLS Source", + "Decoder", + "HTTP Live Streaming source", + "Marc-Andre Lureau \n" + "Andoni Morales Alastruey "); +} + +static void +gst_hls_demux_dispose (GObject * obj) +{ + GstHLSDemux *demux = GST_HLS_DEMUX (obj); + + gst_hls_demux_stop_fetcher (demux, TRUE); + g_cond_free (demux->fetcher_cond); + g_mutex_free (demux->fetcher_lock); + + if (demux->client) { + gst_m3u8_client_free (demux->client); + demux->client = NULL; + } + + g_cond_free (demux->thread_cond); + g_mutex_free (demux->thread_lock); + + if (GST_TASK_STATE (demux->task) != GST_TASK_STOPPED) { + gst_task_stop (demux->task); + gst_task_join (demux->task); + } + gst_object_unref (demux->task); + g_static_rec_mutex_free (&demux->task_lock); + + while (!g_queue_is_empty (demux->queue)) { + GstBuffer *buf = g_queue_pop_head (demux->queue); + gst_buffer_unref (buf); + } + g_queue_free (demux->queue); + + gst_object_unref (demux->fetcher_bus); + gst_object_unref (demux->fetcherpad); + + G_OBJECT_CLASS (parent_class)->dispose (obj); +} + +static void +gst_hls_demux_class_init (GstHLSDemuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = (GstElementClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = gst_hls_demux_set_property; + gobject_class->get_property = gst_hls_demux_get_property; + gobject_class->dispose = gst_hls_demux_dispose; + + g_object_class_install_property (gobject_class, PROP_FRAGMENTS_CACHE, + g_param_spec_uint ("fragments-cache", "Fragments cache", + "Number of fragments needed to be cached to start playing", + 2, G_MAXUINT, DEFAULT_FRAGMENTS_CACHE, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_BITRATE_SWITCH_TOLERANCE, + g_param_spec_float ("bitrate-switch-tolerance", + "Bitrate switch tolerance", + "Tolerance with respect of the fragment duration to switch to " + "a different bitrate if the client is too slow/fast.", + 0, 1, DEFAULT_BITRATE_SWITCH_TOLERANCE, G_PARAM_READWRITE)); + + gstelement_class->change_state = gst_hls_demux_change_state; +} + +static void +gst_hls_demux_init (GstHLSDemux * demux, GstHLSDemuxClass * klass) +{ + /* sink pad */ + demux->sinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink"); + gst_pad_set_chain_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_hls_demux_chain)); + gst_pad_set_event_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_hls_demux_sink_event)); + gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); + + /* demux pad */ + demux->srcpad = gst_pad_new_from_static_template (&srctemplate, "src"); + gst_element_add_pad (GST_ELEMENT (demux), demux->srcpad); + + /* fetcher pad */ + demux->fetcherpad = gst_pad_new_from_static_template (&fetchertemplate, "sink"); + gst_pad_set_chain_function (demux->fetcherpad, + GST_DEBUG_FUNCPTR (gst_hls_demux_fetcher_chain)); + gst_pad_set_event_function (demux->fetcherpad, + GST_DEBUG_FUNCPTR (gst_hls_demux_fetcher_sink_event)); + gst_pad_set_element_private (demux->fetcherpad, demux); + gst_pad_activate_push (demux->fetcherpad, TRUE); + + /* Properties */ + demux->fragments_cache = DEFAULT_FRAGMENTS_CACHE; + demux->bitrate_switch_tol = DEFAULT_BITRATE_SWITCH_TOLERANCE; + + demux->fetcher_bus = gst_bus_new (); + gst_bus_set_sync_handler (demux->fetcher_bus, gst_hls_demux_fetcher_bus_handler, + demux); + demux->thread_cond = g_cond_new (); + demux->thread_lock = g_mutex_new (); + demux->fetcher_cond = g_cond_new (); + demux->fetcher_lock = g_mutex_new (); + demux->queue = g_queue_new (); + g_static_rec_mutex_init (&demux->task_lock); + demux->task = gst_task_create ((GstTaskFunction) gst_hls_demux_loop, demux); + gst_task_set_lock (demux->task, &demux->task_lock); + + gst_hls_demux_reset (demux); +} + +static void +gst_hls_demux_set_property (GObject * object, guint prop_id, const GValue * value, + GParamSpec * pspec) +{ + GstHLSDemux *demux = GST_HLS_DEMUX (object); + + switch (prop_id) { + case PROP_FRAGMENTS_CACHE: + demux->fragments_cache = g_value_get_uint (value); + break; + case PROP_BITRATE_SWITCH_TOLERANCE: + demux->bitrate_switch_tol = g_value_get_float (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_hls_demux_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstHLSDemux *demux = GST_HLS_DEMUX (object); + + switch (prop_id) { + case PROP_FRAGMENTS_CACHE: + g_value_set_uint (value, demux->fragments_cache); + break; + case PROP_BITRATE_SWITCH_TOLERANCE: + g_value_set_float (value, demux->bitrate_switch_tol); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_hls_demux_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstHLSDemux *demux = GST_HLS_DEMUX (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + gst_hls_demux_reset (demux); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + return ret; +} + +static gboolean +gst_hls_demux_sink_event (GstPad * pad, GstEvent * event) +{ + GstHLSDemux *demux = GST_HLS_DEMUX (gst_pad_get_parent (pad)); + GstQuery *query; + gchar *uri; + + + switch (event->type) { + case GST_EVENT_EOS:{ + gchar *playlist; + + if (demux->playlist == NULL) { + GST_WARNING_OBJECT (demux, "Received EOS without a playlist."); + break; + } + + GST_DEBUG_OBJECT (demux, "Got EOS on the sink pad: main playlist fetched"); + + playlist = g_strndup ((gchar *) GST_BUFFER_DATA (demux->playlist), + GST_BUFFER_SIZE (demux->playlist)); + gst_m3u8_client_update (demux->client, playlist); + gst_buffer_unref (demux->playlist); + + query = gst_query_new_uri (); + if (gst_pad_peer_query (demux->sinkpad, query)) { + gst_query_parse_uri (query, &uri); + gst_hls_demux_set_location (demux, uri); + g_free (uri); + } else if (gst_m3u8_client_is_live (demux->client)) { + GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND, + ("Failed querying the playlist uri, " + "required for live sources."), NULL); + return FALSE; + } + + gst_task_start (demux->task); + gst_event_unref (event); + return TRUE; + } + default: + break; + } + + return gst_pad_event_default (pad, event); +} + +static gboolean +gst_hls_demux_fetcher_sink_event (GstPad * pad, GstEvent * event) +{ + GstHLSDemux *demux = GST_HLS_DEMUX (gst_pad_get_element_private (pad)); + + switch (event->type) { + case GST_EVENT_EOS:{ + GST_DEBUG_OBJECT (demux, "Got EOS on the fetcher pad"); + /* signal we have fetched the URI */ + g_cond_signal (demux->fetcher_cond); + } + default: + break; + } + + gst_event_unref (event); + return FALSE; +} + +static GstFlowReturn +gst_hls_demux_chain (GstPad * pad, GstBuffer * buf) +{ + GstHLSDemux *demux = GST_HLS_DEMUX (gst_pad_get_parent (pad)); + + if (demux->playlist == NULL) { + gst_buffer_ref (buf); + demux->playlist = buf; + } else { + demux->playlist = gst_buffer_join (demux->playlist, buf); + } + + gst_object_unref (demux); + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_hls_demux_fetcher_chain (GstPad * pad, GstBuffer * buf) +{ + GstHLSDemux *demux = GST_HLS_DEMUX (gst_pad_get_element_private (pad)); + + /* The source element can be an http source element. In case we get a 404, + * the html response will be sent downstream and demux->downloaded_uri + * will not be null, which might make us think that the request proceed + * successfully. But it it will also post an error message in the bus that + * is handled synchronously and that will set demux->fetcher_error to TRUE, + * which is used to discard this buffer with the html response. */ + if (demux->fetcher_error) { + goto done; + } + + GST_LOG_OBJECT (demux, "Received new buffer in the fecther"); + if (demux->downloaded_uri == NULL) + demux->downloaded_uri = buf; + else + demux->downloaded_uri = gst_buffer_join (demux->downloaded_uri, buf); + +done: + { + return GST_FLOW_OK; + } +} + +static void +gst_hls_demux_stop_fetcher (GstHLSDemux * demux, gboolean cancelled) +{ + GstPad *pad; + + if (demux->fetcher == NULL) + return; + + GST_DEBUG_OBJECT (demux, "Stopping fetcher."); + gst_element_set_state (demux->fetcher, GST_STATE_NULL); + pad = gst_pad_get_peer (demux->fetcherpad); + if (pad) { + gst_pad_unlink (pad, demux->fetcherpad); + gst_object_unref (pad); + } + gst_object_unref (demux->fetcher); + demux->fetcher = NULL; + if (cancelled && demux->downloaded_uri != NULL) { + gst_buffer_unref (demux->downloaded_uri); + demux->downloaded_uri = NULL; + g_cond_signal (demux->fetcher_cond); + } +} + +static void +gst_hls_demux_stop (GstHLSDemux * demux) +{ + gst_hls_demux_stop_fetcher (demux, TRUE); + if (GST_TASK_STATE (demux->task) != GST_TASK_STOPPED) + gst_task_stop (demux->task); + g_cond_signal (demux->thread_cond); +} + +static void +gst_hls_demux_loop (GstHLSDemux * demux) +{ + GstBuffer *buf; + GstFlowReturn ret; + + /* cache the first fragments if we need it */ + if (G_UNLIKELY (demux->need_cache)) { + gboolean ret; + ret = gst_hls_demux_cache_fragments (demux); + if (!ret) { + goto cache_error; + } + gst_hls_demux_start_update (demux); + GST_INFO_OBJECT (demux, "First fragments cached successfully"); + } + + if (g_queue_is_empty (demux->queue)) { + if (demux->end_of_playlist) { + goto end_of_playlist; + } + GST_TASK_WAIT (demux->task); + } + + if (demux->end_of_playlist) { + goto end_of_playlist; + } + + buf = g_queue_pop_head (demux->queue); + ret = gst_pad_push (demux->srcpad, buf); + if (ret != GST_FLOW_OK) + goto error; + + return; + +end_of_playlist: + { + GST_DEBUG_OBJECT (demux, "Reached end of playlist, sending EOS"); + gst_pad_push_event (demux->srcpad, gst_event_new_eos ()); + gst_hls_demux_stop (demux); + return; + } + +cache_error: + { + GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND, + ("Could not cache the first fragments"), NULL); + gst_hls_demux_stop (demux); + return; + } + +error: + { + /* FIXME: handle error */ + gst_hls_demux_stop (demux); + return; + } +} + +static GstBusSyncReply +gst_hls_demux_fetcher_bus_handler (GstBus * bus, + GstMessage * message, gpointer data) +{ + GstHLSDemux *demux = GST_HLS_DEMUX (data); + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) { + demux->fetcher_error = TRUE; + g_cond_signal (demux->fetcher_cond); + } + + gst_message_unref (message); + return GST_BUS_DROP; +} + +static gboolean +gst_hls_demux_make_fetcher (GstHLSDemux * demux, const gchar * uri) +{ + GstPad *pad; + + if (!gst_uri_is_valid (uri)) + return FALSE; + + GST_DEBUG_OBJECT (demux, "Creating fetcher for the URI:%s", uri); + demux->fetcher = gst_element_make_from_uri (GST_URI_SRC, uri, NULL); + if (!demux->fetcher) + return FALSE; + + demux->fetcher_error = FALSE; + gst_element_set_bus (GST_ELEMENT (demux->fetcher), demux->fetcher_bus); + + g_object_set (G_OBJECT (demux->fetcher), "location", uri, NULL); + pad = gst_element_get_static_pad (demux->fetcher, "src"); + if (pad) { + gst_pad_link (pad, demux->fetcherpad); + gst_object_unref (pad); + } + return TRUE; +} + +static void +gst_hls_demux_reset (GstHLSDemux * demux) +{ + demux->need_cache = TRUE; + demux->thread_return = FALSE; + demux->accumulated_delay = 0; + demux->end_of_playlist = FALSE; + + if (demux->playlist) { + gst_buffer_unref (demux->playlist); + demux->playlist = NULL; + } + + if (demux->downloaded_uri) { + gst_buffer_unref (demux->downloaded_uri); + demux->downloaded_uri = NULL; + } + + if (demux->client) + gst_m3u8_client_free (demux->client); + demux->client = gst_m3u8_client_new (""); + + while (!g_queue_is_empty (demux->queue)) { + GstBuffer *buf = g_queue_pop_head (demux->queue); + gst_buffer_unref (buf); + } + g_queue_clear (demux->queue); +} + +static gboolean +gst_hls_demux_set_location (GstHLSDemux * demux, const gchar * uri) +{ + if (demux->client) + gst_m3u8_client_free (demux->client); + demux->client = gst_m3u8_client_new (uri); + GST_INFO_OBJECT (demux, "Changed location: %s", uri); + return TRUE; +} + +static gboolean +gst_hls_demux_update_thread (GstHLSDemux * demux) +{ + g_mutex_lock (demux->thread_lock); + while (TRUE) { + /* block until the next scheduled update or the signal to quit this thread */ + if (g_cond_timed_wait (demux->thread_cond, demux->thread_lock, + &demux->next_update)) { + goto quit; + } + + /* update the playlist for live sources */ + if (gst_m3u8_client_is_live (demux->client)) { + if (!gst_hls_demux_update_playlist (demux, TRUE)) { + GST_ERROR_OBJECT (demux, "Could not update the playlist"); + goto quit; + } + } + + /* schedule the next update */ + gst_hls_demux_schedule (demux); + + /* if it's a live source and the playlist couldn't be updated, there aren't + * more fragments in the playlist so we just wait for the next schedulled + * update */ + if (gst_m3u8_client_is_live (demux->client) && + demux->client->update_failed_count > 0) { + GST_WARNING_OBJECT (demux, + "The playlist hasn't been updated, failed count is %d", + demux->client->update_failed_count); + continue; + } + + /* fetch the next fragment */ + if (!gst_hls_demux_get_next_fragment (demux, TRUE)) { + if (!demux->end_of_playlist) + GST_ERROR_OBJECT (demux, "Could not fetch the next fragment"); + goto quit; + } + + /* try to switch to another bitrate if needed */ + gst_hls_demux_switch_playlist (demux); + } + +quit: + { + g_mutex_unlock (demux->thread_lock); + return TRUE; + } +} + +static gboolean +gst_hls_demux_start_update (GstHLSDemux * demux) +{ + GError *error; + + /* create a new thread for the updates so that we don't block in the streaming + * thread */ + demux->updates_thread = g_thread_create ( + (GThreadFunc) gst_hls_demux_update_thread, demux, TRUE, &error); + return (error != NULL); +} + +static gboolean +gst_hls_demux_cache_fragments (GstHLSDemux * demux) +{ + gint i; + + /* Start parsing the main playlist */ + gst_m3u8_client_set_current (demux->client, demux->client->main); + + if (gst_m3u8_client_is_live (demux->client)) { + if (!gst_hls_demux_update_playlist (demux, FALSE)) { + GST_ERROR_OBJECT (demux, "Could not fetch the main playlist %s", + demux->client->main->uri); + return FALSE; + } + } + + /* If this playlist is a list of playlists, select the first one + * and update it */ + if (gst_m3u8_client_has_variant_playlist (demux->client)) { + GstM3U8 *child = demux->client->main->lists->data; + gst_m3u8_client_set_current (demux->client, child); + if (!gst_hls_demux_update_playlist (demux, FALSE)) { + GST_ERROR_OBJECT (demux, "Could not fetch the child playlist %s", + child->uri); + return FALSE; + } + } + + /* If it's a live source, set the sequence number to the end of the list + * and substract the 'fragmets_cache' to start from the last fragment*/ + if (gst_m3u8_client_is_live (demux->client)) { + demux->client->sequence += g_list_length (demux->client->current->files); + if (demux->client->sequence >= demux->fragments_cache) + demux->client->sequence -= demux->fragments_cache; + else + demux->client->sequence = 0; + } + + /* Cache the first fragments */ + for (i = 0; i < demux->fragments_cache - 1; i++) { + if (!gst_hls_demux_get_next_fragment (demux, FALSE)) { + GST_ERROR_OBJECT (demux, "Error caching the first fragments"); + return FALSE; + } + } + + g_get_current_time (&demux->next_update); + + demux->need_cache = FALSE; + return TRUE; +} + +static gboolean +gst_hls_demux_fetch_location (GstHLSDemux * demux, const gchar * uri) +{ + GstStateChangeReturn ret; + + g_mutex_lock (demux->fetcher_lock); + + if (!gst_hls_demux_make_fetcher (demux, uri)) { + goto uri_error; + } + + ret = gst_element_set_state (demux->fetcher, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) + goto state_change_error; + + /* wait until we have fetched the element */ + GST_DEBUG_OBJECT (demux, "Waiting to fetch the URI"); + g_cond_wait (demux->fetcher_cond, demux->fetcher_lock); + + gst_hls_demux_stop_fetcher (demux, FALSE); + + if (demux->downloaded_uri != NULL) { + GST_INFO_OBJECT (demux, "URI fetched successfully"); + ret = TRUE; + goto quit; + } + ret = FALSE; + goto quit; + +uri_error: + { + GST_ELEMENT_ERROR (demux, RESOURCE, OPEN_READ, + ("Could not create an element to fetch the given URI."), ("URI: \"%s\"", + uri)); + ret = FALSE; + goto quit; + } + +state_change_error: + { + GST_ELEMENT_ERROR (demux, CORE, STATE_CHANGE, + ("Error changing state of the fetcher element."), NULL); + ret = FALSE; + goto quit; + } + +quit: + { + g_mutex_unlock (demux->fetcher_lock); + return ret; + } +} + +static gboolean +gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean retry) +{ + gchar *playlist; + + GST_INFO_OBJECT (demux, "Updating the playlist %s", demux->client->current->uri); + if (!gst_hls_demux_fetch_location (demux, demux->client->current->uri)) + return FALSE; + + playlist = g_strndup ((gchar *) GST_BUFFER_DATA (demux->downloaded_uri), + GST_BUFFER_SIZE (demux->downloaded_uri)); + gst_m3u8_client_update (demux->client, playlist); + gst_buffer_unref (demux->downloaded_uri); + demux->downloaded_uri = NULL; + return TRUE; +} + +static gboolean +gst_hls_demux_change_playlist (GstHLSDemux * demux, gboolean is_fast) +{ + if (is_fast) { + if (!demux->client->main->lists->next) + return TRUE; + demux->client->main->lists = g_list_next (demux->client->main->lists); + } else { + if (!demux->client->main->lists->prev) + return TRUE; + demux->client->main->lists = g_list_previous (demux->client->main->lists); + } + + gst_m3u8_client_set_current (demux->client, demux->client->main->lists->data); + gst_hls_demux_update_playlist (demux, TRUE); + GST_INFO_OBJECT (demux, "Client is %s, switching to bitrate %d", + is_fast ? "fast" : "slow", demux->client->current->bandwidth); + + return TRUE; +} + +static gboolean +gst_hls_demux_schedule (GstHLSDemux * demux) +{ + gfloat update_factor; + gint count; + + /* As defined in ยง6.3.4. Reloading the Playlist file: + * "If the client reloads a Playlist file and finds that it has not + * changed then it MUST wait for a period of time before retrying. The + * minimum delay is a multiple of the target duration. This multiple is + * 0.5 for the first attempt, 1.5 for the second, and 3.0 thereafter." + */ + count = demux->client->update_failed_count; + if (count < 3) + update_factor = update_interval_factor[count]; + else + update_factor = update_interval_factor[3]; + + /* schedule the next update using the target duration field of the + * playlist */ + g_time_val_add (&demux->next_update, + demux->client->current->targetduration * update_factor * 1000000); + GST_DEBUG_OBJECT (demux, "Next update scheduled at %s", + g_time_val_to_iso8601 (&demux->next_update)); + + return TRUE; +} + +static gboolean +gst_hls_demux_switch_playlist (GstHLSDemux * demux) +{ + GTimeVal now; + gint64 diff, limit; + + g_get_current_time (&now); + if (!demux->client->main->lists) + return TRUE; + + /* compare the time when the fragment was downloaded with the time when it was + * scheduled */ + diff = (GST_TIMEVAL_TO_TIME (demux->next_update) - GST_TIMEVAL_TO_TIME (now)); + limit = demux->client->current->targetduration * GST_SECOND * + demux->bitrate_switch_tol; + + /* if we are on time switch to a higher bitrate */ + if (diff > limit) { + gst_hls_demux_change_playlist (demux, TRUE); + } else if (diff < 0) { + /* if the client is to slow wait until it has accumulate a certain delay to + * switch to a lower bitrate */ + demux->accumulated_delay -= diff; + if (demux->accumulated_delay >= limit) { + gst_hls_demux_change_playlist (demux, FALSE); + } else if (demux->accumulated_delay < 0) { + demux->accumulated_delay = 0; + } + } + return TRUE; +} + +static gboolean +gst_hls_demux_get_next_fragment (GstHLSDemux * demux, gboolean retry) +{ + const gchar *next_fragment_uri; + gboolean discont; + + next_fragment_uri = gst_m3u8_client_get_next_fragment (demux->client, &discont); + + if (!next_fragment_uri) { + GST_INFO_OBJECT (demux, "This playlist doesn't contain more fragments"); + demux->end_of_playlist = TRUE; + GST_TASK_SIGNAL (demux->task); + return FALSE; + } + + GST_INFO_OBJECT (demux, "Fetching next fragment %s", next_fragment_uri); + + if (!gst_hls_demux_fetch_location (demux, next_fragment_uri)) + return FALSE; + + if (discont) { + GST_DEBUG_OBJECT (demux, "Marking fragment has discontinuous"); + GST_BUFFER_FLAG_SET (demux->downloaded_uri, GST_BUFFER_FLAG_DISCONT); + } + + g_queue_push_tail (demux->queue, demux->downloaded_uri); + GST_TASK_SIGNAL (demux->task); + demux->downloaded_uri = NULL; + return TRUE; +} diff --git a/gst/hls/gsthlsdemux.h b/gst/hls/gsthlsdemux.h new file mode 100644 index 0000000000..04c6b6985b --- /dev/null +++ b/gst/hls/gsthlsdemux.h @@ -0,0 +1,96 @@ +/* GStreamer + * Copyright (C) 2010 Marc-Andre Lureau + * Copyright (C) 2010 Andoni Morales Alastruey + * + * gsthlsdemux.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_HLS_DEMUX_H__ +#define __GST_HLS_DEMUX_H__ + +#include +#include "m3u8.h" + +G_BEGIN_DECLS +#define GST_TYPE_HLS_DEMUX \ + (gst_hls_demux_get_type()) +#define GST_HLS_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_HLS_DEMUX,GstHLSDemux)) +#define GST_HLS_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_HLS_DEMUX,GstHLSDemuxClass)) +#define GST_IS_HLS_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_HLS_DEMUX)) +#define GST_IS_HLS_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_HLS_DEMUX)) +typedef struct _GstHLSDemux GstHLSDemux; +typedef struct _GstHLSDemuxClass GstHLSDemuxClass; + +/** + * GstHLSDemux: + * + * Opaque #GstHLSDemux data structure. + */ +struct _GstHLSDemux +{ + GstElement parent; + + GstTask *task; + GStaticRecMutex task_lock; + GstPad *srcpad; + GstPad *sinkpad; + GstBuffer *playlist; + GstM3U8Client *client; /* M3U8 client */ + GQueue *queue; /* Queue storing the fetched fragments */ + gboolean need_cache; /* Wheter we need to cache some fragments before starting to push data */ + gboolean end_of_playlist; + + + /* Properties */ + guint fragments_cache; /* number of fragments needed to be cached to start playing */ + gfloat bitrate_switch_tol; /* tolerance with respect to the fragment duration to switch the bitarate*/ + + /* Updates thread */ + GThread *updates_thread; /* Thread handling the playlist and fragments updates */ + GMutex *thread_lock; /* Thread lock */ + GCond *thread_cond; /* Signals the thread to quit */ + gboolean thread_return; /* Instructs the thread to return after the thread_quit condition is meet */ + GTimeVal next_update; /* Time of the next update */ + gint64 accumulated_delay; /* Delay accumulated fetching fragments, used to decide a playlist switch */ + + /* Fragments fetcher */ + GstElement *fetcher; + GstBus *fetcher_bus; + GstPad *fetcherpad; + GMutex *fetcher_lock; + GCond *fetcher_cond; + GTimeVal *timeout; + gboolean fetcher_error; + GstBuffer *downloaded_uri; + +}; + +struct _GstHLSDemuxClass +{ + GstElementClass parent_class; +}; + +GType gst_hls_demux_get_type (void); + +G_END_DECLS +#endif /* __GST_HLS_DEMUX_H__ */ diff --git a/gst/hls/m3u8.c b/gst/hls/m3u8.c new file mode 100644 index 0000000000..ea5aa68590 --- /dev/null +++ b/gst/hls/m3u8.c @@ -0,0 +1,452 @@ +/* GStreamer + * Copyright (C) 2010 Marc-Andre Lureau + * + * m3u8.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 +#include + +#include "gstfragmented.h" +#include "m3u8.h" + +#define GST_CAT_DEFAULT fragmented_debug + +static GstM3U8 *gst_m3u8_new (void); +static void gst_m3u8_free (GstM3U8 * m3u8); +static gboolean gst_m3u8_update (GstM3U8 * m3u8, gchar * data, + gboolean * updated); +static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri, + gchar * title, gint duration, guint sequence); +static void gst_m3u8_media_file_free (GstM3U8MediaFile * self); + +static GstM3U8 * +gst_m3u8_new (void) +{ + GstM3U8 *m3u8; + + m3u8 = g_new0 (GstM3U8, 1); + + return m3u8; +} + +static void +gst_m3u8_set_uri (GstM3U8 * self, gchar * uri) +{ + g_return_if_fail (self != NULL); + + if (self->uri) + g_free (self->uri); + self->uri = uri; +} + +static void +gst_m3u8_free (GstM3U8 * self) +{ + g_return_if_fail (self != NULL); + + g_free (self->uri); + g_free (self->allowcache); + g_free (self->codecs); + + g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL); + g_list_free (self->files); + + g_free (self->last_data); + g_list_foreach (self->lists, (GFunc) gst_m3u8_free, NULL); + g_list_free (self->lists); + + g_free (self); +} + +static GstM3U8MediaFile * +gst_m3u8_media_file_new (gchar * uri, gchar * title, gint duration, + guint sequence) +{ + GstM3U8MediaFile *file; + + file = g_new0 (GstM3U8MediaFile, 1); + file->uri = uri; + file->title = title; + file->duration = duration; + file->sequence = sequence; + + return file; +} + +static void +gst_m3u8_media_file_free (GstM3U8MediaFile * self) +{ + g_return_if_fail (self != NULL); + + g_free (self->title); + g_free (self->uri); + g_free (self); +} + +static gboolean +int_from_string (gchar * ptr, gchar ** endptr, gint * val) +{ + gchar *end; + + g_return_val_if_fail (ptr != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + errno = 0; + *val = strtol (ptr, &end, 10); + if ((errno == ERANGE && (*val == LONG_MAX || *val == LONG_MIN)) + || (errno != 0 && *val == 0)) { + GST_WARNING (g_strerror (errno)); + return FALSE; + } + + if (endptr) + *endptr = end; + + return end != ptr; +} + +static gboolean +parse_attributes (gchar ** ptr, gchar ** a, gchar ** v) +{ + gchar *end, *p; + + g_return_val_if_fail (ptr != NULL, FALSE); + g_return_val_if_fail (*ptr != NULL, FALSE); + g_return_val_if_fail (a != NULL, FALSE); + g_return_val_if_fail (v != NULL, FALSE); + + /* [attribute=value,]* */ + + *a = *ptr; + end = p = g_utf8_strchr (*ptr, -1, ','); + if (end) { + do { + end = g_utf8_next_char (end); + } while (end && *end == ' '); + *p = '\0'; + } + + *v = p = g_utf8_strchr (*ptr, -1, '='); + if (*v) { + *v = g_utf8_next_char (*v); + *p = '\0'; + } else { + GST_WARNING ("missing = after attribute"); + return FALSE; + } + + *ptr = end; + return TRUE; +} + +static gint +_m3u8_compare_uri (GstM3U8 * a, gchar * uri) +{ + g_return_val_if_fail (a != NULL, 0); + g_return_val_if_fail (uri != NULL, 0); + + return g_strcmp0 (a->uri, uri); +} + +static gint +gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b) +{ + return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth; +} + +/* + * @data: a m3u8 playlist text data, taking ownership + */ +static gboolean +gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated) +{ + gint val, duration; + gchar *title, *end; + gboolean discontinuity; + GstM3U8 *list; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (updated != NULL, FALSE); + + *updated = TRUE; + + /* check if the data changed since last update */ + if (self->last_data && g_str_equal (self->last_data, data)) { + GST_DEBUG ("Playlist is the same as previous one"); + *updated = FALSE; + g_free (data); + return TRUE; + } + + if (!g_str_has_prefix (data, "#EXTM3U")) { + GST_WARNING ("Data doesn't start with #EXTM3U"); + g_free (data); + return FALSE; + } + + g_free (self->last_data); + self->last_data = data; + + list = NULL; + duration = -1; + title = NULL; + data += 7; + while (TRUE) { + end = g_utf8_strchr (data, -1, '\n'); /* FIXME: support \r\n */ + if (end) + *end = '\0'; + + if (data[0] != '#') { + if (duration < 0 && list == NULL) { + GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data); + goto next_line; + } + + if (!gst_uri_is_valid (data)) { + gchar *slash; + if (!self->uri) { + GST_WARNING ("uri not set, can't build a valid uri"); + goto next_line; + } + slash = g_utf8_strrchr (self->uri, -1, '/'); + if (!slash) { + GST_WARNING ("Can't build a valid uri"); + goto next_line; + } + + *slash = '\0'; + data = g_strdup_printf ("%s/%s", self->uri, data); + *slash = '/'; + } else + data = g_strdup (data); + + if (list != NULL) { + if (g_list_find_custom (self->lists, data, + (GCompareFunc) _m3u8_compare_uri)) { + GST_DEBUG ("Already have a list with this URI"); + gst_m3u8_free (list); + g_free (data); + } else { + gst_m3u8_set_uri (list, data); + self->lists = g_list_append (self->lists, list); + } + list = NULL; + } else { + GstM3U8MediaFile *file; + file = + gst_m3u8_media_file_new (data, title, duration, + self->mediasequence++); + duration = -1; + title = NULL; + self->files = g_list_append (self->files, file); + } + + } else if (g_str_has_prefix (data, "#EXT-X-ENDLIST")) { + self->endlist = TRUE; + } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) { + if (int_from_string (data + 15, &data, &val)) + self->version = val; + } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:")) { + gchar *v, *a; + + if (list != NULL) { + GST_WARNING ("Found a list without a uri..., dropping"); + gst_m3u8_free (list); + } + + list = gst_m3u8_new (); + data = data + 18; + while (data && parse_attributes (&data, &a, &v)) { + if (g_str_equal (a, "BANDWIDTH")) { + if (!int_from_string (v, NULL, &list->bandwidth)) + GST_WARNING ("Error while reading BANDWIDTH"); + } else if (g_str_equal (a, "PROGRAM-ID")) { + if (!int_from_string (v, NULL, &list->program_id)) + GST_WARNING ("Error while reading PROGRAM-ID"); + } else if (g_str_equal (a, "CODECS")) { + g_free (list->codecs); + list->codecs = g_strdup (v); + } else if (g_str_equal (a, "RESOLUTION")) { + if (!int_from_string (v, &v, &list->width)) + GST_WARNING ("Error while reading RESOLUTION width"); + if (!v || *v != '=') { + GST_WARNING ("Missing height"); + } else { + v = g_utf8_next_char (v); + if (!int_from_string (v, NULL, &list->height)) + GST_WARNING ("Error while reading RESOLUTION height"); + } + } + } + } else if (g_str_has_prefix (data, "#EXT-X-TARGETDURATION:")) { + if (int_from_string (data + 22, &data, &val)) + self->targetduration = val; + } else if (g_str_has_prefix (data, "#EXT-X-MEDIA-SEQUENCE:")) { + if (int_from_string (data + 22, &data, &val)) + self->mediasequence = val; + } else if (g_str_has_prefix (data, "#EXT-X-DISCONTINUITY")) { + discontinuity = TRUE; + } else if (g_str_has_prefix (data, "#EXT-X-PROGRAM-DATE-TIME:")) { + /* */ + GST_DEBUG ("FIXME parse date"); + } else if (g_str_has_prefix (data, "#EXT-X-ALLOW-CACHE:")) { + g_free (self->allowcache); + self->allowcache = g_strdup (data + 19); + } else if (g_str_has_prefix (data, "#EXTINF:")) { + if (!int_from_string (data + 8, &data, &val)) { + GST_WARNING ("Can't read EXTINF duration"); + goto next_line; + } + duration = val; + if (duration > self->targetduration) + GST_WARNING ("EXTINF duration > TARGETDURATION"); + if (!data || *data != ',') + goto next_line; + data = g_utf8_next_char (data); + if (data != end) { + g_free (title); + title = g_strdup (data); + } + } else { + GST_LOG ("Ignored line: %s", data); + } + + next_line: + if (!end) + break; + data = g_utf8_next_char (end); /* skip \n */ + } + + /* redorder playlists by bitrate */ + if (self->lists) + self->lists = + g_list_sort (self->lists, + (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate); + + return TRUE; +} + +GstM3U8Client * +gst_m3u8_client_new (const gchar * uri) +{ + GstM3U8Client *client; + + g_return_val_if_fail (uri != NULL, NULL); + + client = g_new0 (GstM3U8Client, 1); + client->main = gst_m3u8_new (); + client->current = NULL; + client->sequence = -1; + client->update_failed_count = 0; + gst_m3u8_set_uri (client->main, g_strdup (uri)); + + return client; +} + +void +gst_m3u8_client_free (GstM3U8Client * self) +{ + g_return_if_fail (self != NULL); + + gst_m3u8_free (self->main); + g_free (self); +} + +void +gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8) +{ + g_return_if_fail (self != NULL); + + if (m3u8 != self->current) { + self->current = m3u8; + self->update_failed_count = 0; + } +} + +gboolean +gst_m3u8_client_update (GstM3U8Client * self, gchar * data) +{ + GstM3U8 *m3u8; + gboolean updated = FALSE; + + g_return_val_if_fail (self != NULL, FALSE); + + m3u8 = self->current ? self->current : self->main; + + if (!gst_m3u8_update (m3u8, data, &updated)) + return FALSE; + + if (!updated) { + self->update_failed_count++; + return FALSE; + } + + /* select the first playlist, for now */ + if (!self->current) { + if (self->main->lists) { + self->current = g_list_first (self->main->lists)->data; + } else { + self->current = self->main; + } + } + + if (m3u8->files && self->sequence == -1) { + self->sequence = + GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence; + GST_DEBUG ("Setting first sequence at %d", self->sequence); + } + + return TRUE; +} + +static gboolean +_find_next (GstM3U8MediaFile * file, GstM3U8Client * client) +{ + GST_DEBUG ("Found fragment %d", file->sequence); + if (file->sequence >= client->sequence) + return FALSE; + return TRUE; +} + +const gchar * +gst_m3u8_client_get_next_fragment (GstM3U8Client * client, + gboolean * discontinuity) +{ + GList *l; + GstM3U8MediaFile *file; + + g_return_val_if_fail (client != NULL, NULL); + g_return_val_if_fail (client->current != NULL, NULL); + g_return_val_if_fail (discontinuity != NULL, NULL); + + GST_DEBUG ("Looking for fragment %d", client->sequence); + l = g_list_find_custom (client->current->files, client, + (GCompareFunc) _find_next); + if (l == NULL) + return NULL; + + file = GST_M3U8_MEDIA_FILE (l->data); + + *discontinuity = client->sequence != file->sequence; + client->sequence = file->sequence + 1; + + return file->uri; +} diff --git a/gst/hls/m3u8.h b/gst/hls/m3u8.h new file mode 100644 index 0000000000..df893e08de --- /dev/null +++ b/gst/hls/m3u8.h @@ -0,0 +1,85 @@ +/* GStreamer + * Copyright (C) 2010 Marc-Andre Lureau + * Copyright (C) 2010 Andoni Morales Alastruey + * + * m3u8.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 __M3U8_H__ +#define __M3U8_H__ + +#include + +G_BEGIN_DECLS typedef struct _GstM3U8 GstM3U8; +typedef struct _GstM3U8MediaFile GstM3U8MediaFile; +typedef struct _GstM3U8Client GstM3U8Client; + +#define GST_M3U8_MEDIA_FILE(f) ((GstM3U8MediaFile*)f) + +struct _GstM3U8 +{ + gchar *uri; + + gboolean endlist; /* if ENDLIST has been reached */ + gint version; /* last EXT-X-VERSION */ + gint targetduration; /* last EXT-X-TARGETDURATION */ + gchar *allowcache; /* last EXT-X-ALLOWCACHE */ + + gint bandwidth; + gint program_id; + gchar *codecs; + gint width; + gint height; + GList *files; + + /*< private > */ + gchar *last_data; + GList *lists; /* list of GstM3U8 from the main playlist */ + GstM3U8 *parent; /* main playlist (if any) */ + guint mediasequence; /* EXT-X-MEDIA-SEQUENCE & increased with new media file */ +}; + +struct _GstM3U8MediaFile +{ + gchar *title; + gint duration; + gchar *uri; + guint sequence; /* the sequence nb of this file */ +}; + +struct _GstM3U8Client +{ + GstM3U8 *main; /* main playlist */ + GstM3U8 *current; + guint update_failed_count; + gint sequence; /* the next sequence for this client */ +}; + + +GstM3U8Client *gst_m3u8_client_new (const gchar * uri); +void gst_m3u8_client_free (GstM3U8Client * client); +gboolean gst_m3u8_client_update (GstM3U8Client * client, gchar * data); +void gst_m3u8_client_set_current (GstM3U8Client * client, GstM3U8 * m3u8); +const gchar *gst_m3u8_client_get_next_fragment (GstM3U8Client * client, + gboolean * discontinuity); +#define gst_m3u8_client_get_uri(Client) ((Client)->main->uri) +#define gst_m3u8_client_has_variant_playlist(Client) ((Client)->main->lists) +#define gst_m3u8_client_is_live(Client) (!(Client)->main->lists && !(Client)->current->endlist) + +G_END_DECLS +#endif /* __M3U8_H__ */