diff --git a/libavformat/hls.c b/libavformat/hls.c index 4e037c1b9a..dfeb520a30 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -167,6 +167,7 @@ typedef struct HLSContext { int end_of_segment; int first_packet; int64_t first_timestamp; + int64_t cur_timestamp; AVIOInterruptCB *interrupt_callback; 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 @@ -948,6 +949,13 @@ cleanup: return ret; } +static int64_t default_reload_interval(struct playlist *pls) +{ + return pls->n_segments > 0 ? + pls->segments[pls->n_segments - 1]->duration : + pls->target_duration; +} + static int read_data(void *opaque, uint8_t *buf, int buf_size) { struct playlist *v = opaque; @@ -962,9 +970,7 @@ restart: if (!v->input) { /* If this is a live stream and the reload interval has elapsed since * the last playlist reload, reload the playlists now. */ - int64_t reload_interval = v->n_segments > 0 ? - v->segments[v->n_segments - 1]->duration : - v->target_duration; + int64_t reload_interval = default_reload_interval(v); reload: if (!v->finished && @@ -1141,6 +1147,43 @@ static int find_timestamp_in_playlist(HLSContext *c, struct playlist *pls, return 0; } +static int select_cur_seq_no(HLSContext *c, struct playlist *pls) +{ + int seq_no; + + if (!pls->finished && !c->first_packet && + av_gettime() - pls->last_load_time >= default_reload_interval(pls)) + /* reload the playlist since it was suspended */ + parse_playlist(c, pls->url, pls, NULL); + + /* If playback is already in progress (we are just selecting a new + * playlist) and this is a complete file, find the matching segment + * by counting durations. */ + if (pls->finished && c->cur_timestamp != AV_NOPTS_VALUE) { + find_timestamp_in_playlist(c, pls, c->cur_timestamp, &seq_no); + return seq_no; + } + + if (!pls->finished) { + if (!c->first_packet && /* we are doing a segment selection during playback */ + c->cur_seq_no >= pls->start_seq_no && + c->cur_seq_no < pls->start_seq_no + pls->n_segments) + /* While spec 3.4.3 says that we cannot assume anything about the + * content at the same sequence number on different playlists, + * in practice this seems to work and doing it otherwise would + * require us to download a segment to inspect its timestamps. */ + return c->cur_seq_no; + + /* If this is a live stream with more than 3 segments, start at the + * third last segment. */ + if (pls->n_segments > 3) + return pls->start_seq_no + pls->n_segments - 3; + } + + /* Otherwise just start on the first segment. */ + return pls->start_seq_no; +} + static int hls_read_header(AVFormatContext *s) { URLContext *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb->opaque; @@ -1149,6 +1192,10 @@ static int hls_read_header(AVFormatContext *s) c->interrupt_callback = &s->interrupt_callback; + c->first_packet = 1; + c->first_timestamp = AV_NOPTS_VALUE; + c->cur_timestamp = AV_NOPTS_VALUE; + // if the URL context is good, read important options we must broker later if (u && u->prot->priv_data_class) { // get the previous user agent & set back to null if string size is zero @@ -1231,12 +1278,7 @@ static int hls_read_header(AVFormatContext *s) pls->index = i; pls->needed = 1; pls->parent = s; - - /* If this is a live stream with more than 3 segments, start at the - * third last segment. */ - pls->cur_seq_no = pls->start_seq_no; - if (!pls->finished && pls->n_segments > 3) - pls->cur_seq_no = pls->start_seq_no + pls->n_segments - 3; + pls->cur_seq_no = select_cur_seq_no(c, pls); pls->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls, @@ -1330,9 +1372,6 @@ static int hls_read_header(AVFormatContext *s) } } - c->first_packet = 1; - c->first_timestamp = AV_NOPTS_VALUE; - return 0; fail: free_playlist_list(c); @@ -1361,9 +1400,14 @@ static int recheck_discard_flags(AVFormatContext *s, int first) if (pls->cur_needed && !pls->needed) { pls->needed = 1; changed = 1; - pls->cur_seq_no = c->cur_seq_no; + pls->cur_seq_no = select_cur_seq_no(c, pls); pls->pb.eof_reached = 0; - av_log(s, AV_LOG_INFO, "Now receiving playlist %d\n", i); + if (c->cur_timestamp != AV_NOPTS_VALUE) { + /* catch up */ + pls->seek_timestamp = c->cur_timestamp; + pls->seek_flags = AVSEEK_FLAG_ANY; + } + av_log(s, AV_LOG_INFO, "Now receiving playlist %d, segment %d\n", i, pls->cur_seq_no); } else if (first && !pls->cur_needed && pls->needed) { if (pls->input) ffurl_close(pls->input); @@ -1505,9 +1549,16 @@ start: } /* If we got a packet, return it */ if (minplaylist >= 0) { - *pkt = c->playlists[minplaylist]->pkt; - pkt->stream_index += c->playlists[minplaylist]->stream_offset; + struct playlist *pls = c->playlists[minplaylist]; + *pkt = pls->pkt; + pkt->stream_index += pls->stream_offset; reset_packet(&c->playlists[minplaylist]->pkt); + + if (pkt->dts != AV_NOPTS_VALUE) + c->cur_timestamp = av_rescale_q(pkt->dts, + pls->ctx->streams[pls->pkt.stream_index]->time_base, + AV_TIME_BASE_Q); + return 0; } return AVERROR_EOF; @@ -1581,6 +1632,8 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, find_timestamp_in_playlist(c, pls, seek_timestamp, &pls->cur_seq_no); } + c->cur_timestamp = seek_timestamp; + return 0; }