#include <vlc_plugin.h>
#include <assert.h>
+#include <gcrypt.h>
#include <vlc_threads.h>
#include <vlc_arrays.h>
#include <vlc_stream.h>
#include <vlc_url.h>
#include <vlc_memory.h>
+#include <vlc_gcrypt.h>
/*****************************************************************************
* Module descriptor
/*****************************************************************************
*
*****************************************************************************/
+#define AES_BLOCK_SIZE 16 /* Only support AES-128 */
typedef struct segment_s
{
int sequence; /* unique sequence number */
uint64_t bandwidth; /* bandwidth usage of segments (bits per second)*/
vlc_url_t url;
+ char *psz_key_path; /* url key path */
+ uint8_t psz_AES_key[16]; /* AES-128 */
+ bool b_key_loaded;
+
vlc_mutex_t lock;
block_t *data; /* data */
} segment_t;
int id; /* program id */
int version; /* protocol version should be 1 */
int sequence; /* media sequence number */
- int duration; /* maximum duration per segment (ms) */
+ int duration; /* maximum duration per segment (s) */
uint64_t bandwidth; /* bandwidth usage of segments (bits per second)*/
- uint64_t size; /* stream length (segment->duration * hls->bandwidth/8) */
+ 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 */
vlc_mutex_t lock;
bool b_cache; /* allow caching */
+
+ char *psz_current_key_path; /* URL path of the encrypted key */
+ uint8_t psz_AES_IV[AES_BLOCK_SIZE]; /* IV used when decypher the block */
+ bool b_iv_loaded;
} hls_stream_t;
struct stream_sys_t
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 */
};
/****************************************************************************
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);
+
/****************************************************************************
*
****************************************************************************/
hls->version = 1; /* default protocol version */
hls->b_cache = true;
vlc_UrlParse(&hls->url, uri, 0);
+ hls->psz_current_key_path = NULL;
hls->segments = vlc_array_new();
vlc_array_append(hls_stream, hls);
vlc_mutex_init(&hls->lock);
}
vlc_array_destroy(hls->segments);
}
-
vlc_UrlClean(&hls->url);
+ free(hls->psz_current_key_path);
free(hls);
hls = NULL;
}
+static hls_stream_t *hls_Copy(hls_stream_t *src, const bool b_cp_segments)
+{
+ assert(src);
+ assert(!b_cp_segments); /* FIXME: copying segments is not implemented */
+
+ hls_stream_t *dst = (hls_stream_t *)malloc(sizeof(hls_stream_t));
+ if (dst == NULL) return NULL;
+
+ dst->id = src->id;
+ dst->bandwidth = src->bandwidth;
+ dst->duration = src->duration;
+ dst->size = src->size;
+ dst->sequence = src->sequence;
+ dst->version = src->version;
+ 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)
+ {
+ free(dst);
+ return NULL;
+ }
+ vlc_UrlParse(&dst->url, uri, 0);
+ if (!b_cp_segments)
+ dst->segments = vlc_array_new();
+ vlc_mutex_init(&dst->lock);
+ return dst;
+}
+
static hls_stream_t *hls_Get(vlc_array_t *hls_stream, const int wanted)
{
int count = vlc_array_count(hls_stream);
* then the deviation from exact byte size will be big and the seek/
* progressbar will not behave entirely as one expects. */
uint64_t size = 0UL;
+
+ /* If there is no valid bandwidth yet, then there is no point in
+ * computing stream size. */
+ if (hls->bandwidth == 0)
+ return size;
+
int count = vlc_array_count(hls->segments);
for (int n = 0; n < count; n++)
{
segment->data = NULL;
vlc_array_append(hls->segments, segment);
vlc_mutex_init(&segment->lock);
+ segment->b_key_loaded = false;
+ segment->psz_key_path = NULL;
+ if (hls->psz_current_key_path)
+ segment->psz_key_path = strdup(hls->psz_current_key_path);
return segment;
}
vlc_mutex_destroy(&segment->lock);
vlc_UrlClean(&segment->url);
+ free(segment->psz_key_path);
if (segment->data)
block_Release(segment->data);
free(segment);
return NULL;
}
-static char *relative_URI(stream_t *s, const char *uri, const char *path)
+static int hex2int(char c)
+{
+ 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')
+ return VLC_EGENERIC;
+ if (*p++ != '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;
- if (p_sys->m3u8.psz_path == 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;
- char *psz_path = strdup(p_sys->m3u8.psz_path);
- if (psz_path == NULL) return NULL;
p = strrchr(psz_path, '/');
if (p) *p = '\0';
- char *psz_uri = NULL;
- if (p_sys->m3u8.psz_password || p_sys->m3u8.psz_username)
- {
- if (asprintf(&psz_uri, "%s://%s:%s@%s:%d%s/%s", p_sys->m3u8.psz_protocol,
- p_sys->m3u8.psz_username, p_sys->m3u8.psz_password,
- p_sys->m3u8.psz_host, p_sys->m3u8.i_port,
- path ? path : psz_path, uri) < 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", p_sys->m3u8.psz_protocol,
- p_sys->m3u8.psz_host, p_sys->m3u8.i_port,
- path ? path : psz_path, uri) < 0)
+ if (asprintf(&psz_uri, "%s://%s:%d%s/%s",
+ psz_protocol, psz_host, i_port,
+ psz_path, uri) < 0)
goto fail;
}
- free(psz_path);
- return psz_uri;
fail:
+ free(psz_password);
+ free(psz_username);
+ free(psz_protocol);
free(psz_path);
- return NULL;
+ free(psz_host);
+ return psz_uri;
}
static char *ConstructUrl(vlc_url_t *url)
assert(uri);
/* Store segment information */
- char *psz_path = NULL;
- if (hls->url.psz_path != NULL)
- {
- psz_path = strdup(hls->url.psz_path);
- if (psz_path == NULL)
- return VLC_ENOMEM;
- char *p = strrchr(psz_path, '/');
- if (p) *p = '\0';
- }
- char *psz_uri = relative_URI(s, uri, psz_path);
- free(psz_path);
-
vlc_mutex_lock(&hls->lock);
+
+ char *psz_uri = relative_URI(s, uri, &hls->url);
+
segment_t *segment = segment_New(hls, duration, psz_uri ? psz_uri : uri);
if (segment)
segment->sequence = hls->sequence + vlc_array_count(hls->segments) - 1;
+ free(psz_uri);
+
vlc_mutex_unlock(&hls->lock);
- free(psz_uri);
return segment ? VLC_SUCCESS : VLC_ENOMEM;
}
}
if (hls->sequence > 0)
- msg_Err(s, "EXT-X-MEDIA-SEQUENCE already present in playlist (new=%d, old=%d)",
- sequence, hls->sequence);
-
+ {
+ 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))
+ msg_Err(s, "EXT-X-MEDIA-SEQUENCE gap in playlist (new=%d, old=%d)",
+ sequence, last->sequence);
+ }
+ else
+ msg_Err(s, "EXT-X-MEDIA-SEQUENCE already present in playlist (new=%d, old=%d)",
+ sequence, hls->sequence);
+ }
hls->sequence = sequence;
return VLC_SUCCESS;
}
if (strncasecmp(attr, "NONE", 4) == 0)
{
-
char *uri = parse_Attributes(p_read, "URI");
if (uri != NULL)
{
free(iv);
}
}
+ else if (strncasecmp(attr, "AES-128", 7) == 0)
+ {
+ char *value, *uri, *iv;
+ if (s->p_sys->b_aesmsg == false)
+ {
+ msg_Info(s, "playback of AES-128 encrypted HTTP Live media detected.");
+ s->p_sys->b_aesmsg = true;
+ }
+ value = uri = parse_Attributes(p_read, "URI");
+ if (value == NULL)
+ {
+ msg_Err(s, "#EXT-X-KEY: URI not found for encrypted HTTP Live media in AES-128");
+ free(attr);
+ return VLC_EGENERIC;
+ }
+
+ /* Url is put between quotes, remove them */
+ if (*value == '"')
+ {
+ /* We need to strip the "" from the attribute value */
+ uri = value + 1;
+ char* end = strchr(uri, '"');
+ if (end != NULL)
+ *end = 0;
+ }
+ hls->psz_current_key_path = strdup(uri);
+ free(value);
+
+ value = iv = parse_Attributes(p_read, "IV");
+ if (iv == NULL)
+ {
+ /*
+ * If the EXT-X-KEY tag does not have the IV attribute, implementations
+ * MUST use the sequence number of the media file as the IV when
+ * encrypting or decrypting that media file. The big-endian binary
+ * representation of the sequence number SHALL be placed in a 16-octet
+ * buffer and padded (on the left) with zeros.
+ */
+ hls->b_iv_loaded = false;
+ }
+ else
+ {
+ /*
+ * If the EXT-X-KEY tag has the IV attribute, implementations MUST use
+ * the attribute value as the IV when encrypting or decrypting with that
+ * key. The value MUST be interpreted as a 128-bit hexadecimal number
+ * and MUST be prefixed with 0x or 0X.
+ */
+
+ if (string_to_IV(iv, hls->psz_AES_IV) == VLC_EGENERIC)
+ err = VLC_EGENERIC;
+ else
+ hls->b_iv_loaded = true;
+ free(value);
+ }
+ }
else
{
msg_Warn(s, "playback of encrypted HTTP Live media is not supported.");
/* Check version */
hls->version = version;
- if (hls->version != 1)
+ if (hls->version <= 0 || hls->version > 3)
{
- msg_Err(s, "#EXT-X-VERSION should be version 1 iso %d", version);
+ msg_Err(s, "#EXT-X-VERSION should be version 1, 2 or 3 iso %d", version);
return VLC_EGENERIC;
}
return VLC_SUCCESS;
err = VLC_ENOMEM;
else
{
- hls_stream_t *hls = NULL;
- err = parse_StreamInformation(s, &streams, &hls, line, uri);
- free(uri);
-
- /* 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
+ if (*uri == '#')
{
- /* Parse HLS m3u8 content. */
- err = parse_M3U8(s, streams, buf, len);
- free(buf);
+ msg_Info(s, "Skipping invalid stream-inf: %s", uri);
+ free(uri);
}
-
- if (hls)
+ else
{
- hls->version = version;
- if (!p_sys->b_live)
- hls->size = hls_GetStreamSize(hls); /* Stream size (approximate) */
+ hls_stream_t *hls = NULL;
+ err = parse_StreamInformation(s, &streams, &hls, line, uri);
+ free(uri);
+
+ /* 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);
+ }
+
+ if (hls)
+ {
+ hls->version = version;
+ if (!p_sys->b_live)
+ hls->size = hls_GetStreamSize(hls); /* Stream size (approximate) */
+ }
}
}
p_begin = p_read;
else
{
/* No Meta playlist used */
- hls = hls_New(streams, 0, -1, NULL);
+ hls = hls_New(streams, 0, 0, NULL);
if (hls)
{
/* Get TARGET-DURATION first */
return err;
}
+
+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)
+ {
+ msg_Err(s, "Failed to load the AES key for segment sequence %d", seg->sequence);
+ return VLC_EGENERIC;
+ }
+
+ len = stream_Read(p_m3u8, aeskey, sizeof(aeskey));
+ if (len != AES_BLOCK_SIZE)
+ {
+ msg_Err(s, "The AES key loaded doesn't have the right size (%d)", len);
+ stream_Delete(p_m3u8);
+ return VLC_EGENERIC;
+ }
+
+ memcpy(seg->psz_AES_key, aeskey, AES_BLOCK_SIZE);
+
+ stream_Delete(p_m3u8);
+
+ return VLC_SUCCESS;
+}
+
+static int hls_ManageSegmentKeys(stream_t *s, hls_stream_t *hls)
+{
+ segment_t *seg = NULL;
+ segment_t *prev_seg;
+ int count = vlc_array_count(hls->segments);
+
+ for (int i = 0; i < count; i++)
+ {
+ prev_seg = seg;
+ seg = segment_GetSegment(hls, i);
+ if (seg->psz_key_path == NULL)
+ continue; /* No key to load ? continue */
+ if (seg->b_key_loaded)
+ continue; /* The key is already loaded */
+
+ /* if the key has not changed, and already available from previous segment,
+ * 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);
+ seg->b_key_loaded = true;
+ continue;
+ }
+ if (hls_DownloadSegmentKey(s, seg) != VLC_SUCCESS)
+ return VLC_EGENERIC;
+ seg->b_key_loaded = true;
+ }
+ return VLC_SUCCESS;
+}
+
+static int hls_DecodeSegmentData(stream_t *s, hls_stream_t *hls, segment_t *segment)
+{
+ /* Did the segment need to be decoded ? */
+ if (segment->psz_key_path == NULL)
+ return VLC_SUCCESS;
+
+ /* Do we have loaded the key ? */
+ if (!segment->b_key_loaded)
+ {
+ /* No ? try to download it now */
+ if (hls_ManageSegmentKeys(s, hls) != VLC_SUCCESS)
+ return VLC_EGENERIC;
+ }
+
+ /* For now, we only decode AES-128 data */
+ gcry_error_t i_gcrypt_err;
+ gcry_cipher_hd_t aes_ctx;
+ /* Setup AES */
+ i_gcrypt_err = gcry_cipher_open(&aes_ctx, GCRY_CIPHER_AES,
+ GCRY_CIPHER_MODE_CBC, 0);
+ if (i_gcrypt_err)
+ {
+ msg_Err(s, "gcry_cipher_open failed: %s", gpg_strerror(i_gcrypt_err));
+ gcry_cipher_close(aes_ctx);
+ return VLC_EGENERIC;
+ }
+
+ /* Set key */
+ i_gcrypt_err = gcry_cipher_setkey(aes_ctx, segment->psz_AES_key,
+ sizeof(segment->psz_AES_key));
+ if (i_gcrypt_err)
+ {
+ msg_Err(s, "gcry_cipher_setkey failed: %s", gpg_strerror(i_gcrypt_err));
+ gcry_cipher_close(aes_ctx);
+ return VLC_EGENERIC;
+ }
+
+ if (hls->b_iv_loaded == false)
+ {
+ memset(hls->psz_AES_IV, 0, AES_BLOCK_SIZE);
+ hls->psz_AES_IV[15] = segment->sequence & 0xff;
+ hls->psz_AES_IV[14] = (segment->sequence >> 8)& 0xff;
+ hls->psz_AES_IV[13] = (segment->sequence >> 16)& 0xff;
+ hls->psz_AES_IV[12] = (segment->sequence >> 24)& 0xff;
+ }
+
+ i_gcrypt_err = gcry_cipher_setiv(aes_ctx, hls->psz_AES_IV,
+ sizeof(hls->psz_AES_IV));
+
+ if (i_gcrypt_err)
+ {
+ msg_Err(s, "gcry_cipher_setiv failed: %s", gpg_strerror(i_gcrypt_err));
+ gcry_cipher_close(aes_ctx);
+ return VLC_EGENERIC;
+ }
+
+ i_gcrypt_err = gcry_cipher_decrypt(aes_ctx,
+ segment->data->p_buffer, /* out */
+ segment->data->i_buffer,
+ NULL, /* in */
+ 0);
+ if (i_gcrypt_err)
+ {
+ msg_Err(s, "gcry_cipher_decrypt failed: %s/%s\n", gcry_strsource(i_gcrypt_err), gcry_strerror(i_gcrypt_err));
+ gcry_cipher_close(aes_ctx);
+ return VLC_EGENERIC;
+ }
+ gcry_cipher_close(aes_ctx);
+ /* remove the PKCS#7 padding from the buffer */
+ int pad = segment->data->p_buffer[segment->data->i_buffer-1];
+ if (pad <= 0 || pad > AES_BLOCK_SIZE)
+ {
+ msg_Err(s, "Bad padding character (0x%x), perhaps we failed to decrypt the segment with the correct key", pad);
+ return VLC_EGENERIC;
+ }
+ int count = pad;
+ while (count--)
+ {
+ if (segment->data->p_buffer[segment->data->i_buffer-1-count] != pad)
+ {
+ msg_Err(s, "Bad ending buffer, perhaps we failed to decrypt the segment with the correct key");
+ return VLC_EGENERIC;
+ }
+ }
+
+ /* not all the data is readable because of padding */
+ segment->data->i_buffer -= pad;
+
+ return VLC_SUCCESS;
+}
+
static int get_HTTPLiveMetaPlaylist(stream_t *s, vlc_array_t **streams)
{
stream_sys_t *p_sys = s->p_sys;
assert(*streams);
+ int err = VLC_EGENERIC;
- /* Download new playlist file from server */
- uint8_t *buffer = NULL;
- ssize_t len = read_M3U8_from_url(s, &p_sys->m3u8, &buffer);
- if (len < 0)
- return VLC_EGENERIC;
+ /* Duplicate HLS stream META information */
+ for (int i = 0; i < vlc_array_count(p_sys->hls_stream); i++)
+ {
+ hls_stream_t *src, *dst;
+ src = (hls_stream_t *)vlc_array_item_at_index(p_sys->hls_stream, i);
+ if (src == NULL)
+ return VLC_EGENERIC;
- /* Parse HLS m3u8 content. */
- int err = parse_M3U8(s, *streams, buffer, len);
- free(buffer);
+ dst = hls_Copy(src, false);
+ if (dst == NULL)
+ return VLC_ENOMEM;
+
+ vlc_array_append(*streams, dst);
+ }
+ /* Download new playlist file from server */
+ for (int i = 0; i < vlc_array_count(*streams); i++)
+ {
+ hls_stream_t *hls;
+ hls = (hls_stream_t *)vlc_array_item_at_index(*streams, i);
+ if (hls == NULL)
+ return VLC_EGENERIC;
+
+ /* 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);
+ }
+ }
return err;
}
msg_Info(s, "updating hls stream (program-id=%d, bandwidth=%"PRIu64") has %d segments",
hls_new->id, hls_new->bandwidth, count);
+
for (int n = 0; n < count; n++)
{
segment_t *p = segment_GetSegment(hls_new, n);
segment_t *segment = segment_Find(*hls, p->sequence);
if (segment)
{
+ vlc_mutex_lock(&segment->lock);
+
assert(p->url.psz_path);
assert(segment->url.psz_path);
{
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)
+ {
+ block_Release(segment->data);
+ segment->data = NULL;
+ }
+ free(segment->psz_key_path);
+ segment->psz_key_path = p->psz_key_path ? strdup(p->psz_key_path) : NULL;
+ vlc_mutex_unlock(&segment->lock);
segment_Free(p);
free(psz_url);
}
}
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);
return VLC_SUCCESS;
fail_and_unlock:
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) &&
- (s->p_sys->download.stream == n))
- {
- /* wake up download thread */
- msg_Err(s, "WAKING UP DOWNLOAD THREAD");
- vlc_mutex_lock(&s->p_sys->download.lock_wait);
- vlc_cond_signal(&s->p_sys->download.wait);
- vlc_mutex_unlock(&s->p_sys->download.lock_wait);
- }
- else
- msg_Info(s, "failed updating HLS stream (id=%d, bandwidth=%"PRIu64")",
- hls_new->id, hls_new->bandwidth);
- }
+ 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);
}
-
vlc_array_destroy(hls_streams);
return VLC_SUCCESS;
}
return candidate;
}
-static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur_stream)
+static int hls_DownloadSegmentData(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur_stream)
{
stream_sys_t *p_sys = s->p_sys;
return VLC_EGENERIC;
}
mtime_t duration = mdate() - start;
+ if (hls->bandwidth == 0)
+ {
+ /* Try to estimate the bandwidth for this stream */
+ hls->bandwidth = (uint64_t)((double)segment->size / ((double)duration / 1000000.0));
+ }
+
+ /* If the segment is encrypted, decode it */
+ if (hls_DecodeSegmentData(s, hls, segment) != VLC_SUCCESS)
+ {
+ vlc_mutex_unlock(&segment->lock);
+ return VLC_EGENERIC;
+ }
vlc_mutex_unlock(&segment->lock);
vlc_mutex_unlock(&hls->lock);
if ((segment != NULL) &&
- (Download(s, hls, segment, &p_sys->download.stream) != VLC_SUCCESS))
+ (hls_DownloadSegmentData(s, hls, segment, &p_sys->download.stream) != VLC_SUCCESS))
{
if (!vlc_object_alive(s)) break;
if (segment == NULL )
return VLC_EGENERIC;
- if (Download(s, hls, segment, current) != VLC_SUCCESS)
+ if (hls_DownloadSegmentData(s, hls, segment, current) != VLC_SUCCESS)
return VLC_EGENERIC;
/* Found better bandwidth match, try again */
continue;
}
- if (Download(s, hls, segment, current) != VLC_SUCCESS)
+ if (hls_DownloadSegmentData(s, hls, segment, current) != VLC_SUCCESS)
return VLC_EGENERIC;
p_sys->download.segment++;
if (size > segment->size)
{
msg_Dbg(s, "size changed %"PRIu64, segment->size);
- segment->data = block_Realloc(segment->data, 0, size);
- if (segment->data == NULL)
+ block_t *p_block = block_Realloc(segment->data, 0, size);
+ if (p_block == NULL)
{
stream_Delete(p_ts);
+ block_Release(segment->data);
+ segment->data = NULL;
return VLC_ENOMEM;
}
+ segment->data = p_block;
segment->size = size;
assert(segment->data->i_buffer == segment->size);
+ p_block = NULL;
}
length = stream_Read(p_ts, segment->data->p_buffer + curlen, segment->size - curlen);
if (length <= 0)
msg_Info(p_this, "HTTP Live Streaming (%s)", s->psz_path);
+ /* Initialize crypto bit */
+ vlc_gcrypt_init();
+
/* */
s->p_sys = p_sys = calloc(1, sizeof(*p_sys));
if (p_sys == NULL)
vlc_UrlParse(&p_sys->m3u8, psz_uri, 0);
free(psz_uri);
- p_sys->bandwidth = -1;
+ p_sys->bandwidth = 0;
p_sys->b_live = true;
p_sys->b_meta = false;
p_sys->b_error = false;
int current = p_sys->playback.stream = 0;
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");
return segment;
}
+static int segment_RestorePos( segment_t *segment )
+{
+ if( segment->data )
+ {
+ uint64_t size = segment->size -segment->data->i_buffer;
+ if( size > 0 )
+ {
+ segment->data->i_buffer += size;
+ segment->data->p_buffer -= size;
+ }
+ }
+ return VLC_SUCCESS;
+}
+
static ssize_t hls_Read(stream_t *s, uint8_t *p_read, unsigned int i_read)
{
stream_sys_t *p_sys = s->p_sys;
segment->data = NULL;
}
else
- { /* reset playback pointer to start of buffer */
- uint64_t size = segment->size - segment->data->i_buffer;
- if (size > 0)
- {
- segment->data->i_buffer += size;
- segment->data->p_buffer -= size;
- }
- }
+ segment_RestorePos( segment );
+
p_sys->playback.segment++;
vlc_mutex_unlock(&segment->lock);
if (hls == NULL) return 0;
vlc_mutex_lock(&hls->lock);
+ if (hls->size == 0)
+ hls->size = hls_GetStreamSize(hls);
uint64_t size = hls->size;
vlc_mutex_unlock(&hls->lock);
return size;
}
+
static int segment_Seek(stream_t *s, const uint64_t pos)
{
stream_sys_t *p_sys = s->p_sys;
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)
+ {
+ 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++)
{
segment_t *segment = vlc_array_item_at_index(hls->segments, n);
vlc_mutex_unlock(&hls->lock);
return VLC_EGENERIC;
}
-
vlc_mutex_lock(&segment->lock);
- if (segment->data)
- {
- uint64_t size = segment->size -segment->data->i_buffer;
- if (size > 0)
- {
- segment->data->i_buffer += size;
- segment->data->p_buffer -= size;
- }
- }
+ segment_RestorePos( segment );
vlc_mutex_unlock(&segment->lock);
/* start download at current playback segment */
switch (i_query)
{
case STREAM_CAN_SEEK:
- case STREAM_CAN_FASTSEEK:
*(va_arg (args, bool *)) = hls_MaySeek(s);
break;
case STREAM_GET_POSITION: