Commit 9f49c9c3 authored by Jean-Paul Saman's avatar Jean-Paul Saman

stream_filter/httplive.c: rewrite parsing of HLS m3u8 files

The parsing of .m3u8 HLS files was too position dependend and did not work well
with some implementations of HLS playlist files. Parsing and reading the .m3u8 file
are now separated.
parent 5532707d
...@@ -136,13 +136,16 @@ static int Read (stream_t *, void *p_read, unsigned int i_read); ...@@ -136,13 +136,16 @@ static int Read (stream_t *, void *p_read, unsigned int i_read);
static int Peek (stream_t *, const uint8_t **pp_peek, unsigned int i_peek); static int Peek (stream_t *, const uint8_t **pp_peek, unsigned int i_peek);
static int Control(stream_t *, int i_query, va_list); static int Control(stream_t *, int i_query, va_list);
static ssize_t access_ReadM3U8(stream_t *s, vlc_url_t *url, uint8_t **buffer);
static ssize_t ReadM3U8(stream_t *s, uint8_t **buffer);
static char *ReadLine(uint8_t *buffer, uint8_t **remain, size_t len);
static int AccessOpen(stream_t *s, vlc_url_t *url); static int AccessOpen(stream_t *s, vlc_url_t *url);
static void AccessClose(stream_t *s); static void AccessClose(stream_t *s);
static char *AccessReadLine(access_t *p_access, uint8_t *psz_tmp, size_t i_len);
static int AccessDownload(stream_t *s, segment_t *segment); static int AccessDownload(stream_t *s, segment_t *segment);
static void* hls_Thread(vlc_object_t *); static void* hls_Thread(vlc_object_t *);
static int get_HTTPLivePlaylist(stream_t *s, hls_stream_t *hls);
static segment_t *segment_GetSegment(hls_stream_t *hls, int wanted); static segment_t *segment_GetSegment(hls_stream_t *hls, int wanted);
static void segment_Free(segment_t *segment); static void segment_Free(segment_t *segment);
...@@ -349,7 +352,7 @@ static segment_t *segment_Find(hls_stream_t *hls, int sequence) ...@@ -349,7 +352,7 @@ static segment_t *segment_Find(hls_stream_t *hls, int sequence)
return NULL; return NULL;
} }
static int live_ChooseSegment(stream_t *s, int current) static int ChooseSegment(stream_t *s, int current)
{ {
stream_sys_t *p_sys = (stream_sys_t *)s->p_sys; stream_sys_t *p_sys = (stream_sys_t *)s->p_sys;
hls_stream_t *hls = hls_Get(p_sys->hls_stream, current); hls_stream_t *hls = hls_Get(p_sys->hls_stream, current);
...@@ -361,21 +364,34 @@ static int live_ChooseSegment(stream_t *s, int current) ...@@ -361,21 +364,34 @@ static int live_ChooseSegment(stream_t *s, int current)
int wanted = -1; int wanted = -1;
int duration = 0; int duration = 0;
int count = vlc_array_count(hls->segments); int count = vlc_array_count(hls->segments);
for (int i = count; i >= 0; i--) int i = p_sys->b_live ? count - 1 : 0;
while((i >= 0) && (i < count) && vlc_object_alive(s))
{ {
segment_t *segment = segment_GetSegment(hls, i); segment_t *segment = segment_GetSegment(hls, i);
if (segment) assert(segment);
if (segment->duration > hls->duration)
{ {
msg_Err(s, "EXTINF:%d duration is larger then EXT-X-TARGETDURATION:%d",
segment->duration, hls->duration);
}
duration += segment->duration; duration += segment->duration;
if (duration >= 3 * hls->duration) if (duration >= 3 * hls->duration)
{ {
/* Start point found */ /* Start point found */
wanted = i; wanted = p_sys->b_live ? i : 0;
break; break;
} }
}
if (p_sys->b_live)
i-- ;
else
i++;
} }
msg_Info(s, "Choose segment %d/%d", wanted, count);
return wanted; return wanted;
} }
...@@ -451,30 +467,32 @@ fail: ...@@ -451,30 +467,32 @@ fail:
return NULL; return NULL;
} }
static void parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_read, char *uri) static int parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_read, char *uri)
{ {
stream_sys_t *p_sys = s->p_sys;
assert(hls); assert(hls);
assert(p_read);
int duration; /* strip of #EXTINF: */
int ret = sscanf(p_read, "#EXTINF:%d,", &duration); char *p_next = NULL;
if (ret != 1) char *token = strtok_r(p_read, ":", &p_next);
{ if (token == NULL)
msg_Err(s, "expected #EXTINF:<s>,"); return VLC_EGENERIC;
p_sys->b_error = true;
return; /* read duration */
} token = strtok_r(NULL, ",", &p_next);
if (token == NULL)
return VLC_EGENERIC;
int duration = atoi(token);
/* Ignore the rest of the line */
/* Store segment information */
char *psz_path = NULL; char *psz_path = NULL;
if (hls->url.psz_path != NULL) if (hls->url.psz_path != NULL)
{ {
psz_path = strdup(hls->url.psz_path); psz_path = strdup(hls->url.psz_path);
if (psz_path == NULL) if (psz_path == NULL)
{ return VLC_ENOMEM;
p_sys->b_error = true;
return;
}
char *p = strrchr(psz_path, '/'); char *p = strrchr(psz_path, '/');
if (p) *p = '\0'; if (p) *p = '\0';
} }
...@@ -485,14 +503,10 @@ static void parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_rea ...@@ -485,14 +503,10 @@ static void parse_SegmentInformation(stream_t *s, hls_stream_t *hls, char *p_rea
segment_t *segment = segment_New(hls, duration, psz_uri ? psz_uri : uri); segment_t *segment = segment_New(hls, duration, psz_uri ? psz_uri : uri);
if (segment) if (segment)
segment->sequence = hls->sequence + vlc_array_count(hls->segments) - 1; segment->sequence = hls->sequence + vlc_array_count(hls->segments) - 1;
if (duration > hls->duration)
{
msg_Err(s, "EXTINF:%d duration is larger then EXT-X-TARGETDURATION:%d",
duration, hls->duration);
}
vlc_mutex_unlock(&hls->lock); vlc_mutex_unlock(&hls->lock);
free(psz_uri); free(psz_uri);
return segment ? VLC_SUCCESS : VLC_ENOMEM;
} }
static int 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)
...@@ -511,21 +525,20 @@ static int parse_TargetDuration(stream_t *s, hls_stream_t *hls, char *p_read) ...@@ -511,21 +525,20 @@ static int parse_TargetDuration(stream_t *s, hls_stream_t *hls, char *p_read)
return VLC_SUCCESS; return VLC_SUCCESS;
} }
static void parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream, static int parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream,
char *p_read, char *uri) hls_stream_t **hls, char *p_read, char *uri)
{ {
stream_sys_t *p_sys = s->p_sys;
int id; int id;
uint64_t bw; uint64_t bw;
char *attr; char *attr;
assert(*hls == NULL);
attr = parse_Attributes(p_read, "PROGRAM-ID"); attr = parse_Attributes(p_read, "PROGRAM-ID");
if (attr == NULL) if (attr == NULL)
{ {
msg_Err(s, "#EXT-X-STREAM-INF: expected PROGRAM-ID=<value>"); msg_Err(s, "#EXT-X-STREAM-INF: expected PROGRAM-ID=<value>");
p_sys->b_error = true; return VLC_EGENERIC;
return;
} }
id = atol(attr); id = atol(attr);
free(attr); free(attr);
...@@ -534,8 +547,7 @@ static void parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream, ...@@ -534,8 +547,7 @@ static void parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream,
if (attr == NULL) if (attr == NULL)
{ {
msg_Err(s, "#EXT-X-STREAM-INF: expected BANDWIDTH=<value>"); msg_Err(s, "#EXT-X-STREAM-INF: expected BANDWIDTH=<value>");
p_sys->b_error = true; return VLC_EGENERIC;
return;
} }
bw = atoll(attr); bw = atoll(attr);
free(attr); free(attr);
...@@ -543,19 +555,18 @@ static void parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream, ...@@ -543,19 +555,18 @@ static void parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream,
if (bw == 0) if (bw == 0)
{ {
msg_Err(s, "#EXT-X-STREAM-INF: bandwidth cannot be 0"); msg_Err(s, "#EXT-X-STREAM-INF: bandwidth cannot be 0");
p_sys->b_error = true; return VLC_EGENERIC;
return;
} }
msg_Info(s, "bandwidth adaption detected (program-id=%d, bandwidth=%"PRIu64").", id, bw); msg_Info(s, "bandwidth adaption detected (program-id=%d, bandwidth=%"PRIu64").", id, bw);
char *psz_uri = relative_URI(s, uri, NULL); char *psz_uri = relative_URI(s, uri, NULL);
hls_stream_t *hls = hls_New(*hls_stream, id, bw, psz_uri ? psz_uri : uri); *hls = hls_New(*hls_stream, id, bw, psz_uri ? psz_uri : uri);
if (hls == NULL)
p_sys->b_error = true;
free(psz_uri); free(psz_uri);
return (*hls == NULL) ? VLC_ENOMEM : VLC_SUCCESS;
} }
static int 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)
...@@ -684,328 +695,207 @@ static int parse_Discontinuity(stream_t *s, hls_stream_t *hls, char *p_read) ...@@ -684,328 +695,207 @@ static int parse_Discontinuity(stream_t *s, hls_stream_t *hls, char *p_read)
return VLC_SUCCESS; return VLC_SUCCESS;
} }
static void parse_M3U8ExtLine(stream_t *s, hls_stream_t *hls, char *line) /* The http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8
{ * document defines the following new tags: EXT-X-TARGETDURATION,
if (*line == '#') * EXT-X-MEDIA-SEQUENCE, EXT-X-KEY, EXT-X-PROGRAM-DATE-TIME, EXT-X-
{ * ALLOW-CACHE, EXT-X-STREAM-INF, EXT-X-ENDLIST, EXT-X-DISCONTINUITY,
int err = VLC_SUCCESS; * and EXT-X-VERSION.
if (strncmp(line, "#EXT-X-TARGETDURATION", 21) == 0) */
err = parse_TargetDuration(s, hls, line); static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, ssize_t len)
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)
err = parse_Discontinuity(s, hls, line);
else if (strncmp(line, "#EXT-X-VERSION", 14) == 0)
err = parse_Version(s, hls, line);
else if (strncmp(line, "#EXT-X-ENDLIST", 14) == 0)
err = parse_EndList(s, hls);
if (err != VLC_SUCCESS)
s->p_sys->b_error = true;
}
}
#define HTTPLIVE_MAX_LINE 4096
static int get_HTTPLivePlaylist(stream_t *s, hls_stream_t *hls)
{ {
stream_sys_t *p_sys = s->p_sys; stream_sys_t *p_sys = s->p_sys;
uint8_t *p_read, *p_begin, *p_end;
/* Download new playlist file from server */ assert(streams);
if (AccessOpen(s, &hls->url) != VLC_SUCCESS) assert(buffer);
return VLC_EGENERIC;
/* Parse the rest of the reply */ msg_Dbg(s, "parse_M3U8\n%s", buffer);
uint8_t *tmp = calloc(1, HTTPLIVE_MAX_LINE); p_begin = buffer;
if (tmp == NULL) p_end = p_begin + len;
{
AccessClose(s); char *line = ReadLine(p_begin, &p_read, p_end - p_begin);
if (line == NULL)
return VLC_ENOMEM; return VLC_ENOMEM;
}
char *line = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE);
if (strncmp(line, "#EXTM3U", 7) != 0) if (strncmp(line, "#EXTM3U", 7) != 0)
{ {
msg_Err(s, "missing #EXTM3U tag"); msg_Err(s, "missing #EXTM3U tag .. aborting");
goto error;
}
free(line); free(line);
line = NULL; return VLC_EGENERIC;
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)) free(line);
goto error; line = NULL;
/* some more checks for actual data */ /* What is the version ? */
if (strncmp(line, "#EXTINF", 7) == 0) int version = 1;
uint8_t *p = (uint8_t *)strstr((const char *)buffer, "#EXT-X-VERSION:");
if (p != NULL)
{ {
char *uri = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE); uint8_t *tmp = NULL;
if (uri == NULL) char *psz_version = ReadLine(p, &tmp, p_end - p);
p_sys->b_error = true; if (psz_version == NULL)
else return VLC_ENOMEM;
int ret = sscanf((const char*)psz_version, "#EXT-X-VERSION:%d", &version);
if (ret != 1)
{ {
parse_SegmentInformation(s, hls, line, uri); msg_Warn(s, "#EXT-X-VERSION: no protocol version found, assuming version 1.");
free(uri); version = 1;
}
} }
else free(psz_version);
{ p = NULL;
parse_M3U8ExtLine(s, hls, line);
} }
/* Error during m3u8 parsing abort */ /* Is it a live stream ? */
if (p_sys->b_error) p_sys->b_live = (strstr((const char *)buffer, "#EXT-X-ENDLIST") == NULL) ? true : false;
goto error;
free(line); /* Is it a meta index file ? */
} bool b_meta = (strstr((const char *)buffer, "#EXT-X-STREAM-INF") == NULL) ? false : true;
free(line); if (b_meta)
free(tmp); msg_Info(s, "Meta playlist");
AccessClose(s); else
return VLC_SUCCESS; msg_Info(s, "%s Playlist HLS protocol version: %d", p_sys->b_live ? "Live": "VOD", version);
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( ; ; ) /* */
int err = VLC_SUCCESS;
do
{ {
line = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE); /* Next line */
p_begin = p_read;
line = ReadLine(p_begin, &p_read, p_end - p_begin);
if (line == NULL) if (line == NULL)
{
msg_Dbg(s, "end of data");
break; break;
}
if (!vlc_object_alive(s)) /* */
goto error; p_begin = p_read;
/* some more checks for actual data */ /* M3U8 Meta Index file */
if (b_meta)
{
if (strncmp(line, "#EXT-X-STREAM-INF", 17) == 0) if (strncmp(line, "#EXT-X-STREAM-INF", 17) == 0)
{ {
p_sys->b_meta = true; p_sys->b_meta = true;
char *uri = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE); char *uri = ReadLine(p_begin, &p_read, p_end - p_begin);
if (uri == NULL) if (uri == NULL)
p_sys->b_error = true; err = VLC_ENOMEM;
else else
{ {
parse_StreamInformation(s, streams, line, uri); hls_stream_t *hls = NULL;
err = parse_StreamInformation(s, &streams, &hls, line, uri);
free(uri); free(uri);
}
} /* Download playlist file from server */
else if (strncmp(line, "#EXTINF", 7) == 0) uint8_t *buf = NULL;
{ ssize_t len = access_ReadM3U8(s, &hls->url, &buf);
char *uri = AccessReadLine(p_sys->p_access, tmp, HTTPLIVE_MAX_LINE); if (len < 0)
if (uri == NULL) err = VLC_EGENERIC;
p_sys->b_error = true;
else else
{ {
hls_stream_t *hls = hls_GetLast(*streams); /* Parse HLS m3u8 content. */
err = parse_M3U8(s, streams, buf, len);
free(buf);
}
if (hls) if (hls)
parse_SegmentInformation(s, hls, line, uri); {
else hls->version = version;
p_sys->b_error = true; if (!p_sys->b_live)
free(uri); hls->size = hls_GetStreamSize(hls); /* Stream size (approximate) */
}
}
} }
} }
else else
{ {
hls_stream_t *hls = hls_GetLast(*streams); hls_stream_t *hls = hls_GetLast(streams);
if ((hls == NULL) && (!p_sys->b_meta))
{
hls = hls_New(*streams, -1, -1, NULL);
if (hls == NULL) if (hls == NULL)
{ {
p_sys->b_error = true; /* No Meta playlist used */
return VLC_ENOMEM; hls = hls_New(streams, 0, -1, NULL);
} if (hls == NULL)
}
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);
AccessClose(s);
return VLC_EGENERIC;
}
#undef HTTPLIVE_MAX_LINE
/* The http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8
* document defines the following new tags: EXT-X-TARGETDURATION,
* EXT-X-MEDIA-SEQUENCE, EXT-X-KEY, EXT-X-PROGRAM-DATE-TIME, EXT-X-
* ALLOW-CACHE, EXT-X-STREAM-INF, EXT-X-ENDLIST, EXT-X-DISCONTINUITY,
* and EXT-X-VERSION.
*/
static int parse_HTTPLiveStreaming(stream_t *s)
{
stream_sys_t *p_sys = s->p_sys;
char *p_read, *p_begin, *p_end;
assert(p_sys->hls_stream);
p_begin = p_read = stream_ReadLine(s->p_source);
if (!p_begin)
return VLC_ENOMEM;
/* */
int i_len = strlen(p_begin);
p_end = p_read + i_len;
if (strncmp(p_read, "#EXTM3U", 7) != 0)
{ {
msg_Err(s, "missing #EXTM3U tag .. aborting"); msg_Err(s, "No HLS structure created");
free(p_begin); err = VLC_ENOMEM;
return VLC_EGENERIC;
}
do {
free(p_begin);
if (p_sys->b_error)
return VLC_EGENERIC;
/* Next line */
p_begin = stream_ReadLine(s->p_source);
if (p_begin == NULL)
break; break;
}
i_len = strlen(p_begin); /* Get TARGET-DURATION first */
p_read = p_begin; p = (uint8_t *)strstr((const char *)buffer, "#EXT-X-TARGETDURATION:");
p_end = p_read + i_len; if (p)
if (strncmp(p_read, "#EXT-X-STREAM-INF", 17) == 0)
{ {
p_sys->b_meta = true; uint8_t *p_rest = NULL;
char *uri = stream_ReadLine(s->p_source); char *psz_duration = ReadLine(p, &p_rest, p_end - p);
if (uri == NULL) if (psz_duration)
p_sys->b_error = true;
else
{ {
parse_StreamInformation(s, &p_sys->hls_stream, p_read, uri); err = parse_TargetDuration(s, hls, psz_duration);
free(uri); free(psz_duration);
p = NULL;
} }
} }
else if (strncmp(p_read, "#EXTINF", 7) == 0)
hls->version = version;
}
if (strncmp(line, "#EXTINF", 7) == 0)
{ {
char *uri = stream_ReadLine(s->p_source); char *uri = ReadLine(p_begin, &p_read, p_end - p_begin);
if (uri == NULL) if (uri == NULL)
p_sys->b_error = true; err = VLC_EGENERIC;
else else
{ {
hls_stream_t *hls = hls_GetLast(p_sys->hls_stream); err = parse_SegmentInformation(s, hls, line, uri);
if (hls)
parse_SegmentInformation(s, hls, p_read, uri);
else
p_sys->b_error = true;
free(uri); free(uri);
} }
p_begin = p_read;
} }
else else if (strncmp(line, "#EXT-X-TARGETDURATION", 21) == 0)
{ err = parse_TargetDuration(s, hls, line);
hls_stream_t *hls = hls_GetLast(p_sys->hls_stream); else if (strncmp(line, "#EXT-X-MEDIA-SEQUENCE", 21) == 0)
if (hls == NULL) err = parse_MediaSequence(s, hls, line);
{ else if (strncmp(line, "#EXT-X-KEY", 10) == 0)
if (!p_sys->b_meta) err = parse_Key(s, hls, line);
{ else if (strncmp(line, "#EXT-X-PROGRAM-DATE-TIME", 24) == 0)
hls = hls_New(p_sys->hls_stream, -1, -1, NULL); err = parse_ProgramDateTime(s, hls, line);
if (hls == NULL) else if (strncmp(line, "#EXT-X-ALLOW-CACHE", 18) == 0)
{ err = parse_AllowCache(s, hls, line);
p_sys->b_error = true; else if (strncmp(line, "#EXT-X-DISCONTINUITY", 20) == 0)
return VLC_ENOMEM; err = parse_Discontinuity(s, hls, line);
} else if (strncmp(line, "#EXT-X-VERSION", 14) == 0)
} err = parse_Version(s, hls, line);
} else if (strncmp(line, "#EXT-X-ENDLIST", 14) == 0)
/* Parse M3U8 Ext Line */ err = parse_EndList(s, hls);
parse_M3U8ExtLine(s, hls, p_read);
} }
} while(p_read < p_end);
free(p_begin); free(line);
line = NULL;
/* */ if (p_begin >= p_end)
int count = vlc_array_count(p_sys->hls_stream); break;
for (int n = 0; n < count; n++)
{
hls_stream_t *hls = hls_Get(p_sys->hls_stream, n);
if (hls == NULL) break;
/* Is it a meta playlist? */ } while ((err == VLC_SUCCESS) && vlc_object_alive(s));
if (p_sys->b_meta)
{ free(line);
msg_Dbg(s, "parsing %s", hls->url.psz_path);
if (get_HTTPLivePlaylist(s, hls) != VLC_SUCCESS) return err;
{ }
msg_Err(s, "could not parse playlist file from meta index." );
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 */
uint8_t *buffer = NULL;
ssize_t len = access_ReadM3U8(s, &p_sys->m3u8, &buffer);
if (len < 0)
return VLC_EGENERIC; return VLC_EGENERIC;
}
}
vlc_mutex_lock(&hls->lock); /* Parse HLS m3u8 content. */
if (!p_sys->b_live) int err = parse_M3U8(s, *streams, buffer, len);
{ free(buffer);
/* Stream size (approximate) */
hls->size = hls_GetStreamSize(hls);
}
vlc_mutex_unlock(&hls->lock);
}
return VLC_SUCCESS; return err;
} }
/* Reload playlist */ /* Reload playlist */
...@@ -1077,23 +967,6 @@ static int hls_ReloadPlaylist(stream_t *s) ...@@ -1077,23 +967,6 @@ static int hls_ReloadPlaylist(stream_t *s)
int count = vlc_array_count(hls_streams); 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 */ /* merge playlists */
for (int n = 0; n < count; n++) for (int n = 0; n < count; n++)
{ {
...@@ -1155,6 +1028,8 @@ static int BandwidthAdaptation(stream_t *s, int progid, uint64_t *bandwidth) ...@@ -1155,6 +1028,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) 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(hls);
assert(segment); assert(segment);
...@@ -1167,10 +1042,10 @@ static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur ...@@ -1167,10 +1042,10 @@ static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur
} }
/* sanity check - can we download this segment on time? */ /* sanity check - can we download this segment on time? */
if (s->p_sys->bandwidth > 0) if ((p_sys->bandwidth > 0) && (hls->bandwidth > 0))
{ {
uint64_t size = (segment->duration * hls->bandwidth); /* bits */ uint64_t size = (segment->duration * hls->bandwidth); /* bits */
int estimated = (int)(size / s->p_sys->bandwidth); int estimated = (int)(size / p_sys->bandwidth);
if (estimated > segment->duration) if (estimated > segment->duration)
{ {
msg_Warn(s,"downloading of segment %d takes %ds, which is longer then its playback (%ds)", msg_Warn(s,"downloading of segment %d takes %ds, which is longer then its playback (%ds)",
...@@ -1197,8 +1072,8 @@ static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur ...@@ -1197,8 +1072,8 @@ static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur
return VLC_SUCCESS; return VLC_SUCCESS;
uint64_t bw = ((double)(segment->size * 8) / ms) * 1000; /* bits / s */ uint64_t bw = ((double)(segment->size * 8) / ms) * 1000; /* bits / s */
s->p_sys->bandwidth = bw; p_sys->bandwidth = bw;
if (hls->bandwidth != bw) if (p_sys->b_meta && (hls->bandwidth != bw))
{ {
int newstream = BandwidthAdaptation(s, hls->id, &bw); int newstream = BandwidthAdaptation(s, hls->id, &bw);
...@@ -1321,7 +1196,7 @@ static int Prefetch(stream_t *s, int *current) ...@@ -1321,7 +1196,7 @@ static int Prefetch(stream_t *s, int *current)
stream_sys_t *p_sys = s->p_sys; stream_sys_t *p_sys = s->p_sys;
int stream; int stream;
/* Try to pick best matching stream */ /* Try to pick best matching stream */;
again: again:
stream = *current; stream = *current;
...@@ -1452,44 +1327,6 @@ static void AccessClose(stream_t *s) ...@@ -1452,44 +1327,6 @@ static void AccessClose(stream_t *s)
} }
} }
static char *AccessReadLine(access_t *p_access, uint8_t *psz_tmp, size_t i_len)
{
char *line = NULL;
char *begin = (char *)psz_tmp;
assert(psz_tmp);
int skip = strlen(begin);
ssize_t len = p_access->pf_read(p_access, psz_tmp + skip, i_len - skip);
if (len < 0) return NULL;
if ((len == 0) && (skip == 0))
return NULL;
char *p = begin;
char *end = p + len + skip;
while (p < end)
{
if (*p == '\n')
break;
p++;
}
/* copy line excluding \n */
line = strndup(begin, p - begin);
p++;
if (p < end)
{
psz_tmp = memmove(begin, p, end - p);
psz_tmp[end - p] = '\0';
}
else memset(psz_tmp, 0, i_len);
return line;
}
static int AccessDownload(stream_t *s, segment_t *segment) static int AccessDownload(stream_t *s, segment_t *segment)
{ {
stream_sys_t *p_sys = (stream_sys_t *) s->p_sys; stream_sys_t *p_sys = (stream_sys_t *) s->p_sys;
...@@ -1539,30 +1376,30 @@ static int AccessDownload(stream_t *s, segment_t *segment) ...@@ -1539,30 +1376,30 @@ static int AccessDownload(stream_t *s, segment_t *segment)
} }
/* Read M3U8 file */ /* Read M3U8 file */
static uint8_t *access_ReadM3U8(stream_t *s, vlc_url_t *url) static ssize_t access_ReadM3U8(stream_t *s, vlc_url_t *url, uint8_t **buffer)
{ {
stream_sys_t *p_sys = (stream_sys_t *) s->p_sys; stream_sys_t *p_sys = (stream_sys_t *) s->p_sys;
assert(*buffer == NULL);
/* Download new playlist file from server */ /* Download new playlist file from server */
if (AccessOpen(s, url) != VLC_SUCCESS) if (AccessOpen(s, url) != VLC_SUCCESS)
return NULL; return VLC_EGENERIC;
ssize_t size = p_sys->p_access->info.i_size; ssize_t size = p_sys->p_access->info.i_size;
if (size == 0) size = 1024; /* no Content-Length */ if (size == 0) size = 1024; /* no Content-Length */
msg_Err(s, "Stream size is %"PRId64, size); *buffer = calloc(1, size);
if (*buffer == NULL)
uint8_t *buffer = calloc(1, size);
if (buffer == NULL)
{ {
AccessClose(s); AccessClose(s);
return NULL; return VLC_ENOMEM;
} }
size_t length = 0, curlen = 0; ssize_t length = 0, curlen = 0;
do do
{ {
length = p_sys->p_access->pf_read(p_sys->p_access, buffer + curlen, size - curlen); length = p_sys->p_access->pf_read(p_sys->p_access, *buffer + curlen, size - curlen);
if ((length <= 0) || (length >= size)) if ((length <= 0) || (length >= size))
break; break;
curlen += length; curlen += length;
...@@ -1577,19 +1414,19 @@ static uint8_t *access_ReadM3U8(stream_t *s, vlc_url_t *url) ...@@ -1577,19 +1414,19 @@ static uint8_t *access_ReadM3U8(stream_t *s, vlc_url_t *url)
} while (vlc_object_alive(s)); } while (vlc_object_alive(s));
AccessClose(s); AccessClose(s);
return buffer; return size;
} }
static uint8_t *ReadM3U8(stream_t *s) static ssize_t ReadM3U8(stream_t *s, uint8_t **buffer)
{ {
int64_t size = stream_Size(s->p_source); assert(*buffer == NULL);
msg_Err(s, "Stream size is %"PRId64, size); int64_t size = stream_Size(s->p_source);
if (size == 0) size = 1024; /* no Content-Length */ if (size == 0) size = 1024; /* no Content-Length */
uint8_t *buffer = calloc(1, size); *buffer = calloc(1, size);
if (buffer == NULL) if (*buffer == NULL)
return NULL; return VLC_ENOMEM;
int64_t len = 0, curlen = 0; int64_t len = 0, curlen = 0;
do { do {
...@@ -1607,10 +1444,11 @@ static uint8_t *ReadM3U8(stream_t *s) ...@@ -1607,10 +1444,11 @@ static uint8_t *ReadM3U8(stream_t *s)
*buffer = tmp; *buffer = tmp;
} }
} while (vlc_object_alive(s)); } while (vlc_object_alive(s));
return buffer;
return size;
} }
static char *ReadLine(uint8_t *buffer, uint8_t *remain, size_t len) static char *ReadLine(uint8_t *buffer, uint8_t **remain, size_t len)
{ {
assert(buffer); assert(buffer);
...@@ -1631,7 +1469,7 @@ static char *ReadLine(uint8_t *buffer, uint8_t *remain, size_t len) ...@@ -1631,7 +1469,7 @@ static char *ReadLine(uint8_t *buffer, uint8_t *remain, size_t len)
/* next pass start after \n */ /* next pass start after \n */
p++; p++;
remain = p; *remain = p;
return line; return line;
} }
...@@ -1681,14 +1519,21 @@ static int Open(vlc_object_t *p_this) ...@@ -1681,14 +1519,21 @@ static int Open(vlc_object_t *p_this)
s->pf_peek = Peek; s->pf_peek = Peek;
s->pf_control = Control; s->pf_control = Control;
/* Select first segment to play */ /* Parse HLS m3u8 content. */
if (parse_HTTPLiveStreaming(s) != VLC_SUCCESS) uint8_t *buffer = NULL;
ssize_t len = ReadM3U8(s, &buffer);
if (len < 0)
goto fail; goto fail;
if (parse_M3U8(s, p_sys->hls_stream, buffer, len) != VLC_SUCCESS)
{
free(buffer);
goto fail;
}
free(buffer);
/* Choose first HLS stream to start with */ /* Choose first HLS stream to start with */
int current = p_sys->playback.stream = 0; int current = p_sys->playback.stream = 0;
p_sys->playback.segment = p_sys->download.segment = p_sys->playback.segment = p_sys->download.segment = ChooseSegment(s, current);
p_sys->b_live ? live_ChooseSegment(s, current) : 0;
if (p_sys->b_live && (p_sys->playback.segment < 0)) if (p_sys->b_live && (p_sys->playback.segment < 0))
{ {
...@@ -1697,7 +1542,7 @@ static int Open(vlc_object_t *p_this) ...@@ -1697,7 +1542,7 @@ static int Open(vlc_object_t *p_this)
if (Prefetch(s, &current) != VLC_SUCCESS) if (Prefetch(s, &current) != VLC_SUCCESS)
{ {
msg_Err(s, "fetching first segment."); msg_Err(s, "fetching first segment failed.");
goto fail; goto fail;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment