demux: hls: add adaptative based hls demuxer
authorFrancois Cartegnie <fcvlcdev@free.fr>
Fri, 8 May 2015 15:10:16 +0000 (17:10 +0200)
committerFrancois Cartegnie <fcvlcdev@free.fr>
Wed, 10 Jun 2015 16:51:49 +0000 (18:51 +0200)
18 files changed:
NEWS
modules/MODULES_LIST
modules/demux/Makefile.am
modules/demux/hls/HLSManager.cpp [new file with mode: 0644]
modules/demux/hls/HLSManager.hpp [new file with mode: 0644]
modules/demux/hls/hls.cpp [new file with mode: 0644]
modules/demux/hls/hls.hpp [new file with mode: 0644]
modules/demux/hls/playlist/HLSSegment.cpp [new file with mode: 0644]
modules/demux/hls/playlist/HLSSegment.hpp [new file with mode: 0644]
modules/demux/hls/playlist/M3U8.cpp [new file with mode: 0644]
modules/demux/hls/playlist/M3U8.hpp [new file with mode: 0644]
modules/demux/hls/playlist/Parser.cpp [new file with mode: 0644]
modules/demux/hls/playlist/Parser.hpp [new file with mode: 0644]
modules/demux/hls/playlist/Representation.cpp [new file with mode: 0644]
modules/demux/hls/playlist/Representation.hpp [new file with mode: 0644]
modules/demux/hls/playlist/Tags.cpp [new file with mode: 0644]
modules/demux/hls/playlist/Tags.hpp [new file with mode: 0644]
po/POTFILES.in

diff --git a/NEWS b/NEWS
index 206a016..6e55b0b 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -57,6 +57,7 @@ Demuxers:
  * Support for Creative ADPCM/alaw/ulaw/S16L in VOC files
  * Support for Creative ADPCM in AVI
  * Directory Demux can now sort items, ignore extensions and hidden files
+ * Replaced httplive stream filter with new hls demuxer
 
 Stream filter:
  * Added ARIB STD-B25 TS streams decoder
index 34ea3e9..e773613 100644 (file)
@@ -175,6 +175,7 @@ $Id$
  * hevc: HEVC demuxer
  * hotkeys: hotkeys control module
  * hqdn3d: High Quality denoising filter
+ * hls: HTTP Live Streaming demuxer
  * http: HTTP Network access module
  * httplive: HTTP Live streaming for playback
  * i420_rgb: planar YUV to packed RGB conversion functions
index 3d7aa47..43089f9 100644 (file)
@@ -244,45 +244,7 @@ if HAVE_DVBPSI
 demux_LTLIBRARIES += libts_plugin.la
 endif
 
-libdash_plugin_la_SOURCES = \
-    demux/dash/mpd/AdaptationSet.cpp \
-    demux/dash/mpd/AdaptationSet.h \
-    demux/dash/mpd/DASHCommonAttributesElements.cpp \
-    demux/dash/mpd/DASHCommonAttributesElements.h \
-    demux/dash/mpd/DASHSegment.cpp \
-    demux/dash/mpd/DASHSegment.h \
-    demux/dash/mpd/ContentDescription.cpp \
-    demux/dash/mpd/ContentDescription.h \
-    demux/dash/mpd/IsoffMainParser.cpp \
-    demux/dash/mpd/IsoffMainParser.h \
-    demux/dash/mpd/MPD.cpp \
-    demux/dash/mpd/MPD.h \
-    demux/dash/mpd/MPDFactory.cpp \
-    demux/dash/mpd/MPDFactory.h \
-    demux/dash/mpd/Period.cpp \
-    demux/dash/mpd/Period.h \
-    demux/dash/mpd/Profile.cpp \
-    demux/dash/mpd/Profile.hpp \
-    demux/dash/mpd/ProgramInformation.cpp \
-    demux/dash/mpd/ProgramInformation.h \
-    demux/dash/mpd/Representation.cpp \
-    demux/dash/mpd/Representation.h \
-    demux/dash/mpd/TrickModeType.cpp \
-    demux/dash/mpd/TrickModeType.h \
-    demux/dash/mp4/AtomsReader.cpp \
-    demux/dash/mp4/AtomsReader.hpp \
-    demux/dash/xml/DOMHelper.cpp \
-    demux/dash/xml/DOMHelper.h \
-    demux/dash/xml/DOMParser.cpp \
-    demux/dash/xml/DOMParser.h \
-    demux/dash/xml/Node.cpp \
-    demux/dash/xml/Node.h \
-    demux/dash/dash.cpp \
-    demux/dash/dash.hpp \
-    demux/dash/DASHManager.cpp \
-    demux/dash/DASHManager.h
-
-libdash_plugin_la_SOURCES += \
+adaptative_SOURCES = \
     demux/adaptative/playlist/AbstractPlaylist.cpp \
     demux/adaptative/playlist/AbstractPlaylist.hpp \
     demux/adaptative/playlist/BaseAdaptationSet.cpp \
@@ -345,8 +307,46 @@ libdash_plugin_la_SOURCES += \
     demux/adaptative/tools/Retrieve.cpp \
     demux/adaptative/tools/Retrieve.hpp
 
-libdash_plugin_la_SOURCES += demux/mp4/libmp4.c demux/mp4/libmp4.h
+libdash_plugin_la_SOURCES = \
+    demux/dash/mpd/AdaptationSet.cpp \
+    demux/dash/mpd/AdaptationSet.h \
+    demux/dash/mpd/DASHCommonAttributesElements.cpp \
+    demux/dash/mpd/DASHCommonAttributesElements.h \
+    demux/dash/mpd/DASHSegment.cpp \
+    demux/dash/mpd/DASHSegment.h \
+    demux/dash/mpd/ContentDescription.cpp \
+    demux/dash/mpd/ContentDescription.h \
+    demux/dash/mpd/IsoffMainParser.cpp \
+    demux/dash/mpd/IsoffMainParser.h \
+    demux/dash/mpd/MPD.cpp \
+    demux/dash/mpd/MPD.h \
+    demux/dash/mpd/MPDFactory.cpp \
+    demux/dash/mpd/MPDFactory.h \
+    demux/dash/mpd/Period.cpp \
+    demux/dash/mpd/Period.h \
+    demux/dash/mpd/Profile.cpp \
+    demux/dash/mpd/Profile.hpp \
+    demux/dash/mpd/ProgramInformation.cpp \
+    demux/dash/mpd/ProgramInformation.h \
+    demux/dash/mpd/Representation.cpp \
+    demux/dash/mpd/Representation.h \
+    demux/dash/mpd/TrickModeType.cpp \
+    demux/dash/mpd/TrickModeType.h \
+    demux/dash/mp4/AtomsReader.cpp \
+    demux/dash/mp4/AtomsReader.hpp \
+    demux/dash/xml/DOMHelper.cpp \
+    demux/dash/xml/DOMHelper.h \
+    demux/dash/xml/DOMParser.cpp \
+    demux/dash/xml/DOMParser.h \
+    demux/dash/xml/Node.cpp \
+    demux/dash/xml/Node.h \
+    demux/dash/dash.cpp \
+    demux/dash/dash.hpp \
+    demux/dash/DASHManager.cpp \
+    demux/dash/DASHManager.h
 
+libdash_plugin_la_SOURCES += $(adaptative_SOURCES)
+libdash_plugin_la_SOURCES += demux/mp4/libmp4.c demux/mp4/libmp4.h
 libdash_plugin_la_CXXFLAGS = $(AM_CFLAGS) -I$(srcdir)/demux/dash
 libdash_plugin_la_LIBADD = $(SOCKET_LIBS) $(LIBM)
 if HAVE_ZLIB
