X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fstream_filter%2Fhttplive.c;h=1e3973306690ea81677d2fe3e6d27878fbcbd45b;hb=90ea97f7971bb4737856d890da2fbad54106cb58;hp=23d5b6b6bc6eda3f645e3e4097024d224042e578;hpb=f372e0fc69074921c1f5b02893770fbee8464ef6;p=vlc diff --git a/modules/stream_filter/httplive.c b/modules/stream_filter/httplive.c index 23d5b6b6bc..1e39733066 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; @@ -89,7 +88,7 @@ typedef struct hls_stream_s 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 +99,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 */ @@ -152,7 +151,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 +162,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; } @@ -224,7 +233,12 @@ static hls_stream_t *hls_New(vlc_array_t *hls_stream, const int id, const uint64 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 +259,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 +281,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 +327,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 +370,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 +390,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); @@ -466,7 +484,8 @@ 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, ','); @@ -484,156 +503,59 @@ 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; - } - - return VLC_SUCCESS; -} - -static char *relative_URI(stream_t *s, const char *uri, const vlc_url_t *url) -{ - stream_sys_t *p_sys = s->p_sys; - char *psz_password = NULL; - char *psz_username = NULL; - char *psz_protocol = NULL; - char *psz_path = NULL; - char *psz_host = NULL; - char *psz_uri = NULL; - int i_port = -1; - - char *p = strchr(uri, ':'); - if (p != NULL) - return NULL; + string_hexa++; - /* 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; + 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; } - /* 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; + for (int i = 7; i >= 0 ; --i) { + iv[ i] = iv_hi & 0xff; + iv[8+i] = iv_lo & 0xff; + iv_hi >>= 8; + iv_lo >>= 8; } -fail: - free(psz_password); - free(psz_username); - free(psz_protocol); - free(psz_path); - free(psz_host); - return psz_uri; + return VLC_SUCCESS; } -static char *ConstructUrl(vlc_url_t *url) +static char *relative_URI(const char *psz_url, const char *psz_path) { - if ((url->psz_protocol == NULL) || - (url->psz_path == NULL)) + assert(psz_url != NULL && psz_path != NULL); + //If the path is actually an absolute URL, don't do anything. + if (strncmp(psz_path, "http", 4) == 0) return NULL; - if (url->i_port <= 0) - { - if (strncmp(url->psz_protocol, "https", 5) == 0) - url->i_port = 443; - else - url->i_port = 80; - } - - 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; - } - - return psz_url; + char *path_end = strrchr(psz_url, '/'); + if (path_end == NULL) + return NULL; + unsigned int url_length = path_end - psz_url + 1; + char *psz_res = malloc(url_length + strlen(psz_path) + 1); + strncpy(psz_res, psz_url, url_length); + psz_res[url_length] = 0; + strcat(psz_res, psz_path); + return psz_res; } static int parse_SegmentInformation(hls_stream_t *hls, char *p_read, int *duration) @@ -653,20 +575,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 +600,14 @@ static int parse_SegmentInformation(hls_stream_t *hls, char *p_read, int *durati value = ((int)d) + 1; else value = ((int)d); + *duration = value; } /* 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 +615,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 +653,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) @@ -752,7 +678,7 @@ static int parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream, msg_Info(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); @@ -849,7 +775,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 +806,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); @@ -1025,6 +960,8 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const /* M3U8 Meta Index file */ do { + bool failed_to_download_stream_m3u8 = false; + /* Next line */ line = ReadLine(p_begin, &p_read, p_end - p_begin); if (line == NULL) @@ -1047,15 +984,29 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const } else { + bool new_stream_added = false; hls_stream_t *hls = NULL; err = parse_StreamInformation(s, &streams, &hls, line, uri); + if (err == VLC_SUCCESS) + new_stream_added = true; + free(uri); /* Download playlist file from server */ uint8_t *buf = NULL; - ssize_t len = read_M3U8_from_url(s, &hls->url, &buf); + ssize_t len = read_M3U8_from_url(s, hls->url, &buf); if (len < 0) - err = VLC_EGENERIC; + { + msg_Warn(s, "failed to read %s, continue for other streams", hls->url); + failed_to_download_stream_m3u8 = true; + + /* 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. */ @@ -1082,6 +1033,13 @@ 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 { @@ -1093,11 +1051,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 +1075,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 +1090,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 +1113,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 +1134,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 +1141,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 +1173,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 +1212,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 +1296,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 +1309,55 @@ 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) { int count = vlc_array_count(hls_new->segments); msg_Info(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); for (int n = 0; n < count; n++) { segment_t *p = segment_GetSegment(hls_new, n); if (p == NULL) return VLC_EGENERIC; - vlc_mutex_lock(&(*hls)->lock); - segment_t *segment = segment_Find(*hls, p->sequence); + 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,39 +1365,35 @@ 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); + vlc_array_append(hls_old->segments, p); msg_Info(s, "- segment %d appended", p->sequence); } - 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) @@ -1483,7 +1436,7 @@ static int hls_ReloadPlaylist(stream_t *s) msg_Info(s, "new HLS stream appended (id=%d, bandwidth=%"PRIu64")", hls_new->id, hls_new->bandwidth); } - else if (hls_UpdatePlaylist(s, hls_new, &hls_old) != VLC_SUCCESS) + else if (hls_UpdatePlaylist(s, hls_new, hls_old) != VLC_SUCCESS) msg_Info(s, "failed updating HLS stream (id=%d, bandwidth=%"PRIu64")", hls_new->id, hls_new->bandwidth); } @@ -1546,7 +1499,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 +1507,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 */ @@ -1578,12 +1531,7 @@ static int hls_DownloadSegmentData(stream_t *s, hls_stream_t *hls, segment_t *se msg_Info(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)) { @@ -1740,8 +1688,13 @@ 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 first 2 segments of this HLS stream if they exist */ + for (int i = 0; i < __MIN(vlc_array_count(hls->segments), 2); i++) { segment_t *segment = segment_GetSegment(hls, p_sys->download.segment); if (segment == NULL ) @@ -1781,13 +1734,7 @@ static int hls_Download(stream_t *s, segment_t *segment) { assert(segment); - /* Construct URL */ - char *psz_url = ConstructUrl(&segment->url); - if (psz_url == NULL) - return VLC_ENOMEM; - - 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 +1754,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) { @@ -1877,17 +1828,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 +1854,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 +1907,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 +1927,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; } @@ -1999,11 +1960,6 @@ static int Open(vlc_object_t *p_this) /* 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."); @@ -2054,7 +2010,7 @@ fail: vlc_array_destroy(p_sys->hls_stream); /* */ - vlc_UrlClean(&p_sys->m3u8); + free(p_sys->m3u8); free(p_sys); return VLC_EGENERIC; } @@ -2071,6 +2027,9 @@ static void Close(vlc_object_t *p_this) /* */ 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); @@ -2090,7 +2049,7 @@ static void Close(vlc_object_t *p_this) vlc_array_destroy(p_sys->hls_stream); /* */ - vlc_UrlClean(&p_sys->m3u8); + free(p_sys->m3u8); if (p_sys->peeked) block_Release (p_sys->peeked); free(p_sys); @@ -2226,11 +2185,11 @@ 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; @@ -2429,16 +2388,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++) { @@ -2453,7 +2408,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) { @@ -2477,7 +2432,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) { @@ -2496,14 +2457,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))) + 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;