int64_t first_timestamp;
int64_t cur_timestamp;
AVIOInterruptCB *interrupt_callback;
- char *referer; ///< holds HTTP referer set as an AVOption to the HTTP protocol context
- char *user_agent; ///< holds HTTP user agent set as an AVOption to the HTTP protocol context
- char *cookies; ///< holds HTTP cookie values set in either the initial response or as an AVOption to the HTTP protocol context
- char *headers; ///< holds HTTP headers set as an AVOption to the HTTP protocol context
- char *http_proxy; ///< holds the address of the HTTP proxy server
AVDictionary *avio_opts;
int strict_std_compliance;
char *allowed_extensions;
AVIOContext *playlist_pb;
} HLSContext;
-static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
+static void free_segment_dynarray(struct segment **segments, int n_segments)
{
- int len = ff_get_line(s, buf, maxlen);
- while (len > 0 && av_isspace(buf[len - 1]))
- buf[--len] = '\0';
- return len;
+ int i;
+ for (i = 0; i < n_segments; i++) {
+ av_freep(&segments[i]->key);
+ av_freep(&segments[i]->url);
+ av_freep(&segments[i]);
+ }
}
static void free_segment_list(struct playlist *pls)
{
- int i;
- for (i = 0; i < pls->n_segments; i++) {
- av_freep(&pls->segments[i]->key);
- av_freep(&pls->segments[i]->url);
- av_freep(&pls->segments[i]);
- }
- av_freep(&pls->segments);
- pls->n_segments = 0;
+ free_segment_dynarray(pls->segments, pls->n_segments);
+ av_freep(&pls->segments);
+ pls->n_segments = 0;
}
static void free_init_section_list(struct playlist *pls)
av_free(pls);
}
av_freep(&c->playlists);
- av_freep(&c->cookies);
- av_freep(&c->user_agent);
- av_freep(&c->headers);
- av_freep(&c->http_proxy);
c->n_playlists = 0;
}
return 0;
}
-static void update_options(char **dest, const char *name, void *src)
-{
- av_freep(dest);
- av_opt_get(src, name, AV_OPT_SEARCH_CHILDREN, (uint8_t**)dest);
- if (*dest && !strlen(*dest))
- av_freep(dest);
-}
-
static int open_url_keepalive(AVFormatContext *s, AVIOContext **pb,
const char *url)
{
if (!(s->flags & AVFMT_FLAG_CUSTOM_IO))
av_opt_get(*pb, "cookies", AV_OPT_SEARCH_CHILDREN, (uint8_t**)&new_cookies);
- if (new_cookies) {
- av_free(c->cookies);
- c->cookies = new_cookies;
- }
-
- av_dict_set(&opts, "cookies", c->cookies, 0);
+ if (new_cookies)
+ av_dict_set(&opts, "cookies", new_cookies, AV_DICT_DONT_STRDUP_VAL);
}
av_dict_free(&tmp);
char tmp_str[MAX_URL_SIZE];
struct segment *cur_init_section = NULL;
int is_http = av_strstart(url, "http", NULL);
+ struct segment **prev_segments = NULL;
+ int prev_n_segments = 0;
+ int prev_start_seq_no = -1;
if (is_http && !in && c->http_persistent && c->playlist_pb) {
in = c->playlist_pb;
}
if (!in) {
-#if 1
AVDictionary *opts = NULL;
- /* Some HLS servers don't like being sent the range header */
- av_dict_set(&opts, "seekable", "0", 0);
-
- // broker prior HTTP options that should be consistent across requests
- av_dict_set(&opts, "user_agent", c->user_agent, 0);
- av_dict_set(&opts, "cookies", c->cookies, 0);
- av_dict_set(&opts, "headers", c->headers, 0);
- av_dict_set(&opts, "http_proxy", c->http_proxy, 0);
+ av_dict_copy(&opts, c->avio_opts, 0);
if (c->http_persistent)
av_dict_set(&opts, "multiple_requests", "1", 0);
c->playlist_pb = in;
else
close_in = 1;
-#else
- ret = open_in(c, &in, url);
- if (ret < 0)
- return ret;
- close_in = 1;
-#endif
}
if (av_opt_get(in, "location", AV_OPT_SEARCH_CHILDREN, &new_url) >= 0)
url = new_url;
- read_chomp_line(in, line, sizeof(line));
+ ff_get_chomp_line(in, line, sizeof(line));
if (strcmp(line, "#EXTM3U")) {
ret = AVERROR_INVALIDDATA;
goto fail;
}
if (pls) {
- free_segment_list(pls);
+ prev_start_seq_no = pls->start_seq_no;
+ prev_segments = pls->segments;
+ prev_n_segments = pls->n_segments;
+ pls->segments = NULL;
+ pls->n_segments = 0;
+
pls->finished = 0;
pls->type = PLS_TYPE_UNSPECIFIED;
}
while (!avio_feof(in)) {
- read_chomp_line(in, line, sizeof(line));
+ ff_get_chomp_line(in, line, sizeof(line));
if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) {
is_variant = 1;
memset(&variant_info, 0, sizeof(variant_info));
ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_init_section_args,
&info);
cur_init_section = new_init_section(pls, &info, url);
+ cur_init_section->key_type = key_type;
+ if (has_iv) {
+ memcpy(cur_init_section->iv, iv, sizeof(iv));
+ } else {
+ int seq = pls->start_seq_no + pls->n_segments;
+ memset(cur_init_section->iv, 0, sizeof(cur_init_section->iv));
+ AV_WB32(cur_init_section->iv + 12, seq);
+ }
+
+ if (key_type != KEY_NONE) {
+ ff_make_absolute_url(tmp_str, sizeof(tmp_str), url, key);
+ cur_init_section->key = av_strdup(tmp_str);
+ if (!cur_init_section->key) {
+ av_free(cur_init_section);
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ } else {
+ cur_init_section->key = NULL;
+ }
+
} else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
if (pls)
pls->finished = 1;
}
}
}
+ if (prev_segments) {
+ if (pls->start_seq_no > prev_start_seq_no && c->first_timestamp != AV_NOPTS_VALUE) {
+ int64_t prev_timestamp = c->first_timestamp;
+ int i, diff = pls->start_seq_no - prev_start_seq_no;
+ for (i = 0; i < prev_n_segments && i < diff; i++) {
+ c->first_timestamp += prev_segments[i]->duration;
+ }
+ av_log(c->ctx, AV_LOG_DEBUG, "Media sequence change (%d -> %d)"
+ " reflected in first_timestamp: %"PRId64" -> %"PRId64"\n",
+ prev_start_seq_no, pls->start_seq_no,
+ prev_timestamp, c->first_timestamp);
+ } else if (pls->start_seq_no < prev_start_seq_no) {
+ av_log(c->ctx, AV_LOG_WARNING, "Media sequence changed unexpectedly: %d -> %d\n",
+ prev_start_seq_no, pls->start_seq_no);
+ }
+ free_segment_dynarray(prev_segments, prev_n_segments);
+ }
if (pls)
pls->last_load_time = av_gettime_relative();
return pls->segments[n];
}
-enum ReadFromURLMode {
- READ_NORMAL,
- READ_COMPLETE,
-};
-
static int read_from_url(struct playlist *pls, struct segment *seg,
- uint8_t *buf, int buf_size,
- enum ReadFromURLMode mode)
+ uint8_t *buf, int buf_size)
{
int ret;
if (seg->size >= 0)
buf_size = FFMIN(buf_size, seg->size - pls->cur_seg_offset);
- if (mode == READ_COMPLETE) {
- ret = avio_read(pls->input, buf, buf_size);
- if (ret != buf_size)
- av_log(NULL, AV_LOG_ERROR, "Could not read complete segment.\n");
- } else
- ret = avio_read(pls->input, buf, buf_size);
-
+ ret = avio_read(pls->input, buf, buf_size);
if (ret > 0)
pls->cur_seg_offset += ret;
while (1) {
/* see if we can retrieve enough data for ID3 header */
if (*len < ID3v2_HEADER_SIZE && buf_size >= ID3v2_HEADER_SIZE) {
- bytes = read_from_url(pls, seg, buf + *len, ID3v2_HEADER_SIZE - *len, READ_COMPLETE);
+ bytes = read_from_url(pls, seg, buf + *len, ID3v2_HEADER_SIZE - *len);
if (bytes > 0) {
if (bytes == ID3v2_HEADER_SIZE - *len)
if (remaining > 0) {
/* read the rest of the tag in */
- if (read_from_url(pls, seg, pls->id3_buf + id3_buf_pos, remaining, READ_COMPLETE) != remaining)
+ if (read_from_url(pls, seg, pls->id3_buf + id3_buf_pos, remaining) != remaining)
break;
id3_buf_pos += remaining;
av_log(pls->ctx, AV_LOG_DEBUG, "Stripped additional %d HLS ID3 bytes\n", remaining);
/* re-fill buffer for the caller unless EOF */
if (*len >= 0 && (fill_buf || *len == 0)) {
- bytes = read_from_url(pls, seg, buf + *len, buf_size - *len, READ_NORMAL);
+ bytes = read_from_url(pls, seg, buf + *len, buf_size - *len);
/* ignore error if we already had some data */
if (bytes >= 0)
int ret;
int is_http = 0;
- // broker prior HTTP options that should be consistent across requests
- av_dict_set(&opts, "user_agent", c->user_agent, 0);
- av_dict_set(&opts, "referer", c->referer, 0);
- av_dict_set(&opts, "cookies", c->cookies, 0);
- av_dict_set(&opts, "headers", c->headers, 0);
- av_dict_set(&opts, "http_proxy", c->http_proxy, 0);
- av_dict_set(&opts, "seekable", "0", 0);
-
if (c->http_persistent)
av_dict_set(&opts, "multiple_requests", "1", 0);
if (seg->key_type == KEY_NONE) {
ret = open_url(pls->parent, in, seg->url, c->avio_opts, opts, &is_http);
} else if (seg->key_type == KEY_AES_128) {
- AVDictionary *opts2 = NULL;
char iv[33], key[33], url[MAX_URL_SIZE];
if (strcmp(seg->key, pls->key_url)) {
AVIOContext *pb = NULL;
else
snprintf(url, sizeof(url), "crypto:%s", seg->url);
- av_dict_copy(&opts2, c->avio_opts, 0);
- av_dict_set(&opts2, "key", key, 0);
- av_dict_set(&opts2, "iv", iv, 0);
-
- ret = open_url(pls->parent, in, url, opts2, opts, &is_http);
-
- av_dict_free(&opts2);
+ av_dict_set(&opts, "key", key, 0);
+ av_dict_set(&opts, "iv", iv, 0);
+ ret = open_url(pls->parent, in, url, c->avio_opts, opts, &is_http);
if (ret < 0) {
goto cleanup;
}
av_fast_malloc(&pls->init_sec_buf, &pls->init_sec_buf_size, sec_size);
ret = read_from_url(pls, seg->init_section, pls->init_sec_buf,
- pls->init_sec_buf_size, READ_COMPLETE);
+ pls->init_sec_buf_size);
ff_format_io_close(pls->parent, &pls->input);
if (ret < 0)
}
seg = current_segment(v);
- ret = read_from_url(v, seg, buf, buf_size, READ_NORMAL);
+ ret = read_from_url(v, seg, buf, buf_size);
if (ret > 0) {
if (just_opened && v->is_id3_timestamped != 0) {
/* Intercept ID3 tags here, elementary audio streams are required
{
HLSContext *c = s->priv_data;
static const char * const opts[] = {
- "headers", "http_proxy", "user_agent", "user-agent", "cookies", "referer", NULL };
+ "headers", "http_proxy", "user_agent", "cookies", "referer", "rw_timeout", NULL };
const char * const * opt = opts;
uint8_t *buf;
int ret = 0;
static int hls_read_header(AVFormatContext *s)
{
- void *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb;
HLSContext *c = s->priv_data;
int ret = 0, i;
int highest_cur_seq_no = 0;
c->first_timestamp = AV_NOPTS_VALUE;
c->cur_timestamp = AV_NOPTS_VALUE;
- if (u) {
- // get the previous user agent & set back to null if string size is zero
- update_options(&c->user_agent, "user_agent", u);
-
- // get the previous cookies & set back to null if string size is zero
- update_options(&c->cookies, "cookies", u);
-
- // get the previous headers & set back to null if string size is zero
- update_options(&c->headers, "headers", u);
-
- // get the previous http proxt & set back to null if string size is zero
- update_options(&c->http_proxy, "http_proxy", u);
- }
-
- if ((ret = parse_playlist(c, s->url, NULL, s->pb)) < 0)
- goto fail;
-
if ((ret = save_avio_options(s)) < 0)
goto fail;
/* Some HLS servers don't like being sent the range header */
av_dict_set(&c->avio_opts, "seekable", "0", 0);
+ if ((ret = parse_playlist(c, s->url, NULL, s->pb)) < 0)
+ goto fail;
+
if (c->n_variants == 0) {
av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
ret = AVERROR_EOF;