@@ -354,6 +354,36 @@ libdash_plugin_la_LIBADD += -lz
 endif
 demux_LTLIBRARIES += libdash_plugin.la
 
+
+libhls_plugin_la_SOURCES = \
+    demux/hls/playlist/M3U8.hpp \
+    demux/hls/playlist/M3U8.cpp \
+    demux/hls/playlist/Parser.hpp \
+    demux/hls/playlist/Parser.cpp \
+    demux/hls/playlist/Representation.hpp \
+    demux/hls/playlist/Representation.cpp \
+    demux/hls/playlist/HLSSegment.hpp \
+    demux/hls/playlist/HLSSegment.cpp \
+    demux/hls/playlist/Tags.hpp \
+    demux/hls/playlist/Tags.cpp \
+    demux/hls/HLSManager.hpp \
+    demux/hls/HLSManager.cpp \
+    demux/hls/hls.cpp \
+    demux/hls/hls.hpp
+
+libhls_plugin_la_SOURCES += $(adaptative_SOURCES)
+libhls_plugin_la_CXXFLAGS = $(AM_CFLAGS) -I$(srcdir)/demux/hls
+libhls_plugin_la_LIBADD = $(SOCKET_LIBS) $(LIBM)
+if HAVE_ZLIB
+libhls_plugin_la_LIBADD += -lz
+endif
+if HAVE_GCRYPT
+libhls_plugin_la_CXXFLAGS += $(GCRYPT_CFLAGS)
+libhls_plugin_la_LIBADD += $(GCRYPT_LIBS)
+endif
+
+demux_LTLIBRARIES += libhls_plugin.la
+
 libttml_plugin_la_SOURCES = demux/ttml.c
 demux_LTLIBRARIES += libttml_plugin.la
 
diff --git a/modules/demux/hls/HLSManager.cpp b/modules/demux/hls/HLSManager.cpp
new file mode 100644 (file)
index 0000000..05aa7e1
--- /dev/null
@@ -0,0 +1,142 @@
+/*****************************************************************************
+ * HLSManager.cpp
+ *****************************************************************************
+ * Copyright © 2015 VideoLAN authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#include "HLSManager.hpp"
+#include "../adaptative/logic/RateBasedAdaptationLogic.h"
+#include "../adaptative/tools/Retrieve.hpp"
+#include "playlist/Parser.hpp"
+#include <vlc_stream.h>
+
+using namespace adaptative;
+using namespace adaptative::logic;
+using namespace hls;
+using namespace hls::playlist;
+
+HLSManager::HLSManager(M3U8 *playlist,
+                       AbstractAdaptationLogic::LogicType type, stream_t *stream) :
+             PlaylistManager(playlist, type, stream)
+{
+}
+
+HLSManager::~HLSManager()
+{
+}
+
+AbstractAdaptationLogic *HLSManager::createLogic(AbstractAdaptationLogic::LogicType type)
+{
+    size_t bps = var_InheritInteger(stream, "hls-prefbw") * 8192;
+    switch(type)
+    {
+        case AbstractAdaptationLogic::FixedRate:
+        {
+            size_t bps = var_InheritInteger(stream, "hls-prefbw") * 8192;
+            return new (std::nothrow) FixedRateAdaptationLogic(bps);
+        }
+        case AbstractAdaptationLogic::Default:
+        case AbstractAdaptationLogic::RateBased:
+        {
+            int width = var_InheritInteger(stream, "hls-prefwidth");
+            int height = var_InheritInteger(stream, "hls-prefheight");
+            return new (std::nothrow) RateBasedAdaptationLogic(width, height);
+        }
+        default:
+            return PlaylistManager::createLogic(type);
+    }
+}
+
+bool HLSManager::updatePlaylist()
+{
+    if(!playlist->isLive() || !playlist->minUpdatePeriod.Get())
+        return true;
+
+    mtime_t now = time(NULL);
+    if(nextPlaylistupdate && now < nextPlaylistupdate)
+        return true;
+
+    M3U8 *updatedplaylist = NULL;
+
+    /* do update */
+    if(nextPlaylistupdate)
+    {
+        std::string url(stream->psz_access);
+        url.append("://");
+        url.append(stream->psz_path);
+
+        uint8_t *p_data = NULL;
+        size_t i_data = Retrieve::HTTP(VLC_OBJECT(stream), url, (void**) &p_data);
+        if(!p_data)
+            return false;
+
+        stream_t *updatestream = stream_MemoryNew(stream, p_data, i_data, false);
+        if(!updatestream)
+        {
+            free(p_data);
+            nextPlaylistupdate = now + playlist->minUpdatePeriod.Get();
+            return false;
+        }
+
+        Parser parser(updatestream);
+        updatedplaylist = parser.parse(url);
+        if(!updatedplaylist)
+        {
+            stream_Delete(updatestream);
+            nextPlaylistupdate = now + playlist->minUpdatePeriod.Get();
+            return false;
+        }
+
+
+        stream_Delete(updatestream);
+    }
+
+    /* Compute new MPD update time */
+    mtime_t mininterval = 0;
+    mtime_t maxinterval = 0;
+    if(updatedplaylist)
+    {
+        updatedplaylist->getPlaylistDurationsRange(&mininterval, &maxinterval);
+        playlist->mergeWith(updatedplaylist);
+        playlist->debug();
+        delete updatedplaylist;
+
+        /* pruning */
+        for(int type=0; type<StreamTypeCount; type++)
+        {
+            if(!streams[type])
+                continue;
+            streams[type]->prune();
+        }
+    }
+    else
+    {
+        playlist->getPlaylistDurationsRange(&mininterval, &maxinterval);
+    }
+
+    if(playlist->minUpdatePeriod.Get() * CLOCK_FREQ > mininterval)
+        mininterval = playlist->minUpdatePeriod.Get() * CLOCK_FREQ;
+
+    if(maxinterval < mininterval)
+        maxinterval = mininterval;
+
+    nextPlaylistupdate = now + (mininterval + maxinterval) / (2 * CLOCK_FREQ);
+
+    msg_Dbg(stream, "Updated playlist, next update in %" PRId64 "s %ld %ld", nextPlaylistupdate - now, mininterval, maxinterval );
+
+    return true;
+}
diff --git a/modules/demux/hls/HLSManager.hpp b/modules/demux/hls/HLSManager.hpp
new file mode 100644 (file)
index 0000000..8509d70
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * HLSManager.hpp
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef HLSMANAGER_HPP
+#define HLSMANAGER_HPP
+
+#include "../adaptative/PlaylistManager.h"
+#include "../adaptative/logic/AbstractAdaptationLogic.h"
+#include "playlist/M3U8.hpp"
+
+namespace hls
+{
+    using namespace adaptative;
+
+    class HLSManager : public PlaylistManager
+    {
+        public:
+            HLSManager( playlist::M3U8 *,
+                        logic::AbstractAdaptationLogic::LogicType type,
+                        stream_t *stream );
+            virtual ~HLSManager();
+            virtual AbstractAdaptationLogic *createLogic(AbstractAdaptationLogic::LogicType);
+            virtual bool updatePlaylist();
+    };
+
+}
+
+#endif // HLSMANAGER_HPP
diff --git a/modules/demux/hls/hls.cpp b/modules/demux/hls/hls.cpp
new file mode 100644 (file)
index 0000000..527342a
--- /dev/null
@@ -0,0 +1,310 @@
+/*****************************************************************************
+ * hls.cpp: HTTP Live Streaming module
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#define __STDC_CONSTANT_MACROS 1
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdint.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_demux.h>
+#include <vlc_meta.h>
+
+#include <errno.h>
+
+#include "../adaptative/logic/AbstractAdaptationLogic.h"
+#include "HLSManager.hpp"
+
+#include "playlist/Parser.hpp"
+#include "playlist/M3U8.hpp"
+#include "hls.hpp"
+
+using namespace adaptative;
+using namespace adaptative::logic;
+using namespace adaptative::playlist;
+using namespace hls;
+using namespace hls::playlist;
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+static int  Open    (vlc_object_t *);
+static void Close   (vlc_object_t *);
+
+#define HLS_WIDTH_TEXT N_("Preferred Width")
+#define HLS_WIDTH_LONGTEXT N_("Preferred Width")
+
+#define HLS_HEIGHT_TEXT N_("Preferred Height")
+#define HLS_HEIGHT_LONGTEXT N_("Preferred Height")
+
+#define HLS_BW_TEXT N_("Fixed Bandwidth in KiB/s")
+#define HLS_BW_LONGTEXT N_("Preferred bandwidth for non adaptative streams")
+
+#define HLS_LOGIC_TEXT N_("Adaptation Logic")
+
+static const int pi_logics[] = {AbstractAdaptationLogic::RateBased,
+                                AbstractAdaptationLogic::FixedRate,
+                                AbstractAdaptationLogic::AlwaysLowest,
+                                AbstractAdaptationLogic::AlwaysBest};
+
+static const char *const ppsz_logics[] = { N_("Bandwidth Adaptive"),
+                                           N_("Fixed Bandwidth"),
+                                           N_("Lowest Bandwidth/Quality"),
+                                           N_("Highest Bandwith/Quality")};
+
+vlc_module_begin ()
+        set_shortname( N_("hls"))
+        set_description( N_("HTTP Live Streaming") )
+        set_capability( "demux", 12 )
+        set_category( CAT_INPUT )
+        set_subcategory( SUBCAT_INPUT_DEMUX )
+        add_integer( "hls-logic", AbstractAdaptationLogic::Default,
+                                             HLS_LOGIC_TEXT, NULL, false )
+            change_integer_list( pi_logics, ppsz_logics )
+        add_integer( "hls-prefwidth",  480, HLS_WIDTH_TEXT,  HLS_WIDTH_LONGTEXT,  true )
+        add_integer( "hls-prefheight", 360, HLS_HEIGHT_TEXT, HLS_HEIGHT_LONGTEXT, true )
+        add_integer( "hls-prefbw",     250, HLS_BW_TEXT,     HLS_BW_LONGTEXT,     false )
+        set_callbacks( Open, Close )
+vlc_module_end ()
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+
+static int  Demux( demux_t * );
+static int  Control         (demux_t *p_demux, int i_query, va_list args);
+
+/*****************************************************************************
+ * Open:
+ *****************************************************************************/
+static bool isHTTPLiveStreaming(stream_t *s)
+{
+    const uint8_t *peek;
+
+    int size = stream_Peek(s, &peek, 46);
+    if (size < 7)
+        return false;
+
+    if (memcmp(peek, "#EXTM3U", 7) != 0)
+        return false;
+
+    peek += 7;
+    size -= 7;
+
+    /* Parse stream and search for
+     * EXT-X-TARGETDURATION or EXT-X-STREAM-INF tag, see
+     * http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8 */
+    while (size--)
+    {
+        static const char *const ext[] = {
+            "TARGETDURATION",
+            "MEDIA-SEQUENCE",
+            "KEY",
+            "ALLOW-CACHE",
+            "ENDLIST",
+            "STREAM-INF",
+            "DISCONTINUITY",
+            "VERSION"
+        };
+
+        if (*peek++ != '#')
+            continue;
+
+        if (size < 6)
+            continue;
+
+        if (memcmp(peek, "EXT-X-", 6))
+            continue;
+
+        peek += 6;
+        size -= 6;
+
+        for (size_t i = 0; i < ARRAY_SIZE(ext); i++)
+        {
+            size_t len = strlen(ext[i]);
+            if (size < 0 || (size_t)size < len)
+                continue;
+            if (!memcmp(peek, ext[i], len))
+                return true;
+        }
+    }
+
+    return false;
+}
+
+static int Open(vlc_object_t *p_obj)
+{
+    demux_t *p_demux = (demux_t*) p_obj;
+
+    if(!isHTTPLiveStreaming(p_demux->s))
+        return VLC_EGENERIC;
+
+    demux_sys_t *p_sys = (demux_sys_t *) malloc(sizeof(demux_sys_t));
+    if (unlikely(p_sys == NULL))
+        return VLC_ENOMEM;
+
+    Parser parser(p_demux->s);
+    p_sys->p_playlist = parser.parse(std::string());
+    if(!p_sys->p_playlist)
+    {
+        free(p_sys);
+        return VLC_EGENERIC;
+    }
+
+    int logic = var_InheritInteger(p_obj, "hls-logic");
+
+    HLSManager *p_manager =
+            new (std::nothrow) HLSManager(p_sys->p_playlist,
+            static_cast<AbstractAdaptationLogic::LogicType>(logic),
+            p_demux->s);
+
+    BasePeriod *period = p_sys->p_playlist->getFirstPeriod();
+    if(period && !p_manager->start(p_demux))
+    {
+        delete p_manager;
+        free( p_sys );
+        return VLC_EGENERIC;
+    }
+    p_sys->p_manager       = p_manager;
+    p_demux->p_sys         = p_sys;
+    p_demux->pf_demux      = Demux;
+    p_demux->pf_control    = Control;
+
+    p_sys->i_nzpcr = VLC_TS_INVALID;
+
+    msg_Dbg(p_obj,"opening mpd file (%s)", p_demux->s->psz_path);
+
+    return VLC_SUCCESS;
+}
+/*****************************************************************************
+ * Close:
+ *****************************************************************************/
+static void Close(vlc_object_t *p_obj)
+{
+    demux_t                            *p_demux       = (demux_t*) p_obj;
+    demux_sys_t                        *p_sys          = (demux_sys_t *) p_demux->p_sys;
+
+    delete p_sys->p_manager;
+    free(p_sys);
+}
+/*****************************************************************************
+ * Callbacks:
+ *****************************************************************************/
+#define DEMUX_INCREMENT (CLOCK_FREQ / 20)
+static int Demux(demux_t *p_demux)
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    Stream::status status =
+            p_sys->p_manager->demux(p_sys->i_nzpcr + DEMUX_INCREMENT);
+    switch(status)
+    {
+    case Stream::status_eof:
+        return VLC_DEMUXER_EOF;
+    case Stream::status_buffering:
+        break;
+    case Stream::status_demuxed:
+        if(p_sys->i_nzpcr == VLC_TS_INVALID)
+            p_sys->i_nzpcr = p_sys->p_manager->getPCR();
+        else
+            p_sys->i_nzpcr += DEMUX_INCREMENT;
+        int group = p_sys->p_manager->getGroup();
+        es_out_Control(p_demux->out, ES_OUT_SET_GROUP_PCR, group, VLC_TS_0 + p_sys->i_nzpcr);
+        break;
+    }
+
+    if( !p_sys->p_manager->updatePlaylist() )
+        msg_Warn(p_demux, "Can't update playlist");
+
+    return VLC_DEMUXER_SUCCESS;
+}
+
+static int  Control         (demux_t *p_demux, int i_query, va_list args)
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    switch (i_query)
+    {
+        case DEMUX_CAN_SEEK:
+            *(va_arg (args, bool *)) = p_sys->p_manager->seekAble();
+            break;
+
+        case DEMUX_CAN_CONTROL_PACE:
+            *(va_arg (args, bool *)) = true;
+            break;
+
+        case DEMUX_CAN_PAUSE:
+            *(va_arg (args, bool *)) = p_sys->p_playlist->isLive();
+            break;
+
+        case DEMUX_GET_TIME:
+            *(va_arg (args, int64_t *)) = p_sys->i_nzpcr;
+            break;
+
+        case DEMUX_GET_LENGTH:
+            *(va_arg (args, int64_t *)) = p_sys->p_manager->getDuration();
+            break;
+
+        case DEMUX_GET_POSITION:
+            if(!p_sys->p_manager->getDuration())
+                return VLC_EGENERIC;
+
+            *(va_arg (args, double *)) = (double) p_sys->i_nzpcr
+                                         / p_sys->p_manager->getDuration();
+            break;
+
+        case DEMUX_SET_POSITION:
+        {
+            int64_t time = p_sys->p_manager->getDuration() * va_arg(args, double);
+            if(p_sys->p_playlist->isLive() ||
+               !p_sys->p_manager->getDuration() ||
+               !p_sys->p_manager->setPosition(time))
+                return VLC_EGENERIC;
+            p_sys->i_nzpcr = VLC_TS_INVALID;
+            break;
+        }
+
+        case DEMUX_SET_TIME:
+        {
+            int64_t time = va_arg(args, int64_t);
+            if(p_sys->p_playlist->isLive() ||
+               !p_sys->p_manager->setPosition(time))
+                return VLC_EGENERIC;
+            p_sys->i_nzpcr = VLC_TS_INVALID;
+            break;
+        }
+
+        case DEMUX_GET_PTS_DELAY:
+            *va_arg (args, int64_t *) = INT64_C(1000) *
+                var_InheritInteger(p_demux, "network-caching");
+             break;
+
+        default:
+            return VLC_EGENERIC;
+    }
+    return VLC_SUCCESS;
+}
diff --git a/modules/demux/hls/hls.hpp b/modules/demux/hls/hls.hpp
new file mode 100644 (file)
index 0000000..4d3d2e6
--- /dev/null
@@ -0,0 +1,32 @@
+/*****************************************************************************
+ * hls.hpp: HTTP Live Streaming module
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef HLS_HPP
+#define HLS_HPP
+
+struct demux_sys_t
+{
+        adaptative::PlaylistManager     *p_manager;
+        hls::playlist::M3U8             *p_playlist;
+        mtime_t              i_nzpcr;
+};
+
+
+#endif // HLS_HPP
+
diff --git a/modules/demux/hls/playlist/HLSSegment.cpp b/modules/demux/hls/playlist/HLSSegment.cpp
new file mode 100644 (file)
index 0000000..ac2a48b
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * HLSSegment.cpp
+ *****************************************************************************
+ * Copyright (C) 2015 VideoLAN authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "HLSSegment.hpp"
+
+#include <vlc_common.h>
+#include <vlc_block.h>
+#ifdef HAVE_GCRYPT
+ #include <vlc_gcrypt.h>
+#endif
+
+using namespace hls::playlist;
+
+SegmentEncryption::SegmentEncryption()
+{
+    method = SegmentEncryption::NONE;
+}
+
+HLSSegment::HLSSegment( ICanonicalUrl *parent, uint64_t seq ) :
+    Segment( parent )
+{
+    sequence = seq;
+#ifdef HAVE_GCRYPT
+    ctx = NULL;
+#endif
+}
+
+HLSSegment::~HLSSegment()
+{
+#ifdef HAVE_GCRYPT
+    if(ctx)
+        gcry_cipher_close(ctx);
+#endif
+}
+
+void HLSSegment::onChunkDownload(block_t **pp_block, Chunk *chunk, BaseRepresentation *)
+{
+    block_t *p_block = *pp_block;
+#ifdef HAVE_GCRYPT
+    if(encryption.method == SegmentEncryption::AES_128)
+    {
+        block_t *p_block = *pp_block;
+        /* first bytes */
+        if(!ctx && chunk->getBytesRead() == p_block->i_buffer)
+        {
+            vlc_gcrypt_init();
+            if (encryption.iv.size() != 16)
+            {
+                encryption.iv.clear();
+                encryption.iv.resize(16);
+                encryption.iv[15] = sequence & 0xff;
+                encryption.iv[14] = (sequence >> 8)& 0xff;
+                encryption.iv[13] = (sequence >> 16)& 0xff;
+                encryption.iv[12] = (sequence >> 24)& 0xff;
+            }
+
+            if( gcry_cipher_open(&ctx, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CBC, 0) ||
+                gcry_cipher_setkey(ctx, &encryption.key[0], 16) ||
+                gcry_cipher_setiv(ctx, &encryption.iv[0], 16) )
+            {
+                gcry_cipher_close(ctx);
+                ctx = NULL;
+            }
+        }
+
+        if(ctx)
+        {
+            if ((p_block->i_buffer % 16) != 0 || p_block->i_buffer < 16 ||
+                gcry_cipher_decrypt(ctx, p_block->p_buffer, p_block->i_buffer, NULL, 0))
+            {
+                p_block->i_buffer = 0;
+                gcry_cipher_close(ctx);
+                ctx = NULL;
+            }
+            else
+            {
+                /* last bytes */
+                if(chunk->getBytesToRead() == 0)
+                {
+                    /* remove the PKCS#7 padding from the buffer */
+                    const uint8_t pad = p_block->p_buffer[p_block->i_buffer - 1];
+                    for(uint8_t i=0; i<pad && i<=16; i++)
+                    {
+                        if(p_block->p_buffer[p_block->i_buffer - i - 1] != pad)
+                            break;
+
+                        if(i==pad)
+                            p_block->i_buffer -= pad;
+                    }
+
+                    gcry_cipher_close(ctx);
+                    ctx = NULL;
+                }
+            }
+        }
+    }
+    else
+#endif
+    if(encryption.method != SegmentEncryption::NONE)
+    {
+        p_block->i_buffer = 0;
+    }
+}
+
+void HLSSegment::setEncryption(SegmentEncryption &enc)
+{
+    encryption = enc;
+}
+
+void HLSSegment::debug(vlc_object_t *obj, int indent) const
+{
+    std::stringstream ss;
+    ss << std::string(indent, ' ') << debugName <<
+    " #" << sequence <<
+    " url=" << getUrlSegment().toString();
+    if(startByte!=endByte)
+        ss << " @" << startByte << ".." << endByte;
+    msg_Dbg(obj, "%s", ss.str().c_str());
+}
+
+int HLSSegment::compare(ISegment *segment) const
+{
+    HLSSegment *hlssegment = dynamic_cast<HLSSegment *>(segment);
+    if(hlssegment)
+    {
+        if (sequence > hlssegment->sequence)
+            return 1;
+        else if(sequence < hlssegment->sequence)
+            return -1;
+        else
+            return 0;
+    }
+    else return ISegment::compare(segment);
+}
diff --git a/modules/demux/hls/playlist/HLSSegment.hpp b/modules/demux/hls/playlist/HLSSegment.hpp
new file mode 100644 (file)
index 0000000..ec5038e
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * HLSSegment.hpp
+ *****************************************************************************
+ * Copyright (C) 2015 VideoLAN authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef HLSSEGMENT_HPP
+#define HLSSEGMENT_HPP
+
+#include "../adaptative/playlist/Segment.h"
+#include <vector>
+#include <gcrypt.h>
+
+namespace hls
+{
+    namespace playlist
+    {
+        using namespace adaptative::playlist;
+
+        class SegmentEncryption
+        {
+            public:
+                SegmentEncryption();
+                enum
+                {
+                    NONE,
+                    AES_128,
+                    AES_SAMPLE,
+                } method;
+                std::vector<uint8_t> key;
+                std::vector<uint8_t> iv;
+        };
+
+        class HLSSegment : public Segment
+        {
+            public:
+                HLSSegment( ICanonicalUrl *parent, uint64_t sequence );
+                virtual ~HLSSegment();
+                void setEncryption(SegmentEncryption &);
+                void debug(vlc_object_t *, int) const; /* reimpl */
+                virtual int compare(ISegment *) const; /* reimpl */
+
+            protected:
+                virtual void onChunkDownload(block_t **, Chunk *, BaseRepresentation *); /* reimpl */
+
+                uint64_t sequence;
+                SegmentEncryption encryption;
+#ifdef HAVE_GCRYPT
+                gcry_cipher_hd_t ctx;
+#endif
+        };
+    }
+}
+
+
+#endif // HLSSEGMENT_HPP
diff --git a/modules/demux/hls/playlist/M3U8.cpp b/modules/demux/hls/playlist/M3U8.cpp
new file mode 100644 (file)
index 0000000..6ab2835
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * M3U8.cpp
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "M3U8.hpp"
+#include "Representation.hpp"
+#include "../adaptative/playlist/BasePeriod.h"
+#include "../adaptative/playlist/BaseAdaptationSet.h"
+
+#include <vlc_common.h>
+#include <vlc_stream.h>
+
+using namespace hls::playlist;
+
+M3U8::M3U8 (stream_t *stream_) :
+    AbstractPlaylist(stream_)
+{
+    minUpdatePeriod.Set( 5 );
+}
+
+M3U8::~M3U8()
+{
+
+}
+
+bool M3U8::isLive() const
+{
+    std::vector<BasePeriod *>::const_iterator itp;
+    for(itp = periods.begin(); itp != periods.end(); ++itp)
+    {
+        const BasePeriod *period = *itp;
+        std::vector<BaseAdaptationSet *>::const_iterator ita;
+        for(ita = period->getAdaptationSets().begin(); ita != period->getAdaptationSets().end(); ++ita)
+        {
+            BaseAdaptationSet *adaptSet = *ita;
+            std::vector<BaseRepresentation *>::iterator itr;
+            for(itr = adaptSet->getRepresentations().begin(); itr != adaptSet->getRepresentations().end(); ++itr)
+            {
+                const Representation *rep = dynamic_cast<const Representation *>(*itr);
+                if(rep->isLive())
+                    return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+void M3U8::debug()
+{
+    std::vector<BasePeriod *>::const_iterator i;
+    for(i = periods.begin(); i != periods.end(); ++i)
+        (*i)->debug(VLC_OBJECT(stream));
+}
+
diff --git a/modules/demux/hls/playlist/M3U8.hpp b/modules/demux/hls/playlist/M3U8.hpp
new file mode 100644 (file)
index 0000000..6e7daee
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * M3U8.hpp
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef M3U8_H_
+#define M3U8_H_
+
+#include "../adaptative/playlist/AbstractPlaylist.hpp"
+
+namespace hls
+{
+    namespace playlist
+    {
+        using namespace adaptative::playlist;
+
+        class M3U8 : public AbstractPlaylist
+        {
+            public:
+                M3U8(stream_t *);
+                virtual ~M3U8();
+
+                virtual bool                    isLive() const;
+                virtual void                    debug();
+
+            private:
+                std::string data;
+        };
+    }
+}
+#endif /* M3U8_H_ */
diff --git a/modules/demux/hls/playlist/Parser.cpp b/modules/demux/hls/playlist/Parser.cpp
new file mode 100644 (file)
index 0000000..a40a32e
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ * Parser.cpp
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "Parser.hpp"
+#include "playlist/HLSSegment.hpp"
+#include "playlist/Representation.hpp"
+#include "../adaptative/playlist/BasePeriod.h"
+#include "../adaptative/playlist/BaseAdaptationSet.h"
+#include "../adaptative/playlist/SegmentList.h"
+#include "../adaptative/tools/Retrieve.hpp"
+#include "M3U8.hpp"
+#include "Tags.hpp"
+
+#include <vlc_strings.h>
+#include <vlc_stream.h>
+#include <cstdio>
+#include <sstream>
+
+using namespace adaptative;
+using namespace adaptative::playlist;
+using namespace hls::playlist;
+
+Parser::Parser(stream_t *stream)
+{
+    p_stream = stream;
+}
+
+Parser::~Parser   ()
+{
+}
+
+static std::list<Tag *> getTagsFromList(std::list<Tag *> &list, int tag)
+{
+    std::list<Tag *> ret;
+    std::list<Tag *>::const_iterator it;
+    for(it = list.begin(); it != list.end(); ++it)
+    {
+        if( (*it)->getType() == tag )
+            ret.push_back(*it);
+    }
+    return ret;
+}
+
+static void releaseTagsList(std::list<Tag *> &list)
+{
+    std::list<Tag *>::const_iterator it;
+    for(it = list.begin(); it != list.end(); ++it)
+        delete *it;
+    list.clear();
+}
+
+void Parser::parseAdaptationSet(BasePeriod *period, const AttributesTag *)
+{
+    BaseAdaptationSet *adaptSet = new (std::nothrow) BaseAdaptationSet(period);
+    if(adaptSet)
+    {
+        period->addAdaptationSet(adaptSet);
+    }
+}
+
+void Parser::parseRepresentation(BaseAdaptationSet *adaptSet, const AttributesTag * streaminftag)
+{
+    if(!streaminftag->getAttributeByName("URI"))
+        return;
+
+    Url url = Url(streaminftag->getAttributeByName("URI")->value);
+    if(!url.hasScheme())
+        url = url.prepend(adaptSet->getUrlSegment());
+
+    void *p_data;
+    const size_t i_data = Retrieve::HTTP((vlc_object_t*)p_stream, url.toString(), &p_data);
+    if(p_data)
+    {
+        stream_t *substream = stream_MemoryNew((vlc_object_t *)p_stream, (uint8_t *)p_data, i_data, false);
+        if(substream)
+        {
+            std::list<Tag *> tagslist = parseEntries(substream);
+            stream_Delete(substream);
+
+            parseRepresentation(adaptSet, streaminftag, tagslist);
+
+            releaseTagsList(tagslist);
+        }
+    }
+}
+
+void Parser::parseRepresentation(BaseAdaptationSet *adaptSet, const AttributesTag * streaminftag,
+                                 const std::list<Tag *> &tagslist)
+{
+    const Attribute *uriAttr = streaminftag->getAttributeByName("URI");
+    const Attribute *bwAttr = streaminftag->getAttributeByName("BANDWIDTH");
+
+    Representation *rep = new (std::nothrow) Representation(adaptSet);
+    if(rep)
+    {
+        rep->setMimeType("video/mp2t");
+
+        if(uriAttr)
+        {
+            size_t pos = uriAttr->value.find_last_of('/');
+            if(pos != std::string::npos)
+                rep->baseUrl.Set(new Url(uriAttr->value.substr(0, pos+1)));
+        }
+
+        if(bwAttr)
+            rep->setBandwidth(bwAttr->decimal());
+
+        parseSegments(rep, tagslist);
+
+        adaptSet->addRepresentation(rep);
+    }
+}
+
+void Parser::parseSegments(Representation *rep, const std::list<Tag *> &tagslist)
+{
+    SegmentList *segmentList = new (std::nothrow) SegmentList(rep);
+    rep->setSegmentList(segmentList);
+
+    rep->timescale.Set(100);
+
+    int64_t totalduration = 0;
+    int64_t nzStartTime = 0;
+    uint64_t sequenceNumber = 0;
+    std::size_t prevbyterangeoffset = 0;
+    const SingleValueTag *ctx_byterange = NULL;
+    SegmentEncryption encryption;
+
+    std::list<Tag *>::const_iterator it;
+    for(it = tagslist.begin(); it != tagslist.end(); ++it)
+    {
+        const Tag *tag = *it;
+        switch(tag->getType())
+        {
+            /* using static cast as attribute type permits avoiding class check */
+            case SingleValueTag::EXTXMEDIASEQUENCE:
+            {
+                sequenceNumber = (static_cast<const SingleValueTag*>(tag))->getValue().decimal();
+            }
+            break;
+
+            case URITag::EXTINF:
+            {
+                const URITag *uritag = static_cast<const URITag *>(tag);
+                HLSSegment *segment = new (std::nothrow) HLSSegment(rep, sequenceNumber++);
+                if(!segment)
+                    break;
+
+                if(uritag->getAttributeByName("URI"))
+                    segment->setSourceUrl(uritag->getAttributeByName("URI")->value);
+
+                if(uritag->getAttributeByName("DURATION"))
+                {
+                    segment->duration.Set(uritag->getAttributeByName("DURATION")->floatingPoint() * rep->timescale.Get());
+                    segment->startTime.Set(nzStartTime);
+                    nzStartTime += segment->duration.Get();
+                    totalduration += segment->duration.Get();
+                }
+
+                segmentList->addSegment(segment);
+
+                if(ctx_byterange)
+                {
+                    std::pair<std::size_t,std::size_t> range = ctx_byterange->getValue().getByteRange();
+                    if(range.first == 0)
+                        range.first = prevbyterangeoffset;
+                    prevbyterangeoffset = range.first + range.second;
+                    segment->setByteRange(range.first, prevbyterangeoffset);
+                    ctx_byterange = NULL;
+                }
+
+                if(encryption.method != SegmentEncryption::NONE)
+                    segment->setEncryption(encryption);
+            }
+            break;
+
+            case SingleValueTag::EXTXPLAYLISTTYPE:
+                rep->b_live = (static_cast<const SingleValueTag *>(tag)->getValue().value != "VOD");
+                break;
+
+            case SingleValueTag::EXTXBYTERANGE:
+                ctx_byterange = static_cast<const SingleValueTag *>(tag);
+                break;
+
+            case AttributesTag::EXTXKEY:
+            {
+                const AttributesTag *keytag = static_cast<const AttributesTag *>(tag);
+                if( keytag->getAttributeByName("METHOD") &&
+                    keytag->getAttributeByName("METHOD")->value == "AES-128" &&
+                    keytag->getAttributeByName("URI") )
+                {
+                    encryption.method = SegmentEncryption::AES_128;
+                    encryption.key.clear();
+                    uint8_t *p_data;
+                    const uint64_t read = Retrieve::HTTP(VLC_OBJECT(p_stream),
+                                                         keytag->getAttributeByName("URI")->quotedString(),
+                                                         (void **) &p_data);
+                    if(p_data)
+                    {
+                        if(read == 16)
+                        {
+                            encryption.key.resize(16);
+                            memcpy(&encryption.key[0], p_data, 16);
+                        }
+                        free(p_data);
+                    }
+
+                    if(keytag->getAttributeByName("IV"))
+                    {
+                        encryption.iv.clear();
+                        encryption.iv = keytag->getAttributeByName("IV")->hexSequence();
+                    }
+                }
+                else
+                {
+                    /* unsupported or invalid */
+                    encryption.method = SegmentEncryption::NONE;
+                    encryption.key.clear();
+                    encryption.iv.clear();
+                }
+            }
+            break;
+
+            case Tag::EXTXENDLIST:
+                rep->b_live = false;
+                break;
+        }
+    }
+
+    if(rep->isLive())
+    {
+        rep->getPlaylist()->duration.Set(0);
+    }
+    else if(totalduration > rep->getPlaylist()->duration.Get())
+    {
+        rep->getPlaylist()->duration.Set(CLOCK_FREQ * totalduration / rep->timescale.Get());
+    }
+}
+
+M3U8 * Parser::parse(const std::string &playlisturl)
+{
+    char *psz_line = stream_ReadLine(p_stream);
+    if(!psz_line || strcmp(psz_line, "#EXTM3U"))
+    {
+        free(psz_line);
+        return NULL;
+    }
+
+    M3U8 *playlist = new (std::nothrow) M3U8(p_stream);
+    if(!playlist)
+        return NULL;
+
+    if(!playlisturl.empty())
+    {
+        size_t pos = playlisturl.find_last_of('/');
+        if(pos != std::string::npos)
+            playlist->addBaseUrl(playlisturl.substr(0, pos + 1));
+    }
+
+    BasePeriod *period = new (std::nothrow) BasePeriod( playlist );
+    if(!period)
+        return playlist;
+
+    std::list<Tag *> tagslist = parseEntries(p_stream);
+    bool b_masterplaylist = !getTagsFromList(tagslist, AttributesTag::EXTXSTREAMINF).empty();
+    if(b_masterplaylist)
+    {
+        BaseAdaptationSet *adaptSet = new (std::nothrow) BaseAdaptationSet(period);
+        if(adaptSet)
+        {
+            std::list<Tag *> mediainfotags = getTagsFromList(tagslist, AttributesTag::EXTXSTREAMINF);
+            std::list<Tag *>::const_iterator it;
+            for(it = mediainfotags.begin(); it != mediainfotags.end(); ++it)
+                parseRepresentation(adaptSet, dynamic_cast<AttributesTag *>(*it));
+            period->addAdaptationSet(adaptSet);
+        }
+    }
+    else
+    {
+        BaseAdaptationSet *adaptSet = new (std::nothrow) BaseAdaptationSet(period);
+        if(adaptSet)
+        {
+            period->addAdaptationSet(adaptSet);
+            AttributesTag *tag = new AttributesTag(AttributesTag::EXTXSTREAMINF, "");
+            parseRepresentation(adaptSet, tag, tagslist);
+            delete tag;
+        }
+    }
+
+    playlist->addPeriod(period);
+
+    releaseTagsList(tagslist);
+
+    playlist->debug();
+    return playlist;
+}
+
+std::list<Tag *> Parser::parseEntries(stream_t *stream)
+{
+    std::list<Tag *> entrieslist;
+    Tag *lastTag = NULL;
+    char *psz_line;
+
+    while((psz_line = stream_ReadLine(stream)))
+    {
+        if(*psz_line == '#')
+        {
+            if(!strncmp(psz_line, "#EXT", 4)) //tag
+            {
+                std::string key;
+                std::string attributes;
+                const char *split = strchr(psz_line, ':');
+                if(split)
+                {
+                    key = std::string(psz_line + 1, split - psz_line - 1);
+                    attributes = std::string(split + 1);
+                }
+                else
+                {
+                    key = std::string(psz_line);
+                }
+
+                if(!key.empty())
+                {
+                    Tag *tag = TagFactory::createTagByName(key, attributes);
+                    if(tag)
+                        entrieslist.push_back(tag);
+                    lastTag = tag;
+                }
+            }
+        }
+        else if(*psz_line && lastTag)
+        {
+            AttributesTag *attrTag = dynamic_cast<AttributesTag *>(lastTag);
+            if(attrTag)
+            {
+                Attribute *uriAttr = new (std::nothrow) Attribute("URI", std::string(psz_line));
+                if(uriAttr)
+                    attrTag->addAttribute(uriAttr);
+            }
+            lastTag = NULL;
+        }
+        else // drop
+        {
+            lastTag = NULL;
+        }
+
+        free(psz_line);
+    }
+
+    return entrieslist;
+}
diff --git a/modules/demux/hls/playlist/Parser.hpp b/modules/demux/hls/playlist/Parser.hpp
new file mode 100644 (file)
index 0000000..89732c7
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Parser.hpp
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef PARSER_HPP
+#define PARSER_HPP
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "../adaptative/playlist/SegmentInfoCommon.h"
+
+#include <cstdlib>
+#include <sstream>
+
+#include <vlc_common.h>
+
+namespace adaptative
+{
+    namespace playlist
+    {
+        class SegmentInformation;
+        class MediaSegmentTemplate;
+        class BasePeriod;
+        class BaseAdaptationSet;
+    }
+}
+
+namespace hls
+{
+    namespace playlist
+    {
+        using namespace adaptative::playlist;
+
+        class M3U8;
+        class AttributesTag;
+        class Tag;
+        class Representation;
+
+        class Parser
+        {
+            public:
+                Parser             (stream_t *p_stream);
+                virtual ~Parser    ();
+
+                M3U8 *             parse  (const std::string &);
+
+            private:
+                void parseAdaptationSet(BasePeriod *, const AttributesTag *);
+                void parseRepresentation(BaseAdaptationSet *, const AttributesTag *);
+                void parseRepresentation(BaseAdaptationSet *, const AttributesTag *,
+                                         const std::list<Tag *>&);
+                void parseSegments(Representation *, const std::list<Tag *>&);
+                std::list<Tag *> parseEntries(stream_t *);
+
+                stream_t        *p_stream;
+        };
+    }
+}
+
+#endif // PARSER_HPP
diff --git a/modules/demux/hls/playlist/Representation.cpp b/modules/demux/hls/playlist/Representation.cpp
new file mode 100644 (file)
index 0000000..1adbf5f
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Representation.cpp
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <cstdlib>
+
+#include "Representation.hpp"
+#include "M3U8.hpp"
+#include "../adaptative/playlist/BasePeriod.h"
+#include "../adaptative/playlist/BaseAdaptationSet.h"
+#include "../adaptative/playlist/SegmentList.h"
+
+using namespace hls::playlist;
+
+Representation::Representation  ( BaseAdaptationSet *set ) :
+                BaseRepresentation( set )
+{
+    b_live = true;
+}
+
+Representation::~Representation ()
+{
+}
+
+bool Representation::isLive() const
+{
+    return b_live;
+}
+
+void Representation::localMergeWithPlaylist(M3U8 *updated, mtime_t prunebarrier)
+{
+    BasePeriod *period = updated->getFirstPeriod();
+    if(!period)
+        return;
+
+    BaseAdaptationSet *adapt = period->getAdaptationSets().front();
+    if(!adapt)
+        return;
+
+    BaseRepresentation *rep = adapt->getRepresentations().front();
+    if(!rep)
+        return;
+
+    this->mergeWith( rep, prunebarrier );
+}
+
+void Representation::mergeWith(SegmentInformation *seginfo, mtime_t prunebarrier)
+{
+    BaseRepresentation::mergeWith(seginfo, prunebarrier);
+}
diff --git a/modules/demux/hls/playlist/Representation.hpp b/modules/demux/hls/playlist/Representation.hpp
new file mode 100644 (file)
index 0000000..c1e5dcf
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Representation.hpp
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef HLSREPRESENTATION_H_
+#define HLSREPRESENTATION_H_
+
+#include "../adaptative/playlist/BaseRepresentation.h"
+#include "../adaptative/tools/Properties.hpp"
+
+namespace hls
+{
+    namespace playlist
+    {
+        class M3U8;
+
+        using namespace adaptative::playlist;
+
+        class Representation : public BaseRepresentation
+        {
+            friend class Parser;
+
+            public:
+                Representation( BaseAdaptationSet * );
+                virtual ~Representation ();
+                void localMergeWithPlaylist(M3U8 *, mtime_t);
+                bool isLive() const;
+                virtual void mergeWith(SegmentInformation *, mtime_t); /* reimpl */
+
+            private:
+                bool b_live;
+                Property<std::string> playlistUrl;
+                Property<std::string> audio;
+                Property<std::string> video;
+                Property<std::string> subtitles;
+                Property<std::string> closedcaptions;
+
+        };
+    }
+}
+
+#endif /* HLSREPRESENTATION_H_ */
diff --git a/modules/demux/hls/playlist/Tags.cpp b/modules/demux/hls/playlist/Tags.cpp
new file mode 100644 (file)
index 0000000..da45104
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+ * Tags.cpp
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#include "Tags.hpp"
+#include <sstream>
+#include <stack>
+
+#define __STDC_CONSTANT_MACROS
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <vlc_common.h>
+
+using namespace hls::playlist;
+
+Attribute::Attribute(const std::string &name_, const std::string &value_)
+{
+    name = name_;
+    value = value_;
+}
+
+uint64_t Attribute::decimal() const
+{
+    std::istringstream is(value);
+    uint64_t ret;
+    is >> ret;
+    return ret;
+}
+
+double Attribute::floatingPoint() const
+{
+    std::istringstream is(value);
+    is.imbue(std::locale(""));
+    double ret;
+    is >> ret;
+    return ret;
+}
+
+std::vector<uint8_t> Attribute::hexSequence() const
+{
+    std::vector<uint8_t> ret;
+    if(value.length() > 2 && (value.substr(0, 2) == "0X" || value.substr(0, 2) == "0x") )
+    {
+        for(size_t i=2; i<=(value.length() - 2); i+=2)
+        {
+            unsigned val;
+            std::stringstream ss(value.substr(i, 2));
+            ss >> std::hex >> val;
+            ret.push_back(val);
+        }
+    }
+    return ret;
+}
+
+std::pair<std::size_t,std::size_t> Attribute::getByteRange() const
+{
+    std::pair<std::size_t,std::size_t> ret;
+    std::size_t length = 0;
+    std::size_t offset = 0;
+    std::istringstream is(value);
+
+    if(!is.eof())
+    {
+        is >> length;
+        if(!is.eof())
+        {
+            char c = is.get();
+            if(c == '@' && !is.eof())
+                is >> offset;
+        }
+    }
+
+    ret = std::make_pair(offset, length);
+    return ret;
+}
+
+std::string Attribute::quotedString() const
+{
+    if(value.length() < 2)
+        return "";
+
+    std::istringstream is(value.substr(1, value.length() - 2));
+    std::ostringstream os;
+
+    char c;
+    while(is.get(c))
+    {
+        if(c == '\\')
+        {
+            if(!is.get(c))
+                break;
+        }
+
+        os << c;
+    }
+
+    return os.str();
+}
+
+Tag::Tag(int type_)
+{
+    type = type_;
+}
+
+Tag::~Tag()
+{
+}
+
+int Tag::getType() const
+{
+    return type;
+}
+
+SingleValueTag::SingleValueTag(int type, const std::string &v)
+    : Tag(type), attr("", v)
+{
+}
+
+SingleValueTag::~SingleValueTag()
+{
+
+}
+
+const Attribute &SingleValueTag::getValue() const
+{
+    return attr;
+}
+
+AttributesTag::AttributesTag(int type, const std::string &v) : Tag(type)
+{
+    parseAttributes(v);
+}
+
+AttributesTag::~AttributesTag()
+{
+    std::list<Attribute *>::const_iterator it;
+    for(it = attributes.begin(); it != attributes.end(); ++it)
+        delete *it;
+}
+
+const Attribute * AttributesTag::getAttributeByName(const char *name) const
+{
+    std::list<Attribute *>::const_iterator it;
+    for(it = attributes.begin(); it != attributes.end(); ++it)
+        if((*it)->name == name)
+            return *it;
+
+    return NULL;
+}
+
+void AttributesTag::addAttribute(Attribute *attr)
+{
+    attributes.push_back(attr);
+}
+
+void AttributesTag::parseAttributes(const std::string &field)
+{
+    std::istringstream iss(field);
+    std::ostringstream oss;
+
+    while(!iss.eof())
+    {
+        /* parse attribute name */
+        while(!iss.eof())
+        {
+            char c = iss.peek();
+            if((c >= 'A' && c <= 'Z') || c == '-')
+            {
+                oss.put((char)iss.get());
+            }
+            else if(c == '=')
+            {
+                iss.get();
+                break;
+            }
+            else /* out of range */
+                return;
+        }
+
+        std::string attrname = oss.str();
+        oss.str("");
+
+        /* parse attributes value */
+        bool b_quoted = false;
+        while(!iss.eof())
+        {
+            char c = iss.peek();
+            if(c == '\\' && b_quoted)
+            {
+                iss.get();
+            }
+            else if(c == ',' && !b_quoted)
+            {
+                iss.get();
+                break;
+            }
+            else if(c == '"')
+            {
+                b_quoted = !b_quoted;
+            }
+
+            if(!iss.eof())
+                oss.put((char)iss.get());
+        }
+
+        std::string attrvalue = oss.str();
+        oss.str("");
+
+        Attribute *attribute = new (std::nothrow) Attribute(attrname, attrvalue);
+        if(attribute)
+            attributes.push_back(attribute);
+    }
+}
+
+
+URITag::URITag(const std::string &v) : AttributesTag(URITag::EXTINF, v)
+{
+    parseAttributes(v);
+}
+
+URITag::~URITag()
+{
+}
+
+void URITag::parseAttributes(const std::string &field)
+{
+    std::size_t pos = field.find(',');
+    if(pos != std::string::npos)
+    {
+        Attribute *attr = new (std::nothrow) Attribute("DURATION", field.substr(0, pos));
+        if(attr)
+            addAttribute(attr);
+
+        attr = new (std::nothrow) Attribute("TITLE", field.substr(pos));
+        if(attr)
+            addAttribute(attr);
+    }
+}
+
+Tag * TagFactory::createTagByName(const std::string &name, const std::string &value)
+{
+    struct
+    {
+        const char *psz;
+        const int i;
+    } const exttagmapping[] = {
+        {"EXT-X-BYTERANGE",                 SingleValueTag::EXTXBYTERANGE},
+        {"EXT-X-DISCONTINUITY",             Tag::EXTXDISCONTINUITY},
+        {"EXT-X-KEY",                       AttributesTag::EXTXKEY},
+        {"EXT-X-MAP",                       AttributesTag::EXTXMAP},
+        {"EXT-X-PROGRAM-DATE-TIME",         SingleValueTag::EXTXPROGRAMDATETIME},
+        {"EXT-X-TARGETDURATION",            SingleValueTag::EXTXTARGETDURATION},
+        {"EXT-X-MEDIA-SEQUENCE",            SingleValueTag::EXTXMEDIASEQUENCE},
+        {"EXT-X-DISCONTINUITY-SEQUENCE",    SingleValueTag::EXTXDISCONTINUITYSEQUENCE},
+        {"EXT-X-ENDLIST",                   Tag::EXTXENDLIST},
+        {"EXT-X-PLAYLIST-TYPE",             SingleValueTag::EXTXPLAYLISTTYPE},
+        {"EXT-X-I-FRAMES-ONLY",             Tag::EXTXIFRAMESONLY},
+        {"EXT-X-MEDIA",                     AttributesTag::EXTXMEDIA},
+        {"EXT-X-STREAM-INF",                AttributesTag::EXTXSTREAMINF},
+        {"EXTINF",                          URITag::EXTINF},
+        {NULL,                              0},
+    };
+
+
+    for(int i=0; exttagmapping[i].psz; i++)
+    {
+        if(name != exttagmapping[i].psz)
+            continue;
+
+        switch(exttagmapping[i].i)
+        {
+        case Tag::EXTXDISCONTINUITY:
+        case Tag::EXTXENDLIST:
+        case Tag::EXTXIFRAMESONLY:
+            return new (std::nothrow) Tag(exttagmapping[i].i);
+
+        case SingleValueTag::EXTXVERSION:
+        case SingleValueTag::EXTXBYTERANGE:
+        case SingleValueTag::EXTXPROGRAMDATETIME:
+        case SingleValueTag::EXTXTARGETDURATION:
+        case SingleValueTag::EXTXMEDIASEQUENCE:
+        case SingleValueTag::EXTXDISCONTINUITYSEQUENCE:
+        case SingleValueTag::EXTXPLAYLISTTYPE:
+            return new (std::nothrow) SingleValueTag(exttagmapping[i].i, value);
+
+        case AttributesTag::EXTXKEY:
+        case AttributesTag::EXTXMAP:
+        case AttributesTag::EXTXMEDIA:
+        case AttributesTag::EXTXSTREAMINF:
+            return new (std::nothrow) AttributesTag(exttagmapping[i].i, value);
+
+        case URITag::EXTINF:
+            return new URITag(value);
+        }
+
+    }
+
+
+    return NULL;
+}
diff --git a/modules/demux/hls/playlist/Tags.hpp b/modules/demux/hls/playlist/Tags.hpp
new file mode 100644 (file)
index 0000000..56976a8
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Tags.hpp
+ *****************************************************************************
+ * Copyright © 2015 - VideoLAN Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef TAGS_HPP
+#define TAGS_HPP
+
+#ifndef __STDC_CONSTANT_MACROS
+ #define __STDC_CONSTANT_MACROS
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+#include <list>
+#include <utility>
+
+namespace hls
+{
+
+    namespace playlist
+    {
+
+        class Attribute
+        {
+            public:
+                Attribute(const std::string &, const std::string &);
+
+                uint64_t decimal() const;
+                std::string quotedString() const;
+                double floatingPoint() const;
+                std::vector<uint8_t> hexSequence() const;
+                std::pair<std::size_t,std::size_t> getByteRange() const;
+
+                std::string name;
+                std::string value;
+        };
+
+        class Tag
+        {
+            public:
+                enum
+                {
+                    EXTXDISCONTINUITY = 0,
+                    EXTXENDLIST,
+                    EXTXIFRAMESONLY,
+                };
+                Tag(int);
+                virtual ~Tag();
+                int getType() const;
+
+            private:
+                int type;
+        };
+
+        class SingleValueTag : public Tag
+        {
+            public:
+                enum
+                {
+                    EXTXVERSION = 10,
+                    EXTXBYTERANGE,
+                    EXTXPROGRAMDATETIME,
+                    EXTXTARGETDURATION,
+                    EXTXMEDIASEQUENCE,
+                    EXTXDISCONTINUITYSEQUENCE,
+                    EXTXPLAYLISTTYPE,
+                };
+                SingleValueTag(int, const std::string &);
+                virtual ~SingleValueTag();
+                const Attribute & getValue() const;
+            private:
+                Attribute attr;
+        };
+
+        class AttributesTag : public Tag
+        {
+            public:
+                enum
+                {
+                    EXTXKEY = 20,
+                    EXTXMAP,
+                    EXTXMEDIA,
+                    EXTXSTREAMINF,
+                };
+                AttributesTag(int, const std::string &);
+                virtual ~AttributesTag();
+                const Attribute * getAttributeByName(const char *) const;
+                void addAttribute(Attribute *);
+
+            protected:
+                virtual void parseAttributes(const std::string &);
+                std::list<Attribute *> attributes;
+        };
+
+        class URITag : public AttributesTag
+        {
+            public:
+                enum
+                {
+                    EXTINF = 30
+                };
+                URITag(const std::string &);
+                virtual ~URITag();
+
+            protected:
+                virtual void parseAttributes(const std::string &);
+        };
+
+        class TagFactory
+        {
+            public:
+                static Tag * createTagByName(const std::string &, const std::string &);
+                static Attribute * createAttributeByName(const std::string &);
+        };
+    }
+}
+#endif // TAGS_HPP
index 867962e..3df64ef 100644 (file)
@@ -465,6 +465,7 @@ modules/demux/demuxdump.c
 modules/demux/dirac.c
 modules/demux/flac.c
 modules/demux/gme.c
+modules/demux/hls/hls.cpp
 modules/demux/image.c
 modules/demux/mjpeg.c
 modules/demux/mkv/chapter_command.cpp