]> git.sesse.net Git - vlc/blobdiff - modules/stream_filter/httplive.c
stream_filter/httplive.c: Cleanup hls_thread_t and stream_sys_t structs.
[vlc] / modules / stream_filter / httplive.c
index 644bfbfd76203f7bee15d11ccede3441a0f3a509..27d027a11528c796632fe2c09f824a69e338dda7 100644 (file)
@@ -90,13 +90,6 @@ typedef struct
     VLC_COMMON_MEMBERS
 
     /* */
-    int         current;    /* current hls_stream  */
-    int         segment;    /* current segment for downloading */
-    int         seek;       /* segment requested by seek (default -1) */
-    vlc_mutex_t lock_wait;  /* protect segment download counter */
-    vlc_cond_t  wait;       /* some condition to wait on */
-    vlc_array_t *hls_stream;/* bandwidth adaptation */
-
     stream_t    *s;
 } hls_thread_t;
 
@@ -110,8 +103,18 @@ struct stream_sys_t
     vlc_array_t  *hls_stream;/* bandwidth adaptation */
     uint64_t      bandwidth; /* measured bandwidth (bits per second) */
 
+    /* Download */
+    struct hls_download_s
+    {
+        int         current;    /* current hls_stream  */
+        int         segment;    /* current segment for downloading */
+        int         seek;       /* segment requested by seek (default -1) */
+        vlc_mutex_t lock_wait;  /* protect segment download counter */
+        vlc_cond_t  wait;       /* some condition to wait on */
+    } download;
+
     /* Playback */
-    struct hls_playback_t
+    struct hls_playback_s
     {
         uint64_t    offset;     /* current offset in media */
         int         current;    /* current hls_stream  */
@@ -247,6 +250,23 @@ static hls_stream_t *hls_GetLast(vlc_array_t *hls_stream)
     return (hls_stream_t *) hls_Get(hls_stream, count);
 }
 
+static hls_stream_t *hls_Find(vlc_array_t *hls_stream, hls_stream_t *hls_new)
+{
+    int count = vlc_array_count(hls_stream);
+    for (int n = 0; n < count; n++)
+    {
+        hls_stream_t *hls = vlc_array_item_at_index(hls_stream, n);
+        if (hls)
+        {
+            /* compare */
+            if ((hls->id == hls_new->id) &&
+                (hls->bandwidth == hls_new->bandwidth))
+                return hls;
+        }
+    }
+    return NULL;
+}
+
 static uint64_t hls_GetStreamSize(hls_stream_t *hls)
 {
     /* NOTE: Stream size is calculated based on segment duration and
@@ -307,6 +327,22 @@ static segment_t *segment_GetSegment(hls_stream_t *hls, int wanted)
     return (segment_t *) vlc_array_item_at_index(hls->segments, wanted);
 }
 
+static segment_t *segment_Find(hls_stream_t *hls, int sequence)
+{
+    assert(hls);
+
+    int count = vlc_array_count(hls->segments);
+    if (count <= 0) return NULL;
+    for (int n = 0; n < count; n++)
+    {
+        segment_t *segment = vlc_array_item_at_index(hls->segments, n);
+        if (segment == NULL) break;
+        if (segment->sequence == sequence)
+            return segment;
+    }
+    return NULL;
+}
+
 /* Parsing */
 static char *parse_Attributes(const char *line, const char *attr)
 {
@@ -339,7 +375,7 @@ static char *parse_Attributes(const char *line, const char *attr)
     return NULL;
 }
 
-static char *relative_URI(stream_t *s, const char *uri, char *psz_uri)
+static char *relative_URI(stream_t *s, const char *uri, const char *path)
 {
     stream_sys_t *p_sys = s->p_sys;
 
@@ -347,11 +383,31 @@ static char *relative_URI(stream_t *s, const char *uri, char *psz_uri)
     if (p != NULL)
         return NULL;
 
-    if (asprintf(&psz_uri, "%s://%s%s/%s", p_sys->m3u8.psz_protocol,
-                 p_sys->m3u8.psz_host, p_sys->m3u8.psz_path, uri) < 0)
-        return NULL;
+    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%s/%s", p_sys->m3u8.psz_protocol,
+                     p_sys->m3u8.psz_username, p_sys->m3u8.psz_password,
+                     p_sys->m3u8.psz_host, path ? path : psz_path, uri) < 0)
+            goto fail;
+    }
+    else
+    {
+        if (asprintf(&psz_uri, "%s://%s%s/%s", p_sys->m3u8.psz_protocol,
+                 p_sys->m3u8.psz_host, path ? path : psz_path, uri) < 0)
+           goto fail;
+    }
+    free(psz_path);
     return psz_uri;
