]> git.sesse.net Git - vlc/blobdiff - modules/stream_filter/httplive.c
stream_filter/httplive.c: do not crash on seeking
[vlc] / modules / stream_filter / httplive.c
index dce8b124888d6ece1bda0b8750ac21e1b82e6c82..ae4ece2a10bd2320a4a3047fb2e9bbfe3a74d5ce 100644 (file)
@@ -61,7 +61,7 @@ vlc_module_end()
 typedef struct segment_s
 {
     int         sequence;   /* unique sequence number */
-    int         length;     /* segment duration (ms) */
+    int         length;     /* segment duration (seconds) */
     uint64_t    size;       /* segment size in bytes */
 
     vlc_url_t   url;
@@ -75,11 +75,9 @@ typedef struct hls_stream_s
     int         version;    /* protocol version should be 1 */
     int         sequence;   /* media sequence number */
     int         duration;   /* maximum duration per segment (ms) */
-    uint64_t    bandwidth;  /* bandwidth usage of segments (kbps)*/
+    uint64_t    bandwidth;  /* bandwidth usage of segments (bits per second)*/
 
     vlc_array_t *segments;  /* list of segments */
-    int         segment;    /* current segment downloading */
-
     vlc_url_t   url;        /* uri to m3u8 */
     vlc_mutex_t lock;
     bool        b_cache;    /* allow caching */
@@ -91,6 +89,7 @@ typedef struct
 
     /* */
     int         current;    /* current hls_stream  */
+    int         segment;    /* current segment for downloading */
     vlc_array_t *hls_stream;/* bandwidth adaptation */
 
     stream_t    *s;
@@ -179,9 +178,9 @@ static hls_stream_t *hls_New(vlc_array_t *hls_stream, int id, uint64_t bw, char
 
     hls->id = id;
     hls->bandwidth = bw;
+    hls->duration = -1;/* unknown */
     hls->sequence = 0; /* default is 0 */
     hls->version = 1;  /* default protocol version */
-    hls->segment = 0;
     hls->b_cache = true;
     vlc_UrlParse(&hls->url, uri, 0);
     hls->segments = vlc_array_new();
@@ -233,6 +232,29 @@ static hls_stream_t *hls_GetLast(vlc_array_t *hls_stream)
     return (hls_stream_t *) hls_Get(hls_stream, count);
 }
 
+static int hls_LowestBandwidthStream(vlc_array_t *hls_stream)
+{
+    int count = vlc_array_count(hls_stream);
+    int i_lowest = 0;
+    uint64_t lowest = 0;
+
+    for (int i = 0; i < count; i++)
+    {
+        hls_stream_t *hls = (hls_stream_t *) vlc_array_item_at_index(hls_stream, i);
+        if (lowest == 0)
+        {
+            lowest = hls->bandwidth;
+            i_lowest = i;
+        }
+        else if (hls->bandwidth < lowest)
+        {
+            lowest = hls->bandwidth;
+            i_lowest = i;
+        }
+    }
+    return i_lowest;
+}
+
 /* Segment */
 static segment_t *segment_New(hls_stream_t* hls, int duration, char *uri)
 {
@@ -305,6 +327,37 @@ 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)
+{
+    char *p = strchr(uri, ':');
+    if (p != NULL)
+        return NULL;
+
+    char *tmp;
+    if (asprintf(&tmp,"%s://%s", s->psz_access, s->psz_path) < 0)
+    {
+        s->p_sys->b_error = true;
+        return NULL;
+    }
+
+    char *psz_path = strrchr(tmp, '/');
+    if (psz_path) *psz_path = '\0';
+
+    vlc_url_t url;
+    vlc_UrlParse(&url, tmp, 0);
+    if (asprintf(&psz_uri, "%s://%s%s/%s",
+           url.psz_protocol, url.psz_host, url.psz_path, uri) < 0)
+    {
+        free(tmp);
+        vlc_UrlClean(&url);
+        s->p_sys->b_error = true;
+        return NULL;
+    }
+    vlc_UrlClean(&url);
+    free(tmp);
+    return psz_uri;
+}
+
 static void parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_read, char *uri)
 {
     stream_sys_t *p_sys = s->p_sys;
@@ -320,8 +373,11 @@ 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);
+
     vlc_mutex_lock(&hls->lock);
