]> git.sesse.net Git - vlc/blobdiff - modules/stream_filter/httplive.c
hls: Fixing hls_Copy
[vlc] / modules / stream_filter / httplive.c
index 998ebd3cc5b3786584fb04edc44daf3c8dc5c9db..3c52b83ae6477392eeed423fe4c4db258b30037a 100644 (file)
 #include <vlc_plugin.h>
 
 #include <assert.h>
+#include <gcrypt.h>
 
 #include <vlc_threads.h>
 #include <vlc_arrays.h>
 #include <vlc_stream.h>
 #include <vlc_url.h>
 #include <vlc_memory.h>
+#include <vlc_gcrypt.h>
 
 /*****************************************************************************
  * Module descriptor
@@ -59,6 +61,7 @@ vlc_module_end()
 /*****************************************************************************
  *
  *****************************************************************************/
+#define AES_BLOCK_SIZE 16 /* Only support AES-128 */
 typedef struct segment_s
 {
     int         sequence;   /* unique sequence number */
@@ -67,6 +70,10 @@ typedef struct segment_s
     uint64_t    bandwidth;  /* bandwidth usage of segments (bits per second)*/
 
     vlc_url_t   url;
+    char       *psz_key_path;         /* url key path */
+    uint8_t     psz_AES_key[16];      /* AES-128 */
+    bool        b_key_loaded;
+
     vlc_mutex_t lock;
     block_t     *data;      /* data */
 } segment_t;
@@ -76,14 +83,19 @@ typedef struct hls_stream_s
     int         id;         /* program id */
     int         version;    /* protocol version should be 1 */
     int         sequence;   /* media sequence number */
-    int         duration;   /* maximum duration per segment (ms) */
+    int         duration;   /* maximum duration per segment (s) */
     uint64_t    bandwidth;  /* bandwidth usage of segments (bits per second)*/
-    uint64_t    size;       /* stream length (segment->duration * hls->bandwidth/8) */
+    uint64_t    size;       /* stream length is calculated by taking the sum
+                               foreach segment of (segment->duration * hls->bandwidth/8) */
 
     vlc_array_t *segments;  /* list of segments */
     vlc_url_t   url;        /* uri to m3u8 */
     vlc_mutex_t lock;
     bool        b_cache;    /* allow caching */
+
+    char        *psz_current_key_path;          /* URL path of the encrypted key */
+    uint8_t      psz_AES_IV[AES_BLOCK_SIZE];    /* IV used when decypher the block */
+    bool         b_iv_loaded;
 } hls_stream_t;
 
 struct stream_sys_t
@@ -129,6 +141,7 @@ struct stream_sys_t
     bool        b_meta;     /* meta playlist */
     bool        b_live;     /* live stream? or vod? */
     bool        b_error;    /* parsing error */
+    bool        b_aesmsg;   /* only print one time that the media is encrypted */
 };
 
 /****************************************************************************
@@ -150,6 +163,8 @@ static void* hls_Reload(void *);
 static segment_t *segment_GetSegment(hls_stream_t *hls, int wanted);
 static void segment_Free(segment_t *segment);
 
+static char *ConstructUrl(vlc_url_t *url);
+
 /****************************************************************************
  *
  ****************************************************************************/
@@ -210,6 +225,7 @@ static hls_stream_t *hls_New(vlc_array_t *hls_stream, const int id, const uint64
     hls->version = 1;  /* default protocol version */
     hls->b_cache = true;
     vlc_UrlParse(&hls->url, uri, 0);
+    hls->psz_current_key_path = NULL;
     hls->segments = vlc_array_new();
     vlc_array_append(hls_stream, hls);
     vlc_mutex_init(&hls->lock);
@@ -229,12 +245,42 @@ static void hls_Free(hls_stream_t *hls)
         }
         vlc_array_destroy(hls->segments);
     }
-
     vlc_UrlClean(&hls->url);
+    free(hls->psz_current_key_path);
     free(hls);
     hls = NULL;
 }
 
+static hls_stream_t *hls_Copy(hls_stream_t *src, const bool b_cp_segments)
+{
+    assert(src);
+    assert(!b_cp_segments); /* FIXME: copying segments is not implemented */
+
+    hls_stream_t *dst = (hls_stream_t *)malloc(sizeof(hls_stream_t));
+    if (dst == NULL) return NULL;
+
+    dst->id = src->id;
+    dst->bandwidth = src->bandwidth;
+    dst->duration = src->duration;
+    dst->size = src->size;
+    dst->sequence = src->sequence;
+    dst->version = src->version;
+    dst->b_cache = src->b_cache;
+    dst->psz_current_key_path = src->psz_current_key_path ?
+                strdup( src->psz_current_key_path ) : NULL;
+    char *uri = ConstructUrl(&src->url);
+    if (uri == NULL)
+    {
+        free(dst);
+        return NULL;
+    }
+    vlc_UrlParse(&dst->url, uri, 0);
+    if (!b_cp_segments)
+        dst->segments = vlc_array_new();
+    vlc_mutex_init(&dst->lock);
+    return dst;
+}
+
 static hls_stream_t *hls_Get(vlc_array_t *hls_stream, const int wanted)
 {
     int count = vlc_array_count(hls_stream);
@@ -283,6 +329,12 @@ static uint64_t hls_GetStreamSize(hls_stream_t *hls)
      * then the deviation from exact byte size will be big and the seek/
      * progressbar will not behave entirely as one expects. */
     uint64_t size = 0UL;
+
+    /* If there is no valid bandwidth yet, then there is no point in
+     * computing stream size. */
+    if (hls->bandwidth == 0)
+        return size;
+
     int count = vlc_array_count(hls->segments);
     for (int n = 0; n < count; n++)
     {
@@ -310,6 +362,10 @@ static segment_t *segment_New(hls_stream_t* hls, const int duration, const char
     segment->data = NULL;
     vlc_array_append(hls->segments, segment);
     vlc_mutex_init(&segment->lock);
+    segment->b_key_loaded = false;
+    segment->psz_key_path = NULL;
+    if (hls->psz_current_key_path)
+        segment->psz_key_path = strdup(hls->psz_current_key_path);
     return segment;
 }
 
@@ -318,6 +374,7 @@ static void segment_Free(segment_t *segment)
     vlc_mutex_destroy(&segment->lock);
 
     vlc_UrlClean(&segment->url);
+    free(segment->psz_key_path);
     if (segment->data)
         block_Release(segment->data);
     free(segment);
@@ -429,44 +486,122 @@ static char *parse_Attributes(const char *line, const char *attr)
     return NULL;
 }
 
-static char *relative_URI(stream_t *s, const char *uri, const char *path)
+static int hex2int(char c)
+{
+    if (c >= '0' && c <= '9')
+        return c - '0';
+    if (c >= 'A' && c <= 'F')
+        return c - 'A' + 10;
+    if (c >= 'a' && c <= 'f')
+        return c - 'a' + 10;
+    return -1;
+}
+
+static int string_to_IV(const char *string_hexa, uint8_t iv[AES_BLOCK_SIZE])
+{
+    const char *p = string_hexa;
+    uint8_t *d = iv;
+    unsigned int c;
+
+    if (*p++ != '0')
+        return VLC_EGENERIC;
+    if (*p++ != 'x')
+        return VLC_EGENERIC;
+
+    while (*p && *(p+1))
+    {
+        c = hex2int(*p++) << 4;
+        c |= hex2int(*p++);
+        *d++ = c;
+    }
+
+    return VLC_SUCCESS;
+}
+
+static char *relative_URI(stream_t *s, const char *uri, const vlc_url_t *url)
 {
     stream_sys_t *p_sys = s->p_sys;
+    char *psz_password = NULL;
+    char *psz_username = NULL;
+    char *psz_protocol = NULL;
+    char *psz_path = NULL;
+    char *psz_host = NULL;
+    char *psz_uri = NULL;
+    int  i_port = -1;
 
     char *p = strchr(uri, ':');
     if (p != NULL)
         return NULL;
 
-    if (p_sys->m3u8.psz_path == NULL)
-        return NULL;
+    /* Determine protocol to use */
+    if (url && url->psz_protocol)
+    {
+        psz_protocol = strdup(url->psz_protocol);
+        i_port = url->i_port;
+    }
+    else if (p_sys->m3u8.psz_protocol)
+    {
+        psz_protocol = strdup(p_sys->m3u8.psz_protocol);
+        i_port = p_sys->m3u8.i_port;
+    }
+
+    /* Determine host to use */
+    if (url && url->psz_host)
+        psz_host = strdup(url->psz_host);
+    else if (p_sys->m3u8.psz_host)
+        psz_host = strdup(p_sys->m3u8.psz_host);
+
+    /* Determine path to use */
+    if (url && url->psz_path != NULL)
+        psz_path = strdup(url->psz_path);
+    else if (p_sys->m3u8.psz_path != NULL)
+        psz_path = strdup(p_sys->m3u8.psz_path);
+
+    if ((psz_protocol == NULL) ||
+        (psz_path == NULL) ||
+        (psz_host == NULL))
+        goto fail;
 
-    char *psz_path = strdup(p_sys->m3u8.psz_path);
-    if (psz_path == NULL) return NULL;
     p = strrchr(psz_path, '/');
     if (p) *p = '\0';
 
-    char *psz_uri = NULL;
-    if (p_sys->m3u8.psz_password || p_sys->m3u8.psz_username)
-    {
-        if (asprintf(&psz_uri, "%s://%s:%s@%s:%d%s/%s", p_sys->m3u8.psz_protocol,
-                     p_sys->m3u8.psz_username, p_sys->m3u8.psz_password,
-                     p_sys->m3u8.psz_host, p_sys->m3u8.i_port,
-                     path ? path : psz_path, uri) < 0)
+    /* Determine credentials to use */
+    if (url && url->psz_username)
+        psz_username = strdup(url->psz_username);
+    else if (p_sys->m3u8.psz_username)
+        psz_username = strdup(p_sys->m3u8.psz_username);
+
+    if (url && url->psz_password)
+        psz_password = strdup(url->psz_password);
+    else if (p_sys->m3u8.psz_password)
+        psz_password = strdup(p_sys->m3u8.psz_password);
+
+    /* */
+    if (psz_password || psz_username)
+    {
+        if (asprintf(&psz_uri, "%s://%s:%s@%s:%d%s/%s",
+                     psz_protocol,
+                     psz_username ? psz_username : "",
+                     psz_password ? psz_password : "",
+                     psz_host, i_port,
+                     psz_path, uri) < 0)
             goto fail;
     }
     else
     {
-        if (asprintf(&psz_uri, "%s://%s:%d%s/%s", p_sys->m3u8.psz_protocol,
-                     p_sys->m3u8.psz_host, p_sys->m3u8.i_port,
-                     path ? path : psz_path, uri) < 0)
+        if (asprintf(&psz_uri, "%s://%s:%d%s/%s",
+                     psz_protocol, psz_host, i_port,
+                     psz_path, uri) < 0)
            goto fail;
     }
-    free(psz_path);
-    return psz_uri;
 
 fail:
+    free(psz_password);
+    free(psz_username);
+    free(psz_protocol);
     free(psz_path);
-    return NULL;
+    free(psz_host);
+    return psz_uri;
 }
 
 static char *ConstructUrl(vlc_url_t *url)
@@ -555,25 +690,17 @@ static int parse_AddSegment(stream_t *s, hls_stream_t *hls, const int duration,
     assert(uri);
 
     /* Store segment information */
-    char *psz_path = NULL;
-    if (hls->url.psz_path != NULL)
-    {
-        psz_path = strdup(hls->url.psz_path);
-        if (psz_path == NULL)
-            return VLC_ENOMEM;
-        char *p = strrchr(psz_path, '/');
-        if (p) *p = '\0';
-    }
-    char *psz_uri = relative_URI(s, uri, psz_path);
-    free(psz_path);
-
     vlc_mutex_lock(&hls->lock);
+
+    char *psz_uri = relative_URI(s, uri, &hls->url);
+
     segment_t *segment = segment_New(hls, duration, psz_uri ? psz_uri : uri);
     if (segment)
         segment->sequence = hls->sequence + vlc_array_count(hls->segments) - 1;
+    free(psz_uri);
+
     vlc_mutex_unlock(&hls->lock);
 
-    free(psz_uri);
     return segment ? VLC_SUCCESS : VLC_ENOMEM;
 }
 
@@ -650,9 +777,18 @@ static int parse_MediaSequence(stream_t *s, hls_stream_t *hls, char *p_read)
     }
 
     if (hls->sequence > 0)
-        msg_Err(s, "EXT-X-MEDIA-SEQUENCE already present in playlist (new=%d, old=%d)",
-                    sequence, hls->sequence);
-
+    {
+        if (s->p_sys->b_live)
+        {
+            hls_stream_t *last = hls_GetLast(s->p_sys->hls_stream);
+            if ((last->sequence < sequence) && (sequence - last->sequence != 1))
+                msg_Err(s, "EXT-X-MEDIA-SEQUENCE gap in playlist (new=%d, old=%d)",
+                            sequence, last->sequence);
+        }
+        else
+            msg_Err(s, "EXT-X-MEDIA-SEQUENCE already present in playlist (new=%d, old=%d)",
+                        sequence, hls->sequence);
+    }
     hls->sequence = sequence;
     return VLC_SUCCESS;
 }
