char *psz_key_path; /* url key path */
uint8_t aes_key[16]; /* AES-128 */
bool b_key_loaded;
+ uint8_t psz_AES_IV[AES_BLOCK_SIZE]; /* IV used when decypher the block */
+ bool b_iv_loaded;
vlc_mutex_t lock;
block_t *data; /* data */
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) */
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;
+ uint8_t psz_current_AES_IV[AES_BLOCK_SIZE]; /* IV used when decypher the block */
+ bool b_current_iv_loaded;
} hls_stream_t;
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;
};
/****************************************************************************
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 */
dst->b_cache = src->b_cache;
dst->psz_current_key_path = src->psz_current_key_path ?
strdup( src->psz_current_key_path ) : NULL;
+ memcpy(dst->psz_current_AES_IV, src->psz_current_AES_IV, AES_BLOCK_SIZE);
+ dst->b_current_iv_loaded = src->b_current_iv_loaded;
dst->url = strdup(src->url);
if (dst->url == NULL)
{
segment->psz_key_path = NULL;
if (hls->psz_current_key_path)
segment->psz_key_path = strdup(hls->psz_current_key_path);
+ segment->b_iv_loaded = hls->b_current_iv_loaded;
+ memcpy(segment->psz_AES_IV, hls->psz_current_AES_IV, AES_BLOCK_SIZE);
return segment;
}
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);
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;
}
if (strncasecmp(begin, attr, strlen(attr)) == 0
&& begin[strlen(attr)] == '=')
{
- /* <attr>=<value>[,]* */
+ /* <attr>="<value>"[,]* */
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 */
if (len <= 16) {
iv_hi = 0;
iv_lo = strtoull(string_hexa, &end, 16);
- if (end)
+ if (*end)
return VLC_EGENERIC;
} else {
- iv_lo = strtoull(&string_hexa[len-16], NULL, 16);
- if (end)
+ iv_lo = strtoull(&string_hexa[len-16], &end, 16);
+ if (*end)
return VLC_EGENERIC;
string_hexa[len-16] = '\0';
- iv_hi = strtoull(string_hexa, NULL, 16);
- if (end)
+ iv_hi = strtoull(string_hexa, &end, 16);
+ if (*end)
return VLC_EGENERIC;
}
static char *relative_URI(const char *psz_url, const char *psz_path)
{
char *ret = NULL;
+ const char *fmt;
assert(psz_url != NULL && psz_path != NULL);
if (unlikely(slash == NULL))
goto end;
*slash = '\0';
+ fmt = "%s%s";
} else {
int levels = 0;
while(len >= 3 && !strncmp(psz_path, "../", 3)) {
goto end;
*slash = '\0';
} while (levels--);
+ fmt = "%s/%s";
}
- if (asprintf(&ret, "%s/%s", new_url, psz_path) < 0)
+ if (asprintf(&ret, fmt, new_url, psz_path) < 0)
ret = NULL;
end:
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;
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->p_sys->m3u8, uri);
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)",
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");
* 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;
+ hls->b_current_iv_loaded = false;
}
else
{
* and MUST be prefixed with 0x or 0X.
*/
- if (string_to_IV(iv, hls->psz_AES_IV) == VLC_EGENERIC)
+ if (string_to_IV(iv, hls->psz_current_AES_IV) == VLC_EGENERIC)
{
msg_Err(s, "IV invalid");
err = VLC_EGENERIC;
}
else
- hls->b_iv_loaded = true;
+ hls->b_current_iv_loaded = true;
free(value);
}
}
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;
}
if (b_meta)
{
- msg_Info(s, "Meta playlist");
+ msg_Dbg(s, "Meta playlist");
/* 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)
{
if (*uri == '#')
{
- msg_Info(s, "Skipping invalid stream-inf: %s", uri);
+ msg_Warn(s, "Skipping invalid stream-inf: %s", uri);
free(uri);
}
else
if (len < 0)
{
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)
}
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)
return VLC_EGENERIC;
}
- if (hls->b_iv_loaded == false)
+ if (segment->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;
+ memset(segment->psz_AES_IV, 0, AES_BLOCK_SIZE);
+ segment->psz_AES_IV[15] = segment->sequence & 0xff;
+ segment->psz_AES_IV[14] = (segment->sequence >> 8)& 0xff;
+ segment->psz_AES_IV[13] = (segment->sequence >> 16)& 0xff;
+ segment->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));
+ i_gcrypt_err = gcry_cipher_setiv(aes_ctx, segment->psz_AES_IV,
+ sizeof(segment->psz_AES_IV));
if (i_gcrypt_err)
{
/* 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)
+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);
p->sequence, l->sequence+1);
}
vlc_array_append(hls_old->segments, p);
- msg_Info(s, "- segment %d appended", p->sequence);
+ 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;
}
}
{
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)
{
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;
}
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);
uint64_t bw = segment->size * 8 * 1000000 / __MAX(1, duration); /* bits / s */
/* 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;
}
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);
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++;
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);
/* 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);
****************************************************************************/
static int hls_Download(stream_t *s, segment_t *segment)
{
+ stream_sys_t *p_sys = s->p_sys;
assert(segment);
+ 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, segment->url);
if (p_ts == NULL)
return VLC_EGENERIC;
segment->size = stream_Size(p_ts);
- assert(segment->size > 0);
- segment->data = block_Alloc(segment->size);
- if (segment->data == NULL)
- {
+ if (segment->size == 0) {
+ int chunk_size = 65536;
+ segment->data = block_Alloc(chunk_size);
+ if (!segment->data)
+ goto nomem;
+ do {
+ if (segment->data->i_buffer - segment->size < chunk_size) {
+ chunk_size *= 2;
+ block_t *p_block = block_Realloc(segment->data, 0, segment->data->i_buffer + chunk_size);
+ if (!p_block) {
+ block_Release(segment->data);
+ segment->data = NULL;
+ goto nomem;
+ }
+ segment->data = p_block;
+ }
+
+ ssize_t length = stream_Read(p_ts, segment->data->p_buffer + segment->size, chunk_size);
+ if (length <= 0) {
+ segment->data->i_buffer = segment->size;
+ break;
+ }
+ segment->size += length;
+ } while (vlc_object_alive(s));
+
stream_Delete(p_ts);
- return VLC_ENOMEM;
+ return VLC_SUCCESS;
}
+ segment->data = block_Alloc(segment->size);
+ if (segment->data == NULL)
+ goto nomem;
+
assert(segment->data->i_buffer == segment->size);
- ssize_t length = 0, curlen = 0;
- uint64_t size;
+ ssize_t curlen = 0;
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);
+ uint64_t size = stream_Size(p_ts);
if (size > segment->size)
{
msg_Dbg(s, "size changed %"PRIu64, segment->size);
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;
+ goto nomem;
}
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);
+ ssize_t length = stream_Read(p_ts, segment->data->p_buffer + curlen, segment->size - curlen);
if (length <= 0)
break;
curlen += length;
stream_Delete(p_ts);
return VLC_SUCCESS;
+
+nomem:
+ stream_Delete(p_ts);
+ return VLC_ENOMEM;
}
/* Read M3U8 file */
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);
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))
{
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++)
}
vlc_array_destroy(p_sys->hls_stream);
+ vlc_mutex_destroy(&p_sys->lock);
+ vlc_cond_destroy(&p_sys->wait);
+
/* */
free(p_sys->m3u8);
free(p_sys);
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 */
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++)
{
vlc_array_destroy(p_sys->hls_stream);
/* */
+
+ 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);
}
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;
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;
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);
vlc_cond_signal(&p_sys->download.wait);
/* Wait for download to be finished */
- msg_Info(s, "seek to segment %d", p_sys->playback.segment);
+ 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)))
*(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:
- case STREAM_CAN_PAUSE: /* TODO */
*(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))
{
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;
}