-    segment_t *segment = segment_New(hls, duration, uri);
+    segment_t *segment = segment_New(hls, duration, psz_uri ? psz_uri : uri);
     if (segment)
         segment->sequence = hls->sequence + vlc_array_count(hls->segments);
     if (duration > hls->duration)
@@ -332,11 +388,13 @@ static void parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_rea
     vlc_mutex_unlock(&hls->lock);
 }
 
-static void parse_TargetDuration(stream_t *s, char *p_read)
+static void parse_TargetDuration(stream_t *s, hls_stream_t *hls, char *p_read)
 {
     stream_sys_t *p_sys = s->p_sys;
 
-    int duration;
+    assert(hls);
+
+    int duration = -1;
     int ret = sscanf(p_read, "#EXT-X-TARGETDURATION:%d", &duration);
     if (ret != 1)
     {
@@ -345,8 +403,6 @@ static void parse_TargetDuration(stream_t *s, char *p_read)
         return;
     }
 
-    hls_stream_t *hls = hls_GetLast(p_sys->hls_stream);
-    assert(hls);
     hls->duration = duration; /* seconds */
 }
 
@@ -387,15 +443,20 @@ 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);
 
-    hls_stream_t *hls = hls_New(p_sys->hls_stream, id, bw, uri);
+    char *psz_uri = NULL;
+    psz_uri = relative_URI(s, uri, psz_uri);
+
+    hls_stream_t *hls = hls_New(p_sys->hls_stream, id, bw, psz_uri ? psz_uri : uri);
     if (hls == NULL)
         p_sys->b_error = true;
 }
 
-static void parse_MediaSequence(stream_t *s, char *p_read)
+static void parse_MediaSequence(stream_t *s, hls_stream_t *hls, char *p_read)
 {
     stream_sys_t *p_sys = s->p_sys;
 
+    assert(hls);
+
     int sequence;
     int ret = sscanf(p_read, "#EXT-X-MEDIA-SEQUENCE:%d", &sequence);
     if (ret != 1)
@@ -405,18 +466,18 @@ static void parse_MediaSequence(stream_t *s, char *p_read)
         return;
     }
 
-    hls_stream_t *hls = hls_GetLast(p_sys->hls_stream);
-    assert(hls);
     if (hls->sequence > 0)
         msg_Err(s, "EXT-X-MEDIA-SEQUENCE already present in playlist");
 
     hls->sequence = sequence;
 }
 
-static void parse_Key(stream_t *s, char *p_read)
+static void 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");
@@ -428,14 +489,22 @@ static void parse_Key(stream_t *s, char *p_read)
     else if (strncasecmp(attr, "NONE", 4) == 0)
     {
         char *uri = parse_Attributes(p_read, "URI");
-        char *iv = parse_Attributes(p_read, "IV");
-        if ((iv != NULL) || (uri != NULL))
+        if (uri != NULL)
         {
-            msg_Err(s, "#EXT-X-KEY: URI and IV not expected");
+            msg_Err(s, "#EXT-X-KEY: URI not expected");
             p_sys->b_error = true;
-
+        }
+        free(uri);
+        /* IV is only supported in version 2 and above */
+        if (hls->version >= 2)
+        {
+            char *iv = parse_Attributes(p_read, "IV");
+            if (iv != NULL)
+            {
+                msg_Err(s, "#EXT-X-KEY: IV not expected");
+                p_sys->b_error = true;
+            }
             free(iv);
-            free(uri);
         }
     }
     else
@@ -446,15 +515,18 @@ static void parse_Key(stream_t *s, char *p_read)
     free(attr);
 }
 
-static void parse_ProgramDateTime(stream_t *s, char *p_read)
+static void 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);
 }
 
-static void parse_AllowCache(stream_t *s, char *p_read)
+static void 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";
     int ret = sscanf(p_read, "#EXT-X-ALLOW-CACHE:%3s", answer);
     if (ret != 1)
@@ -464,16 +536,15 @@ static void parse_AllowCache(stream_t *s, char *p_read)
         return;
     }
 
-    hls_stream_t *hls = hls_GetLast(p_sys->hls_stream);
-
-    assert(hls);
     hls->b_cache = (strncmp(answer, "NO", 2) != 0);
 }
 