+
+fail:
+    free(psz_path);
+    return NULL;
 }
 
 static void parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_read, char *uri)
@@ -369,8 +425,16 @@ static void parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_rea
         return;
     }
 
-    char *psz_uri = NULL;
-    psz_uri = relative_URI(s, uri, psz_uri);
+    char *psz_path = strdup(hls->url.psz_path);
+    if (psz_path == NULL)
+    {
+        p_sys->b_error = true;
+        return;
+    }
+    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);
     segment_t *segment = segment_New(hls, duration, psz_uri ? psz_uri : uri);
@@ -386,10 +450,8 @@ static void parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_rea
     free(psz_uri);
 }
 
-static void parse_TargetDuration(stream_t *s, hls_stream_t *hls, char *p_read)
+static int parse_TargetDuration(stream_t *s, hls_stream_t *hls, char *p_read)
 {
-    stream_sys_t *p_sys = s->p_sys;
-
     assert(hls);
 
     int duration = -1;
@@ -397,14 +459,15 @@ static void parse_TargetDuration(stream_t *s, hls_stream_t *hls, char *p_read)
     if (ret != 1)
     {
         msg_Err(s, "expected #EXT-X-TARGETDURATION:<s>");
-        p_sys->b_error = true;
-        return;
+        return VLC_EGENERIC;
     }
 
     hls->duration = duration; /* seconds */
+    return VLC_SUCCESS;
 }
 
-static void parse_StreamInformation(stream_t *s, char *p_read, char *uri)
+static void parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream,
+                                    char *p_read, char *uri)
 {
     stream_sys_t *p_sys = s->p_sys;
 
@@ -441,20 +504,17 @@ static void parse_StreamInformation(stream_t *s, char *p_read, char *uri)
 
     msg_Info(s, "bandwidth adaption detected (program-id=%d, bandwidth=%"PRIu64").", id, bw);
 
-    char *psz_uri = NULL;
-    psz_uri = relative_URI(s, uri, psz_uri);
+    char *psz_uri = relative_URI(s, uri, NULL);
 
-    hls_stream_t *hls = hls_New(p_sys->hls_stream, id, bw, psz_uri ? psz_uri : uri);
+    hls_stream_t *hls = hls_New(*hls_stream, id, bw, psz_uri ? psz_uri : uri);
     if (hls == NULL)
         p_sys->b_error = true;
 
     free(psz_uri);
 }
 
-static void parse_MediaSequence(stream_t *s, hls_stream_t *hls, char *p_read)
+static int parse_MediaSequence(stream_t *s, hls_stream_t *hls, char *p_read)
 {
-    stream_sys_t *p_sys = s->p_sys;
-
     assert(hls);
 
     int sequence;
@@ -462,37 +522,37 @@ static void parse_MediaSequence(stream_t *s, hls_stream_t *hls, char *p_read)
     if (ret != 1)
     {
         msg_Err(s, "expected #EXT-X-MEDIA-SEQUENCE:<s>");
-        p_sys->b_error = true;
-        return;
+        return VLC_EGENERIC;
     }
 
     if (hls->sequence > 0)
         msg_Err(s, "EXT-X-MEDIA-SEQUENCE already present in playlist");
 
     hls->sequence = sequence;
+    return VLC_SUCCESS;
 }
 
-static void parse_Key(stream_t *s, hls_stream_t *hls, char *p_read)
+static int parse_Key(stream_t *s, hls_stream_t *hls, char *p_read)
 {
-    stream_sys_t *p_sys = s->p_sys;
-
     assert(hls);
 
     /* #EXT-X-KEY:METHOD=<method>[,URI="<URI>"][,IV=<IV>] */
-    char *attr;
-    attr = parse_Attributes(p_read, "METHOD");
+    int err = VLC_SUCCESS;
+    char *attr = parse_Attributes(p_read, "METHOD");
     if (attr == NULL)
     {
         msg_Err(s, "#EXT-X-KEY: expected METHOD=<value>");
-        p_sys->b_error = true;
+        return err;
     }
-    else if (strncasecmp(attr, "NONE", 4) == 0)
+
+    if (strncasecmp(attr, "NONE", 4) == 0)
     {
+
         char *uri = parse_Attributes(p_read, "URI");
         if (uri != NULL)
         {
             msg_Err(s, "#EXT-X-KEY: URI not expected");
-            p_sys->b_error = true;
+            err = VLC_EGENERIC;
         }
         free(uri);
         /* IV is only supported in version 2 and above */
@@ -502,7 +562,7 @@ static void parse_Key(stream_t *s, hls_stream_t *hls, char *p_read)
             if (iv != NULL)
             {
                 msg_Err(s, "#EXT-X-KEY: IV not expected");
-                p_sys->b_error = true;
+                err = VLC_EGENERIC;
             }
             free(iv);
         }
@@ -510,21 +570,21 @@ static void parse_Key(stream_t *s, hls_stream_t *hls, char *p_read)
     else
     {
         msg_Warn(s, "playback of encrypted HTTP Live media is not supported.");
-        p_sys->b_error = true;
+        err = VLC_EGENERIC;
     }
     free(attr);
+    return err;
 }
 
