X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fstream_filter%2Fhttplive.c;h=fa745af1052ff13c1f9dd22a088fffcc441ff904;hb=741f4af4c6384d247aa69ba4c68fae92b9369d15;hp=536e228db1d8db0484aeb77d5d387c63c9caf043;hpb=348b17855b171300e0f07c81b995325d71f0b564;p=vlc diff --git a/modules/stream_filter/httplive.c b/modules/stream_filter/httplive.c index 536e228db1..fa745af105 100644 --- a/modules/stream_filter/httplive.c +++ b/modules/stream_filter/httplive.c @@ -6,19 +6,19 @@ * * Author: Jean-Paul Saman * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /***************************************************************************** @@ -29,18 +29,17 @@ #endif #include -#include #include #include #include +#include #include #include #include #include -#include #include #include @@ -69,9 +68,9 @@ typedef struct segment_s uint64_t size; /* segment size in bytes */ uint64_t bandwidth; /* bandwidth usage of segments (bits per second)*/ - vlc_url_t url; + char *url; char *psz_key_path; /* url key path */ - uint8_t psz_AES_key[16]; /* AES-128 */ + uint8_t aes_key[16]; /* AES-128 */ bool b_key_loaded; vlc_mutex_t lock; @@ -84,12 +83,13 @@ typedef struct hls_stream_s int version; /* protocol version should be 1 */ int sequence; /* media sequence number */ int duration; /* maximum duration per segment (s) */ + int max_segment_length; /* maximum duration segments */ uint64_t bandwidth; /* bandwidth usage of segments (bits per second)*/ 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 */ + char *url; /* uri to m3u8 */ vlc_mutex_t lock; bool b_cache; /* allow caching */ @@ -100,7 +100,7 @@ typedef struct hls_stream_s struct stream_sys_t { - vlc_url_t m3u8; /* M3U8 url */ + char *m3u8; /* M3U8 url */ vlc_thread_t reload; /* HLS m3u8 reload thread */ vlc_thread_t thread; /* HLS segment download thread */ @@ -136,12 +136,23 @@ struct stream_sys_t int tries; /* times it was not changed */ } playlist; + struct hls_read_s + { + vlc_mutex_t lock_wait; /* used by read condition variable */ + vlc_cond_t wait; /* some condition to wait on during read */ + } read; + /* state */ bool b_cache; /* can cache files */ 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 */ + + /* Shared data */ + vlc_cond_t wait; + vlc_mutex_t lock; + bool paused; }; /**************************************************************************** @@ -152,7 +163,7 @@ 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 ssize_t read_M3U8_from_stream(stream_t *s, uint8_t **buffer); -static ssize_t read_M3U8_from_url(stream_t *s, vlc_url_t *url, uint8_t **buffer); +static ssize_t read_M3U8_from_url(stream_t *s, const char *psz_url, uint8_t **buffer); static char *ReadLine(uint8_t *buffer, uint8_t **pos, size_t len); static int hls_Download(stream_t *s, segment_t *segment); @@ -163,50 +174,60 @@ 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); - /**************************************************************************** * ****************************************************************************/ -static const char *const ext[] = { - "#EXT-X-TARGETDURATION", - "#EXT-X-MEDIA-SEQUENCE", - "#EXT-X-KEY", - "#EXT-X-ALLOW-CACHE", - "#EXT-X-ENDLIST", - "#EXT-X-STREAM-INF", - "#EXT-X-DISCONTINUITY", - "#EXT-X-VERSION" -}; - static bool isHTTPLiveStreaming(stream_t *s) { - const uint8_t *peek, *peek_end; + const uint8_t *peek; - int64_t i_size = stream_Peek(s->p_source, &peek, 46); - if (i_size < 1) + int size = stream_Peek(s->p_source, &peek, 46); + if (size < 7) return false; - if (strncasecmp((const char*)peek, "#EXTM3U", 7) != 0) + if (memcmp(peek, "#EXTM3U", 7) != 0) return false; + peek += 7; + size -= 7; + /* Parse stream and search for * EXT-X-TARGETDURATION or EXT-X-STREAM-INF tag, see * http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8 */ - peek_end = peek + i_size; - while(peek <= peek_end) - { - if (*peek == '#') + while (size--) + { + static const char *const ext[] = { + "TARGETDURATION", + "MEDIA-SEQUENCE", + "KEY", + "ALLOW-CACHE", + "ENDLIST", + "STREAM-INF", + "DISCONTINUITY", + "VERSION" + }; + + if (*peek++ != '#') + continue; + + if (size < 6) + continue; + + if (memcmp(peek, "EXT-X-", 6)) + continue; + + peek += 6; + size -= 6; + + for (size_t i = 0; i < ARRAY_SIZE(ext); i++) { - for (unsigned int i = 0; i < ARRAY_SIZE(ext); i++) - { - char *p = strstr((const char*)peek, ext[i]); - if (p != NULL) - return true; - } + size_t len = strlen(ext[i]); + if (size < 0 || (size_t)size < len) + continue; + if (!memcmp(peek, ext[i], len)) + return true; } - peek++; - }; + } return false; } @@ -220,11 +241,17 @@ static hls_stream_t *hls_New(vlc_array_t *hls_stream, const int id, const uint64 hls->id = id; hls->bandwidth = bw; hls->duration = -1;/* unknown */ + hls->max_segment_length = -1;/* unknown */ hls->size = 0; hls->sequence = 0; /* default is 0 */ hls->version = 1; /* default protocol version */ hls->b_cache = true; - vlc_UrlParse(&hls->url, uri, 0); + hls->url = strdup(uri); + if (hls->url == NULL) + { + free(hls); + return NULL; + } hls->psz_current_key_path = NULL; hls->segments = vlc_array_new(); vlc_array_append(hls_stream, hls); @@ -245,7 +272,7 @@ static void hls_Free(hls_stream_t *hls) } vlc_array_destroy(hls->segments); } - vlc_UrlClean(&hls->url); + free(hls->url); free(hls->psz_current_key_path); free(hls); } @@ -267,13 +294,12 @@ static hls_stream_t *hls_Copy(hls_stream_t *src, const bool b_cp_segments) 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) + dst->url = strdup(src->url); + if (dst->url == 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); @@ -314,7 +340,7 @@ static hls_stream_t *hls_Find(vlc_array_t *hls_stream, hls_stream_t *hls_new) { /* compare */ if ((hls->id == hls_new->id) && - (hls->bandwidth == hls_new->bandwidth)) + ((hls->bandwidth == hls_new->bandwidth)||(hls_new->bandwidth==0))) return hls; } } @@ -357,7 +383,12 @@ static segment_t *segment_New(hls_stream_t* hls, const int duration, const char segment->size = 0; /* bytes */ segment->sequence = 0; segment->bandwidth = 0; - vlc_UrlParse(&segment->url, uri, 0); + segment->url = strdup(uri); + if (segment->url == NULL) + { + free(segment); + return NULL; + } segment->data = NULL; vlc_array_append(hls->segments, segment); vlc_mutex_init(&segment->lock); @@ -372,7 +403,7 @@ static void segment_Free(segment_t *segment) { vlc_mutex_destroy(&segment->lock); - vlc_UrlClean(&segment->url); + free(segment->url); free(segment->psz_key_path); if (segment->data) block_Release(segment->data); @@ -420,8 +451,9 @@ static int ChooseSegment(stream_t *s, const int current) int duration = 0; int sequence = 0; int count = vlc_array_count(hls->segments); - int i = p_sys->b_live ? count - 1 : 0; + int i = p_sys->b_live ? count - 1 : -1; + /* We do while loop only with live case, otherwise return 0*/ while((i >= 0) && (i < count)) { segment_t *segment = segment_GetSegment(hls, i); @@ -437,18 +469,15 @@ static int ChooseSegment(stream_t *s, const int current) if (duration >= 3 * hls->duration) { /* Start point found */ - wanted = p_sys->b_live ? i : 0; + wanted = i; sequence = segment->sequence; break; } - if (p_sys->b_live) - i-- ; - else - i++; + i-- ; } - msg_Info(s, "Choose segment %d/%d (sequence=%d)", wanted, count, sequence); + msg_Dbg(s, "Choose segment %d/%d (sequence=%d)", wanted, count, sequence); return wanted; } @@ -466,11 +495,24 @@ static char *parse_Attributes(const char *line, const char *attr) begin = p; do { - if (strncasecmp(begin, attr, strlen(attr)) == 0) + if (strncasecmp(begin, attr, strlen(attr)) == 0 + && begin[strlen(attr)] == '=') { - /* =[,]* */ + /* =""[,]* */ p = strchr(begin, ','); begin += strlen(attr) + 1; + + /* Check if we have " " marked value*/ + if( begin[0] == '"' ) + { + char *valueend = strchr( begin+1, '"'); + + /* No ending " so bail out */ + if( unlikely( !valueend ) ) + return NULL; + + p = strchr( valueend, ','); + } if (begin >= end) return NULL; if (p == NULL) /* last attribute */ @@ -484,156 +526,91 @@ static char *parse_Attributes(const char *line, const char *attr) return NULL; } -static int hex2int(char c) +static int string_to_IV(char *string_hexa, uint8_t iv[AES_BLOCK_SIZE]) { - 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') + unsigned long long iv_hi, iv_lo; + char *end = NULL; + if (*string_hexa++ != '0') return VLC_EGENERIC; - if (*p++ != 'x') + if (*string_hexa != 'x' && *string_hexa != 'X') return VLC_EGENERIC; - while (*p && *(p+1)) - { - c = hex2int(*p++) << 4; - c |= hex2int(*p++); - *d++ = c; + string_hexa++; + + size_t len = strlen(string_hexa); + if (len <= 16) { + iv_hi = 0; + iv_lo = strtoull(string_hexa, &end, 16); + if (end) + return VLC_EGENERIC; + } else { + iv_lo = strtoull(&string_hexa[len-16], NULL, 16); + if (end) + return VLC_EGENERIC; + string_hexa[len-16] = '\0'; + iv_hi = strtoull(string_hexa, NULL, 16); + if (end) + return VLC_EGENERIC; + } + + for (int i = 7; i >= 0 ; --i) { + iv[ i] = iv_hi & 0xff; + iv[8+i] = iv_lo & 0xff; + iv_hi >>= 8; + iv_lo >>= 8; } return VLC_SUCCESS; } -static char *relative_URI(stream_t *s, const char *uri, const vlc_url_t *url) +static char *relative_URI(const char *psz_url, const char *psz_path) { - 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 *ret = NULL; + const char *fmt; + assert(psz_url != NULL && psz_path != NULL); - char *p = strchr(uri, ':'); - if (p != 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; - - p = strrchr(psz_path, '/'); - if (p) *p = '\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", - psz_protocol, psz_host, i_port, - psz_path, uri) < 0) - goto fail; - } + //If the path is actually an absolute URL, don't do anything. + if (strncmp(psz_path, "http", 4) == 0) + return NULL; -fail: - free(psz_password); - free(psz_username); - free(psz_protocol); - free(psz_path); - free(psz_host); - return psz_uri; -} + size_t len = strlen(psz_path); -static char *ConstructUrl(vlc_url_t *url) -{ - if ((url->psz_protocol == NULL) || - (url->psz_path == NULL)) + char *new_url = strdup(psz_url); + if (unlikely(!new_url)) return NULL; - if (url->i_port <= 0) - { - if (strncmp(url->psz_protocol, "https", 5) == 0) - url->i_port = 443; - else - url->i_port = 80; + if( psz_path[0] == '/' ) //Relative URL with absolute path + { + //Try to find separator for name and path, try to skip + //access and first :// + char *slash = strchr(&new_url[8], '/'); + if (unlikely(slash == NULL)) + goto end; + *slash = '\0'; + fmt = "%s%s"; + } else { + int levels = 0; + while(len >= 3 && !strncmp(psz_path, "../", 3)) { + psz_path += 3; + len -= 3; + levels++; + } + do { + char *slash = strrchr(new_url, '/'); + if (unlikely(slash == NULL)) + goto end; + *slash = '\0'; + } while (levels--); + fmt = "%s/%s"; } - char *psz_url = NULL; - if (url->psz_password || url->psz_username) - { - if (asprintf(&psz_url, "%s://%s:%s@%s:%d%s", - url->psz_protocol, - url->psz_username, url->psz_password, - url->psz_host, url->i_port, url->psz_path) < 0) - return NULL; - } - else - { - if (asprintf(&psz_url, "%s://%s:%d%s", - url->psz_protocol, - url->psz_host, url->i_port, url->psz_path) < 0) - return NULL; - } + if (asprintf(&ret, fmt, new_url, psz_path) < 0) + ret = NULL; - return psz_url; +end: + free(new_url); + return ret; } static int parse_SegmentInformation(hls_stream_t *hls, char *p_read, int *duration) @@ -653,20 +630,23 @@ static int parse_SegmentInformation(hls_stream_t *hls, char *p_read, int *durati return VLC_EGENERIC; int value; + char *endptr; if (hls->version < 3) { - value = strtol(token, NULL, 10); - if (errno == ERANGE) - { - *duration = -1; - return VLC_EGENERIC; - } - *duration = value; + errno = 0; + value = strtol(token, &endptr, 10); + if (token == endptr || errno == ERANGE) + { + *duration = -1; + return VLC_EGENERIC; + } + *duration = value; } else { - double d = strtod(token, (char **) NULL); - if (errno == ERANGE) + errno = 0; + double d = strtof(token, &endptr); + if (token == endptr || errno == ERANGE) { *duration = -1; return VLC_EGENERIC; @@ -675,13 +655,16 @@ static int parse_SegmentInformation(hls_stream_t *hls, char *p_read, int *durati value = ((int)d) + 1; else value = ((int)d); + *duration = value; } + if( *duration > hls->max_segment_length) + hls->max_segment_length = *duration; /* Ignore the rest of the line */ return VLC_SUCCESS; } -static int parse_AddSegment(stream_t *s, hls_stream_t *hls, const int duration, const char *uri) +static int parse_AddSegment(hls_stream_t *hls, const int duration, const char *uri) { assert(hls); assert(uri); @@ -689,7 +672,7 @@ static int parse_AddSegment(stream_t *s, hls_stream_t *hls, const int duration, /* Store segment information */ vlc_mutex_lock(&hls->lock); - char *psz_uri = relative_URI(s, uri, &hls->url); + char *psz_uri = relative_URI(hls->url, uri); segment_t *segment = segment_New(hls, duration, psz_uri ? psz_uri : uri); if (segment) @@ -727,13 +710,13 @@ static int parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream, assert(*hls == NULL); attr = parse_Attributes(p_read, "PROGRAM-ID"); - if (attr == NULL) + if (attr) { - msg_Err(s, "#EXT-X-STREAM-INF: expected PROGRAM-ID="); - return VLC_EGENERIC; + id = atol(attr); + free(attr); } - id = atol(attr); - free(attr); + else + id = 0; attr = parse_Attributes(p_read, "BANDWIDTH"); if (attr == NULL) @@ -750,9 +733,9 @@ static int parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream, return VLC_EGENERIC; } - msg_Info(s, "bandwidth adaptation detected (program-id=%d, bandwidth=%"PRIu64").", id, bw); + msg_Dbg(s, "bandwidth adaptation detected (program-id=%d, bandwidth=%"PRIu64").", id, bw); - char *psz_uri = relative_URI(s, uri, NULL); + char *psz_uri = relative_URI(s->p_sys->m3u8, uri); *hls = hls_New(*hls_stream, id, bw, psz_uri ? psz_uri : uri); @@ -778,9 +761,11 @@ static int parse_MediaSequence(stream_t *s, hls_stream_t *hls, char *p_read) 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)) + segment_t *last_segment = segment_GetSegment( last, vlc_array_count( last->segments ) - 1 ); + if ( ( last_segment->sequence < sequence) && + ( sequence - last_segment->sequence >= 1 )) msg_Err(s, "EXT-X-MEDIA-SEQUENCE gap in playlist (new=%d, old=%d)", - sequence, last->sequence); + sequence, last_segment->sequence); } else msg_Err(s, "EXT-X-MEDIA-SEQUENCE already present in playlist (new=%d, old=%d)", @@ -829,7 +814,7 @@ static int parse_Key(stream_t *s, hls_stream_t *hls, char *p_read) char *value, *uri, *iv; if (s->p_sys->b_aesmsg == false) { - msg_Info(s, "playback of AES-128 encrypted HTTP Live media detected."); + msg_Dbg(s, "playback of AES-128 encrypted HTTP Live media detected."); s->p_sys->b_aesmsg = true; } value = uri = parse_Attributes(p_read, "URI"); @@ -849,7 +834,13 @@ static int parse_Key(stream_t *s, hls_stream_t *hls, char *p_read) if (end != NULL) *end = 0; } - hls->psz_current_key_path = strdup(uri); + /* For absolute URI, just duplicate it + * don't limit to HTTP, maybe some sanity checking + * should be done more in here? */ + if( strstr( uri , "://" ) ) + hls->psz_current_key_path = strdup( uri ); + else + hls->psz_current_key_path = relative_URI(hls->url, uri); free(value); value = iv = parse_Attributes(p_read, "IV"); @@ -874,7 +865,10 @@ static int parse_Key(stream_t *s, hls_stream_t *hls, char *p_read) */ if (string_to_IV(iv, hls->psz_AES_IV) == VLC_EGENERIC) + { + msg_Err(s, "IV invalid"); err = VLC_EGENERIC; + } else hls->b_iv_loaded = true; free(value); @@ -939,7 +933,7 @@ static int parse_EndList(stream_t *s, hls_stream_t *hls) assert(hls); s->p_sys->b_live = false; - msg_Info(s, "video on demand (vod) mode"); + msg_Dbg(s, "video on demand (vod) mode"); return VLC_SUCCESS; } @@ -956,7 +950,8 @@ static int hls_CompareStreams( const void* a, const void* b ) { hls_stream_t* stream_a = *(hls_stream_t**)a; hls_stream_t* stream_b = *(hls_stream_t**)b; - return stream_a->bandwidth > stream_b->bandwidth; + + return stream_a->bandwidth - stream_b->bandwidth; } /* The http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8 @@ -1021,7 +1016,7 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const if (b_meta) { - msg_Info(s, "Meta playlist"); + msg_Dbg(s, "Meta playlist"); /* M3U8 Meta Index file */ do { @@ -1042,29 +1037,42 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const { if (*uri == '#') { - msg_Info(s, "Skipping invalid stream-inf: %s", uri); + msg_Warn(s, "Skipping invalid stream-inf: %s", uri); free(uri); } else { + bool new_stream_added = false; hls_stream_t *hls = NULL; err = parse_StreamInformation(s, &streams, &hls, line, uri); - free(uri); + if (err == VLC_SUCCESS) + new_stream_added = true; - /* 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); - } + free(uri); if (hls) { + /* Download playlist file from server */ + uint8_t *buf = NULL; + ssize_t len = read_M3U8_from_url(s, hls->url, &buf); + if (len < 0) + { + msg_Warn(s, "failed to read %s, continue for other streams", hls->url); + + /* remove stream just added */ + if (new_stream_added) + vlc_array_remove(streams, vlc_array_count(streams) - 1); + + /* ignore download error, so we have chance to try other streams */ + err = VLC_SUCCESS; + } + else + { + /* Parse HLS m3u8 content. */ + err = parse_M3U8(s, streams, buf, len); + free(buf); + } + hls->version = version; if (!p_sys->b_live) hls->size = hls_GetStreamSize(hls); /* Stream size (approximate) */ @@ -1082,10 +1090,17 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const } while (err == VLC_SUCCESS); + size_t stream_count = vlc_array_count(streams); + msg_Dbg(s, "%d streams loaded in Meta playlist", (int)stream_count); + if (stream_count == 0) + { + msg_Err(s, "No playable streams found in Meta playlist"); + err = VLC_EGENERIC; + } } else { - msg_Info(s, "%s Playlist HLS protocol version: %d", p_sys->b_live ? "Live": "VOD", version); + msg_Dbg(s, "%s Playlist HLS protocol version: %d", p_sys->b_live ? "Live": "VOD", version); hls_stream_t *hls = NULL; if (p_sys->b_meta) @@ -1093,11 +1108,7 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const else { /* No Meta playlist used */ - char* uri = ConstructUrl( &s->p_sys->m3u8 ); - if (uri == NULL) - return VLC_EGENERIC; - hls = hls_New(streams, 0, 0, uri); - free( uri ); + hls = hls_New(streams, 0, 0, p_sys->m3u8); if (hls) { /* Get TARGET-DURATION first */ @@ -1121,6 +1132,7 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const assert(hls); /* */ + bool media_sequence_loaded = false; int segment_duration = -1; do { @@ -1135,7 +1147,15 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const else if (strncmp(line, "#EXT-X-TARGETDURATION", 21) == 0) err = parse_TargetDuration(s, hls, line); else if (strncmp(line, "#EXT-X-MEDIA-SEQUENCE", 21) == 0) - err = parse_MediaSequence(s, hls, line); + { + /* A Playlist file MUST NOT contain more than one EXT-X-MEDIA-SEQUENCE tag. */ + /* We only care about first one */ + if (!media_sequence_loaded) + { + err = parse_MediaSequence(s, hls, line); + media_sequence_loaded = true; + } + } 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) @@ -1150,7 +1170,7 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const err = parse_EndList(s, hls); else if ((strncmp(line, "#", 1) != 0) && (*line != '\0') ) { - err = parse_AddSegment(s, hls, segment_duration, line); + err = parse_AddSegment(hls, segment_duration, line); segment_duration = -1; /* reset duration */ } @@ -1171,9 +1191,6 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const 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) { @@ -1181,18 +1198,14 @@ static int hls_DownloadSegmentKey(stream_t *s, segment_t *seg) return VLC_EGENERIC; } - len = stream_Read(p_m3u8, aeskey, sizeof(aeskey)); + int len = stream_Read(p_m3u8, seg->aes_key, sizeof(seg->aes_key)); + stream_Delete(p_m3u8); if (len != AES_BLOCK_SIZE) { - msg_Err(s, "The AES key loaded doesn't have the right size (%zd)", len); - stream_Delete(p_m3u8); + msg_Err(s, "The AES key loaded doesn't have the right size (%d)", len); return VLC_EGENERIC; } - memcpy(seg->psz_AES_key, aeskey, AES_BLOCK_SIZE); - - stream_Delete(p_m3u8); - return VLC_SUCCESS; } @@ -1217,7 +1230,7 @@ static int hls_ManageSegmentKeys(stream_t *s, hls_stream_t *hls) * 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); + memcpy(seg->aes_key, prev_seg->aes_key, AES_BLOCK_SIZE); seg->b_key_loaded = true; continue; } @@ -1256,8 +1269,8 @@ static int hls_DecodeSegmentData(stream_t *s, hls_stream_t *hls, segment_t *segm } /* Set key */ - i_gcrypt_err = gcry_cipher_setkey(aes_ctx, segment->psz_AES_key, - sizeof(segment->psz_AES_key)); + i_gcrypt_err = gcry_cipher_setkey(aes_ctx, segment->aes_key, + sizeof(segment->aes_key)); if (i_gcrypt_err) { msg_Err(s, "gcry_cipher_setkey failed: %s", gpg_strerror(i_gcrypt_err)); @@ -1340,7 +1353,7 @@ static int get_HTTPLiveMetaPlaylist(stream_t *s, vlc_array_t **streams) /* Download playlist file from server */ uint8_t *buf = NULL; - ssize_t len = read_M3U8_from_url(s, &dst->url, &buf); + ssize_t len = read_M3U8_from_url(s, dst->url, &buf); if (len < 0) err = VLC_EGENERIC; else @@ -1353,54 +1366,60 @@ static int get_HTTPLiveMetaPlaylist(stream_t *s, vlc_array_t **streams) return err; } -/* Reload playlist */ -static int hls_UpdatePlaylist(stream_t *s, hls_stream_t *hls_new, hls_stream_t **hls) +/* Update hls_old (an existing member of p_sys->hls_stream) to match hls_new + (which represents a downloaded, perhaps newer version of the same playlist) */ +static int hls_UpdatePlaylist(stream_t *s, hls_stream_t *hls_new, hls_stream_t *hls_old, bool *stream_appended) { int count = vlc_array_count(hls_new->segments); - msg_Info(s, "updating hls stream (program-id=%d, bandwidth=%"PRIu64") has %d segments", + msg_Dbg(s, "updating hls stream (program-id=%d, bandwidth=%"PRIu64") has %d segments", hls_new->id, hls_new->bandwidth, count); + vlc_mutex_lock(&hls_old->lock); + hls_old->max_segment_length=-1; for (int n = 0; n < count; n++) { segment_t *p = segment_GetSegment(hls_new, n); - if (p == NULL) return VLC_EGENERIC; + if (p == NULL) + { + vlc_mutex_unlock(&hls_old->lock); + return VLC_EGENERIC; + } - vlc_mutex_lock(&(*hls)->lock); - segment_t *segment = segment_Find(*hls, p->sequence); + segment_t *segment = segment_Find(hls_old, p->sequence); if (segment) { vlc_mutex_lock(&segment->lock); - assert(p->url.psz_path); - assert(segment->url.psz_path); + assert(p->url); + assert(segment->url); /* they should be the same */ if ((p->sequence != segment->sequence) || (p->duration != segment->duration) || - (strcmp(p->url.psz_path, segment->url.psz_path) != 0)) + (strcmp(p->url, segment->url) != 0)) { msg_Warn(s, "existing segment found with different content - resetting"); msg_Warn(s, "- sequence: new=%d, old=%d", p->sequence, segment->sequence); msg_Warn(s, "- duration: new=%d, old=%d", p->duration, segment->duration); - msg_Warn(s, "- file: new=%s", p->url.psz_path); - msg_Warn(s, " old=%s", segment->url.psz_path); + msg_Warn(s, "- file: new=%s", p->url); + msg_Warn(s, " old=%s", segment->url); /* Resetting content */ - char *psz_url = ConstructUrl(&p->url); - if (psz_url == NULL) + segment->sequence = p->sequence; + segment->duration = p->duration; + free(segment->url); + segment->url = strdup(p->url); + if ( segment->url == NULL ) { 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) + if ((p->psz_key_path || p->b_key_loaded) && + segment->data) { block_Release(segment->data); segment->data = NULL; @@ -1408,50 +1427,54 @@ static int hls_UpdatePlaylist(stream_t *s, hls_stream_t *hls_new, hls_stream_t * free(segment->psz_key_path); segment->psz_key_path = p->psz_key_path ? strdup(p->psz_key_path) : NULL; segment_Free(p); - free(psz_url); } vlc_mutex_unlock(&segment->lock); } else { - int last = vlc_array_count((*hls)->segments) - 1; - segment_t *l = segment_GetSegment(*hls, last); - if (l == NULL) goto fail_and_unlock; + int last = vlc_array_count(hls_old->segments) - 1; + segment_t *l = segment_GetSegment(hls_old, last); + if (l == NULL) { + vlc_mutex_unlock(&hls_old->lock); + return VLC_EGENERIC; + } if ((l->sequence + 1) != p->sequence) { msg_Err(s, "gap in sequence numbers found: new=%d expected %d", p->sequence, l->sequence+1); } - vlc_array_append((*hls)->segments, p); - msg_Info(s, "- segment %d appended", p->sequence); + vlc_array_append(hls_old->segments, p); + msg_Dbg(s, "- segment %d appended", p->sequence); + hls_old->max_segment_length = __MAX(hls_old->max_segment_length, p->duration); + msg_Dbg(s, " - segments new max duration %d", hls_old->max_segment_length); + + // Signal download thread otherwise the segment will not get downloaded + *stream_appended = true; } - 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); + hls_old->sequence = hls_new->sequence; + hls_old->duration = (hls_new->duration == -1) ? hls_old->duration : hls_new->duration; + hls_old->b_cache = hls_new->b_cache; + vlc_mutex_unlock(&hls_old->lock); return VLC_SUCCESS; -fail_and_unlock: - assert(0); - vlc_mutex_unlock(&(*hls)->lock); - return VLC_EGENERIC; } static int hls_ReloadPlaylist(stream_t *s) { stream_sys_t *p_sys = s->p_sys; + // Flag to indicate if we should signal download thread + bool stream_appended = false; + vlc_array_t *hls_streams = vlc_array_new(); if (hls_streams == NULL) return VLC_ENOMEM; - msg_Info(s, "Reloading HLS live meta playlist"); + msg_Dbg(s, "Reloading HLS live meta playlist"); if (get_HTTPLiveMetaPlaylist(s, &hls_streams) != VLC_SUCCESS) { @@ -1480,14 +1503,26 @@ static int hls_ReloadPlaylist(stream_t *s) 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")", + msg_Dbg(s, "new HLS stream appended (id=%d, bandwidth=%"PRIu64")", hls_new->id, hls_new->bandwidth); + + // New segment available - signal download thread + stream_appended = true; } - else if (hls_UpdatePlaylist(s, hls_new, &hls_old) != VLC_SUCCESS) - msg_Info(s, "failed updating HLS stream (id=%d, bandwidth=%"PRIu64")", + else if (hls_UpdatePlaylist(s, hls_new, hls_old, &stream_appended) != VLC_SUCCESS) + msg_Warn(s, "failed updating HLS stream (id=%d, bandwidth=%"PRIu64")", hls_new->id, hls_new->bandwidth); } vlc_array_destroy(hls_streams); + + // Must signal the download thread otherwise new segments will not be downloaded at all! + if (stream_appended == true) + { + vlc_mutex_lock(&p_sys->download.lock_wait); + vlc_cond_signal(&p_sys->download.wait); + vlc_mutex_unlock(&p_sys->download.lock_wait); + } + return VLC_SUCCESS; } @@ -1546,7 +1581,7 @@ static int hls_DownloadSegmentData(stream_t *s, hls_stream_t *hls, segment_t *se int estimated = (int)(size / p_sys->bandwidth); if (estimated > segment->duration) { - msg_Warn(s,"downloading of segment %d takes %ds, which is longer than its playback (%ds)", + msg_Warn(s,"downloading segment %d predicted to take %ds, which exceeds its length (%ds)", segment->sequence, estimated, segment->duration); } } @@ -1554,16 +1589,16 @@ static int hls_DownloadSegmentData(stream_t *s, hls_stream_t *hls, segment_t *se mtime_t start = mdate(); if (hls_Download(s, segment) != VLC_SUCCESS) { - msg_Err(s, "downloaded segment %d from stream %d failed", + msg_Err(s, "downloading segment %d from stream %d failed", segment->sequence, *cur_stream); vlc_mutex_unlock(&segment->lock); return VLC_EGENERIC; } mtime_t duration = mdate() - start; - if (hls->bandwidth == 0) + if (hls->bandwidth == 0 && segment->duration > 0) { /* Try to estimate the bandwidth for this stream */ - hls->bandwidth = (uint64_t)((double)segment->size / ((double)duration / 1000000.0)); + hls->bandwidth = (uint64_t)(((double)segment->size * 8) / ((double)segment->duration)); } /* If the segment is encrypted, decode it */ @@ -1575,15 +1610,10 @@ static int hls_DownloadSegmentData(stream_t *s, hls_stream_t *hls, segment_t *se vlc_mutex_unlock(&segment->lock); - msg_Info(s, "downloaded segment %d from stream %d", + msg_Dbg(s, "downloaded segment %d from stream %d", segment->sequence, *cur_stream); - /* 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 */ + uint64_t bw = segment->size * 8 * 1000000 / __MAX(1, duration); /* bits / s */ p_sys->bandwidth = bw; if (p_sys->b_meta && (hls->bandwidth != bw)) { @@ -1592,7 +1622,7 @@ static int hls_DownloadSegmentData(stream_t *s, hls_stream_t *hls, segment_t *se /* FIXME: we need an average here */ if ((newstream >= 0) && (newstream != *cur_stream)) { - msg_Info(s, "detected %s bandwidth (%"PRIu64") stream", + msg_Dbg(s, "detected %s bandwidth (%"PRIu64") stream", (bw >= hls->bandwidth) ? "faster" : "lower", bw); *cur_stream = newstream; } @@ -1672,6 +1702,11 @@ static void* hls_Thread(void *p_this) p_sys->download.segment++; vlc_cond_signal(&p_sys->download.wait); vlc_mutex_unlock(&p_sys->download.lock_wait); + + // In case of a successful download signal the read thread that data is available + vlc_mutex_lock(&p_sys->read.lock_wait); + vlc_cond_signal(&p_sys->read.wait); + vlc_mutex_unlock(&p_sys->read.lock_wait); } vlc_restorecancel(canc); @@ -1687,20 +1722,21 @@ static void* hls_Reload(void *p_this) int canc = vlc_savecancel(); - double wait = 0.5; + double wait = 1.0; while (vlc_object_alive(s)) { mtime_t now = mdate(); if (now >= p_sys->playlist.wakeup) { - /* reload the m3u8 */ - if (hls_ReloadPlaylist(s) != VLC_SUCCESS) + /* reload the m3u8 if there are less than 3 segments what aren't downloaded */ + if ( ( p_sys->download.segment - p_sys->playback.segment < 3 ) && + ( hls_ReloadPlaylist(s) != VLC_SUCCESS) ) { /* No change in playlist, then backoff */ p_sys->playlist.tries++; if (p_sys->playlist.tries == 1) wait = 0.5; else if (p_sys->playlist.tries == 2) wait = 1; - else if (p_sys->playlist.tries >= 3) wait = 2; + else if (p_sys->playlist.tries >= 3) wait = 1.5; /* Can we afford to backoff? */ if (p_sys->download.segment - p_sys->playback.segment < 3) @@ -1712,7 +1748,7 @@ static void* hls_Reload(void *p_this) else { p_sys->playlist.tries = 0; - wait = 0.5; + wait = 1.0; } hls_stream_t *hls = hls_Get(p_sys->hls_stream, p_sys->download.stream); @@ -1720,8 +1756,12 @@ static void* hls_Reload(void *p_this) /* 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; + /* If there is no new segments,use playlist duration as sleep period base */ + if( likely( hls->max_segment_length > 0 ) ) + p_sys->playlist.wakeup += (mtime_t)((hls->max_segment_length * wait) * CLOCK_FREQ); + else + p_sys->playlist.wakeup += (mtime_t)((hls->duration * wait) * CLOCK_FREQ); } mwait(p_sys->playlist.wakeup); @@ -1740,8 +1780,14 @@ static int Prefetch(stream_t *s, int *current) if (hls == NULL) return VLC_EGENERIC; - /* Download first 2 segments of this HLS stream */ - for (int i = 0; i < 2; i++) + if (vlc_array_count(hls->segments) == 0) + return VLC_EGENERIC; + else if (vlc_array_count(hls->segments) == 1 && p_sys->b_live) + msg_Warn(s, "Only 1 segment available to prefetch in live stream; may stall"); + + /* Download ~10s worth of segments of this HLS stream if they exist */ + unsigned segment_amount = (0.5f + 10/hls->duration); + for (int i = 0; i < __MIN(vlc_array_count(hls->segments), segment_amount); i++) { segment_t *segment = segment_GetSegment(hls, p_sys->download.segment); if (segment == NULL ) @@ -1779,15 +1825,15 @@ static int Prefetch(stream_t *s, int *current) ****************************************************************************/ static int hls_Download(stream_t *s, segment_t *segment) { + stream_sys_t *p_sys = s->p_sys; assert(segment); - /* Construct URL */ - char *psz_url = ConstructUrl(&segment->url); - if (psz_url == NULL) - return VLC_ENOMEM; + vlc_mutex_lock(&p_sys->lock); + while (p_sys->paused) + vlc_cond_wait(&p_sys->wait, &p_sys->lock); + vlc_mutex_unlock(&p_sys->lock); - stream_t *p_ts = stream_UrlNew(s, psz_url); - free(psz_url); + stream_t *p_ts = stream_UrlNew(s, segment->url); if (p_ts == NULL) return VLC_EGENERIC; @@ -1807,6 +1853,10 @@ static int hls_Download(stream_t *s, segment_t *segment) uint64_t size; do { + /* NOTE: Beware the size reported for a segment by the HLS server may not + * be correct, when downloading the segment data. Therefore check the size + * and enlarge the segment data block if necessary. + */ size = stream_Size(p_ts); if (size > segment->size) { @@ -1850,7 +1900,10 @@ static ssize_t read_M3U8_from_stream(stream_t *s, uint8_t **buffer) if (bytes == 0) break; /* EOF ? */ else if (bytes < 0) + { + free (p); return bytes; + } if ( (total_bytes + bytes + 1) > total_allocated ) { @@ -1877,17 +1930,12 @@ static ssize_t read_M3U8_from_stream(stream_t *s, uint8_t **buffer) return total_bytes; } -static ssize_t read_M3U8_from_url(stream_t *s, vlc_url_t *url, uint8_t **buffer) +static ssize_t read_M3U8_from_url(stream_t *s, const char* psz_url, uint8_t **buffer) { assert(*buffer == NULL); /* Construct URL */ - char *psz_url = ConstructUrl(url); - if (psz_url == NULL) - return VLC_ENOMEM; - stream_t *p_m3u8 = stream_UrlNew(s, psz_url); - free(psz_url); if (p_m3u8 == NULL) return VLC_EGENERIC; @@ -1908,21 +1956,27 @@ static char *ReadLine(uint8_t *buffer, uint8_t **pos, const size_t len) while (p < end) { - if ((*p == '\n') || (*p == '\0')) + if ((*p == '\r') || (*p == '\n') || (*p == '\0')) break; p++; } - /* copy line excluding \n or \0 */ + /* copy line excluding \r \n or \0 */ line = strndup((char *)begin, p - begin); - if (*p == '\0') - *pos = end; - else + while ((*p == '\r') || (*p == '\n') || (*p == '\0')) { - /* next pass start after \n */ - p++; - *pos = p; + if (*p == '\0') + { + *pos = end; + break; + } + else + { + /* next pass start after \r and \n */ + p++; + *pos = p; + } } return line; @@ -1955,8 +2009,17 @@ static int Open(vlc_object_t *p_this) free(p_sys); return VLC_ENOMEM; } - vlc_UrlParse(&p_sys->m3u8, psz_uri, 0); - free(psz_uri); + p_sys->m3u8 = psz_uri; + + char *new_path; + if (asprintf(&new_path, "%s.ts", s->psz_path) < 0) + { + free(p_sys->m3u8); + free(p_sys); + return VLC_ENOMEM; + } + free(s->psz_path); + s->psz_path = new_path; p_sys->bandwidth = 0; p_sys->b_live = true; @@ -1966,7 +2029,7 @@ static int Open(vlc_object_t *p_this) p_sys->hls_stream = vlc_array_new(); if (p_sys->hls_stream == NULL) { - vlc_UrlClean(&p_sys->m3u8); + free(p_sys->m3u8); free(p_sys); return VLC_ENOMEM; } @@ -1976,6 +2039,11 @@ static int Open(vlc_object_t *p_this) s->pf_peek = Peek; s->pf_control = Control; + p_sys->paused = false; + + vlc_cond_init(&p_sys->wait); + vlc_mutex_init(&p_sys->lock); + /* Parse HLS m3u8 content. */ uint8_t *buffer = NULL; ssize_t len = read_M3U8_from_stream(s->p_source, &buffer); @@ -1993,17 +2061,12 @@ static int Open(vlc_object_t *p_this) sizeof( hls_stream_t* ), &hls_CompareStreams ); /* Choose first HLS stream to start with */ - int current = p_sys->playback.stream = 0; + int current = p_sys->playback.stream = p_sys->hls_stream->i_count-1; 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"); - } - if (Prefetch(s, ¤t) != VLC_SUCCESS) { msg_Err(s, "fetching first segment failed."); @@ -2017,13 +2080,16 @@ static int Open(vlc_object_t *p_this) vlc_mutex_init(&p_sys->download.lock_wait); vlc_cond_init(&p_sys->download.wait); + vlc_mutex_init(&p_sys->read.lock_wait); + vlc_cond_init(&p_sys->read.wait); + /* Initialize HLS live stream */ if (p_sys->b_live) { hls_stream_t *hls = hls_Get(p_sys->hls_stream, current); p_sys->playlist.last = mdate(); p_sys->playlist.wakeup = p_sys->playlist.last + - ((mtime_t)hls->duration * UINT64_C(1000000)); + ((mtime_t)hls->duration * CLOCK_FREQ ); if (vlc_clone(&p_sys->reload, hls_Reload, s, VLC_THREAD_PRIORITY_LOW)) { @@ -2044,6 +2110,9 @@ fail_thread: vlc_mutex_destroy(&p_sys->download.lock_wait); vlc_cond_destroy(&p_sys->download.wait); + vlc_mutex_destroy(&p_sys->read.lock_wait); + vlc_cond_destroy(&p_sys->read.wait); + fail: /* Free hls streams */ for (int i = 0; i < vlc_array_count(p_sys->hls_stream); i++) @@ -2053,8 +2122,11 @@ fail: } vlc_array_destroy(p_sys->hls_stream); + vlc_mutex_destroy(&p_sys->lock); + vlc_cond_destroy(&p_sys->wait); + /* */ - vlc_UrlClean(&p_sys->m3u8); + free(p_sys->m3u8); free(p_sys); return VLC_EGENERIC; } @@ -2069,8 +2141,16 @@ static void Close(vlc_object_t *p_this) assert(p_sys->hls_stream); + vlc_mutex_lock(&p_sys->lock); + p_sys->paused = false; + vlc_cond_signal(&p_sys->wait); + vlc_mutex_unlock(&p_sys->lock); + /* */ vlc_mutex_lock(&p_sys->download.lock_wait); + /* negate the condition variable's predicate */ + p_sys->download.segment = p_sys->playback.segment = 0; + p_sys->download.seek = 0; /* better safe than sorry */ vlc_cond_signal(&p_sys->download.wait); vlc_mutex_unlock(&p_sys->download.lock_wait); @@ -2081,6 +2161,9 @@ static void Close(vlc_object_t *p_this) vlc_mutex_destroy(&p_sys->download.lock_wait); vlc_cond_destroy(&p_sys->download.wait); + vlc_mutex_destroy(&p_sys->read.lock_wait); + vlc_cond_destroy(&p_sys->read.wait); + /* Free hls streams */ for (int i = 0; i < vlc_array_count(p_sys->hls_stream); i++) { @@ -2090,7 +2173,11 @@ static void Close(vlc_object_t *p_this) vlc_array_destroy(p_sys->hls_stream); /* */ - vlc_UrlClean(&p_sys->m3u8); + + vlc_mutex_destroy(&p_sys->lock); + vlc_cond_destroy(&p_sys->wait); + + free(p_sys->m3u8); if (p_sys->peeked) block_Release (p_sys->peeked); free(p_sys); @@ -2226,18 +2313,18 @@ static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read) else segment_RestorePos(segment); - p_sys->playback.segment++; vlc_mutex_unlock(&segment->lock); /* signal download thread */ vlc_mutex_lock(&p_sys->download.lock_wait); + p_sys->playback.segment++; 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", + msg_Dbg(s, "playing segment %d from stream %d", segment->sequence, p_sys->playback.stream); ssize_t len = -1; @@ -2269,13 +2356,55 @@ static int Read(stream_t *s, void *buffer, unsigned int i_read) assert(p_sys->hls_stream); - if (p_sys->b_error) - return 0; + while (length == 0) + { + // In case an error occurred or the stream was closed return 0 + if (p_sys->b_error || !vlc_object_alive(s)) + return 0; - /* NOTE: buffer might be NULL if caller wants to skip data */ - length = hls_Read(s, (uint8_t*) buffer, i_read); - if (length < 0) - return 0; + // Lock the mutex before trying to read to avoid a race condition with the download thread + vlc_mutex_lock(&p_sys->read.lock_wait); + + /* NOTE: buffer might be NULL if caller wants to skip data */ + length = hls_Read(s, (uint8_t*) buffer, i_read); + + // An error has occurred in hls_Read + if (length < 0) + { + vlc_mutex_unlock(&p_sys->read.lock_wait); + + return 0; + } + + // There is no data available yet for the demuxer so we need to wait until reload and + // download operation are over. + // Download thread will signal once download is finished. + // A timed wait is used to avoid deadlock in case data never arrives since the thread + // running this read operation is also responsible for closing the stream + if (length == 0) + { + mtime_t start = mdate(); + + // Wait for 10 seconds + mtime_t timeout_limit = start + (10 * CLOCK_FREQ); + + int res = vlc_cond_timedwait(&p_sys->read.wait, &p_sys->read.lock_wait, timeout_limit); + + // Error - reached a timeout of 10 seconds without data arriving - kill the stream + if (res == ETIMEDOUT) + { + msg_Warn(s, "timeout limit reached!"); + + vlc_mutex_unlock(&p_sys->read.lock_wait); + + return 0; + } + else if (res == EINVAL) + return 0; // Error - lock is not locked so we can just return + } + + vlc_mutex_unlock(&p_sys->read.lock_wait); + } p_sys->playback.offset += length; return length; @@ -2300,7 +2429,7 @@ static int Peek(stream_t *s, const uint8_t **pp_peek, unsigned int i_peek) size_t i_buff = segment->data->i_buffer; uint8_t *p_buff = segment->data->p_buffer; - if (i_peek < i_buff) + if ( likely(i_peek < i_buff)) { *pp_peek = p_buff; vlc_mutex_unlock(&segment->lock); @@ -2321,7 +2450,11 @@ static int Peek(stream_t *s, const uint8_t **pp_peek, unsigned int i_peek) else if (peeked->i_buffer < i_peek) peeked = block_Realloc (peeked, 0, i_peek); if (peeked == NULL) + { + vlc_mutex_unlock(&segment->lock); return 0; + } + p_sys->peeked = peeked; memcpy(peeked->p_buffer, p_buff, i_buff); curlen = i_buff; @@ -2428,16 +2561,12 @@ 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) + segment_t *currentSegment = segment_GetSegment(hls, p_sys->playback.segment); + if (currentSegment == 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++) { @@ -2452,7 +2581,7 @@ static int segment_Seek(stream_t *s, const uint64_t pos) length += segment->duration * (hls->bandwidth/8); vlc_mutex_unlock(&segment->lock); - if (!b_found && (pos <= length)) + if (pos <= length) { if (count - n >= 3) { @@ -2476,7 +2605,13 @@ static int segment_Seek(stream_t *s, const uint64_t pos) /* */ if (b_found) { - /* restore segment to start position */ + + /* restore current segment to start position */ + vlc_mutex_lock(¤tSegment->lock); + segment_RestorePos(currentSegment); + vlc_mutex_unlock(¤tSegment->lock); + + /* restore seeked segment to start position */ segment_t *segment = segment_GetSegment(hls, p_sys->playback.segment); if (segment == NULL) { @@ -2495,14 +2630,12 @@ static int segment_Seek(stream_t *s, const uint64_t pos) 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))) + msg_Dbg(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))) { vlc_cond_wait(&p_sys->download.wait, &p_sys->download.lock_wait); if (!vlc_object_alive(s) || s->b_error) break; @@ -2525,9 +2658,26 @@ static int Control(stream_t *s, int i_query, va_list args) case STREAM_CAN_SEEK: *(va_arg (args, bool *)) = hls_MaySeek(s); break; + case STREAM_CAN_CONTROL_PACE: + case STREAM_CAN_PAUSE: + *(va_arg (args, bool *)) = true; + break; + case STREAM_CAN_FASTSEEK: + *(va_arg (args, bool *)) = false; + break; case STREAM_GET_POSITION: *(va_arg (args, uint64_t *)) = p_sys->playback.offset; break; + case STREAM_SET_PAUSE_STATE: + { + bool paused = va_arg (args, unsigned); + + vlc_mutex_lock(&p_sys->lock); + p_sys->paused = paused; + vlc_cond_signal(&p_sys->wait); + vlc_mutex_unlock(&p_sys->lock); + break; + } case STREAM_SET_POSITION: if (hls_MaySeek(s)) { @@ -2542,6 +2692,10 @@ static int Control(stream_t *s, int i_query, va_list args) case STREAM_GET_SIZE: *(va_arg (args, uint64_t *)) = GetStreamSize(s); break; + case STREAM_GET_PTS_DELAY: + *va_arg (args, int64_t *) = INT64_C(1000) * + var_InheritInteger(s, "network-caching"); + break; default: return VLC_EGENERIC; }