-static void parse_Version(stream_t *s, char *p_read)
+static void parse_Version(stream_t *s, hls_stream_t *hls, char *p_read)
 {
     stream_sys_t *p_sys = s->p_sys;
 
+    assert(hls);
+
     int version;
     int ret = sscanf(p_read, "#EXT-X-VERSION:%d", &version);
     if (ret != 1)
@@ -483,9 +554,6 @@ static void parse_Version(stream_t *s, char *p_read)
         return;
     }
 
-    hls_stream_t *hls = hls_GetLast(p_sys->hls_stream);
-    assert(hls);
-
     /* Check version */
     hls->version = version;
     if (hls->version != 1)
@@ -495,40 +563,44 @@ static void parse_Version(stream_t *s, char *p_read)
     }
 }
 
-static void parse_EndList(stream_t *s)
+static void parse_EndList(stream_t *s, hls_stream_t *hls)
 {
     stream_sys_t *p_sys = s->p_sys;
-    p_sys->b_live = false;
 
+    assert(hls);
+
+    p_sys->b_live = false;
     msg_Info(s, "video on demand (vod) mode");
 }
 
-static void parse_Discontinuity(stream_t *s, char *p_read)
+static void 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);
 }
 
-static void parse_M3U8ExtLine(stream_t *s, char *line)
+static void parse_M3U8ExtLine(stream_t *s, hls_stream_t *hls, char *line)
 {
     if (*line == '#')
     {
         if (strncmp(line, "#EXT-X-TARGETDURATION", 21) == 0)
-            parse_TargetDuration(s, line);
+            parse_TargetDuration(s, hls, line);
         else if (strncmp(line, "#EXT-X-MEDIA-SEQUENCE", 22) == 0)
-            parse_MediaSequence(s, line);
+            parse_MediaSequence(s, hls, line);
         else if (strncmp(line, "#EXT-X-KEY", 11) == 0)
-            parse_Key(s, line);
+            parse_Key(s, hls, line);
         else if (strncmp(line, "#EXT-X-PROGRAM-DATE-TIME", 25) == 0)
-            parse_ProgramDateTime(s, line);
+            parse_ProgramDateTime(s, hls, line);
         else if (strncmp(line, "#EXT-X-ALLOW-CACHE", 17) == 0)
-            parse_AllowCache(s, line);
+            parse_AllowCache(s, hls, line);
         else if (strncmp(line, "#EXT-X-DISCONTINUITY", 20) == 0)
-            parse_Discontinuity(s, line);
+            parse_Discontinuity(s, hls, line);
         else if (strncmp(line, "#EXT-X-VERSION", 14) == 0)
-            parse_Version(s, line);
+            parse_Version(s, hls, line);
         else if (strncmp(line, "#EXT-X-ENDLIST", 14) == 0)
-            parse_EndList(s);
+            parse_EndList(s, hls);
     }
 }
 
@@ -584,7 +656,7 @@ static int get_HTTPLivePlaylist(stream_t *s, hls_stream_t *hls)
         }
         else
         {
-            parse_M3U8ExtLine(s, line);
+            parse_M3U8ExtLine(s, hls, line);
         }
 
         /* Error during m3u8 parsing abort */
@@ -679,10 +751,10 @@ static int parse_HTTPLiveStreaming(stream_t *s)
         }
         else
         {
-            if (!p_sys->b_meta)
+            hls_stream_t *hls = hls_GetLast(p_sys->hls_stream);
+            if (hls == NULL)
             {
-                hls_stream_t *hls = hls_GetLast(p_sys->hls_stream);
-                if (hls == NULL)
+                if (!p_sys->b_meta)
                 {
                     hls = hls_New(p_sys->hls_stream, -1, -1, NULL);
                     if (hls == NULL)
@@ -693,7 +765,7 @@ static int parse_HTTPLiveStreaming(stream_t *s)
                 }
             }
             /* Parse M3U8 Ext Line */
-            parse_M3U8ExtLine(s, p_read);
+            parse_M3U8ExtLine(s, hls, p_read);
         }
     } while(p_read < p_end);
 
@@ -709,6 +781,7 @@ static int parse_HTTPLiveStreaming(stream_t *s)
         /* Is it a meta playlist? */
         if (p_sys->b_meta)
         {
+            msg_Dbg(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." );
@@ -751,8 +824,6 @@ static int BandwidthAdaptation(stream_t *s, int progid, uint64_t *bandwidth)
     int candidate = -1;
     uint64_t bw = *bandwidth;
 
-    msg_Dbg(s, "bandwidth (bits/s) %"PRIu64, bw * 1000); /* bits / s */
-
     int count = vlc_array_count(p_sys->hls_stream);
     for (int n = 0; n < count; n++)
     {
@@ -763,8 +834,10 @@ static int BandwidthAdaptation(stream_t *s, int progid, uint64_t *bandwidth)
         /* only consider streams with the same PROGRAM-ID */
         if (hls->id == progid)
         {
-            if (bw <= hls->bandwidth)
+            if (bw >= hls->bandwidth)
             {
+                msg_Dbg(s, "candidate %d bandwidth (bits/s) %"PRIu64" >= %"PRIu64,
+                         n, bw, hls->bandwidth); /* bits / s */
                 *bandwidth = hls->bandwidth;
                 candidate = n; /* possible candidate */
             }
@@ -775,6 +848,8 @@ static int BandwidthAdaptation(stream_t *s, int progid, uint64_t *bandwidth)
 
 static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur_stream)
 {
+    stream_sys_t *p_sys = s->p_sys;
+
     assert(hls);
     assert(segment);
 
@@ -796,14 +871,35 @@ static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur
 
     vlc_mutex_unlock(&segment->lock);
 
-    uint64_t bw = (segment->size * 8) / (duration/1000);      /* bits / ms */
+    /* thread is not started yet */
+    if (p_sys->thread == NULL)
+    {
+        msg_Info(s, "downloaded segment %d from stream %d",
+                    p_sys->segment, p_sys->current);
+        return VLC_SUCCESS;
+    }
+
+    /* Do bandwidth calculations when there are at least 3 segments
+       downloaded */
+    msg_Info(s, "downloaded segment %d from stream %d",
+                p_sys->thread->segment, p_sys->thread->current);
+    /* FIXME: we need an average here */
+    if (p_sys->thread->segment - p_sys->segment < 3)
+        return VLC_SUCCESS;
+
+    /* check for division by zero */
+    double ms = (double)duration / 1000.0; /* ms */
+    if (ms <= 0.0)
+        return VLC_SUCCESS;
+
+    uint64_t bw = ((double)(segment->size * 8) / ms) * 1000; /* bits / s */
     if (hls->bandwidth != bw)
     {
         int newstream = BandwidthAdaptation(s, hls->id, &bw);
-        if ((newstream > 0) && (newstream != *cur_stream))
+        if ((newstream >= 0) && (newstream != *cur_stream))
         {
-            msg_Info(s, "switching to %s bandwidth (%"PRIu64") stream",
-                     (hls->bandwidth <= bw) ? "faster" : "lower", bw);
+            msg_Info(s, "detected %s bandwidth (%"PRIu64") stream",
+                     (bw >= hls->bandwidth) ? "faster" : "lower", bw);
             *cur_stream = newstream;
         }
     }
@@ -824,8 +920,7 @@ static void* hls_Thread(vlc_object_t *p_this)
         assert(hls);
 
         vlc_mutex_lock(&hls->lock);
-        segment_t *segment = segment_GetSegment(hls, hls->segment);
-        if (segment) hls->segment++;
+        segment_t *segment = segment_GetSegment(hls, client->segment);
         vlc_mutex_unlock(&hls->lock);
 
         /* Is there a new segment to process? */
@@ -839,6 +934,9 @@ static void* hls_Thread(vlc_object_t *p_this)
             if (!p_sys->b_live) break;
         }
 
+        /* download succeeded */
+        client->segment++;
+
         /* FIXME: Reread the m3u8 index file */
         if (p_sys->b_live)
         {
@@ -868,29 +966,21 @@ static void* hls_Thread(vlc_object_t *p_this)
     return NULL;
 }
 
-static int Prefetch(stream_t *s)
+static int Prefetch(stream_t *s, int *current)
 {
     stream_sys_t *p_sys = s->p_sys;
-    int current;
-
-again:
-    current = p_sys->current;
 
     hls_stream_t *hls = hls_Get(p_sys->hls_stream, p_sys->current);
     if (hls == NULL)
         return VLC_EGENERIC;
 
-    segment_t *segment = segment_GetSegment(hls, hls->segment);
+    segment_t *segment = segment_GetSegment(hls, p_sys->segment);
     if (segment == NULL )
         return VLC_EGENERIC;
 
-    if (Download(s, hls, segment, &p_sys->current) != VLC_SUCCESS)
+    if (Download(s, hls, segment, current) != VLC_SUCCESS)
         return VLC_EGENERIC;
 
-    /* Bandwidth changed? */
-    if (current != p_sys->current)
-        goto again;
-
     return VLC_SUCCESS;
 }
 
@@ -988,10 +1078,6 @@ static char *AccessReadLine(access_t *p_access, uint8_t *psz_tmp, size_t i_len)
         p++;
     }
 
-    /* EOL */
-    line = calloc(1, p - begin + 1);
-    if (line == NULL)
-        return NULL;
     /* copy line excluding \n */
     line = strndup(begin, p - begin);
 
@@ -1035,8 +1121,12 @@ static int AccessDownload(stream_t *s, segment_t *segment)
         {
             msg_Dbg(s, "size changed %"PRIu64, segment->size);
             segment->data = block_Realloc(segment->data, 0, p_sys->p_access->info.i_size);
-            if (segment->data)
-                segment->size = p_sys->p_access->info.i_size;
+            if (segment->data == NULL)
+            {
+                AccessClose(s);
+                return VLC_ENOMEM;
+            }
+            segment->size = p_sys->p_access->info.i_size;
             assert(segment->data->i_buffer == segment->size);
         }
         length = p_sys->p_access->pf_read(p_sys->p_access,
@@ -1091,10 +1181,10 @@ static int Open(vlc_object_t *p_this)
     }
 
     /* */
+    int current = p_sys->current = hls_LowestBandwidthStream(p_sys->hls_stream);
     p_sys->segment = 0;
-    p_sys->current = 0;
 
-    if (Prefetch(s) != VLC_SUCCESS)
+    if (Prefetch(s, &current) != VLC_SUCCESS)
     {
         msg_Err(s, "fetching first segment.");
         goto fail;
@@ -1108,7 +1198,8 @@ static int Open(vlc_object_t *p_this)
     }
 
     p_sys->thread->hls_stream = p_sys->hls_stream;
-    p_sys->thread->current = p_sys->current;
+    p_sys->thread->current = current;
+    p_sys->thread->segment = p_sys->segment + 1;
     p_sys->thread->s = s;
 
     if (vlc_thread_create(p_sys->thread, "HTTP Live Streaming client",
@@ -1163,12 +1254,13 @@ static void Close(vlc_object_t *p_this)
 static segment_t *NextSegment(stream_t *s)
 {
     stream_sys_t *p_sys = s->p_sys;
-    segment_t *segment;
+    segment_t *segment = NULL;
+    int i_stream = 0;
 
-    do
+    while(vlc_object_alive(s))
     {
         /* Is the next segment ready */
-        hls_stream_t *hls = hls_Get(p_sys->hls_stream, p_sys->current);
+        hls_stream_t *hls = hls_Get(p_sys->hls_stream, i_stream);
         if (hls == NULL) return NULL;
 
         segment = segment_GetSegment(hls, p_sys->segment);
@@ -1178,26 +1270,18 @@ static segment_t *NextSegment(stream_t *s)
         if (segment->data != NULL)
             return segment;
 
-        /* Was the stream changed to another bitrate? */
         if (!p_sys->b_meta) return NULL;
 
-        if (p_sys->current != p_sys->thread->current)
-        {
-            /* YES it was */
-            p_sys->current = p_sys->thread->current;
-        }
-        else
-        {
+        /* Was the stream changed to another bitrate? */
+        i_stream++;
+        if (i_stream >= vlc_array_count(p_sys->hls_stream))
+            return NULL;
 #if 0
-            /* Not downloaded yet, do it here */
-            if (Download(s, hls, segment, &p_sys->current) != VLC_SUCCESS)
-                return segment;
-            else
+        msg_Info(s, "playback is switching from stream %d to %d",
+                 p_sys->current, i_stream);
 #endif
-                return NULL;
-        }
+        p_sys->current = i_stream;
     }
-    while(vlc_object_alive(s));
 
     return segment;
 }
@@ -1224,8 +1308,9 @@ static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read)
                 block_Release(segment->data);
                 segment->data = NULL;
             }
-            msg_Dbg(s, "switching to segment %d", p_sys->segment);
             p_sys->segment++;
+            msg_Info(s, "playing segment %d from stream %d",
+                        p_sys->segment, p_sys->current);
             vlc_mutex_unlock(&segment->lock);
             continue;
         }
@@ -1279,18 +1364,22 @@ static int Peek(stream_t *s, const uint8_t **pp_peek, unsigned int i_peek)
 {
     stream_sys_t *p_sys = s->p_sys;
     size_t curlen = 0;
+    segment_t *segment;
 
-    hls_stream_t *hls = hls_Get(p_sys->hls_stream, p_sys->current);
-    if (hls == NULL)
-        return 0;
+again:
+    segment = NextSegment(s);
+    if (segment == NULL)
+    {
+        msg_Err(s, "segment should have been available");
+        return 0; /* eof? */
+    }
 
+    vlc_mutex_lock(&segment->lock);
+
+    /* remember segment to peek */
     int peek_segment = p_sys->segment;
     do
     {
-        segment_t *segment = segment_GetSegment(hls, peek_segment);
-        if (segment == NULL) return 0;
-
-        vlc_mutex_lock(&segment->lock);
         if (i_peek < segment->data->i_buffer)
         {
             *pp_peek = segment->data->p_buffer;
@@ -1298,11 +1387,17 @@ static int Peek(stream_t *s, const uint8_t **pp_peek, unsigned int i_peek)
         }
         else
         {
-             peek_segment++;
+            p_sys->segment++;
+            vlc_mutex_unlock(&segment->lock);
+            goto again;
         }
-        vlc_mutex_unlock(&segment->lock);
     } while ((curlen < i_peek) && vlc_object_alive(s));
 
+    /* restore segment to read */
+    p_sys->segment = peek_segment;
+
+    vlc_mutex_unlock(&segment->lock);
+
     return curlen;
 }
 
@@ -1318,9 +1413,10 @@ static bool hls_MaySeek(stream_t *s)
 
     if (p_sys->b_live)
     {
-       int count = vlc_array_count(hls->segments);
        vlc_mutex_lock(&hls->lock);
-       bool may_seek = (hls->segment < count - 2);
+       int count = vlc_array_count(hls->segments);
+       bool may_seek = (p_sys->thread == NULL) ? false :
+                            (p_sys->thread->segment < count - 2);
        vlc_mutex_unlock(&hls->lock);
        return may_seek;
     }
@@ -1368,7 +1464,7 @@ static int segment_Seek(stream_t *s, uint64_t pos)
     for (int n = 0; n < count; n++)
     {
         /* FIXME: Seeking in segments not dowloaded is not supported. */
-        if (n >= hls->segment)
+        if (n >= p_sys->thread->segment)
         {
             msg_Err(s, "seeking in segment not downloaded yet.");
             return VLC_EGENERIC;
@@ -1379,23 +1475,39 @@ static int segment_Seek(stream_t *s, uint64_t pos)
             return VLC_EGENERIC;
 
         vlc_mutex_lock(&segment->lock);
-        length += segment->size;
-        uint64_t size = segment->size -segment->data->i_buffer;
-        if (size > 0)
+        if (segment->data)
         {
-            segment->data->i_buffer += size;
-            segment->data->p_buffer -= size;
-        }
+            length += segment->size;
+            uint64_t size = segment->size -segment->data->i_buffer;
+            if (size > 0)
+            {
+                segment->data->i_buffer += size;
+                segment->data->p_buffer -= size;
+            }
 
-        if (!b_found && (pos <= length))
+            if (!b_found && (pos <= length))
+            {
+                uint64_t used = length - pos;
+                segment->data->i_buffer -= used;
+                segment->data->p_buffer += used;
+
+                count = p_sys->segment;
+                p_sys->segment = n;
+                b_found = true;
+            }
+        }
+        else
         {
-            uint64_t used = length - pos;
-            segment->data->i_buffer -= used;
-            segment->data->p_buffer += used;
+            /* FIXME: seeking is weird when seeking in segments
+               that have not been downloaded yet */
+            length += segment->length * hls->bandwidth;
 
-            count = p_sys->segment;
-            p_sys->segment = n;
-            b_found = true;
+            if (!b_found && (pos <= length))
+            {
+                count = p_sys->segment;
+                p_sys->segment = n;
+                b_found = true;
+            }
         }
         vlc_mutex_unlock(&segment->lock);
     }