-static void parse_ProgramDateTime(stream_t *s, hls_stream_t *hls, char *p_read)
+static int parse_ProgramDateTime(stream_t *s, hls_stream_t *hls, char *p_read)
 {
     VLC_UNUSED(hls);
     msg_Dbg(s, "tag not supported: #EXT-X-PROGRAM-DATE-TIME %s", p_read);
+    return VLC_SUCCESS;
 }
 
-static void parse_AllowCache(stream_t *s, hls_stream_t *hls, char *p_read)
+static int parse_AllowCache(stream_t *s, hls_stream_t *hls, char *p_read)
 {
-    stream_sys_t *p_sys = s->p_sys;
-
     assert(hls);
 
     char answer[4] = "\0";
@@ -532,17 +592,15 @@ static void parse_AllowCache(stream_t *s, hls_stream_t *hls, char *p_read)
     if (ret != 1)
     {
         msg_Err(s, "#EXT-X-ALLOW-CACHE, ignoring ...");
-        p_sys->b_error = true;
-        return;
+        return VLC_EGENERIC;
     }
 
     hls->b_cache = (strncmp(answer, "NO", 2) != 0);
+    return VLC_SUCCESS;
 }
 
-static void parse_Version(stream_t *s, hls_stream_t *hls, char *p_read)
+static int parse_Version(stream_t *s, hls_stream_t *hls, char *p_read)
 {
-    stream_sys_t *p_sys = s->p_sys;
-
     assert(hls);
 
     int version;
@@ -550,8 +608,7 @@ static void parse_Version(stream_t *s, hls_stream_t *hls, char *p_read)
     if (ret != 1)
     {
         msg_Err(s, "#EXT-X-VERSION: no protocol version found, should be version 1.");
-        p_sys->b_error = true;
-        return;
+        return VLC_EGENERIC;
     }
 
     /* Check version */
@@ -559,48 +616,53 @@ static void parse_Version(stream_t *s, hls_stream_t *hls, char *p_read)
     if (hls->version != 1)
     {
         msg_Err(s, "#EXT-X-VERSION should be version 1 iso %d", version);
-        p_sys->b_error = true;
+        return VLC_EGENERIC;
     }
+    return VLC_SUCCESS;
 }
 
-static void parse_EndList(stream_t *s, hls_stream_t *hls)
+static int parse_EndList(stream_t *s, hls_stream_t *hls)
 {
-    stream_sys_t *p_sys = s->p_sys;
-
     assert(hls);
 
-    p_sys->b_live = false;
+    s->p_sys->b_live = false;
     msg_Info(s, "video on demand (vod) mode");
+    return VLC_SUCCESS;
 }
 
-static void parse_Discontinuity(stream_t *s, hls_stream_t *hls, char *p_read)
+static int parse_Discontinuity(stream_t *s, hls_stream_t *hls, char *p_read)
 {
     assert(hls);
 
     /* FIXME: Do we need to act on discontinuity ?? */
     msg_Dbg(s, "#EXT-X-DISCONTINUITY %s", p_read);
+    return VLC_SUCCESS;
 }
 
 static void parse_M3U8ExtLine(stream_t *s, hls_stream_t *hls, char *line)
 {
     if (*line == '#')
     {
+        int err = VLC_SUCCESS;
         if (strncmp(line, "#EXT-X-TARGETDURATION", 21) == 0)
-            parse_TargetDuration(s, hls, line);
-        else if (strncmp(line, "#EXT-X-MEDIA-SEQUENCE", 22) == 0)
-            parse_MediaSequence(s, hls, line);
-        else if (strncmp(line, "#EXT-X-KEY", 11) == 0)
-            parse_Key(s, hls, line);
-        else if (strncmp(line, "#EXT-X-PROGRAM-DATE-TIME", 25) == 0)
-            parse_ProgramDateTime(s, hls, line);
-        else if (strncmp(line, "#EXT-X-ALLOW-CACHE", 17) == 0)
-            parse_AllowCache(s, hls, line);
+            err = parse_TargetDuration(s, hls, line);
+        else if (strncmp(line, "#EXT-X-MEDIA-SEQUENCE", 21) == 0)
+            err = parse_MediaSequence(s, hls, line);
+        else if (strncmp(line, "#EXT-X-KEY", 10) == 0)
+            err = parse_Key(s, hls, line);
+        else if (strncmp(line, "#EXT-X-PROGRAM-DATE-TIME", 24) == 0)
+            err = parse_ProgramDateTime(s, hls, line);
+        else if (strncmp(line, "#EXT-X-ALLOW-CACHE", 18) == 0)
+            err = parse_AllowCache(s, hls, line);
         else if (strncmp(line, "#EXT-X-DISCONTINUITY", 20) == 0)
-            parse_Discontinuity(s, hls, line);
+            err = parse_Discontinuity(s, hls, line);
         else if (strncmp(line, "#EXT-X-VERSION", 14) == 0)
-            parse_Version(s, hls, line);
+            err = parse_Version(s, hls, line);
         else if (strncmp(line, "#EXT-X-ENDLIST", 14) == 0)
-            parse_EndList(s, hls);
+            err = parse_EndList(s, hls);
+
+        if (err != VLC_SUCCESS)
+            s->p_sys->b_error = true;
     }
 }
 
@@ -671,6 +733,106 @@ static int get_HTTPLivePlaylist(stream_t *s, hls_stream_t *hls)
     AccessClose(s);
     return VLC_SUCCESS;
 
+error:
+    free(line);
+    free(tmp);
+    AccessClose(s);
+    return VLC_EGENERIC;
+}
+
+static int get_HTTPLiveMetaPlaylist(stream_t *s, vlc_array_t **streams)
+{
+    stream_sys_t *p_sys = s->p_sys;
+    assert(*streams);
+
+    /* Download new playlist file from server */
+    if (AccessOpen(s, &p_sys->m3u8) != VLC_SUCCESS)
+        return VLC_EGENERIC;
+
+    /* Parse the rest of the reply */
+    uint8_t *tmp = calloc(1, HTTPLIVE_MAX_LINE);
+    if (tmp == NULL)
+    {
+        AccessClose(s);
+        return VLC_ENOMEM;
+    }
+
+    char *line = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
+    if (strncmp(line, "#EXTM3U", 7) != 0)
+    {
+        msg_Err(s, "missing #EXTM3U tag");
+        goto error;
+    }
+    free(line);
+    line = NULL;
+
+    for( ; ; )
+    {
+        line = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
+        if (line == NULL)
+        {
+            msg_Dbg(s, "end of data");
+            break;
+        }
+
+        if (!vlc_object_alive(s))
+            goto error;
+
+        /* some more checks for actual data */
+        if (strncmp(line, "#EXT-X-STREAM-INF", 17) == 0)
+        {
+            p_sys->b_meta = true;
+            char *uri = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
+            if (uri == NULL)
+                p_sys->b_error = true;
+            else
+            {
+                parse_StreamInformation(s, streams, line, uri);
+                free(uri);
+            }
+        }
+        else if (strncmp(line, "#EXTINF", 7) == 0)
+        {
+            char *uri = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
+            if (uri == NULL)
+                p_sys->b_error = true;
+            else
+            {
+                hls_stream_t *hls = hls_GetLast(*streams);
+                if (hls)
+                    parse_SegmentInformation(s, hls, line, uri);
+                else
+                    p_sys->b_error = true;
+                free(uri);
+            }
+        }
+        else
+        {
+            hls_stream_t *hls = hls_GetLast(*streams);
+            if ((hls == NULL) && (!p_sys->b_meta))
+            {
+                hls = hls_New(*streams, -1, -1, NULL);
+                if (hls == NULL)
+                {
+                    p_sys->b_error = true;
+                    return VLC_ENOMEM;
+                }
+            }
+            parse_M3U8ExtLine(s, hls, line);
+        }
+
+        /* Error during m3u8 parsing abort */
+        if (p_sys->b_error)
+            goto error;
+
+        free(line);
+    }
+
+    free(line);
+    free(tmp);
+    AccessClose(s);
+    return VLC_SUCCESS;
+
 error:
     free(line);
     free(tmp);
@@ -730,7 +892,7 @@ static int parse_HTTPLiveStreaming(stream_t *s)
                 p_sys->b_error = true;
             else
             {
-                parse_StreamInformation(s, p_read, uri);
+                parse_StreamInformation(s, &p_sys->hls_stream, p_read, uri);
                 free(uri);
             }
         }
@@ -809,17 +971,127 @@ static int parse_HTTPLiveStreaming(stream_t *s)
                 return VLC_EGENERIC;
             }
         }
