int n_segments;
struct segment **segments;
int needed;
+ int broken;
int cur_seq_no;
+ int last_seq_no;
+ int m3u8_hold_counters;
int64_t cur_seg_offset;
int64_t last_load_time;
struct rendition **renditions;
int cur_seq_no;
+ int m3u8_hold_counters;
int live_start_index;
int first_packet;
int64_t first_timestamp;
int64_t cur_timestamp;
AVIOInterruptCB *interrupt_callback;
AVDictionary *avio_opts;
- int strict_std_compliance;
char *allowed_extensions;
int max_reload;
int http_persistent;
int http_multiple;
+ int http_seekable;
AVIOContext *playlist_pb;
} HLSContext;
static void free_segment_list(struct playlist *pls)
{
- free_segment_dynarray(pls->segments, pls->n_segments);
- 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_freep(&pls->init_sec_buf);
av_packet_unref(&pls->pkt);
av_freep(&pls->pb.buffer);
- if (pls->input)
- ff_format_io_close(c->ctx, &pls->input);
+ ff_format_io_close(c->ctx, &pls->input);
pls->input_read_done = 0;
- if (pls->input_next)
- ff_format_io_close(c->ctx, &pls->input_next);
+ ff_format_io_close(c->ctx, &pls->input_next);
pls->input_next_requested = 0;
if (pls->ctx) {
pls->ctx->pb = NULL;
}
/*
- * Used to reset a statically allocated AVPacket to a clean slate,
+ * Used to reset a statically allocated AVPacket to a clean state,
* containing no data.
*/
static void reset_packet(AVPacket *pkt)
const char *url_base)
{
struct segment *sec;
- char *ptr;
- char tmp_str[MAX_URL_SIZE];
+ char tmp_str[MAX_URL_SIZE], *ptr = tmp_str;
if (!info->uri[0])
return NULL;
if (!sec)
return NULL;
- ff_make_absolute_url(tmp_str, sizeof(tmp_str), url_base, info->uri);
- sec->url = av_strdup(tmp_str);
+ if (!av_strncasecmp(info->uri, "data:", 5)) {
+ ptr = info->uri;
+ } else {
+ ff_make_absolute_url(tmp_str, sizeof(tmp_str), url_base, info->uri);
+ }
+ sec->url = av_strdup(ptr);
if (!sec->url) {
av_free(sec);
return NULL;
* AVC SEI RBSP anyway */
return NULL;
- if (type == AVMEDIA_TYPE_UNKNOWN)
+ if (type == AVMEDIA_TYPE_UNKNOWN) {
+ av_log(c->ctx, AV_LOG_WARNING, "Can't support the type: %s\n", info->type);
return NULL;
+ }
/* URI is mandatory for subtitles as per spec */
- if (type == AVMEDIA_TYPE_SUBTITLE && !info->uri[0])
+ if (type == AVMEDIA_TYPE_SUBTITLE && !info->uri[0]) {
+ av_log(c->ctx, AV_LOG_ERROR, "The URI tag is REQUIRED for subtitle.\n");
return NULL;
+ }
/* TODO: handle subtitles (each segment has to parsed separately) */
- if (c->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL)
- if (type == AVMEDIA_TYPE_SUBTITLE)
+ if (c->ctx->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL)
+ if (type == AVMEDIA_TYPE_SUBTITLE) {
+ av_log(c->ctx, AV_LOG_WARNING, "Can't support the subtitle(uri: %s)\n", info->uri);
return NULL;
+ }
rend = av_mallocz(sizeof(struct rendition));
if (!rend)
}
static int open_url_keepalive(AVFormatContext *s, AVIOContext **pb,
- const char *url)
+ const char *url, AVDictionary **options)
{
#if !CONFIG_HTTP_PROTOCOL
return AVERROR_PROTOCOL_NOT_FOUND;
URLContext *uc = ffio_geturlcontext(*pb);
av_assert0(uc);
(*pb)->eof_reached = 0;
- ret = ff_http_do_new_request(uc, url);
+ ret = ff_http_do_new_request2(uc, url, options);
if (ret < 0) {
ff_format_io_close(s, pb);
}
int ret;
int is_http = 0;
- av_dict_copy(&tmp, opts, 0);
- av_dict_copy(&tmp, opts2, 0);
-
if (av_strstart(url, "crypto", NULL)) {
if (url[6] == '+' || url[6] == ':')
proto_name = avio_find_protocol_name(url + 7);
+ } else if (av_strstart(url, "data", NULL)) {
+ if (url[4] == '+' || url[4] == ':')
+ proto_name = avio_find_protocol_name(url + 5);
}
if (!proto_name)
}
} else if (av_strstart(proto_name, "http", NULL)) {
is_http = 1;
+ } else if (av_strstart(proto_name, "data", NULL)) {
+ ;
} else
return AVERROR_INVALIDDATA;
;
else if (av_strstart(url, "crypto", NULL) && !strncmp(proto_name, url + 7, strlen(proto_name)) && url[7 + strlen(proto_name)] == ':')
;
+ else if (av_strstart(url, "data", NULL) && !strncmp(proto_name, url + 5, strlen(proto_name)) && url[5 + strlen(proto_name)] == ':')
+ ;
else if (strcmp(proto_name, "file") || !strncmp(url, "file,", 5))
return AVERROR_INVALIDDATA;
+ av_dict_copy(&tmp, opts, 0);
+ av_dict_copy(&tmp, opts2, 0);
+
if (is_http && c->http_persistent && *pb) {
- ret = open_url_keepalive(c->ctx, pb, url);
+ ret = open_url_keepalive(c->ctx, pb, url, &tmp);
if (ret == AVERROR_EXIT) {
+ av_dict_free(&tmp);
return ret;
} else if (ret < 0) {
if (ret != AVERROR_EOF)
av_log(s, AV_LOG_WARNING,
- "keepalive request failed for '%s', retrying with new connection: %s\n",
+ "keepalive request failed for '%s' with error: '%s' when opening url, retrying with new connection\n",
url, av_err2str(ret));
ret = s->io_open(s, pb, url, AVIO_FLAG_READ, &tmp);
}
if (is_http && !in && c->http_persistent && c->playlist_pb) {
in = c->playlist_pb;
- ret = open_url_keepalive(c->ctx, &c->playlist_pb, url);
+ ret = open_url_keepalive(c->ctx, &c->playlist_pb, url, NULL);
if (ret == AVERROR_EXIT) {
return ret;
} else if (ret < 0) {
if (ret != AVERROR_EOF)
av_log(c->ctx, AV_LOG_WARNING,
- "keepalive request failed for '%s', retrying with new connection: %s\n",
+ "keepalive request failed for '%s' with error: '%s' when parsing playlist\n",
url, av_err2str(ret));
in = NULL;
}
if (ptr)
seg_offset = strtoll(ptr+1, NULL, 10);
} else if (av_strstart(line, "#", NULL)) {
+ av_log(c->ctx, AV_LOG_INFO, "Skip ('%s')\n", line);
continue;
} else if (line[0]) {
if (is_variant) {
}
if (is_segment) {
struct segment *seg;
- if (!pls) {
- if (!new_variant(c, 0, url, NULL)) {
- ret = AVERROR(ENOMEM);
- goto fail;
- }
- pls = c->playlists[c->n_playlists - 1];
- }
+ ret = ensure_playlist(c, &pls, url);
+ if (ret < 0)
+ goto fail;
seg = av_malloc(sizeof(struct segment));
if (!seg) {
ret = AVERROR(ENOMEM);
prev_start_seq_no, pls->start_seq_no);
}
free_segment_dynarray(prev_segments, prev_n_segments);
+ av_freep(&prev_segments);
}
if (pls)
pls->last_load_time = av_gettime_relative();
} else {
if (!pls->id3_changed && id3_has_changed_values(pls, metadata, apic)) {
- avpriv_report_missing_feature(pls->ctx, "Changing ID3 metadata in HLS audio elementary stream");
+ avpriv_report_missing_feature(pls->parent, "Changing ID3 metadata in HLS audio elementary stream");
pls->id3_changed = 1;
}
av_dict_free(&metadata);
int remaining = taglen - tag_got_bytes;
if (taglen > maxsize) {
- av_log(pls->ctx, AV_LOG_ERROR, "Too large HLS ID3 tag (%d > %"PRId64" bytes)\n",
+ av_log(pls->parent, AV_LOG_ERROR, "Too large HLS ID3 tag (%d > %"PRId64" bytes)\n",
taglen, maxsize);
break;
}
/* strip the intercepted bytes */
*len -= tag_got_bytes;
memmove(buf, buf + tag_got_bytes, *len);
- av_log(pls->ctx, AV_LOG_DEBUG, "Stripped %d HLS ID3 bytes\n", tag_got_bytes);
+ av_log(pls->parent, AV_LOG_DEBUG, "Stripped %d HLS ID3 bytes\n", tag_got_bytes);
if (remaining > 0) {
/* read the rest of the tag in */
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);
+ av_log(pls->parent, AV_LOG_DEBUG, "Stripped additional %d HLS ID3 bytes\n", remaining);
}
} else {
if (open_url(pls->parent, &pb, seg->key, c->avio_opts, opts, NULL) == 0) {
ret = avio_read(pb, pls->key, sizeof(pls->key));
if (ret != sizeof(pls->key)) {
- av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n",
+ av_log(pls->parent, AV_LOG_ERROR, "Unable to read key file %s\n",
seg->key);
}
ff_format_io_close(pls->parent, &pb);
} else {
- av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n",
+ av_log(pls->parent, AV_LOG_ERROR, "Unable to open key file %s\n",
seg->key);
}
av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url));
v->needed = playlist_needed(v);
if (!v->needed) {
- av_log(v->parent, AV_LOG_INFO, "No longer receiving playlist %d\n",
- v->index);
+ av_log(v->parent, AV_LOG_INFO, "No longer receiving playlist %d ('%s')\n",
+ v->index, v->url);
return AVERROR_EOF;
}
reload_interval = v->target_duration / 2;
}
if (v->cur_seq_no < v->start_seq_no) {
- av_log(NULL, AV_LOG_WARNING,
+ av_log(v->parent, AV_LOG_WARNING,
"skipping %d segments ahead, expired from playlists\n",
v->start_seq_no - v->cur_seq_no);
v->cur_seq_no = v->start_seq_no;
}
+ if (v->cur_seq_no > v->last_seq_no) {
+ v->last_seq_no = v->cur_seq_no;
+ v->m3u8_hold_counters = 0;
+ } else if (v->last_seq_no == v->cur_seq_no) {
+ v->m3u8_hold_counters++;
+ if (v->m3u8_hold_counters >= c->m3u8_hold_counters) {
+ return AVERROR_EOF;
+ }
+ } else {
+ av_log(v->parent, AV_LOG_WARNING, "maybe the m3u8 list sequence have been wraped.\n");
+ }
if (v->cur_seq_no >= v->start_seq_no + v->n_segments) {
if (v->finished)
return AVERROR_EOF;
if (c->http_multiple == 1 && v->input_next_requested) {
FFSWAP(AVIOContext *, v->input, v->input_next);
+ v->cur_seg_offset = 0;
v->input_next_requested = 0;
ret = 0;
} else {
uint8_t *http_version_opt = NULL;
int r = av_opt_get(v->input, "http_version", AV_OPT_SEARCH_CHILDREN, &http_version_opt);
if (r >= 0) {
- c->http_multiple = strncmp((const char *)http_version_opt, "1.1", 3) == 0;
+ c->http_multiple = (!strncmp((const char *)http_version_opt, "1.1", 3) || !strncmp((const char *)http_version_opt, "2.0", 3));
av_freep(&http_version_opt);
}
}
if (ret < 0) {
if (ff_check_interrupt(c->interrupt_callback))
return AVERROR_EXIT;
- av_log(v->parent, AV_LOG_WARNING, "Failed to open segment %d of playlist %d\n",
+ av_log(v->parent, AV_LOG_WARNING, "Failed to open next segment %d of playlist %d\n",
v->cur_seq_no + 1,
v->index);
} else {
{
HLSContext *c = s->priv_data;
static const char * const opts[] = {
- "headers", "http_proxy", "user_agent", "cookies", "referer", "rw_timeout", NULL };
+ "headers", "http_proxy", "user_agent", "cookies", "referer", "rw_timeout", "icy", NULL };
const char * const * opt = opts;
uint8_t *buf;
int ret = 0;
c->ctx = s;
c->interrupt_callback = &s->interrupt_callback;
- c->strict_std_compliance = s->strict_std_compliance;
c->first_packet = 1;
c->first_timestamp = AV_NOPTS_VALUE;
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);
+ /* XXX: Some HLS servers don't like being sent the range header,
+ in this case, need to setting http_seekable = 0 to disable
+ the range header */
+ av_dict_set_int(&c->avio_opts, "seekable", c->http_seekable, 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");
+ av_log(s, AV_LOG_WARNING, "Empty playlist\n");
ret = AVERROR_EOF;
goto fail;
}
if (c->n_playlists > 1 || c->playlists[0]->n_segments == 0) {
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
- if ((ret = parse_playlist(c, pls->url, pls, NULL)) < 0)
+ pls->m3u8_hold_counters = 0;
+ if ((ret = parse_playlist(c, pls->url, pls, NULL)) < 0) {
+ av_log(s, AV_LOG_WARNING, "parse_playlist error %s [%s]\n", av_err2str(ret), pls->url);
+ pls->broken = 1;
+ if (c->n_playlists > 1)
+ continue;
goto fail;
+ }
}
}
- if (c->variants[0]->playlists[0]->n_segments == 0) {
- av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
- ret = AVERROR_EOF;
- goto fail;
+ for (i = 0; i < c->n_variants; i++) {
+ if (c->variants[i]->playlists[0]->n_segments == 0) {
+ av_log(s, AV_LOG_WARNING, "Empty segment [%s]\n", c->variants[i]->playlists[0]->url);
+ c->variants[i]->playlists[0]->broken = 1;
+ }
}
/* If this isn't a live stream, calculate the total duration of the
/* Open the demuxer for each playlist */
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
- AVInputFormat *in_fmt = NULL;
+ ff_const59 AVInputFormat *in_fmt = NULL;
if (!(pls->ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM);
}
ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls,
read_data, NULL, NULL);
- pls->pb.seekable = 0;
+ pls->ctx->probesize = s->probesize > 0 ? s->probesize : 1024 * 4;
+ pls->ctx->max_analyze_duration = s->max_analyze_duration > 0 ? s->max_analyze_duration : 4 * AV_TIME_BASE;
ret = av_probe_input_buffer(&pls->pb, &in_fmt, pls->segments[0]->url,
NULL, 0, 0);
if (ret < 0) {
* but for other streams we can rely on our user calling avformat_find_stream_info()
* on us if they want to.
*/
- if (pls->is_id3_timestamped) {
+ if (pls->is_id3_timestamped || (pls->n_renditions > 0 && pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) {
ret = avformat_find_stream_info(pls->ctx, NULL);
if (ret < 0)
goto fail;
cur_needed = playlist_needed(c->playlists[i]);
+ if (pls->broken) {
+ continue;
+ }
if (cur_needed && !pls->needed) {
pls->needed = 1;
changed = 1;
}
av_log(s, AV_LOG_INFO, "Now receiving playlist %d, segment %d\n", i, pls->cur_seq_no);
} else if (first && !cur_needed && pls->needed) {
- if (pls->input)
- ff_format_io_close(pls->parent, &pls->input);
+ ff_format_io_close(pls->parent, &pls->input);
pls->input_read_done = 0;
- if (pls->input_next)
- ff_format_io_close(pls->parent, &pls->input_next);
+ ff_format_io_close(pls->parent, &pls->input_next);
pls->input_next_requested = 0;
pls->needed = 0;
changed = 1;
}
}
av_packet_unref(&pls->pkt);
- reset_packet(&pls->pkt);
}
}
/* Check if this stream has the packet with the lowest dts */
ret = update_streams_from_subdemuxer(s, pls);
if (ret < 0) {
av_packet_unref(&pls->pkt);
- reset_packet(&pls->pkt);
return ret;
}
av_log(s, AV_LOG_ERROR, "stream index inconsistency: index %d, %d main streams, %d subdemuxer streams\n",
pls->pkt.stream_index, pls->n_main_streams, pls->ctx->nb_streams);
av_packet_unref(&pls->pkt);
- reset_packet(&pls->pkt);
return AVERROR_BUG;
}
ist = pls->ctx->streams[pls->pkt.stream_index];
st = pls->main_streams[pls->pkt.stream_index];
- *pkt = pls->pkt;
+ av_packet_move_ref(pkt, &pls->pkt);
pkt->stream_index = st->index;
- reset_packet(&c->playlists[minplaylist]->pkt);
if (pkt->dts != AV_NOPTS_VALUE)
c->cur_timestamp = av_rescale_q(pkt->dts,
if (ist->codecpar->codec_id != st->codecpar->codec_id) {
ret = set_stream_info_from_input_stream(st, pls, ist);
if (ret < 0) {
- av_packet_unref(pkt);
return ret;
}
}
for (i = 0; i < c->n_playlists; i++) {
/* Reset reading */
struct playlist *pls = c->playlists[i];
- if (pls->input)
- ff_format_io_close(pls->parent, &pls->input);
+ ff_format_io_close(pls->parent, &pls->input);
pls->input_read_done = 0;
- if (pls->input_next)
- ff_format_io_close(pls->parent, &pls->input_next);
+ ff_format_io_close(pls->parent, &pls->input_next);
pls->input_next_requested = 0;
av_packet_unref(&pls->pkt);
- reset_packet(&pls->pkt);
pls->pb.eof_reached = 0;
/* Clear any buffered data */
pls->pb.buf_end = pls->pb.buf_ptr = pls->pb.buffer;
return 0;
}
-static int hls_probe(AVProbeData *p)
+static int hls_probe(const AVProbeData *p)
{
/* Require #EXTM3U at the start, and either one of the ones below
* somewhere for a proper match. */
INT_MIN, INT_MAX, FLAGS},
{"max_reload", "Maximum number of times a insufficient list is attempted to be reloaded",
OFFSET(max_reload), AV_OPT_TYPE_INT, {.i64 = 1000}, 0, INT_MAX, FLAGS},
+ {"m3u8_hold_counters", "The maximum number of times to load m3u8 when it refreshes without new segments",
+ OFFSET(m3u8_hold_counters), AV_OPT_TYPE_INT, {.i64 = 1000}, 0, INT_MAX, FLAGS},
{"http_persistent", "Use persistent HTTP connections",
OFFSET(http_persistent), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS },
{"http_multiple", "Use multiple HTTP connections for fetching segments",
OFFSET(http_multiple), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, FLAGS},
+ {"http_seekable", "Use HTTP partial requests, 0 = disable, 1 = enable, -1 = auto",
+ OFFSET(http_seekable), AV_OPT_TYPE_BOOL, { .i64 = -1}, -1, 1, FLAGS},
{NULL}
};
static const AVClass hls_class = {
- .class_name = "hls,applehttp",
+ .class_name = "hls demuxer",
.item_name = av_default_item_name,
.option = hls_options,
.version = LIBAVUTIL_VERSION_INT,
};
AVInputFormat ff_hls_demuxer = {
- .name = "hls,applehttp",
+ .name = "hls",
.long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"),
.priv_class = &hls_class,
.priv_data_size = sizeof(HLSContext),
- .flags = AVFMT_NOGENSEARCH,
+ .flags = AVFMT_NOGENSEARCH | AVFMT_TS_DISCONT,
.read_probe = hls_probe,
.read_header = hls_read_header,
.read_packet = hls_read_packet,