@@ -672,7 +808,6 @@ static int parse_Key(stream_t *s, hls_stream_t *hls, char *p_read)
 
     if (strncasecmp(attr, "NONE", 4) == 0)
     {
-
         char *uri = parse_Attributes(p_read, "URI");
         if (uri != NULL)
         {
@@ -692,6 +827,62 @@ static int parse_Key(stream_t *s, hls_stream_t *hls, char *p_read)
             free(iv);
         }
     }
+    else if (strncasecmp(attr, "AES-128", 7) == 0)
+    {
+        char *value, *uri, *iv;
+        if (s->p_sys->b_aesmsg == false)
+        {
+            msg_Info(s, "playback of AES-128 encrypted HTTP Live media detected.");
+            s->p_sys->b_aesmsg = true;
+        }
+        value = uri = parse_Attributes(p_read, "URI");
+        if (value == NULL)
+        {
+            msg_Err(s, "#EXT-X-KEY: URI not found for encrypted HTTP Live media in AES-128");
+            free(attr);
+            return VLC_EGENERIC;
+        }
+
+        /* Url is put between quotes, remove them */
+        if (*value == '"')
+        {
+            /* We need to strip the "" from the attribute value */
+            uri = value + 1;
+            char* end = strchr(uri, '"');
+            if (end != NULL)
+                *end = 0;
+        }
+        hls->psz_current_key_path = strdup(uri);
+        free(value);
+
+        value = iv = parse_Attributes(p_read, "IV");
+        if (iv == NULL)
+        {
+            /*
+            * If the EXT-X-KEY tag does not have the IV attribute, implementations
+            * MUST use the sequence number of the media file as the IV when
+            * encrypting or decrypting that media file.  The big-endian binary
+            * representation of the sequence number SHALL be placed in a 16-octet
+            * buffer and padded (on the left) with zeros.
+            */
+            hls->b_iv_loaded = false;
+        }
+        else
+        {
+            /*
+            * If the EXT-X-KEY tag has the IV attribute, implementations MUST use
+            * the attribute value as the IV when encrypting or decrypting with that
+            * key.  The value MUST be interpreted as a 128-bit hexadecimal number
+            * and MUST be prefixed with 0x or 0X.
+            */
+
+            if (string_to_IV(iv, hls->psz_AES_IV) == VLC_EGENERIC)
+                err = VLC_EGENERIC;
+            else
+                hls->b_iv_loaded = true;
+            free(value);
+        }
+    }
     else
     {
         msg_Warn(s, "playback of encrypted HTTP Live media is not supported.");
@@ -738,9 +929,9 @@ static int parse_Version(stream_t *s, hls_stream_t *hls, char *p_read)
 
     /* Check version */
     hls->version = version;
-    if (hls->version != 1)
+    if (hls->version <= 0 || hls->version > 3)
     {
-        msg_Err(s, "#EXT-X-VERSION should be version 1 iso %d", version);
+        msg_Err(s, "#EXT-X-VERSION should be version 1, 2 or 3 iso %d", version);
         return VLC_EGENERIC;
     }
     return VLC_SUCCESS;
@@ -845,27 +1036,35 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const
                     err = VLC_ENOMEM;
                 else
                 {
-                    hls_stream_t *hls = NULL;
-                    err = parse_StreamInformation(s, &streams, &hls, line, uri);
-                    free(uri);
-
-                    /* Download playlist file from server */
-                    uint8_t *buf = NULL;
-                    ssize_t len = read_M3U8_from_url(s, &hls->url, &buf);
-                    if (len < 0)
-                        err = VLC_EGENERIC;
-                    else
+                    if (*uri == '#')
                     {
-                        /* Parse HLS m3u8 content. */
-                        err = parse_M3U8(s, streams, buf, len);
-                        free(buf);
+                        msg_Info(s, "Skipping invalid stream-inf: %s", uri);
+                        free(uri);
                     }
-
-                    if (hls)
+                    else
                     {
-                        hls->version = version;
-                        if (!p_sys->b_live)
-                            hls->size = hls_GetStreamSize(hls); /* Stream size (approximate) */
+                        hls_stream_t *hls = NULL;
+                        err = parse_StreamInformation(s, &streams, &hls, line, uri);
+                        free(uri);
+
+                        /* Download playlist file from server */
+                        uint8_t *buf = NULL;
+                        ssize_t len = read_M3U8_from_url(s, &hls->url, &buf);
+                        if (len < 0)
+                            err = VLC_EGENERIC;
+                        else
+                        {
+                            /* Parse HLS m3u8 content. */
+                            err = parse_M3U8(s, streams, buf, len);
+                            free(buf);
+                        }
+
+                        if (hls)
+                        {
+                            hls->version = version;
+                            if (!p_sys->b_live)
+                                hls->size = hls_GetStreamSize(hls); /* Stream size (approximate) */
+                        }
                     }
                 }
                 p_begin = p_read;
@@ -890,7 +1089,7 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const
         else
         {
             /* No Meta playlist used */
-            hls = hls_New(streams, 0, -1, NULL);
+            hls = hls_New(streams, 0, 0, NULL);
             if (hls)
             {
                 /* Get TARGET-DURATION first */
@@ -961,21 +1160,196 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const
     return err;
 }
 
+
+static int hls_DownloadSegmentKey(stream_t *s, segment_t *seg)
+{
+    uint8_t         aeskey[32]; /* AES-512 can use up to 32 bytes */
+    ssize_t len;
+
+    stream_t *p_m3u8 = stream_UrlNew(s, seg->psz_key_path);
+    if (p_m3u8 == NULL)
+    {
+        msg_Err(s, "Failed to load the AES key for segment sequence %d", seg->sequence);
+        return VLC_EGENERIC;
+    }
+
+    len = stream_Read(p_m3u8, aeskey, sizeof(aeskey));
+    if (len != AES_BLOCK_SIZE)
+    {
+        msg_Err(s, "The AES key loaded doesn't have the right size (%d)", len);
+        stream_Delete(p_m3u8);
+        return VLC_EGENERIC;
+    }
+
+    memcpy(seg->psz_AES_key, aeskey, AES_BLOCK_SIZE);
+
+    stream_Delete(p_m3u8);
+
+    return VLC_SUCCESS;
+}
+
+static int hls_ManageSegmentKeys(stream_t *s, hls_stream_t *hls)
+{
+    segment_t   *seg = NULL;
+    segment_t   *prev_seg;
+    int         count = vlc_array_count(hls->segments);
+
+    for (int i = 0; i < count; i++)
+    {
+        prev_seg = seg;
+        seg = segment_GetSegment(hls, i);
+        if (seg->psz_key_path == NULL)
+            continue;   /* No key to load ? continue */
+        if (seg->b_key_loaded)
+            continue;   /* The key is already loaded */
+
+        /* if the key has not changed, and already available from previous segment,
+         * try to copy it, and don't load the key */
+        if (prev_seg && prev_seg->b_key_loaded && strcmp(seg->psz_key_path, prev_seg->psz_key_path) == 0)
+        {
+            memcpy(seg->psz_AES_key, prev_seg->psz_AES_key, AES_BLOCK_SIZE);
+            seg->b_key_loaded = true;
+            continue;
+        }
+        if (hls_DownloadSegmentKey(s, seg) != VLC_SUCCESS)
+            return VLC_EGENERIC;
+       seg->b_key_loaded = true;
+    }
+    return VLC_SUCCESS;
+}
+
+static int hls_DecodeSegmentData(stream_t *s, hls_stream_t *hls, segment_t *segment)
+{
+    /* Did the segment need to be decoded ? */
+    if (segment->psz_key_path == NULL)
+        return VLC_SUCCESS;
+
+    /* Do we have loaded the key ? */
+    if (!segment->b_key_loaded)
+    {
+        /* No ? try to download it now */
+        if (hls_ManageSegmentKeys(s, hls) != VLC_SUCCESS)
+            return VLC_EGENERIC;
+    }
+
+    /* For now, we only decode AES-128 data */
+    gcry_error_t i_gcrypt_err;
+    gcry_cipher_hd_t aes_ctx;
+    /* Setup AES */
+    i_gcrypt_err = gcry_cipher_open(&aes_ctx, GCRY_CIPHER_AES,
+                                     GCRY_CIPHER_MODE_CBC, 0);
+    if (i_gcrypt_err)
+    {
+        msg_Err(s, "gcry_cipher_open failed: %s", gpg_strerror(i_gcrypt_err));
+        gcry_cipher_close(aes_ctx);
+        return VLC_EGENERIC;
+    }
+
+    /* Set key */
+    i_gcrypt_err = gcry_cipher_setkey(aes_ctx, segment->psz_AES_key,
+                                       sizeof(segment->psz_AES_key));
+    if (i_gcrypt_err)
+    {
+        msg_Err(s, "gcry_cipher_setkey failed: %s", gpg_strerror(i_gcrypt_err));
+        gcry_cipher_close(aes_ctx);
+        return VLC_EGENERIC;
+    }
+
+    if (hls->b_iv_loaded == false)
+    {
+        memset(hls->psz_AES_IV, 0, AES_BLOCK_SIZE);
+        hls->psz_AES_IV[15] = segment->sequence & 0xff;
+        hls->psz_AES_IV[14] = (segment->sequence >> 8)& 0xff;
+        hls->psz_AES_IV[13] = (segment->sequence >> 16)& 0xff;
+        hls->psz_AES_IV[12] = (segment->sequence >> 24)& 0xff;
+    }
+
+    i_gcrypt_err = gcry_cipher_setiv(aes_ctx, hls->psz_AES_IV,
+                                      sizeof(hls->psz_AES_IV));
+
+    if (i_gcrypt_err)
+    {
+        msg_Err(s, "gcry_cipher_setiv failed: %s", gpg_strerror(i_gcrypt_err));
+        gcry_cipher_close(aes_ctx);
+        return VLC_EGENERIC;
+    }
+
+    i_gcrypt_err = gcry_cipher_decrypt(aes_ctx,
+                                       segment->data->p_buffer, /* out */
+                                       segment->data->i_buffer,
+                                       NULL, /* in */
+                                       0);
+    if (i_gcrypt_err)
+    {
+        msg_Err(s, "gcry_cipher_decrypt failed:  %s/%s\n", gcry_strsource(i_gcrypt_err), gcry_strerror(i_gcrypt_err));
+        gcry_cipher_close(aes_ctx);
+        return VLC_EGENERIC;
+    }
+    gcry_cipher_close(aes_ctx);
+    /* remove the PKCS#7 padding from the buffer */
+    int pad = segment->data->p_buffer[segment->data->i_buffer-1];
+    if (pad <= 0 || pad > AES_BLOCK_SIZE)
+    {
+        msg_Err(s, "Bad padding character (0x%x), perhaps we failed to decrypt the segment with the correct key", pad);
+        return VLC_EGENERIC;
+    }
+    int count = pad;
+    while (count--)
+    {
+        if (segment->data->p_buffer[segment->data->i_buffer-1-count] != pad)
+        {
+                msg_Err(s, "Bad ending buffer, perhaps we failed to decrypt the segment with the correct key");
+                return VLC_EGENERIC;
+        }
+    }
+
+    /* not all the data is readable because of padding */
+    segment->data->i_buffer -= pad;
+
+    return VLC_SUCCESS;
+}
+
 static int get_HTTPLiveMetaPlaylist(stream_t *s, vlc_array_t **streams)
 {
     stream_sys_t *p_sys = s->p_sys;
     assert(*streams);
+    int err = VLC_EGENERIC;
 
-    /* Download new playlist file from server */
-    uint8_t *buffer = NULL;
-    ssize_t len = read_M3U8_from_url(s, &p_sys->m3u8, &buffer);
-    if (len < 0)
-        return VLC_EGENERIC;
+    /* Duplicate HLS stream META information */
+    for (int i = 0; i < vlc_array_count(p_sys->hls_stream); i++)
+    {
+        hls_stream_t *src, *dst;
+        src = (hls_stream_t *)vlc_array_item_at_index(p_sys->hls_stream, i);
+        if (src == NULL)
+            return VLC_EGENERIC;
 
-    /* Parse HLS m3u8 content. */
-    int err = parse_M3U8(s, *streams, buffer, len);
-    free(buffer);
+        dst = hls_Copy(src, false);
+        if (dst == NULL)
+            return VLC_ENOMEM;
+
+        vlc_array_append(*streams, dst);
+    }
 
+    /* Download new playlist file from server */
+    for (int i = 0; i < vlc_array_count(*streams); i++)
+    {
+        hls_stream_t *hls;
+        hls = (hls_stream_t *)vlc_array_item_at_index(*streams, i);
+        if (hls == NULL)
+            return VLC_EGENERIC;
+
+        /* Download playlist file from server */
+        uint8_t *buf = NULL;
+        ssize_t len = read_M3U8_from_url(s, &hls->url, &buf);
+        if (len < 0)
+            err = VLC_EGENERIC;
+        else
+        {
+            /* Parse HLS m3u8 content. */
+            err = parse_M3U8(s, *streams, buf, len);
+            free(buf);
+        }
+    }
     return err;
 }
 
@@ -986,6 +1360,7 @@ static int hls_UpdatePlaylist(stream_t *s, hls_stream_t *hls_new, hls_stream_t *
 
     msg_Info(s, "updating hls stream (program-id=%d, bandwidth=%"PRIu64") has %d segments",
              hls_new->id, hls_new->bandwidth, count);
+
     for (int n = 0; n < count; n++)
     {
         segment_t *p = segment_GetSegment(hls_new, n);
@@ -995,6 +1370,8 @@ static int hls_UpdatePlaylist(stream_t *s, hls_stream_t *hls_new, hls_stream_t *
         segment_t *segment = segment_Find(*hls, p->sequence);
         if (segment)
         {
+            vlc_mutex_lock(&segment->lock);
+
             assert(p->url.psz_path);
             assert(segment->url.psz_path);
 
@@ -1015,12 +1392,22 @@ static int hls_UpdatePlaylist(stream_t *s, hls_stream_t *hls_new, hls_stream_t *
                 {
                     msg_Err(s, "Failed updating segment %d - skipping it",  p->sequence);
                     segment_Free(p);
+                    vlc_mutex_unlock(&segment->lock);
                     continue;
                 }
                 segment->sequence = p->sequence;
                 segment->duration = p->duration;
                 vlc_UrlClean(&segment->url);
                 vlc_UrlParse(&segment->url, psz_url, 0);
+                /* We must free the content, because if the key was not downloaded, content can't be decrypted */
+                if (segment->data)
+                {
+                    block_Release(segment->data);
+                    segment->data = NULL;
+                }
+                free(segment->psz_key_path);
+                segment->psz_key_path = p->psz_key_path ? strdup(p->psz_key_path) : NULL;
+                vlc_mutex_unlock(&segment->lock);
                 segment_Free(p);
                 free(psz_url);
             }
@@ -1041,6 +1428,13 @@ static int hls_UpdatePlaylist(stream_t *s, hls_stream_t *hls_new, hls_stream_t *
         }
         vlc_mutex_unlock(&(*hls)->lock);
     }
+
+    /* update meta information */
+    vlc_mutex_lock(&(*hls)->lock);
+    (*hls)->sequence = hls_new->sequence;
+    (*hls)->duration = (hls_new->duration == -1) ? (*hls)->duration : hls_new->duration;
+    (*hls)->b_cache = hls_new->b_cache;
+    vlc_mutex_unlock(&(*hls)->lock);
     return VLC_SUCCESS;
 
 fail_and_unlock:
@@ -1089,23 +1483,10 @@ static int hls_ReloadPlaylist(stream_t *s)
             msg_Info(s, "new HLS stream appended (id=%d, bandwidth=%"PRIu64")",
                      hls_new->id, hls_new->bandwidth);
         }
-        else
-        {
-            if ((hls_UpdatePlaylist(s, hls_new, &hls_old) == VLC_SUCCESS) &&
-                    (s->p_sys->download.stream == n))
-            {
-                /* wake up download thread */
-                msg_Err(s, "WAKING UP DOWNLOAD THREAD");
-                vlc_mutex_lock(&s->p_sys->download.lock_wait);
-                vlc_cond_signal(&s->p_sys->download.wait);
-                vlc_mutex_unlock(&s->p_sys->download.lock_wait);
-            }
-            else
-                msg_Info(s, "failed updating HLS stream (id=%d, bandwidth=%"PRIu64")",
-                             hls_new->id, hls_new->bandwidth);
-        }
+        else if (hls_UpdatePlaylist(s, hls_new, &hls_old) != VLC_SUCCESS)
+            msg_Info(s, "failed updating HLS stream (id=%d, bandwidth=%"PRIu64")",
+                     hls_new->id, hls_new->bandwidth);
     }
-
     vlc_array_destroy(hls_streams);
     return VLC_SUCCESS;
 }
@@ -1143,7 +1524,7 @@ static int BandwidthAdaptation(stream_t *s, int progid, uint64_t *bandwidth)
     return candidate;
 }
 
-static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur_stream)
+static int hls_DownloadSegmentData(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur_stream)
 {
     stream_sys_t *p_sys = s->p_sys;
 
@@ -1179,6 +1560,18 @@ static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur
         return VLC_EGENERIC;
     }
     mtime_t duration = mdate() - start;