+        else
+        {
+            /* Stream size (approximate) */
+            hls->size = hls_GetStreamSize(hls);
+        }
+        vlc_mutex_unlock(&hls->lock);
+    }
 
-        /* Stream size (approximate) */
-        hls->size = hls_GetStreamSize(hls);
+    return VLC_SUCCESS;
+}
 
-        /* Can we cache files after playback */
-        p_sys->b_cache = hls->b_cache;
+/* Reload playlist */
+static int hls_UpdatePlaylist(stream_t *s, hls_stream_t *hls_new, hls_stream_t **hls)
+{
+    int count = vlc_array_count(hls_new->segments);
 
-        vlc_mutex_unlock(&hls->lock);
+    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);
+        if (p == NULL) return VLC_EGENERIC;
+
+        vlc_mutex_lock(&(*hls)->lock);
+        segment_t *segment = segment_Find(*hls, p->sequence);
+        if (segment)
+        {
+            /* they should be the same */
+            if ((p->sequence != segment->sequence) ||
+                (p->duration != segment->duration) ||
+                (strcmp(p->url.psz_path, segment->url.psz_path) != 0))
+            {
+                msg_Err(s, "existing segment %d found with different content",
+                        p->sequence);
+                msg_Err(s, "- sequence: new=%d, old=%d", p->sequence, segment->sequence);
+                msg_Err(s, "- duration: new=%d, old=%d", p->duration, segment->duration);
+                msg_Err(s, "- file: new=%s, old=%s", p->url.psz_path, segment->url.psz_path);
+            }
+        }
+        else
+        {
+            int last = vlc_array_count((*hls)->segments) - 1;
+            segment_t *l = segment_GetSegment(*hls, last);
+            if (l == NULL) goto fail_and_unlock;
+
+            if ((l->sequence + 1) == p->sequence)
+            {
+                vlc_array_append((*hls)->segments, p);
+                msg_Info(s, "- segment %d appended", p->sequence);
+            }
+            else /* there is a gap */
+            {
+                msg_Err(s, "gap in sequence numbers found: new=%d expected old=%d",
+                        p->sequence, l->sequence);
+                goto fail_and_unlock;
+            }
+        }
+        vlc_mutex_unlock(&(*hls)->lock);
+    }
+    return VLC_SUCCESS;
+
+fail_and_unlock:
+    vlc_mutex_unlock(&(*hls)->lock);
+    return VLC_EGENERIC;
+}
+
+static int hls_ReloadPlaylist(stream_t *s)
+{
+    stream_sys_t *p_sys = s->p_sys;
+
+    vlc_array_t *hls_streams = vlc_array_new();
+    if (hls_streams == NULL)
+        return VLC_ENOMEM;
+
+    msg_Info(s, "Reloading HLS live meta playlist");
+    if (get_HTTPLiveMetaPlaylist(s, &hls_streams) != VLC_SUCCESS)
+        goto fail;
+
+    int count = vlc_array_count(hls_streams);
+
+    /* Is it a meta playlist? */
+    if (p_sys->b_meta)
+    {
+        for (int n = 0; n < count; n++)
+        {
+            hls_stream_t *hls = hls_Get(hls_streams, n);
+            if (hls == NULL) goto fail;
+
+            msg_Info(s, "parsing %s", hls->url.psz_path);
+            if (get_HTTPLivePlaylist(s, hls) != VLC_SUCCESS)
+            {
+                msg_Err(s, "could not parse playlist file from meta index." );
+                goto fail;
+            }
+        }
     }
 
+    /* merge playlists */
+    for (int n = 0; n < count; n++)
+    {
+        hls_stream_t *hls_new = hls_Get(hls_streams, n);
+        if (hls_new == NULL) goto fail;
+
+        hls_stream_t *hls_old = hls_Find(p_sys->hls_stream, hls_new);
+        if (hls_old == NULL)
+        {   /* new hls stream - append */
+            vlc_array_append(p_sys->hls_stream, hls_new);
+            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)
+            goto fail;
+    }
+
+    vlc_array_destroy(hls_streams);
     return VLC_SUCCESS;
+
+fail:
+    msg_Err(s, "reloading playlist failed");
+    vlc_array_destroy(hls_streams);
+    return VLC_EGENERIC;
 }
 
 /****************************************************************************
@@ -928,7 +1200,7 @@ static void* hls_Thread(vlc_object_t *p_this)
 
     while (vlc_object_alive(p_this))
     {
-        hls_stream_t *hls = hls_Get(client->hls_stream, client->current);
+        hls_stream_t *hls = hls_Get(p_sys->hls_stream, p_sys->download.current);
         assert(hls);
 
         /* Sliding window (~60 seconds worth of movie) */
