<div dir="ltr">This patch should take care of the above concerns; it also fixes some bugs.<div><br></div><div>The following is noted:</div><div>-volatile was necessary because the compiler was optimizing out some memory accesses that were necessary because two threads were (safely) operating on the same data structure.  I've reshuffled some code since that time, so it may not still occur, but shouldn't hurt.</div>
<div><br></div><div>-I put the data definitions in a separate header file because I'm likely to add some functionality (bitrate changing, seeking, etc), that may require adding more .c files to prevent things from getting out of hand.  A separate header is a step in that direction.</div>
<div><br></div><div>-The ultra-large values for segment runs and fragments runs are what I imagine are the reasonable worst cases.  The spec. defines nothing in terms of hard limits here.  In fact, its quite vague.</div><div>
<br></div><div>--Jonathan</div><div><br></div></div><div class="gmail_extra"><br><br><div class="gmail_quote">On Wed, May 28, 2014 at 3:12 PM, Jonathan Thambidurai <span dir="ltr"><<a href="mailto:jonathan@fastly.com" target="_blank">jonathan@fastly.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">---<br>
 NEWS                              |    1 +<br>
 modules/MODULES_LIST              |    1 +<br>
 modules/stream_filter/Makefile.am |    8 +<br>
 modules/stream_filter/hds/hds.c   | 1556 +++++++++++++++++++++++++++++++++++++<br>
 modules/stream_filter/hds/hds.h   |  127 +++<br>
 po/POTFILES.in                    |    1 +<br>
 6 files changed, 1694 insertions(+)<br>
 create mode 100644 modules/stream_filter/hds/hds.c<br>
 create mode 100644 modules/stream_filter/hds/hds.h<br>
<br>
diff --git a/NEWS b/NEWS<br>
index 67f2819..92c9511 100644<br>
--- a/NEWS<br>
+++ b/NEWS<br>
@@ -86,6 +86,7 @@ Streaming:<br>
     :std{access=http{mime=video/webm},mux=webm,dst=:4212}'<br>
  * GSM, VP8, Opus and JPEG RTP packetization<br>
  * HLS: Allow setting the first segment number and numerous improvements<br>
+ * HDS: Http Dynamic Streaming - based on public specs from Adobe (f4m, f4v, etc.)<br>
  * new stats module to output block timing values and md5 sums<br>
  * transcode module access fps values as rationals now, eg 30000/1001<br>
  * VLC now streams all elementary streams, you can revert to previous behaviour<br>
diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST<br>
index effd694..082176f 100644<br>
--- a/modules/MODULES_LIST<br>
+++ b/modules/MODULES_LIST<br>
@@ -152,6 +152,7 @@ $Id$<br>
  * grey_yuv: grayscale to others conversion module<br>
  * growl: announce currently playing stream to growl<br>
  * h264: H264 decoder<br>
+ * hds: HTTP Dynamic Streaming, per Adobe's specs<br>
  * headphone_channel_mixer:  headphone channel mixer with virtual spatialization effect<br>
  * hevc: HEVC demuxer<br>
  * hotkeys: hotkeys control module<br>
diff --git a/modules/stream_filter/Makefile.am b/modules/stream_filter/Makefile.am<br>
index 158b46a..cbf8751 100644<br>
--- a/modules/stream_filter/Makefile.am<br>
+++ b/modules/stream_filter/Makefile.am<br>
@@ -104,6 +104,14 @@ libsmooth_plugin_la_SOURCES = \<br>
 libsmooth_plugin_la_CFLAGS = $(AM_CFLAGS)<br>
 stream_filter_LTLIBRARIES += <a href="http://libsmooth_plugin.la" target="_blank">libsmooth_plugin.la</a><br>
<br>
+libhds_plugin_la_SOURCES = \<br>
+    stream_filter/hds/hds.c \<br>
+    stream_filter/hds/hds.h<br>
+<br>
+libhds_plugin_la_CFLAGS = $(AM_CFLAGS)<br>
+stream_filter_LTLIBRARIES += <a href="http://libhds_plugin.la" target="_blank">libhds_plugin.la</a><br>
+<br>
+<br>
 libhttplive_plugin_la_SOURCES = stream_filter/httplive.c<br>
 libhttplive_plugin_la_CFLAGS = $(AM_CFLAGS) $(GCRYPT_CFLAGS)<br>
 libhttplive_plugin_la_LIBADD = $(GCRYPT_LIBS) -lgpg-error<br>
diff --git a/modules/stream_filter/hds/hds.c b/modules/stream_filter/hds/hds.c<br>
new file mode 100644<br>
index 0000000..84f0210<br>
--- /dev/null<br>
+++ b/modules/stream_filter/hds/hds.c<br>
@@ -0,0 +1,1556 @@<br>
+/*****************************************************************************<br>
+ * hds.c: Http Dynamic Streaming (HDS) stream filter<br>
+ *****************************************************************************<br>
+ *<br>
+ * Author: Jonathan Thambidurai <jonathan _AT_ fastly _DOT_ com><br>
+ * Heavily inspired by HDS module of Frédéric Yhuel <fyhuel _AT_ viotech _DOT_ net><br>
+ *<br>
+ * This library is free software; you can redistribute it and/or<br>
+ * modify it under the terms of the GNU Lesser General Public<br>
+ * License as published by the Free Software Foundation; either<br>
+ * version 2.1 of the License, or (at your option) any later version.<br>
+ *<br>
+ * This library is distributed in the hope that it will be useful,<br>
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU<br>
+ * Lesser General Public License for more details.<br>
+ *<br>
+ * You should have received a copy of the GNU Lesser General Public<br>
+ * License along with this library; if not, write to the Free Software<br>
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA<br>
+ *****************************************************************************/<br>
+<br>
+#ifdef HAVE_CONFIG_H<br>
+# include "config.h"<br>
+#endif<br>
+<br>
+#include <inttypes.h><br>
+<br>
+#include <vlc_common.h><br>
+#include <vlc_strings.h><br>
+#include <vlc_plugin.h><br>
+#include <vlc_xml.h><br>
+#include <vlc_charset.h><br>
+#include <vlc_stream.h><br>
+#include <vlc_es.h><br>
+<br>
+#include "hds.h"<br>
+<br>
+/*****************************************************************************<br>
+ * Module descriptor<br>
+ *****************************************************************************/<br>
+static int  Open( vlc_object_t * );<br>
+static void Close( vlc_object_t * );<br>
+<br>
+vlc_module_begin()<br>
+    set_category( CAT_INPUT )<br>
+    set_subcategory( SUBCAT_INPUT_STREAM_FILTER )<br>
+    set_description( N_("HTTP Dynamic Streaming") )<br>
+    set_shortname( "Dynamic Streaming")<br>
+    add_shortcut( "hds" )<br>
+    set_capability( "stream_filter", 30 )<br>
+    set_callbacks( Open, Close )<br>
+vlc_module_end()<br>
+<br>
+static int   Read( stream_t *, void *, unsigned );<br>
+static int   Peek( stream_t *, const uint8_t **, unsigned );<br>
+static int   Control( stream_t *, int , va_list );<br>
+<br>
+static bool isFQUrl( char* url )<br>
+{<br>
+    return ( NULL != vlc_strcasestr( url, "https://") ||<br>
+             NULL != vlc_strcasestr( url, "http://" ) );<br>
+}<br>
+<br>
+static bool isHDS( stream_t *s )<br>
+{<br>
+    const uint8_t *peek;<br>
+    int i_size = stream_Peek( s->p_source, &peek, 512 );<br>
+    if( i_size < 512 )<br>
+        return false;<br>
+<br>
+    char *str;<br>
+<br>
+    if( !memcmp( peek, "\xFF\xFE", 2 ) )<br>
+    {<br>
+        str = FromCharset( "UTF-16LE", peek, 512 );<br>
+    }<br>
+    else if( !memcmp( peek, "\xFE\xFF", 2 ) )<br>
+    {<br>
+        str = FromCharset( "UTF-16BE", peek, 512 );<br>
+    }<br>
+    else<br>
+        str = peek;<br>
+<br>
+    if( str == NULL )<br>
+        return false;<br>
+<br>
+    bool ret = strstr( str, "<manifest" ) != NULL;<br>
+    return ret;<br>
+}<br>
+<br>
+typedef struct _bootstrap_info {<br>
+    char* data;<br>
+    char* id;<br>
+    char* url;<br>
+    char* profile;<br>
+    int data_len;<br>
+} bootstrap_info;<br>
+<br>
+typedef struct _media_info {<br>
+    char* stream_id;<br>
+    char* media_url;<br>
+    char* bootstrap_id;<br>
+} media_info;<br>
+<br>
+static char* parse_asrt( vlc_object_t* p_this,<br>
+                        hds_stream_t* s,<br>
+                        char* data,<br>
+                        char* data_end )<br>
+{<br>
+    char* data_p = data;<br>
+<br>
+    uint32_t asrt_len = 0;<br>
+    asrt_len = U32_AT( data_p );<br>
+    if( asrt_len > data_end - data ||<br>
+        data_end - data <  14 )<br>
+    {<br>
+        msg_Err( p_this, "Not enough asrt data (%u, %u)", asrt_len, data_end - data );<br>
+        return NULL;<br>
+    }<br>
+<br>
+    data_p += sizeof(asrt_len);<br>
+<br>
+    if( 0 != memcmp( "asrt", data_p, 4 ) )<br>
+    {<br>
+        msg_Err( p_this, "Cant find asrt in bootstrap" );<br>
+        return NULL;<br>
+    }<br>
+    data_p += 4;<br>
+<br>
+    /* ignore flags and versions (we don't handle multiple updates) */<br>
+    data_p += 4;<br>
+<br>
+    uint8_t quality_entry_count = *data_p;<br>
+    bool quality_found = false;<br>
+    data_p++;<br>
+<br>
+    if( ! s->quality_segment_modifier )<br>
+    {<br>
+        quality_found = true;<br>
+    }<br>
+<br>
+    while( quality_entry_count-- > 0 )<br>
+    {<br>
+        char* str_start = data_p;<br>
+        data_p = memchr( data_p, '\0', data_end - data_p );<br>
+        if( ! data_p )<br>
+        {<br>
+            msg_Err( p_this, "Couldn't find quality entry string in asrt" );<br>
+            return NULL;<br>
+        }<br>
+        data_p++;<br>
+<br>
+        if( ! quality_found )<br>
+        {<br>
+            if( ! strncmp( str_start, s->quality_segment_modifier,<br>
+                           strlen(s->quality_segment_modifier) ) )<br>
+            {<br>
+                quality_found = true;<br>
+            }<br>
+        }<br>
+<br>
+        if( data_p >= data_end )<br>
+        {<br>
+            msg_Err( p_this, "Premature end of asrt in quality entries" );<br>
+            return NULL;<br>
+        }<br>
+    }<br>
+<br>
+    if( data_end - data_p < 4 )<br>
+    {<br>
+        msg_Err( p_this, "Premature end of asrt after quality entries" );<br>
+        return NULL;<br>
+    }<br>
+<br>
+    uint32_t segment_run_entry_count = U32_AT( data_p );<br>
+    data_p += sizeof(segment_run_entry_count);<br>
+<br>
+    if( data_end - data_p < 8*segment_run_entry_count )<br>
+    {<br>
+        msg_Err( p_this, "Not enough data in asrt for segment run entries" );<br>
+        return NULL;<br>
+    }<br>
+<br>
+    if( segment_run_entry_count >= MAX_HDS_SEGMENT_RUNS )<br>
+    {<br>
+        msg_Err( p_this, "Too many segment runs" );<br>
+        return NULL;<br>
+    }<br>
+<br>
+    while( segment_run_entry_count-- > 0 )<br>
+    {<br>
+        if( quality_found )<br>
+        {<br>
+            s->segment_runs[s->segment_run_count].first_segment = U32_AT(data_p);<br>
+        }<br>
+        data_p+=4;<br>
+<br>
+        if( quality_found )<br>
+        {<br>
+            s->segment_runs[s->segment_run_count].fragments_per_segment = U32_AT(data_p);<br>
+        }<br>
+        data_p+=4;<br>
+<br>
+        s->segment_run_count++;<br>
+    }<br>
+<br>
+    return data_p;<br>
+}<br>
+<br>
+static char* parse_afrt( vlc_object_t* p_this,<br>
+                        hds_stream_t* s,<br>
+                        char* data,<br>
+                        char* data_end )<br>
+{<br>
+    char* data_p = data;<br>
+<br>
+    uint32_t afrt_len = U32_AT( data_p );<br>
+    if( afrt_len > data_end - data ||<br>
+        data_end - data <  9 )<br>
+    {<br>
+        msg_Err( p_this, "Not enough afrt data %u, %u", afrt_len, data_end - data );<br>
+        return NULL;<br>
+    }<br>
+    data_p += sizeof(afrt_len);<br>
+<br>
+    if( 0 != memcmp( data_p, "afrt", 4 ) )<br>
+    {<br>
+        msg_Err( p_this, "Cant find afrt in bootstrap" );<br>
+        return NULL;<br>
+    }<br>
+    data_p += 4;<br>
+<br>
+    /* ignore flags and versions (we don't handle multiple updates) */<br>
+    data_p += 4;<br>
+<br>
+    if( data_end - data_p < 9 )<br>
+    {<br>
+        msg_Err( p_this, "afrt is too short" );<br>
+        return NULL;<br>
+    }<br>
+<br>
+    s->afrt_timescale = U32_AT( data_p );<br>
+    data_p += 4;<br>
+<br>
+    bool quality_found = false;<br>
+    if( ! s->quality_segment_modifier )<br>
+    {<br>
+        quality_found = true;<br>
+    }<br>
+<br>
+    uint32_t quality_entry_count = *data_p;<br>
+    data_p++;<br>
+    while( quality_entry_count-- > 0 )<br>
+    {<br>
+        char* str_start = data_p;<br>
+        data_p = memchr( data_p, '\0', data_end - data_p );<br>
+        if( ! data_p )<br>
+        {<br>
+            msg_Err( p_this, "Couldn't find quality entry string in afrt" );<br>
+            return NULL;<br>
+        }<br>
+        data_p++;<br>
+<br>
+        if( ! quality_found )<br>
+        {<br>
+            if( ! strncmp( str_start, s->quality_segment_modifier,<br>
+                           strlen(s->quality_segment_modifier) ) )<br>
+            {<br>
+                quality_found = true;<br>
+            }<br>
+        }<br>
+    }<br>
+<br>
+    if( data_end - data_p < 5 )<br>
+    {<br>
+        msg_Err( p_this, "No more space in afrt after quality entries" );<br>
+        return NULL;<br>
+    }<br>
+<br>
+    uint32_t fragment_run_entry_count = U32_AT( data_p );<br>
+    data_p += sizeof(uint32_t);<br>
+<br>
+    while(fragment_run_entry_count-- > 0)<br>
+    {<br>
+        if( data_end - data_p < 16 )<br>
+        {<br>
+            msg_Err( p_this, "Not enough data in afrt" );<br>
+            return NULL;<br>
+        }<br>
+<br>
+        if( s->fragment_run_count >= MAX_HDS_FRAGMENT_RUNS )<br>
+        {<br>
+            msg_Err( p_this, "Too many fragment runs, exiting" );<br>
+            return NULL;<br>
+        }<br>
+<br>
+        s->fragment_runs[s->fragment_run_count].fragment_number_start = U32_AT(data_p);<br>
+        data_p += 4;<br>
+<br>
+        s->fragment_runs[s->fragment_run_count].fragment_timestamp = U64_AT( data_p );<br>
+        data_p += 8;<br>
+<br>
+        s->fragment_runs[s->fragment_run_count].fragment_duration = U32_AT( data_p );<br>
+        data_p += 4;<br>
+<br>
+        s->fragment_runs[s->fragment_run_count].discont = 0;<br>
+        if( s->fragment_runs[s->fragment_run_count].fragment_duration == 0 )<br>
+        {<br>
+            /* discontinuity flag */<br>
+            s->fragment_runs[s->fragment_run_count].discont = *(data_p++);<br>
+        }<br>
+<br>
+        s->fragment_run_count++;<br>
+    }<br>
+<br>
+    return data_p;<br>
+}<br>
+<br>
+static volatile chunk_t* chunk_new()<br>
+{<br>
+    volatile chunk_t* chunk = malloc(sizeof(chunk_t));<br>
+    memset( chunk, 0, sizeof(chunk_t) );<br>
+    return chunk;<br>
+}<br>
+<br>
+static void chunk_free( volatile chunk_t * chunk )<br>
+{<br>
+    FREENULL( chunk->data );<br>
+    free( chunk );<br>
+}<br>
+<br>
+static void parse_BootstrapData( vlc_object_t* p_this,<br>
+                                 hds_stream_t * s,<br>
+                                 char* data,<br>
+                                 char* data_end )<br>
+{<br>
+    char* data_p = data;<br>
+<br>
+    uint32_t abst_len = U32_AT( data_p );<br>
+    if( abst_len > data_end - data<br>
+        || data_end - data < 29 /* min size of data */ )<br>
+    {<br>
+        msg_Warn( p_this, "Not enough bootstrap data" );<br>
+        return;<br>
+    }<br>
+    data_p += sizeof(abst_len);<br>
+<br>
+    if( 0 != memcmp( data_p, "abst", 4 ) )<br>
+    {<br>
+        msg_Warn( p_this, "Cant find abst in bootstrap" );<br>
+        return;<br>
+    }<br>
+    data_p += 4;<br>
+<br>
+    /* version, flags*/<br>
+    data_p += 4;<br>
+<br>
+    /* we ignore the version */<br>
+    data_p += 4;<br>
+<br>
+    /* some flags we don't care about here because they are<br>
+     * in the manifest<br>
+     */<br>
+    data_p += 1;<br>
+<br>
+    /* timescale */<br>
+    s->timescale = U32_AT( data_p );<br>
+    data_p += sizeof(s->timescale);<br>
+<br>
+    s->live_current_time = U64_AT( data_p );<br>
+    data_p += sizeof(s->live_current_time);<br>
+<br>
+    /* smtpe time code offset */<br>
+    data_p += 8;<br>
+<br>
+    s->movie_id = strndup( data_p, data_end - data_p );<br>
+    data_p += ( strlen( s->movie_id ) + 1 );<br>
+<br>
+    if( data_end - data_p < 4 ) {<br>
+        msg_Warn( p_this, "Not enough bootstrap after Movie Identifier" );<br>
+        return;<br>
+    }<br>
+<br>
+    uint8_t server_entry_count = 0;<br>
+    server_entry_count = (uint8_t) *data_p;<br>
+    data_p++;<br>
+<br>
+    s->server_entry_count = 0;<br>
+    while( server_entry_count-- > 0 )<br>
+    {<br>
+        if( s->server_entry_count < MAX_HDS_SERVERS )<br>
+        {<br>
+            s->server_entries[s->server_entry_count++] = strndup( data_p,<br>
+                                                                  data_end - data_p );<br>
+            data_p += strlen( s->server_entries[s->server_entry_count-1] ) + 1;<br>
+        }<br>
+        else<br>
+        {<br>
+            msg_Warn( p_this, "Too many servers" );<br>
+            data_p = memchr( data_p, '\0', data_end );<br>
+            if( ! data_p )<br>
+            {<br>
+                msg_Err( p_this, "Couldn't find server entry" );<br>
+                return NULL;<br>
+            }<br>
+            data_p++;<br>
+        }<br>
+<br>
+        if( data_p >= data_end )<br>
+        {<br>
+            msg_Warn( p_this, "Premature end of bootstrap info while reading servers" );<br>
+            return;<br>
+        }<br>
+    }<br>
+<br>
+    if( data_end - data_p < 3 ) {<br>
+        msg_Warn( p_this, "Not enough bootstrap after Servers" );<br>
+        return;<br>
+    }<br>
+<br>
+    s->quality_segment_modifier = 0;<br>
+<br>
+    uint8_t quality_entry_count = *data_p;<br>
+    data_p++;<br>
+<br>
+    if( quality_entry_count > 1 )<br>
+    {<br>
+        msg_Err( p_this, "I don't know what to do with multiple quality levels in the bootstrap - shouldn't this be handled at the manifest level?" );<br>
+        return;<br>
+    }<br>
+<br>
+    s->quality_segment_modifier = 0;<br>
+    while( quality_entry_count-- > 0 )<br>
+    {<br>
+        if( s->quality_segment_modifier != 0 )<br>
+        {<br>
+            s->quality_segment_modifier = strndup( data_p, data_end - data_p );<br>
+        }<br>
+        data_p += strnlen( data_p, data_end - data_p ) + 1;<br>
+    }<br>
+<br>
+    if( data_end - data_p < 2 ) {<br>
+        msg_Warn( p_this, "Not enough bootstrap after quality entries" );<br>
+        return;<br>
+    }<br>
+<br>
+    /* ignoring "DrmData" */<br>
+    data_p = memchr( data_p, '\0', data_end - data_p );<br>
+    if( ! data_p )<br>
+    {<br>
+        msg_Err( p_this, "Couldn't find DRM Data" );<br>
+        return NULL;<br>
+    }<br>
+    data_p++;<br>
+<br>
+    if( data_end - data_p < 2 ) {<br>
+        msg_Warn( p_this, "Not enough bootstrap after drm data" );<br>
+        return;<br>
+    }<br>
+<br>
+    /* ignoring "metadata" */<br>
+    data_p = memchr( data_p, '\0', data_end - data_p );<br>
+    if( ! data_p )<br>
+    {<br>
+        msg_Err( p_this, "Couldn't find metadata");<br>
+        return NULL;<br>
+    }<br>
+    data_p++;<br>
+<br>
+    if( data_end - data_p < 2 ) {<br>
+        msg_Warn( p_this, "Not enough bootstrap after drm data" );<br>
+        return;<br>
+    }<br>
+<br>
+    uint8_t asrt_count = *data_p;<br>
+    data_p++;<br>
+<br>
+    s->segment_run_count = 0;<br>
+    while( asrt_count-- > 0 &&<br>
+           data_end > data_p &&<br>
+           (data_p = parse_asrt( p_this, s, data_p, data_end )) );<br>
+<br>
+    uint8_t afrt_count = *data_p;<br>
+    data_p++;<br>
+<br>
+    s->fragment_run_count = 0;<br>
+    while( afrt_count-- > 0 &&<br>
+           data_end > data_p &&<br>
+           (data_p = parse_afrt( p_this, s, data_p, data_end )) );<br>
+}<br>
+<br>
+/* this only works with ANSI characters - this is ok<br>
+   for the bootstrapinfo field which this function is<br>
+   exclusively used for since it is merely a base64 encoding<br>
+*/<br>
+static bool is_whitespace( char c )<br>
+{<br>
+    return ( ' '  == c ||<br>
+             '\t' == c ||<br>
+             '\n' == c ||<br>
+             '\v' == c ||<br>
+             '\f' == c ||<br>
+             '\r' == c );<br>
+}<br>
+<br>
+/* see above note for is_whitespace */<br>
+static void whitespace_substr( char** start,<br>
+                               char** end )<br>
+{<br>
+    while( is_whitespace( **start ) && *start != *end ) {<br>
+        (*start)++;<br>
+    }<br>
+<br>
+    if( *start == *end )<br>
+        return;<br>
+<br>
+    while( is_whitespace(*(*end - 1) ) ) {<br>
+        end--;<br>
+    }<br>
+}<br>
+<br>
+/* returns length (could be zero, indicating all of remaining data) */<br>
+/* ptr is to start of data, right after 'mdat' string */<br>
+static uint32_t find_chunk_mdat( vlc_object_t* p_this,<br>
+                                 uint8_t* chunkdata, uint8_t* chunkdata_end,<br>
+                                 uint8_t** mdatptr )<br>
+{<br>
+    char* boxname = 0;<br>
+    char* boxdata = 0;<br>
+    uint64_t boxsize = 0;<br>
+<br>
+    do {<br>
+        if( chunkdata_end < chunkdata ||<br>
+            chunkdata_end - chunkdata < 8 )<br>
+        {<br>
+            msg_Err( p_this, "Couldn't find mdat in box 1!" );<br>
+            *mdatptr = 0;<br>
+            return 0;<br>
+        }<br>
+<br>
+        boxsize = U32_AT( chunkdata );<br>
+        chunkdata += 4;<br>
+<br>
+        boxname = chunkdata;<br>
+        chunkdata += 4;<br>
+<br>
+        if( boxsize == 1 )<br>
+        {<br>
+            if( chunkdata_end - chunkdata >= 12 )<br>
+            {<br>
+                boxsize =  U64_AT(chunkdata);<br>
+                chunkdata += 8;<br>
+            }<br>
+            else<br>
+            {<br>
+                msg_Err( p_this, "Couldn't find mdat in box 2!");<br>
+                *mdatptr = 0;<br>
+                return 0;<br>
+            }<br>
+            boxdata = chunkdata;<br>
+            chunkdata += (boxsize - 16);<br>
+        }<br>
+        else<br>
+        {<br>
+            boxdata = chunkdata;<br>
+            chunkdata += (boxsize - 8);<br>
+        }<br>
+    } while ( 0 != memcmp( boxname, "mdat", 4 ) );<br>
+<br>
+    *mdatptr = boxdata;<br>
+<br>
+    return chunkdata_end - ((uint8_t*)boxdata);<br>
+}<br>
+<br>
+/* returns data ptr if valid (updating the chunk itself<br>
+   tells the reader that the chunk is safe to read, which is not yet correct)*/<br>
+static uint8_t* download_chunk( stream_t *s,<br>
+                                stream_sys_t* sys,<br>
+                                hds_stream_t* stream, volatile chunk_t* chunk )<br>
+{<br>
+    const char* quality = "";<br>
+    char* server_base = sys->base_url;<br>
+    if( stream->server_entry_count > 0 &&<br>
+        strlen(stream->server_entries[0]) > 0 )<br>
+    {<br>
+        server_base = stream->server_entries[0];<br>
+    }<br>
+<br>
+    if( stream->quality_segment_modifier )<br>
+    {<br>
+        quality = stream->quality_segment_modifier;<br>
+    }<br>
+<br>
+    const char* movie_id = "";<br>
+    bool free_movie_id = false;<br>
+    if( stream->url && strlen(stream->url) > 0 )<br>
+    {<br>
+        if( isFQUrl( stream->url ) )<br>
+        {<br>
+            server_base = stream->url;<br>
+        }<br>
+        else<br>
+        {<br>
+            movie_id = stream->url;<br>
+        }<br>
+    }<br>
+<br>
+    char* fragment_url;<br>
+    asprintf( &fragment_url, "%s/%s%sSeg%u-Frag%u",<br>
+              server_base,<br>
+              movie_id,<br>
+              quality,<br>
+              chunk->seg_num,<br>
+              chunk->frag_num );<br>
+    if( ! fragment_url )<br>
+    {<br>
+        msg_Err(s, "Failed to allocate memory for fragment url" );<br>
+        return NULL;<br>
+    }<br>
+<br>
+    msg_Info(s, "Downloading fragment %s",<br>
+             fragment_url );<br>
+<br>
+    stream_t* download_stream = stream_UrlNew( s, fragment_url );<br>
+    if( ! download_stream )<br>
+    {<br>
+        msg_Err(s, "Failed to download fragment %s", fragment_url );<br>
+        free( fragment_url );<br>
+        chunk->failed = true;<br>
+        return NULL;<br>
+    }<br>
+    free( fragment_url );<br>
+<br>
+    int64_t size = stream_Size( download_stream );<br>
+    chunk->data_len = (uint32_t) size;<br>
+<br>
+    if( size > 50*1024*1024 )<br>
+    {<br>
+        msg_Err(s, "Strangely-large chunk of %"PRIi64" Bytes" );<br>
+        return NULL;<br>
+    }<br>
+<br>
+    uint8_t* data = malloc( size );<br>
+    if( ! data )<br>
+    {<br>
+        msg_Err(s, "Couldn't allocate chunk" );<br>
+        return NULL;<br>
+    }<br>
+<br>
+    int read = stream_Read( download_stream, data,<br>
+                            size );<br>
+    chunk->data_len = read;<br>
+<br>
+    if( read < size )<br>
+    {<br>
+        msg_Err( s, "Requested %"PRIi64" bytes, "\<br>
+                 "but only got %d", size, read );<br>
+        data = realloc( chunk->data, read );<br>
+        chunk->failed = true;<br>
+        return NULL;<br>
+    }<br>
+    else<br>
+    {<br>
+        chunk->failed = false;<br>
+    }<br>
+<br>
+    stream_Delete( download_stream );<br>
+    return data;<br>
+}<br>
+<br>
+static void* download_thread( void* p )<br>
+{<br>
+    vlc_object_t* p_this = (vlc_object_t*)p;<br>
+    stream_t* s = (stream_t*) p_this;<br>
+    stream_sys_t* sys = s->p_sys;<br>
+<br>
+    // TODO: Change here for selectable stream<br>
+    hds_stream_t* hds_stream = sys->hds_streams->pp_elems[0];<br>
+<br>
+    int canc = vlc_savecancel();<br>
+<br>
+    vlc_mutex_lock( & hds_stream->dl_lock );<br>
+<br>
+    while( ! sys->closed )<br>
+    {<br>
+        if( ! hds_stream->chunks_downloadpos )<br>
+        {<br>
+            volatile chunk_t* chunk = hds_stream->chunks_head;<br>
+            while(chunk && chunk->data )<br>
+            {<br>
+                chunk = chunk->next;<br>
+            }<br>
+<br>
+            if( chunk && ! chunk->data )<br>
+                hds_stream->chunks_downloadpos = chunk;<br>
+        }<br>
+<br>
+        while( hds_stream->chunks_downloadpos )<br>
+        {<br>
+            volatile chunk_t *chunk = hds_stream->chunks_downloadpos;<br>
+<br>
+            uint8_t *data = download_chunk( (stream_t*)p_this,<br>
+                                            sys,<br>
+                                            hds_stream,<br>
+                                            chunk );<br>
+<br>
+            if( ! chunk->failed )<br>
+            {<br>
+                chunk->mdat_len =<br>
+                    find_chunk_mdat( p_this,<br>
+                                     data,<br>
+                                     data + chunk->data_len,<br>
+                                     & chunk->mdat_data );<br>
+                if( chunk->mdat_len == 0 ) {<br>
+                    chunk->mdat_len = chunk->data_len - (chunk->mdat_data - data);<br>
+                }<br>
+                hds_stream->chunks_downloadpos = chunk->next;<br>
+                chunk->data = data;<br>
+<br>
+                sys->chunk_count++;<br>
+            }<br>
+        }<br>
+<br>
+        vlc_cond_wait( & hds_stream->dl_cond,<br>
+                       & hds_stream->dl_lock );<br>
+    }<br>
+<br>
+    vlc_mutex_unlock( & hds_stream->dl_lock );<br>
+<br>
+    vlc_restorecancel( canc );<br>
+    return NULL;<br>
+}<br>
+<br>
+static volatile chunk_t* generate_new_chunk(<br>
+    vlc_object_t* p_this,<br>
+    volatile chunk_t* last_chunk,<br>
+    hds_stream_t* hds_stream )<br>
+{<br>
+    stream_t* s = (stream_t*) p_this;<br>
+    stream_sys_t *sys = s->p_sys;<br>
+    volatile chunk_t *chunk = chunk_new();<br>
+    int frun_entry = 0;<br>
+<br>
+    if( last_chunk )<br>
+    {<br>
+        chunk->timestamp = last_chunk->timestamp + last_chunk->duration;<br>
+        chunk->frag_num = last_chunk->frag_num + 1;<br>
+<br>
+        if( ! sys->live )<br>
+        {<br>
+            frun_entry = last_chunk->frun_entry;<br>
+        }<br>
+    }<br>
+    else<br>
+    {<br>
+        fragment_run_t* first_frun  = hds_stream->fragment_runs;<br>
+        fragment_run_t* latest_frun = hds_stream->fragment_runs + ( hds_stream->fragment_run_count - 1 );<br>
+        if( sys->live )<br>
+        {<br>
+            chunk->timestamp = (hds_stream->live_current_time * ((uint64_t)hds_stream->afrt_timescale)) / ((uint64_t)hds_stream->timescale);<br>
+        }<br>
+        else<br>
+        {<br>
+            chunk->timestamp = first_frun->fragment_timestamp;<br>
+            chunk->frag_num =  first_frun->fragment_number_start;<br>
+        }<br>
+    }<br>
+<br>
+    for( frun_entry; frun_entry < hds_stream->fragment_run_count;<br>
+         frun_entry++ )<br>
+    {<br>
+        /* check for discontinuity first */<br>
+        if( hds_stream->fragment_runs[frun_entry].fragment_duration == 0 )<br>
+        {<br>
+            if( frun_entry == hds_stream->fragment_run_count - 1 )<br>
+            {<br>
+                msg_Err( p_this, "Discontinuity but can't find next timestamp!");<br>
+                return NULL;<br>
+            }<br>
+<br>
+            chunk->frag_num = hds_stream->fragment_runs[frun_entry+1].fragment_number_start;<br>
+            chunk->duration = hds_stream->fragment_runs[frun_entry+1].fragment_duration;<br>
+            chunk->timestamp = hds_stream->fragment_runs[frun_entry+1].fragment_timestamp;<br>
+<br>
+            frun_entry++;<br>
+            break;<br>
+        }<br>
+<br>
+        if( chunk->frag_num == 0 )<br>
+        {<br>
+            if( frun_entry == hds_stream->fragment_run_count - 1 ||<br>
+                ( chunk->timestamp >= hds_stream->fragment_runs[frun_entry].fragment_timestamp &&<br>
+                  chunk->timestamp < hds_stream->fragment_runs[frun_entry+1].fragment_timestamp )<br>
+                )<br>
+            {<br>
+                fragment_run_t* frun = hds_stream->fragment_runs + frun_entry;<br>
+                chunk->frag_num = frun->fragment_number_start + ( (chunk->timestamp - frun->fragment_timestamp) /<br>
+                                                                  frun->fragment_duration );<br>
+                chunk->duration = frun->fragment_duration;<br>
+            }<br>
+<br>
+        }<br>
+<br>
+        if( hds_stream->fragment_runs[frun_entry].fragment_number_start <=<br>
+            chunk->frag_num &&<br>
+            (frun_entry == hds_stream->fragment_run_count - 1 ||<br>
+             hds_stream->fragment_runs[frun_entry+1].fragment_number_start > chunk->frag_num ) )<br>
+        {<br>
+            chunk->duration = hds_stream->fragment_runs[frun_entry].fragment_duration;<br>
+            chunk->timestamp = hds_stream->fragment_runs[frun_entry].fragment_timestamp +<br>
+                chunk->duration * (chunk->frag_num - hds_stream->fragment_runs[frun_entry].fragment_number_start);<br>
+            break;<br>
+        }<br>
+    }<br>
+<br>
+    if( frun_entry == hds_stream->fragment_run_count )<br>
+    {<br>
+        msg_Err( p_this, "Couldn'd find the fragment run!" );<br>
+        return NULL;<br>
+    }<br>
+<br>
+    int srun_entry = 0;<br>
+    int segment = 0;<br>
+    uint64_t fragments_accum = chunk->frag_num;<br>
+    for( srun_entry = 0; srun_entry < hds_stream->segment_run_count;<br>
+         srun_entry++ )<br>
+    {<br>
+        segment = hds_stream->segment_runs[srun_entry].first_segment +<br>
+            (chunk->frag_num - fragments_accum ) / hds_stream->segment_runs[srun_entry].fragments_per_segment;<br>
+<br>
+        if( srun_entry + 1 == hds_stream->segment_run_count ||<br>
+            hds_stream->segment_runs[srun_entry+1].first_segment > segment )<br>
+        {<br>
+            break;<br>
+        }<br>
+<br>
+        fragments_accum += (<br>
+            (hds_stream->segment_runs[srun_entry+1].first_segment -<br>
+             hds_stream->segment_runs[srun_entry].first_segment) *<br>
+            hds_stream->segment_runs[srun_entry].fragments_per_segment );<br>
+    }<br>
+<br>
+    chunk->seg_num = segment;<br>
+    chunk->frun_entry = frun_entry;<br>
+<br>
+    if( ! sys->live )<br>
+    {<br>
+        if( (chunk->timestamp + chunk->duration) / hds_stream->afrt_timescale  >= sys->duration_seconds )<br>
+        {<br>
+            chunk->eof = true;<br>
+        }<br>
+    }<br>
+<br>
+    return chunk;<br>
+}<br>
+<br>
+static void* maintain_live_chunks(<br>
+    vlc_object_t* p_this,<br>
+    hds_stream_t* hds_stream<br>
+    )<br>
+{<br>
+    if( ! hds_stream->chunks_head )<br>
+    {<br>
+        /* just start with the earliest in the abst<br>
+         * maybe it would be better to use the currentMediaTime?<br>
+         * but then we are right on the edge of buffering, esp for<br>
+         * small fragments */<br>
+        hds_stream->chunks_head = generate_new_chunk( p_this, 0, hds_stream );<br>
+        hds_stream->chunks_livereadpos = hds_stream->chunks_head;<br>
+    }<br>
+<br>
+    volatile chunk_t* chunk = hds_stream->chunks_head;<br>
+    bool dl = false;<br>
+    while( ( chunk->timestamp * ((uint64_t)hds_stream->timescale) )<br>
+           / ((uint64_t)hds_stream->afrt_timescale)<br>
+           <= hds_stream->live_current_time )<br>
+    {<br>
+        if( chunk->next )<br>
+        {<br>
+            chunk = chunk->next;<br>
+        }<br>
+        else<br>
+        {<br>
+            chunk->next = generate_new_chunk( p_this, chunk, hds_stream );<br>
+            chunk = chunk->next;<br>
+            dl = true;<br>
+        }<br>
+    }<br>
+<br>
+    if( dl )<br>
+        vlc_cond_signal( & hds_stream->dl_cond );<br>
+<br>
+    chunk = hds_stream->chunks_head;<br>
+    while( chunk && chunk->data && chunk->mdat_pos >= chunk->mdat_len && chunk->next )<br>
+    {<br>
+        volatile chunk_t* next_chunk = chunk->next;<br>
+        chunk_free( chunk );<br>
+        chunk = chunk->next;<br>
+    }<br>
+<br>
+    if( ! hds_stream->chunks_livereadpos )<br>
+        hds_stream->chunks_livereadpos = hds_stream->chunks_head;<br>
+<br>
+    hds_stream->chunks_head = chunk;<br>
+}<br>
+<br>
+static void* live_thread( void* p )<br>
+{<br>
+    vlc_object_t* p_this = (vlc_object_t*)p;<br>
+    stream_t* s = (stream_t*) p_this;<br>
+    stream_sys_t* sys = s->p_sys;<br>
+<br>
+    // TODO: Change here for selectable stream<br>
+    hds_stream_t* hds_stream = sys->hds_streams->pp_elems[0];<br>
+<br>
+    int canc = vlc_savecancel();<br>
+<br>
+    char* abst_url;<br>
+<br>
+    if( hds_stream->abst_url &&<br>
+        ( isFQUrl( hds_stream->abst_url ) ) )<br>
+    {<br>
+        abst_url = strdup ( hds_stream->abst_url );<br>
+    }<br>
+    else<br>
+    {<br>
+        char* server_base = sys->base_url;<br>
+<br>
+        asprintf( &abst_url, "%s/%s",<br>
+                  server_base,<br>
+                  hds_stream->abst_url );<br>
+    }<br>
+<br>
+    mtime_t last_dl_start_time;<br>
+<br>
+    while( ! sys->closed )<br>
+    {<br>
+        last_dl_start_time = mdate();<br>
+        stream_t* download_stream = stream_UrlNew( p_this, abst_url );<br>
+        if( ! download_stream )<br>
+        {<br>
+            msg_Err( p_this, "Failed to download abst %s", abst_url );<br>
+        }<br>
+        else<br>
+        {<br>
+            int64_t size = stream_Size( download_stream );<br>
+            uint8_t* data = malloc( size );<br>
+            int read = stream_Read( download_stream, data,<br>
+                                    size );<br>
+            if( read < size )<br>
+            {<br>
+                msg_Err( p_this, "Requested %"PRIi64" bytes, "  \<br>
+                         "but only got %d", size, read );<br>
+<br>
+            }<br>
+            else<br>
+            {<br>
+                vlc_mutex_lock( & hds_stream->abst_lock );<br>
+                parse_BootstrapData( p_this, hds_stream,<br>
+                                     data, data + read );<br>
+                vlc_mutex_unlock( & hds_stream->abst_lock );<br>
+                maintain_live_chunks( p_this, hds_stream );<br>
+            }<br>
+<br>
+            free( data );<br>
+<br>
+            stream_Delete( download_stream );<br>
+        }<br>
+<br>
+        mwait( last_dl_start_time + ( ((uint64_t)hds_stream->fragment_runs[hds_stream->fragment_run_count-1].fragment_duration) * 1000000ULL) / ((uint64_t)hds_stream->afrt_timescale) );<br>
+<br>
+<br>
+    }<br>
+<br>
+    free( abst_url );<br>
+<br>
+    vlc_restorecancel( canc );<br>
+    return NULL;<br>
+}<br>
+<br>
+static int parse_Manifest( stream_t *s )<br>
+{<br>
+    xml_t *vlc_xml = NULL;<br>
+    xml_reader_t *vlc_reader = NULL;<br>
+    int type = UNKNOWN_ES;<br>
+    stream_t *st = s->p_source;<br>
+<br>
+    msg_Dbg( s, "Manifest parsing\n" );<br>
+<br>
+    vlc_xml = xml_Create( st );<br>
+    if( !vlc_xml )<br>
+    {<br>
+        msg_Err( s, "Failed to open XML parser" );<br>
+        return VLC_EGENERIC;<br>
+    }<br>
+<br>
+    vlc_reader = xml_ReaderCreate( vlc_xml, st );<br>
+    if( !vlc_reader )<br>
+    {<br>
+        msg_Err( s, "Failed to open source for parsing" );<br>
+        xml_Delete( vlc_xml );<br>
+        return VLC_EGENERIC;<br>
+    }<br>
+<br>
+    char *node;<br>
+<br>
+    stream_sys_t *sys = s->p_sys;<br>
+<br>
+    sys->duration_seconds = 0;<br>
+<br>
+#define MAX_BOOTSTRAP_INFO 10<br>
+    bootstrap_info bootstraps[MAX_BOOTSTRAP_INFO];<br>
+    uint8_t bootstrap_idx = 0;<br>
+    memset( bootstraps, 0, sizeof(bootstrap_info) * MAX_BOOTSTRAP_INFO );<br>
+<br>
+#define MAX_MEDIA_ELEMENTS 10<br>
+    media_info medias[MAX_MEDIA_ELEMENTS];<br>
+    uint8_t media_idx = 0;<br>
+    memset( medias, 0, sizeof(media_info) * MAX_MEDIA_ELEMENTS );<br>
+<br>
+#define MAX_XML_DEPTH 256<br>
+    char* element_stack[256];<br>
+    uint8_t current_element_idx = 0;<br>
+    char* current_element = 0;<br>
+    memset( element_stack, 0, sizeof(char*) * MAX_XML_DEPTH );<br>
+<br>
+    const char* attr_name;<br>
+    const char* attr_value;<br>
+<br>
+    char* media_id = 0;<br>
+<br>
+#define TIMESCALE 10000000<br>
+    while( (type = xml_ReaderNextNode( vlc_reader, &node )) > 0 )<br>
+    {<br>
+        switch( type )<br>
+        {<br>
+        case XML_READER_STARTELEM:<br>
+            if( current_element_idx == 0 && element_stack[current_element_idx] == 0 ) {<br>
+                element_stack[current_element_idx] = strdup( node );<br>
+            } else {<br>
+                element_stack[++current_element_idx] = strdup( node );<br>
+            }<br>
+            break;<br>
+        case XML_READER_ENDELEM:<br>
+            if( ! strcmp( current_element, "bootstrapInfo") ) {<br>
+                if( bootstrap_idx + 1 == MAX_BOOTSTRAP_INFO ) {<br>
+                    msg_Warn( (vlc_object_t*) s, "Too many bootstraps, ignoring" );<br>
+                } else {<br>
+                    bootstrap_idx++;<br>
+                }<br>
+            }<br>
+<br>
+            free( current_element );<br>
+            element_stack[current_element_idx--] = 0;<br>
+            break;<br>
+        }<br>
+<br>
+        if( ! element_stack[current_element_idx] ) {<br>
+            continue;<br>
+        }<br>
+<br>
+        current_element = element_stack[current_element_idx];<br>
+<br>
+        if( type == XML_READER_STARTELEM && ! strcmp( current_element, "media") )<br>
+        {<br>
+            if( media_idx == MAX_MEDIA_ELEMENTS )<br>
+            {<br>
+                msg_Err( (vlc_object_t*) s, "Too many media elements, quitting" );<br>
+                return VLC_EGENERIC;<br>
+            }<br>
+<br>
+            while( attr_name = xml_ReaderNextAttr( vlc_reader, &attr_value ) )<br>
+            {<br>
+                if( !strcmp(attr_name, "streamId" ) )<br>
+                {<br>
+                    medias[media_idx].stream_id = strdup( attr_value );<br>
+                }<br>
+                if( !strcmp(attr_name, "url" ) )<br>
+                {<br>
+                    medias[media_idx].media_url = strdup( attr_value );<br>
+                }<br>
+                if( !strcmp(attr_name, "bootstrapInfoId" ) )<br>
+                {<br>
+                    medias[media_idx].bootstrap_id = strdup( attr_value );<br>
+                }<br>
+            }<br>
+<br>
+            media_idx++;<br>
+        }<br>
+<br>
+        if( type == XML_READER_STARTELEM && ! strcmp( current_element, "bootstrapInfo") )<br>
+        {<br>
+            while( attr_name = xml_ReaderNextAttr( vlc_reader, &attr_value ) )<br>
+            {<br>
+                if( !strcmp(attr_name, "url" ) )<br>
+                {<br>
+                    bootstraps[bootstrap_idx].url = strdup( attr_value );<br>
+                }<br>
+                if( !strcmp(attr_name, "id" ) )<br>
+                {<br>
+                    bootstraps[bootstrap_idx].id = strdup( attr_value );<br>
+                }<br>
+                if( !strcmp(attr_name, "profile" ) )<br>
+                {<br>
+                    bootstraps[bootstrap_idx].profile = strdup( attr_value );<br>
+                }<br>
+            }<br>
+        }<br>
+<br>
+        if( type == XML_READER_TEXT )<br>
+        {<br>
+            if( ! strcmp( current_element, "bootstrapInfo" ) )<br>
+            {<br>
+                char* start = node;<br>
+                char* end = start + strlen(start);<br>
+                whitespace_substr( &start, &end );<br>
+                *end = '\0';<br>
+<br>
+                bootstraps[bootstrap_idx].data_len =<br>
+                    vlc_b64_decode_binary( (uint8_t**)&bootstraps[bootstrap_idx].data, start );<br>
+                if( ! bootstraps[bootstrap_idx].data )<br>
+                {<br>
+                    msg_Err( (vlc_object_t*) s, "Couldn't decode bootstrap info" );<br>
+                }<br>
+            }<br>
+            if( ! strcmp( current_element, "duration" ) )<br>
+            {<br>
+                sys->duration_seconds = (int)atof( node );<br>
+            }<br>
+            if( ! strcmp( current_element, "id" ) )<br>
+            {<br>
+                if( current_element != 0 &&<br>
+                    ! strcmp( element_stack[current_element_idx-1], "manifest" ) )<br>
+                {<br>
+                    media_id = strdup( node );<br>
+                }<br>
+            }<br>
+        }<br>
+    }<br>
+<br>
+    xml_ReaderDelete( vlc_reader );<br>
+    xml_Delete( vlc_xml );<br>
+<br>
+    for( int i = 0; i <= media_idx; i++ )<br>
+    {<br>
+        for( int j = 0; j < bootstrap_idx; j++ )<br>
+        {<br>
+            if( ( ! medias[i].bootstrap_id && ! bootstraps[j].id ) ||<br>
+                (medias[i].bootstrap_id && bootstraps[j].id &&<br>
+                 ! strcmp( medias[i].bootstrap_id, bootstraps[j].id ) ) )<br>
+            {<br>
+                hds_stream_t* new_stream = malloc(sizeof(hds_stream_t));<br>
+                memset( new_stream, 0, sizeof(hds_stream_t));<br>
+<br>
+                vlc_mutex_init( & new_stream->abst_lock );<br>
+                vlc_mutex_init( & new_stream->dl_lock );<br>
+                vlc_cond_init( & new_stream->dl_cond );<br>
+<br>
+                if( sys->duration_seconds )<br>
+                {<br>
+                    sys->live = false;<br>
+                }<br>
+                else<br>
+                {<br>
+                    sys->live = true;<br>
+                }<br>
+<br>
+                if( medias[i].media_url )<br>
+                {<br>
+                    new_stream->url = strdup( medias[i].media_url );<br>
+                }<br>
+<br>
+                if( ! sys->live )<br>
+                {<br>
+                    parse_BootstrapData( (vlc_object_t*)s,<br>
+                                         new_stream,<br>
+                                         bootstraps[j].data,<br>
+                                         bootstraps[j].data + bootstraps[j].data_len );<br>
+<br>
+                    new_stream->download_leadtime = 15;<br>
+<br>
+                    new_stream->chunks_head = generate_new_chunk( s, 0, new_stream );<br>
+                    volatile chunk_t* chunk = new_stream->chunks_head;<br>
+                    uint64_t total_duration = chunk->duration;<br>
+                    while( chunk && total_duration/new_stream->afrt_timescale < new_stream->download_leadtime )<br>
+                    {<br>
+                        chunk->next = generate_new_chunk( s, chunk, new_stream );<br>
+                        chunk = chunk->next;<br>
+                        if( chunk )<br>
+                            total_duration += chunk->duration;<br>
+                    }<br>
+                }<br>
+                else<br>
+                {<br>
+                    new_stream->abst_url = strdup( bootstraps[j].url );<br>
+                }<br>
+<br>
+                vlc_array_append( sys->hds_streams, new_stream );<br>
+<br>
+                msg_Info( (vlc_object_t*)s, "New track with quality_segment(%s), timescale(%u), movie_id(%s), segment_run_count(%d), fragment_run_count(%u)",<br>
+                          new_stream->quality_segment_modifier?"":new_stream->quality_segment_modifier, new_stream->timescale,<br>
+                          new_stream->movie_id, new_stream->segment_run_count, new_stream->fragment_run_count );<br>
+<br>
+            }<br>
+        }<br>
+    }<br>
+<br>
+    for( int i = 0; i < MAX_MEDIA_ELEMENTS; i++ )<br>
+    {<br>
+        FREENULL( medias[media_idx].stream_id );<br>
+        FREENULL( medias[media_idx].media_url );<br>
+        FREENULL( medias[media_idx].bootstrap_id );<br>
+    }<br>
+<br>
+    for( int i = 0; i < MAX_BOOTSTRAP_INFO; i++ )<br>
+    {<br>
+        FREENULL( bootstraps[i].data );<br>
+        FREENULL( bootstraps[i].id );<br>
+        FREENULL( bootstraps[i].url );<br>
+        FREENULL( bootstraps[i].profile );<br>
+    }<br>
+<br>
+    FREENULL( media_id );<br>
+<br>
+    return VLC_SUCCESS;<br>
+}<br>
+<br>
+static void hds_free( hds_stream_t *p_stream )<br>
+{<br>
+    FREENULL( p_stream->quality_segment_modifier );<br>
+<br>
+    FREENULL( p_stream->abst_url );<br>
+<br>
+    FREENULL( p_stream->url );<br>
+    FREENULL( p_stream->movie_id );<br>
+    for( int i = 0; i < p_stream->server_entry_count; i++ )<br>
+    {<br>
+        FREENULL( p_stream->server_entries[i] );<br>
+    }<br>
+<br>
+    volatile chunk_t* chunk = p_stream->chunks_head;<br>
+    while( chunk )<br>
+    {<br>
+        volatile chunk_t* next = chunk->next;<br>
+        chunk_free( chunk );<br>
+        chunk = next;<br>
+    }<br>
+<br>
+    free( p_stream );<br>
+}<br>
+<br>
+static void SysCleanup( stream_sys_t *p_sys )<br>
+{<br>
+    if ( p_sys->hds_streams )<br>
+    {<br>
+        for ( int i=0; i< p_sys->hds_streams->i_count ; i++ )<br>
+            hds_free( p_sys->hds_streams->pp_elems[i] );<br>
+        vlc_array_destroy( p_sys->hds_streams );<br>
+    }<br>
+    free( p_sys->base_url );<br>
+}<br>
+<br>
+static int Open( vlc_object_t *p_this )<br>
+{<br>
+    stream_t *s = (stream_t*)p_this;<br>
+    stream_sys_t *p_sys;<br>
+<br>
+    if( !isHDS( s ) )<br>
+        return VLC_EGENERIC;<br>
+<br>
+    msg_Info( p_this, "HTTP Dynamic Streaming (%s)", s->psz_path );<br>
+<br>
+    s->p_sys = p_sys = calloc( 1, sizeof(*p_sys ) );<br>
+    if( unlikely( p_sys == NULL ) )<br>
+        return VLC_ENOMEM;<br>
+<br>
+    char *uri = NULL;<br>
+    if( unlikely( asprintf( &uri, "%s://%s", s->psz_access, s->psz_path ) < 0 ) )<br>
+    {<br>
+        free( p_sys );<br>
+        return VLC_ENOMEM;<br>
+    }<br>
+<br>
+    /* remove the last part of the url */<br>
+    char *pos = strrchr( uri, '/');<br>
+    *pos = '\0';<br>
+    p_sys->base_url = uri;<br>
+<br>
+    p_sys->flv_header_bytes_sent = 0;<br>
+<br>
+    p_sys->hds_streams = vlc_array_new();<br>
+<br>
+    if( parse_Manifest( s ) != VLC_SUCCESS )<br>
+    {<br>
+        SysCleanup( p_sys );<br>
+        free( p_sys );<br>
+        return VLC_EGENERIC;<br>
+    }<br>
+<br>
+    s->pf_read = Read;<br>
+    s->pf_peek = Peek;<br>
+    s->pf_control = Control;<br>
+<br>
+    if( vlc_clone( &p_sys->dl_thread, download_thread, s, VLC_THREAD_PRIORITY_INPUT ) )<br>
+    {<br>
+        SysCleanup( p_sys );<br>
+        free( p_sys );<br>
+        return VLC_EGENERIC;<br>
+    }<br>
+<br>
+    if( p_sys->live ) {<br>
+        msg_Info( p_this, "Live stream detected" );<br>
+<br>
+        if( vlc_clone( &p_sys->live_thread, live_thread, s, VLC_THREAD_PRIORITY_INPUT ) )<br>
+        {<br>
+            SysCleanup( p_sys );<br>
+            free( p_sys );<br>
+            return VLC_EGENERIC;<br>
+        }<br>
+    }<br>
+<br>
+<br>
+    return VLC_SUCCESS;<br>
+}<br>
+<br>
+static void Close( vlc_object_t *p_this )<br>
+{<br>
+    stream_t *s = (stream_t*)p_this;<br>
+    stream_sys_t *p_sys = s->p_sys;<br>
+<br>
+    // TODO: Change here for selectable stream<br>
+    hds_stream_t *stream = s->p_sys->hds_streams->pp_elems[0];<br>
+<br>
+    p_sys->closed = true;<br>
+    vlc_cond_signal( & stream->dl_cond );<br>
+<br>
+    vlc_join( p_sys->dl_thread, NULL );<br>
+    vlc_mutex_destroy( &stream->dl_lock );<br>
+    vlc_cond_destroy( &stream->dl_cond );<br>
+    vlc_mutex_destroy( &stream->abst_lock );<br>
+<br>
+    if( p_sys->live )<br>
+    {<br>
+        vlc_join( p_sys->live_thread, NULL );<br>
+    }<br>
+<br>
+    SysCleanup( p_sys );<br>
+    free( p_sys );<br>
+}<br>
+<br>
+static unsigned char flv_header[] = {<br>
+        'F',<br>
+        'L',<br>
+        'V',<br>
+        0x1, //version<br>
+        0x5, //indicates audio and video<br>
+        0x0, // length<br>
+        0x0, // length<br>
+        0x0, // length<br>
+        0x9, // length of header<br>
+        0x0,<br>
+        0x0,<br>
+        0x0,<br>
+        0x0, // initial "trailer"<br>
+};<br>
+<br>
+static int send_flv_header( stream_sys_t* p_sys, void* buffer, unsigned i_read,<br>
+                            bool peek )<br>
+{<br>
+    uint32_t to_be_read = i_read;<br>
+    if( to_be_read > 13 - p_sys->flv_header_bytes_sent ) {<br>
+        to_be_read = 13 - p_sys->flv_header_bytes_sent;<br>
+    }<br>
+<br>
+    memcpy( buffer, flv_header + p_sys->flv_header_bytes_sent, to_be_read );<br>
+<br>
+    if( ! peek )<br>
+    {<br>
+        p_sys->flv_header_bytes_sent += to_be_read;<br>
+    }<br>
+    return to_be_read;<br>
+}<br>
+<br>
+static unsigned read_chunk_data(<br>
+    vlc_object_t* p_this,<br>
+    uint8_t* buffer, unsigned read_len,<br>
+    hds_stream_t* stream,<br>
+    bool* eof<br>
+    )<br>
+{<br>
+    stream_t* s = (stream_t*) p_this;<br>
+    stream_sys_t* sys = s->p_sys;<br>
+    volatile chunk_t* chunk = stream->chunks_head;<br>
+    uint8_t* buffer_start = buffer;<br>
+    bool dl = false;<br>
+<br>
+    while( chunk && chunk->data && read_len > 0 )<br>
+    {<br>
+        /* in the live case, it is necessary to store the next<br>
+         * pointer here, since as soon as we increment the mdat_pos, that<br>
+         * chunk may be deleted */<br>
+        volatile chunk_t* next = chunk->next;<br>
+<br>
+        if( chunk->mdat_pos < chunk->mdat_len )<br>
+        {<br>
+            unsigned cp_len = chunk->mdat_len - chunk->mdat_pos;<br>
+            if( cp_len > read_len )<br>
+                cp_len = read_len;<br>
+            memcpy( buffer, chunk->mdat_data + chunk->mdat_pos,<br>
+                    cp_len );<br>
+<br>
+            read_len -= cp_len;<br>
+            buffer += cp_len;<br>
+            chunk->mdat_pos += cp_len;<br>
+        }<br>
+<br>
+        if( ! sys->live && (chunk->mdat_pos >= chunk->mdat_len || chunk->failed) )<br>
+        {<br>
+            if( chunk->eof )<br>
+            {<br>
+                *eof = true;<br>
+            }<br>
+<br>
+            /* make sure there is at least one chunk in the queue */<br>
+            if( ! chunk->next && ! chunk->eof )<br>
+            {<br>
+                chunk->next = generate_new_chunk( p_this, chunk,  stream );<br>
+                dl = true;<br>
+            }<br>
+<br>
+            chunk_free( chunk );<br>
+            chunk = next;<br>
+            stream->chunks_head = chunk;<br>
+        }<br>
+        else if( sys->live && (chunk->mdat_pos >= chunk->mdat_len || chunk->failed) )<br>
+        {<br>
+            chunk = next;<br>
+        }<br>
+    }<br>
+<br>
+    if( sys->live )<br>
+    {<br>
+        stream->chunks_livereadpos = chunk;<br>
+    }<br>
+<br>
+    /* new chunk generation is handled by a different thread in live case */<br>
+    if( ! sys->live )<br>
+    {<br>
+        chunk = stream->chunks_head;<br>
+        if( chunk )<br>
+        {<br>
+            uint64_t total_duration = chunk->duration;<br>
+            while( chunk && total_duration/stream->afrt_timescale < stream->download_leadtime )<br>
+            {<br>
+                if( ! chunk->next )<br>
+                {<br>
+                    chunk->next = generate_new_chunk( p_this, chunk, stream );<br>
+                    dl = true;<br>
+                }<br>
+                chunk = chunk->next;<br>
+                total_duration += chunk->duration;<br>
+            }<br>
+        }<br>
+<br>
+        if( dl )<br>
+            vlc_cond_signal( & stream->dl_cond );<br>
+    }<br>
+<br>
+    return ( ((uint8_t*)buffer) - ((uint8_t*)buffer_start));<br>
+}<br>
+<br>
+static int Read( stream_t *s, void *buffer, unsigned i_read )<br>
+{<br>
+    stream_sys_t *p_sys = s->p_sys;<br>
+<br>
+    // TODO: change here for selectable stream<br>
+    hds_stream_t *stream = s->p_sys->hds_streams->pp_elems[0];<br>
+    int length = 0;<br>
+<br>
+    uint8_t *buffer_uint8 = (uint8_t*) buffer;<br>
+<br>
+    unsigned hdr_bytes = send_flv_header( p_sys, buffer, i_read, false );<br>
+    length += hdr_bytes;<br>
+    i_read -= hdr_bytes;<br>
+    buffer_uint8 += hdr_bytes;<br>
+<br>
+    bool eof = false;<br>
+    while( i_read > 0 && ! eof )<br>
+    {<br>
+        int tmp_length = read_chunk_data( s, buffer_uint8, i_read, stream, &eof );<br>
+        buffer_uint8 += tmp_length;<br>
+        i_read -= tmp_length;<br>
+        length += tmp_length;<br>
+    }<br>
+<br>
+    return length;<br>
+}<br>
+<br>
+static int Peek( stream_t *s, const uint8_t **pp_peek, unsigned i_peek )<br>
+{<br>
+    stream_sys_t *p_sys = s->p_sys;<br>
+<br>
+    // TODO: change here for selectable stream<br>
+    hds_stream_t *stream = p_sys->hds_streams->pp_elems[0];<br>
+<br>
+    if( p_sys->flv_header_bytes_sent < 13 )<br>
+    {<br>
+        *pp_peek = flv_header + p_sys->flv_header_bytes_sent;<br>
+        return 13 - p_sys->flv_header_bytes_sent;<br>
+    }<br>
+<br>
+    if( stream->chunks_head && ! stream->chunks_head->failed && stream->chunks_head->data )<br>
+    {<br>
+        // TODO: change here for selectable stream<br>
+        volatile chunk_t* chunk = stream->chunks_head;<br>
+        *pp_peek = chunk->mdat_data + chunk->mdat_pos;<br>
+        if( chunk->mdat_len - chunk->mdat_pos < i_peek )<br>
+        {<br>
+            return chunk->mdat_len - chunk->mdat_pos;<br>
+        }<br>
+        else<br>
+        {<br>
+            return i_peek;<br>
+        }<br>
+    } else<br>
+    {<br>
+        return 0;<br>
+    }<br>
+}<br>
+<br>
+static int Control( stream_t *s, int i_query, va_list args )<br>
+{<br>
+    stream_sys_t *p_sys = s->p_sys;<br>
+<br>
+    switch( i_query )<br>
+    {<br>
+        case STREAM_CAN_SEEK:<br>
+            *(va_arg( args, bool * )) = false;<br>
+            break;<br>
+        case STREAM_CAN_FASTSEEK:<br>
+        case STREAM_CAN_PAUSE: /* TODO */<br>
+            *(va_arg( args, bool * )) = false;<br>
+            break;<br>
+        case STREAM_CAN_CONTROL_PACE:<br>
+            *(va_arg( args, bool * )) = true;<br>
+            break;<br>
+        case STREAM_GET_PTS_DELAY:<br>
+            *va_arg (args, int64_t *) = INT64_C(1000) *<br>
+                var_InheritInteger(s, "network-caching");<br>
+             break;<br>
+        default:<br>
+            return VLC_EGENERIC;<br>
+    }<br>
+    return VLC_SUCCESS;<br>
+}<br>
diff --git a/modules/stream_filter/hds/hds.h b/modules/stream_filter/hds/hds.h<br>
new file mode 100644<br>
index 0000000..5a3b825<br>
--- /dev/null<br>
+++ b/modules/stream_filter/hds/hds.h<br>
@@ -0,0 +1,127 @@<br>
+/*****************************************************************************<br>
+ * hds.h: misc. stuff<br>
+ *****************************************************************************<br>
+ *<br>
+ * Author: Jonathan Thambidurai <jonathan AT fastly DOT com><br>
+ *<br>
+ * This library is free software; you can redistribute it and/or<br>
+ * modify it under the terms of the GNU Lesser General Public<br>
+ * License as published by the Free Software Foundation; either<br>
+ * version 2.1 of the License, or (at your option) any later version.<br>
+ *<br>
+ * This library is distributed in the hope that it will be useful,<br>
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU<br>
+ * Lesser General Public License for more details.<br>
+ *<br>
+ * You should have received a copy of the GNU Lesser General Public<br>
+ * License along with this library; if not, write to the Free Software<br>
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA<br>
+ *****************************************************************************/<br>
+<br>
+#ifndef _VLC_HDS_H<br>
+#define _VLC_HDS_H 1<br>
+<br>
+typedef struct chunk_s<br>
+{<br>
+    int64_t     duration;   /* chunk duration in afrt timescale units */<br>
+    int64_t     timestamp;<br>
+    int32_t     frag_num;<br>
+    uint32_t    seg_num;<br>
+    uint32_t frun_entry;     /* Used to speed things up in vod situations */<br>
+<br>
+    uint32_t    data_len;<br>
+<br>
+    uint32_t    mdat_pos;   /* position in the mdat */<br>
+    uint32_t    mdat_len;<br>
+<br>
+    void    *next;<br>
+<br>
+    uint8_t     *mdat_data;<br>
+    uint8_t     *data;<br>
+    bool failed;<br>
+    bool eof;<br>
+} chunk_t;<br>
+<br>
+typedef struct segment_run_s<br>
+{<br>
+    uint32_t first_segment;<br>
+    uint32_t fragments_per_segment;<br>
+} segment_run_t;<br>
+<br>
+typedef struct fragment_run_s<br>
+{<br>
+    uint32_t fragment_number_start;<br>
+    uint32_t fragment_duration;<br>
+    uint64_t fragment_timestamp;<br>
+    uint8_t  discont;<br>
+} fragment_run_t;<br>
+<br>
+typedef struct hds_stream_s<br>
+{<br>
+    /* linked-list of chunks */<br>
+    volatile chunk_t        *chunks_head;<br>
+    volatile chunk_t        *chunks_livereadpos;<br>
+    volatile chunk_t        *chunks_downloadpos;<br>
+<br>
+    char*          quality_segment_modifier;<br>
+<br>
+    /* we can make this configurable */<br>
+    uint64_t       download_leadtime;<br>
+<br>
+    /* in timescale units */<br>
+    uint32_t       total_duration;<br>
+<br>
+    uint32_t       afrt_timescale;<br>
+<br>
+    /* these two values come from the abst */<br>
+    uint32_t       timescale;<br>
+    uint64_t       live_current_time;<br>
+<br>
+    vlc_mutex_t  abst_lock;<br>
+<br>
+    vlc_mutex_t  dl_lock;<br>
+    vlc_cond_t   dl_cond;<br>
+<br>
+    /* can be left as null */<br>
+    char* abst_url;<br>
+<br>
+    /* this comes from the manifest media section  */<br>
+    char*          url;<br>
+<br>
+    /* this comes from the bootstrap info */<br>
+    char*          movie_id;<br>
+<br>
+#define MAX_HDS_SERVERS 10<br>
+    char*          server_entries[MAX_HDS_SERVERS];<br>
+    uint8_t        server_entry_count;<br>
+<br>
+#define MAX_HDS_SEGMENT_RUNS 256<br>
+    segment_run_t  segment_runs[MAX_HDS_SEGMENT_RUNS];<br>
+    uint8_t        segment_run_count;<br>
+<br>
+#define MAX_HDS_FRAGMENT_RUNS 10000<br>
+    fragment_run_t fragment_runs[MAX_HDS_FRAGMENT_RUNS];<br>
+    uint32_t        fragment_run_count;<br>
+} hds_stream_t;<br>
+<br>
+struct stream_sys_t<br>
+{<br>
+    char         *base_url;    /* URL common part for chunks */<br>
+    vlc_thread_t live_thread;<br>
+    vlc_thread_t dl_thread;<br>
+<br>
+    /* we pend on peek until some number of segments arrives; otherwise<br>
+     * the downstream system dies in case of playback */<br>
+    volatile uint64_t         chunk_count;<br>
+<br>
+    vlc_array_t  *hds_streams; /* available streams */<br>
+<br>
+    uint32_t     flv_header_bytes_sent;<br>
+    uint32_t duration_seconds;<br>
+<br>
+    bool live;<br>
+    bool         closed;<br>
+};<br>
+<br>
+#endif<br>
diff --git a/po/POTFILES.in b/po/POTFILES.in<br>
index 0a86072..cacff6a 100644<br>
--- a/po/POTFILES.in<br>
+++ b/po/POTFILES.in<br>
@@ -1009,6 +1009,7 @@ modules/services_discovery/windrive.c<br>
 modules/services_discovery/xcb_apps.c<br>
 modules/stream_filter/dash/dash.cpp<br>
 modules/stream_filter/decomp.c<br>
+modules/stream_filter/hds/hds.c<br>
 modules/stream_filter/httplive.c<br>
 modules/stream_filter/record.c<br>
 modules/stream_filter/smooth/smooth.c<br>
<span class="HOEnZb"><font color="#888888">--<br>
1.9.1<br>
<br>
</font></span></blockquote></div><br><br clear="all"><div><br></div>-- <br><div dir="ltr"><img src="https://www.fastly.com/sites/default/files/logo_email_siganture-01.png" width="96" height="44"><br><div><br></div><div><b>Jonathan Thambidurai</b> | Engineer | 650 440 3421</div>
<div><a href="http://fastly.com" target="_blank">fastly.com</a> | <a href="https://twitter.com/fastly" target="_blank">@fastly</a> | <a href="http://www.linkedin.com/company/fastly" target="_blank">LinkedIn</a><br></div></div>

</div>