+    if (hls->bandwidth == 0)
+    {
+        /* Try to estimate the bandwidth for this stream */
+        hls->bandwidth = (uint64_t)((double)segment->size / ((double)duration / 1000000.0));
+    }
+
+    /* If the segment is encrypted, decode it */
+    if (hls_DecodeSegmentData(s, hls, segment) != VLC_SUCCESS)
+    {
+        vlc_mutex_unlock(&segment->lock);
+        return VLC_EGENERIC;
+    }
 
     vlc_mutex_unlock(&segment->lock);
 
@@ -1256,7 +1649,7 @@ static void* hls_Thread(void *p_this)
         vlc_mutex_unlock(&hls->lock);
 
         if ((segment != NULL) &&
-            (Download(s, hls, segment, &p_sys->download.stream) != VLC_SUCCESS))
+            (hls_DownloadSegmentData(s, hls, segment, &p_sys->download.stream) != VLC_SUCCESS))
         {
             if (!vlc_object_alive(s)) break;
 
@@ -1355,7 +1748,7 @@ again:
     if (segment == NULL )
         return VLC_EGENERIC;
 
-    if (Download(s, hls, segment, current) != VLC_SUCCESS)
+    if (hls_DownloadSegmentData(s, hls, segment, current) != VLC_SUCCESS)
         return VLC_EGENERIC;
 
     /* Found better bandwidth match, try again */
@@ -1376,7 +1769,7 @@ again:
             continue;
         }
 