@@ -937,66 +1209,38 @@ static void* hls_Thread(vlc_object_t *p_this)
         vlc_mutex_unlock(&hls->lock);
 
         /* Is there a new segment to process? */
-        if ((p_sys->playback.segment < (count - 6)) ||
-            (client->segment >= count))
+        if ((!p_sys->b_live && (p_sys->playback.segment < (count - 6))) ||
+            (p_sys->download.segment >= count))
         {
             /* wait */
-            vlc_mutex_lock(&client->lock_wait);
-            while (((client->segment - p_sys->playback.segment > 6) ||
-                    (client->segment >= count)) &&
-                   (client->seek == -1))
+            vlc_mutex_lock(&p_sys->download.lock_wait);
+            while (((p_sys->download.segment - p_sys->playback.segment > 6) ||
+                    (p_sys->download.segment >= count)) &&
+                   (p_sys->download.seek == -1) &&
+                   (mdate() < p_sys->playlist.wakeup))
             {
-                vlc_cond_wait(&client->wait, &client->lock_wait);
+                vlc_cond_wait(&p_sys->download.wait, &p_sys->download.lock_wait);
                 if (!vlc_object_alive(p_this)) break;
             }
             /* */
-            if (client->seek >= 0)
+            if (p_sys->download.seek >= 0)
             {
-                client->segment = client->seek;
-                client->seek = -1;
+                p_sys->download.segment = p_sys->download.seek;
+                p_sys->download.seek = -1;
             }
-            vlc_mutex_unlock(&client->lock_wait);
+            vlc_mutex_unlock(&p_sys->download.lock_wait);
         }
 
         if (!vlc_object_alive(p_this)) break;
 
-        vlc_mutex_lock(&hls->lock);
-        segment_t *segment = segment_GetSegment(hls, client->segment);
-        assert(segment);
-        vlc_mutex_unlock(&hls->lock);
-
-        if (Download(client->s, hls, segment, &client->current) != VLC_SUCCESS)
-        {
-            if (!p_sys->b_live)
-            {
-                p_sys->b_error = true;
-                break;
-            }
-        }
-
-        /* download succeeded */
-        /* determine next segment to download */
-        vlc_mutex_lock(&client->lock_wait);
-        if (client->seek >= 0)
-        {
-            client->segment = client->seek;
-            client->seek = -1;
-        }
-        else if (client->segment < count)
-            client->segment++;
-        vlc_cond_signal(&client->wait);
-        vlc_mutex_unlock(&client->lock_wait);
-
-        /* FIXME: Reread the m3u8 index file */
+        /* reload the m3u8 index file */
         if (p_sys->b_live)
         {
             double wait = 1;
             mtime_t now = mdate();
             if (now >= p_sys->playlist.wakeup)
             {
-#if 0
-                /** FIXME: Implement m3u8 playlist reloading */
-                if (!hls_ReloadPlaylist(client->s))
+                if (hls_ReloadPlaylist(client->s) != VLC_SUCCESS)
                 {
                     /* No change in playlist, then backoff */
                     p_sys->playlist.tries++;
@@ -1004,12 +1248,45 @@ static void* hls_Thread(vlc_object_t *p_this)
                     else if (p_sys->playlist.tries == 2) wait = 1;
                     else if (p_sys->playlist.tries >= 3) wait = 3;
                 }
-#endif
+                else p_sys->playlist.tries = 0;
+
                 /* determine next time to update playlist */
                 p_sys->playlist.last = now;
-                p_sys->playlist.wakeup = now + ((mtime_t)(hls->duration * wait) * (mtime_t)1000000);
+                p_sys->playlist.wakeup = now + ((mtime_t)(hls->duration * wait)
+                                                * (mtime_t)1000000);
             }
+
+            if (!vlc_object_alive(p_this)) break;
+        }
+
+        vlc_mutex_lock(&hls->lock);
+        segment_t *segment = segment_GetSegment(hls, p_sys->download.segment);
+        assert(segment);
+        vlc_mutex_unlock(&hls->lock);
+
+        if (Download(client->s, hls, segment, &p_sys->download.current) != VLC_SUCCESS)
+        {
+            if (!vlc_object_alive(p_this)) break;
+
+            if (!p_sys->b_live)
+            {
+                p_sys->b_error = true;
+                break;
+            }
+        }
+
+        /* download succeeded */
+        /* determine next segment to download */
+        vlc_mutex_lock(&p_sys->download.lock_wait);
+        if (p_sys->download.seek >= 0)
+        {
+            p_sys->download.segment = p_sys->download.seek;
+            p_sys->download.seek = -1;
         }
