<div dir="ltr"><div>mwait is unfortunately necessary in this case.<br></div><div><br></div><div>The maximum size was added at Remy's suggestion as a sanity check. 50 MB seems reasonable for a single chunk maximum.</div>
<div><br></div><div>The XML depth is also a sanity check, in case there is some bug or wildly malformed XML file.</div><div><br></div><div>The other #defines (bootstrap_info, etc..) are not defined in the spec, which says nothing about maximums here. I chose these as reasonable high (absurdly high in some cases) to not limit anything in practice and similarly prevent malformed manifests and/or buggy code from causing larger problems than necessary.</div>
<div><br></div><div>Regards,</div><div>Jonathan</div><div><br></div></div><div class="gmail_extra"><br><br><div class="gmail_quote">On Mon, Jun 30, 2014 at 11:17 AM, 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 | 7 +<br>
modules/stream_filter/hds/hds.c | 1672 +++++++++++++++++++++++++++++++++++++<br>
po/POTFILES.in | 1 +<br>
5 files changed, 1682 insertions(+)<br>
create mode 100644 modules/stream_filter/hds/hds.c<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..99a0647 100644<br>
--- a/modules/stream_filter/Makefile.am<br>
+++ b/modules/stream_filter/Makefile.am<br>
@@ -104,6 +104,13 @@ 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>
+<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..0f40c91<br>
--- /dev/null<br>
+++ b/modules/stream_filter/hds/hds.c<br>
@@ -0,0 +1,1672 @@<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 <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>
+typedef struct chunk_s<br>
+{<br>
+ int64_t duration; /* chunk duration in afrt timescale units */<br>
+ uint64_t timestamp;<br>
+ uint32_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>
+ chunk_t *chunks_head;<br>
+ chunk_t *chunks_livereadpos;<br>
+ 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>
+/* this is effectively just a sanity check mechanism */<br>
+#define MAX_REQUEST_SIZE (50*1024*1024)<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>
+ uint64_t chunk_count;<br>
+<br>
+ vlc_array_t *hds_streams; /* available streams */<br>
+<br>
+ uint32_t flv_header_bytes_sent;<br>
+ uint64_t duration_seconds;<br>
+<br>
+ bool live;<br>
+ bool closed;<br>
+};<br>
+<br>
+typedef struct _bootstrap_info {<br>
+ uint8_t* 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>
+/*****************************************************************************<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 char *peek;<br>
+ int i_size = stream_Peek( s->p_source, (const uint8_t**) &peek, 200 );<br>
+ if( i_size < 200 )<br>
+ return false;<br>
+<br>
+ const 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>
+static uint8_t* parse_asrt( vlc_object_t* p_this,<br>
+ hds_stream_t* s,<br>
+ uint8_t* data,<br>
+ uint8_t* data_end )<br>
+{<br>
+ uint8_t* 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 (%"PRIu32", %lu)", 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 = (char*) 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 uint8_t* parse_afrt( vlc_object_t* p_this,<br>
+ hds_stream_t* s,<br>
+ uint8_t* data,<br>
+ uint8_t* data_end )<br>
+{<br>
+ uint8_t* 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, %ld", 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 = (char*)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 chunk_t* chunk_new()<br>
+{<br>
+ chunk_t* chunk = calloc(1, sizeof(chunk_t));<br>
+ return chunk;<br>
+}<br>
+<br>
+static void chunk_free( 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>
+ uint8_t* data,<br>
+ uint8_t* data_end )<br>
+{<br>
+ uint8_t* 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( (char*)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( (char*)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 - data_p );<br>
+ if( ! data_p )<br>
+ {<br>
+ msg_Err( p_this, "Couldn't find server entry" );<br>
+ return;<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( (char*)data_p, data_end - data_p );<br>
+ }<br>
+ data_p += strnlen( (char*)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;<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;<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>
+ uint8_t* boxname = 0;<br>
+ uint8_t* 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 = (uint64_t)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, 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>
+ 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>
+ if( 0 > 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>
+ msg_Err(s, "Failed to allocate memory for fragment url" );<br>
+ return NULL;<br>
+ }<br>
+<br>
+ msg_Info(s, "Downloading fragment %s", 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 > MAX_REQUEST_SIZE )<br>
+ {<br>
+ msg_Err(s, "Strangely-large chunk of %"PRIi64" Bytes", size );<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>
+ 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>
+ 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 chunk_t* generate_new_chunk(<br>
+ vlc_object_t* p_this,<br>
+ 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>
+ chunk_t *chunk = chunk_new();<br>
+ unsigned 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>
+ 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 < 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>
+ unsigned 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(<br>
+ p_this, 0, hds_stream );<br>
+ hds_stream->chunks_livereadpos = hds_stream->chunks_head;<br>
+ }<br>
+<br>
+ chunk_t* chunk = hds_stream->chunks_head;<br>
+ bool dl = false;<br>
+ while( chunk && ( 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>
+ chunk_t* next_chunk = chunk->next;<br>
+ chunk_free( chunk );<br>
+ chunk = next_chunk;<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>
+<br>
+ if( 0 > asprintf( &abst_url, "%s/%s",<br>
+ server_base,<br>
+ hds_stream->abst_url ) )<br>
+ {<br>
+ return 0;<br>
+ }<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 + ( ((int64_t)hds_stream->fragment_runs[hds_stream->fragment_run_count-1].fragment_duration) * 1000000LL) / ((int64_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, (const char**) &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>
+ double shutup_gcc = atof( node );<br>
+ sys->duration_seconds = (uint64_t) shutup_gcc;<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(<br>
+ (vlc_object_t*) s, 0, new_stream );<br>
+ 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(<br>
+ (vlc_object_t*) 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>
+ chunk_t* chunk = p_stream->chunks_head;<br>
+ while( chunk )<br>
+ {<br>
+ 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>
+ chunk_t* chunk = stream->chunks_head;<br>
+ uint8_t* buffer_start = buffer;<br>
+ bool dl = false;<br>
+<br>
+ if( chunk && chunk->eof && chunk->mdat_pos >= chunk->mdat_len ) {<br>
+ *eof = true;<br>
+ return 0;<br>
+ }<br>
+<br>
+ while( chunk && chunk->data && read_len > 0 && ! (chunk->eof && chunk->mdat_pos >= chunk->mdat_len ) )<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>
+ 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>
+ if( ! chunk->eof )<br>
+ {<br>
+ chunk_free( chunk );<br>
+ chunk = next;<br>
+ stream->chunks_head = chunk;<br>
+ }<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 && ! chunk->eof )<br>
+ {<br>
+ if( ! chunk->next && ! chunk->eof )<br>
+ {<br>
+ chunk->next = generate_new_chunk( p_this, chunk, stream );<br>
+ dl = true;<br>
+ }<br>
+<br>
+ if( ! chunk->eof )<br>
+ {<br>
+ chunk = chunk->next;<br>
+ if( chunk )<br>
+ {<br>
+ total_duration += chunk->duration;<br>
+ }<br>
+ }<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( (vlc_object_t*)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>
+ 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>
+ 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/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>