-        if (Download(s, hls, segment, current) != VLC_SUCCESS)
+        if (hls_DownloadSegmentData(s, hls, segment, current) != VLC_SUCCESS)
             return VLC_EGENERIC;
 
         p_sys->download.segment++;
@@ -1432,14 +1825,18 @@ static int hls_Download(stream_t *s, segment_t *segment)
         if (size > segment->size)
         {
             msg_Dbg(s, "size changed %"PRIu64, segment->size);
-            segment->data = block_Realloc(segment->data, 0, size);
-            if (segment->data == NULL)
+            block_t *p_block = block_Realloc(segment->data, 0, size);
+            if (p_block == NULL)
             {
                 stream_Delete(p_ts);
+                block_Release(segment->data);
+                segment->data = NULL;
                 return VLC_ENOMEM;
             }
+            segment->data = p_block;
             segment->size = size;
             assert(segment->data->i_buffer == segment->size);
+            p_block = NULL;
         }
         length = stream_Read(p_ts, segment->data->p_buffer + curlen, segment->size - curlen);
         if (length <= 0)
@@ -1558,6 +1955,9 @@ static int Open(vlc_object_t *p_this)
 
     msg_Info(p_this, "HTTP Live Streaming (%s)", s->psz_path);
 
+    /* Initialize crypto bit */
+    vlc_gcrypt_init();
+
     /* */
     s->p_sys = p_sys = calloc(1, sizeof(*p_sys));
     if (p_sys == NULL)
@@ -1572,7 +1972,7 @@ static int Open(vlc_object_t *p_this)
     vlc_UrlParse(&p_sys->m3u8, psz_uri, 0);
     free(psz_uri);
 
-    p_sys->bandwidth = -1;
+    p_sys->bandwidth = 0;
     p_sys->b_live = true;
     p_sys->b_meta = false;
     p_sys->b_error = false;
@@ -1606,6 +2006,9 @@ static int Open(vlc_object_t *p_this)
     int current = p_sys->playback.stream = 0;
     p_sys->playback.segment = p_sys->download.segment = ChooseSegment(s, current);
 
+    /* manage encryption key if needed */
+    hls_ManageSegmentKeys(s, hls_Get(p_sys->hls_stream, current));
+
     if (p_sys->b_live && (p_sys->playback.segment < 0))
     {
         msg_Warn(s, "less data than 3 times 'target duration' available for live playback, playback may stall");
@@ -1795,6 +2198,20 @@ check:
     return segment;
 }
 
+static int segment_RestorePos( segment_t *segment )
+{
+    if( segment->data )
+    {
+        uint64_t size = segment->size -segment->data->i_buffer;
+        if( size > 0 )
+        {
+            segment->data->i_buffer += size;
+            segment->data->p_buffer -= size;
+        }
+    }
+    return VLC_SUCCESS;
+}
+
 static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read)
 {
     stream_sys_t *p_sys = s->p_sys;
@@ -1818,14 +2235,8 @@ static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read)
                 segment->data = NULL;
             }
             else