+        else if (p_sys->download.segment < count)
+            p_sys->download.segment++;
+        vlc_cond_signal(&p_sys->download.wait);
+        vlc_mutex_unlock(&p_sys->download.lock_wait);
     }
 
     vlc_restorecancel(canc);
@@ -1253,8 +1530,6 @@ static int Open(vlc_object_t *p_this)
         free(p_sys);
         return VLC_ENOMEM;
     }
-    char *psz_path = strrchr(psz_uri, '/');
-    if (psz_path) *psz_path = '\0';
     vlc_UrlParse(&p_sys->m3u8, psz_uri, 0);
     free(psz_uri);
 
@@ -1307,16 +1582,15 @@ static int Open(vlc_object_t *p_this)
                 ((mtime_t)hls->duration * UINT64_C(1000000));
     }
 
-    p_sys->thread->hls_stream = p_sys->hls_stream;
-    p_sys->thread->current = current;
+    p_sys->download.current = current;
     p_sys->playback.current = current;
-    p_sys->thread->segment = p_sys->playback.segment;
-    p_sys->thread->seek = -1;
+    p_sys->download.segment = p_sys->playback.segment;
+    p_sys->download.seek = -1;
     p_sys->playback.segment = 0; /* reset to first segment */
     p_sys->thread->s = s;
 
-    vlc_mutex_init(&p_sys->thread->lock_wait);
-    vlc_cond_init(&p_sys->thread->wait);
+    vlc_mutex_init(&p_sys->download.lock_wait);
+    vlc_cond_init(&p_sys->download.wait);
 
     if (vlc_thread_create(p_sys->thread, "HTTP Live Streaming client",
                           hls_Thread, VLC_THREAD_PRIORITY_INPUT))
@@ -1346,15 +1620,15 @@ static void Close(vlc_object_t *p_this)
     /* */
     if (p_sys->thread)
     {
-        vlc_mutex_lock(&p_sys->thread->lock_wait);
+        vlc_mutex_lock(&p_sys->download.lock_wait);
         vlc_object_kill(p_sys->thread);
-        vlc_cond_signal(&p_sys->thread->wait);
-        vlc_mutex_unlock(&p_sys->thread->lock_wait);
+        vlc_cond_signal(&p_sys->download.wait);
+        vlc_mutex_unlock(&p_sys->download.lock_wait);
 
         /* */
         vlc_thread_join(p_sys->thread);
-        vlc_mutex_destroy(&p_sys->thread->lock_wait);
-        vlc_cond_destroy(&p_sys->thread->wait);
+        vlc_mutex_destroy(&p_sys->download.lock_wait);
+        vlc_cond_destroy(&p_sys->download.wait);
         vlc_object_release(p_sys->thread);
     }
 
@@ -1391,8 +1665,9 @@ static segment_t *GetSegment(stream_t *s)
             /* This segment is ready? */
             if (segment->data != NULL)
             {
+                p_sys->b_cache = hls->b_cache;
                 vlc_mutex_unlock(&hls->lock);
-                return segment;
+                goto check;
             }
         }
         vlc_mutex_unlock(&hls->lock);
@@ -1416,17 +1691,18 @@ static segment_t *GetSegment(stream_t *s)
             break;
         }
 
-        vlc_mutex_lock(&p_sys->thread->lock_wait);
-        int i_segment = p_sys->thread->segment;
-        vlc_mutex_unlock(&p_sys->thread->lock_wait);
+        vlc_mutex_lock(&p_sys->download.lock_wait);
+        int i_segment = p_sys->download.segment;
+        vlc_mutex_unlock(&p_sys->download.lock_wait);
 
         /* This segment is ready? */
         if ((segment->data != NULL) &&
             (p_sys->playback.segment < i_segment))
         {
             p_sys->playback.current = i_stream;
+            p_sys->b_cache = hls->b_cache;
             vlc_mutex_unlock(&hls->lock);
-            return segment;
+            goto check;
         }
         vlc_mutex_unlock(&hls->lock);
 
@@ -1440,6 +1716,14 @@ static segment_t *GetSegment(stream_t *s)
     }
     /* */
     return NULL;
+
+check:
+    /* sanity check */
+    if (p_sys->download.segment - p_sys->playback.segment == 0)
+        msg_Err(s, "playback will stall");
+    else if (p_sys->download.segment - p_sys->playback.segment < 3)
+        msg_Warn(s, "playback in danger of stalling");
+    return segment;
 }
 
 static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read)
@@ -1459,7 +1743,7 @@ static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read)
         vlc_mutex_lock(&segment->lock);
         if (segment->data->i_buffer == 0)
         {
-            if (!p_sys->b_cache)
+            if (!p_sys->b_cache || p_sys->b_live)
             {
                 block_Release(segment->data);
                 segment->data = NULL;
@@ -1477,15 +1761,15 @@ static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read)
             vlc_mutex_unlock(&segment->lock);
 
             /* signal download thread */
-            vlc_mutex_lock(&p_sys->thread->lock_wait);
-            vlc_cond_signal(&p_sys->thread->wait);
-            vlc_mutex_unlock(&p_sys->thread->lock_wait);
+            vlc_mutex_lock(&p_sys->download.lock_wait);
+            vlc_cond_signal(&p_sys->download.wait);
+            vlc_mutex_unlock(&p_sys->download.lock_wait);
             continue;
         }
 
         if (segment->size == segment->data->i_buffer)
             msg_Info(s, "playing segment %d from stream %d",
-                        p_sys->playback.segment, p_sys->playback.current);
+                     segment->sequence, p_sys->playback.current);
 
         ssize_t len = -1;
         if (i_read <= segment->data->i_buffer)
@@ -1581,8 +1865,7 @@ static bool hls_MaySeek(stream_t *s)
 {
     stream_sys_t *p_sys = s->p_sys;
 
-    if ((p_sys->hls_stream == NULL) ||
-        (p_sys->thread == NULL))
+    if (p_sys->hls_stream == NULL)
         return false;
 
     hls_stream_t *hls = hls_Get(p_sys->hls_stream, p_sys->playback.current);
@@ -1594,9 +1877,9 @@ static bool hls_MaySeek(stream_t *s)
         int count = vlc_array_count(hls->segments);
         vlc_mutex_unlock(&hls->lock);
 
-        vlc_mutex_lock(&p_sys->thread->lock_wait);
-        bool may_seek = (p_sys->thread->segment < (count - 2));
-        vlc_mutex_unlock(&p_sys->thread->lock_wait);
+        vlc_mutex_lock(&p_sys->download.lock_wait);
+        bool may_seek = (p_sys->download.segment < (count - 2));
+        vlc_mutex_unlock(&p_sys->download.lock_wait);
         return may_seek;
     }
     return true;
@@ -1692,30 +1975,27 @@ static int segment_Seek(stream_t *s, uint64_t pos)
         vlc_mutex_unlock(&segment->lock);
 
         /* start download at current playback segment */
-        if (p_sys->thread)
-        {
-            vlc_mutex_unlock(&hls->lock);
-
-            /* Wake up download thread */
-            vlc_mutex_lock(&p_sys->thread->lock_wait);
-            p_sys->thread->seek = p_sys->playback.segment;
-            vlc_cond_signal(&p_sys->thread->wait);
-            vlc_mutex_unlock(&p_sys->thread->lock_wait);
-
-            /* Wait for download to be finished */
-            vlc_mutex_lock(&p_sys->thread->lock_wait);
-            msg_Info(s, "seek to segment %d", p_sys->playback.segment);
-            while (((p_sys->thread->seek != -1) ||
-                    (p_sys->thread->segment - p_sys->playback.segment < 3)) &&
-                    (p_sys->thread->segment < (count - 6)))
-            {
-                vlc_cond_wait(&p_sys->thread->wait, &p_sys->thread->lock_wait);
-                if (!vlc_object_alive(s) || s->b_error) break;
-            }
-            vlc_mutex_unlock(&p_sys->thread->lock_wait);
+        vlc_mutex_unlock(&hls->lock);
 
-            return VLC_SUCCESS;
+        /* Wake up download thread */
+        vlc_mutex_lock(&p_sys->download.lock_wait);
+        p_sys->download.seek = p_sys->playback.segment;
+        vlc_cond_signal(&p_sys->download.wait);
+        vlc_mutex_unlock(&p_sys->download.lock_wait);
+
+        /* Wait for download to be finished */
+        vlc_mutex_lock(&p_sys->download.lock_wait);
+        msg_Info(s, "seek to segment %d", p_sys->playback.segment);
+        while (((p_sys->download.seek != -1) ||
+                (p_sys->download.segment - p_sys->playback.segment < 3)) &&
+                (p_sys->download.segment < (count - 6)))
+        {
+            vlc_cond_wait(&p_sys->download.wait, &p_sys->download.lock_wait);
+            if (!vlc_object_alive(s) || s->b_error) break;
         }
+        vlc_mutex_unlock(&p_sys->download.lock_wait);
+
+        return VLC_SUCCESS;
     }
     vlc_mutex_unlock(&hls->lock);