-            {   /* reset playback pointer to start of buffer */
-                uint64_t size = segment->size - segment->data->i_buffer;
-                if (size > 0)
-                {
-                    segment->data->i_buffer += size;
-                    segment->data->p_buffer -= size;
-                }
-            }
+                segment_RestorePos( segment );
+
             p_sys->playback.segment++;
             vlc_mutex_unlock(&segment->lock);
 
@@ -2012,12 +2423,15 @@ static uint64_t GetStreamSize(stream_t *s)
     if (hls == NULL) return 0;
 
     vlc_mutex_lock(&hls->lock);
+    if (hls->size == 0)
+        hls->size = hls_GetStreamSize(hls);
     uint64_t size = hls->size;
     vlc_mutex_unlock(&hls->lock);
 
     return size;
 }
 
+
 static int segment_Seek(stream_t *s, const uint64_t pos)
 {
     stream_sys_t *p_sys = s->p_sys;
@@ -2033,6 +2447,17 @@ static int segment_Seek(stream_t *s, const uint64_t pos)
     uint64_t size = hls->size;
     int count = vlc_array_count(hls->segments);
 
+    /* restore current segment to start position */
+    segment_t *segment = segment_GetSegment(hls, p_sys->playback.segment);
+    if (segment == NULL)
+    {
+        vlc_mutex_unlock(&hls->lock);
+        return VLC_EGENERIC;
+    }
+    vlc_mutex_lock(&segment->lock);
+    segment_RestorePos( segment );
+    vlc_mutex_unlock(&segment->lock);
+
     for (int n = 0; n < count; n++)
     {
         segment_t *segment = vlc_array_item_at_index(hls->segments, n);
@@ -2077,17 +2502,8 @@ static int segment_Seek(stream_t *s, const uint64_t pos)
             vlc_mutex_unlock(&hls->lock);
             return VLC_EGENERIC;
         }
-
         vlc_mutex_lock(&segment->lock);
-        if (segment->data)
-        {
-            uint64_t size = segment->size -segment->data->i_buffer;
-            if (size > 0)
-            {
-                segment->data->i_buffer += size;
-                segment->data->p_buffer -= size;
-            }
-        }
+        segment_RestorePos( segment );
         vlc_mutex_unlock(&segment->lock);
 
         /* start download at current playback segment */
@@ -2125,7 +2541,6 @@ static int Control(stream_t *s, int i_query, va_list args)
     switch (i_query)
     {
         case STREAM_CAN_SEEK:
-        case STREAM_CAN_FASTSEEK:
             *(va_arg (args, bool *)) = hls_MaySeek(s);
             break;
         case STREAM_GET_POSITION: