avformat/hls: track seeking on a per-playlist basis
authorAnssi Hannula <anssi.hannula@iki.fi>
Mon, 30 Dec 2013 09:13:56 +0000 (11:13 +0200)
committerAnssi Hannula <anssi.hannula@iki.fi>
Sun, 6 Apr 2014 14:55:03 +0000 (17:55 +0300)
Seeking needs to be tracked on a per-playlist basis, since the resyncing
code in hls_read_packet() has to sync each playlist to the seek
timestamp instead of stopping after the first playlist has reached it.

Signed-off-by: Anssi Hannula <anssi.hannula@iki.fi>
libavformat/hls.c

index dd683e4..4e037c1 100644 (file)
@@ -117,6 +117,9 @@ struct playlist {
     int id3_changed; /* ID3 tag data has changed at some point */
     ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer is opened */
 
+    int64_t seek_timestamp;
+    int seek_flags;
+
     /* Renditions associated with this playlist, if any.
      * Alternative rendition playlists have a single rendition associated
      * with them, and variant main Media Playlists may have
@@ -164,8 +167,6 @@ typedef struct HLSContext {
     int end_of_segment;
     int first_packet;
     int64_t first_timestamp;
-    int64_t seek_timestamp;
-    int seek_flags;
     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
@@ -254,6 +255,7 @@ static struct playlist *new_playlist(HLSContext *c, const char *url,
         return NULL;
     reset_packet(&pls->pkt);
     ff_make_absolute_url(pls->url, sizeof(pls->url), base, url);
+    pls->seek_timestamp = AV_NOPTS_VALUE;
 
     pls->is_id3_timestamped = -1;
     pls->id3_mpegts_timestamp = AV_NOPTS_VALUE;
@@ -1111,6 +1113,34 @@ static void add_metadata_from_renditions(AVFormatContext *s, struct playlist *pl
     }
 }
 
+/* if timestamp was in valid range: returns 1 and sets seq_no
+ * if not: returns 0 and sets seq_no to closest segment */
+static int find_timestamp_in_playlist(HLSContext *c, struct playlist *pls,
+                                      int64_t timestamp, int *seq_no)
+{
+    int i;
+    int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ?
+                  0 : c->first_timestamp;
+
+    if (timestamp < pos) {
+        *seq_no = pls->start_seq_no;
+        return 0;
+    }
+
+    for (i = 0; i < pls->n_segments; i++) {
+        int64_t diff = pos + pls->segments[i]->duration - timestamp;
+        if (diff > 0) {
+            *seq_no = pls->start_seq_no + i;
+            return 1;
+        }
+        pos += pls->segments[i]->duration;
+    }
+
+    *seq_no = pls->start_seq_no + pls->n_segments - 1;
+
+    return 0;
+}
+
 static int hls_read_header(AVFormatContext *s)
 {
     URLContext *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb->opaque;
@@ -1302,7 +1332,6 @@ static int hls_read_header(AVFormatContext *s)
 
     c->first_packet = 1;
     c->first_timestamp = AV_NOPTS_VALUE;
-    c->seek_timestamp  = AV_NOPTS_VALUE;
 
     return 0;
 fail:
@@ -1419,21 +1448,21 @@ start:
                             get_timebase(pls), AV_TIME_BASE_Q);
                 }
 
-                if (c->seek_timestamp == AV_NOPTS_VALUE)
+                if (pls->seek_timestamp == AV_NOPTS_VALUE)
                     break;
 
                 if (pls->pkt.dts == AV_NOPTS_VALUE) {
-                    c->seek_timestamp = AV_NOPTS_VALUE;
+                    pls->seek_timestamp = AV_NOPTS_VALUE;
                     break;
                 }
 
                 tb = get_timebase(pls);
                 ts_diff = av_rescale_rnd(pls->pkt.dts, AV_TIME_BASE,
                                          tb.den, AV_ROUND_DOWN) -
-                          c->seek_timestamp;
-                if (ts_diff >= 0 && (c->seek_flags  & AVSEEK_FLAG_ANY ||
+                          pls->seek_timestamp;
+                if (ts_diff >= 0 && (pls->seek_flags  & AVSEEK_FLAG_ANY ||
                                      pls->pkt.flags & AV_PKT_FLAG_KEY)) {
-                    c->seek_timestamp = AV_NOPTS_VALUE;
+                    pls->seek_timestamp = AV_NOPTS_VALUE;
                     break;
                 }
                 av_free_packet(&pls->pkt);
@@ -1498,32 +1527,40 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
                                int64_t timestamp, int flags)
 {
     HLSContext *c = s->priv_data;
-    int i, j, ret;
+    int i;
+    int64_t seek_timestamp;
+    int valid_for = -1;
 
     if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->playlists[0]->finished)
         return AVERROR(ENOSYS);
 
-    c->seek_flags     = flags;
-    c->seek_timestamp = stream_index < 0 ? timestamp :
-                        av_rescale_rnd(timestamp, AV_TIME_BASE,
-                                       s->streams[stream_index]->time_base.den,
-                                       flags & AVSEEK_FLAG_BACKWARD ?
-                                       AV_ROUND_DOWN : AV_ROUND_UP);
-    timestamp = av_rescale_rnd(timestamp, AV_TIME_BASE, stream_index >= 0 ?
-                               s->streams[stream_index]->time_base.den :
-                               AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ?
-                               AV_ROUND_DOWN : AV_ROUND_UP);
-    if (s->duration < c->seek_timestamp) {
-        c->seek_timestamp = AV_NOPTS_VALUE;
+    seek_timestamp = stream_index < 0 ? timestamp :
+                     av_rescale_rnd(timestamp, AV_TIME_BASE,
+                                    s->streams[stream_index]->time_base.den,
+                                    flags & AVSEEK_FLAG_BACKWARD ?
+                                    AV_ROUND_DOWN : AV_ROUND_UP);
+
+    if (s->duration < seek_timestamp)
         return AVERROR(EIO);
+
+    for (i = 0; i < c->n_playlists; i++) {
+        /* check first that the timestamp is valid for some playlist */
+        struct playlist *pls = c->playlists[i];
+        int seq_no;
+        if (find_timestamp_in_playlist(c, pls, seek_timestamp, &seq_no)) {
+            /* set segment now so we do not need to search again below */
+            pls->cur_seq_no = seq_no;
+            valid_for = i;
+            break;
+        }
     }
 
-    ret = AVERROR(EIO);
+    if (valid_for < 0)
+        return AVERROR(EIO);
+
     for (i = 0; i < c->n_playlists; i++) {
         /* Reset reading */
         struct playlist *pls = c->playlists[i];
-        int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ?
-                      0 : c->first_timestamp;
         if (pls->input) {
             ffurl_close(pls->input);
             pls->input = NULL;
@@ -1536,20 +1573,15 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
         /* Reset the pos, to let the mpegts demuxer know we've seeked. */
         pls->pb.pos = 0;
 
-        /* Locate the segment that contains the target timestamp */
-        for (j = 0; j < pls->n_segments; j++) {
-            if (timestamp >= pos &&
-                timestamp < pos + pls->segments[j]->duration) {
-                pls->cur_seq_no = pls->start_seq_no + j;
-                ret = 0;
-                break;
-            }
-            pos += pls->segments[j]->duration;
-        }
-        if (ret)
-            c->seek_timestamp = AV_NOPTS_VALUE;
+        pls->seek_timestamp = seek_timestamp;
+        pls->seek_flags = flags;
+
+        /* set closest segment seq_no for playlists not handled above */
+        if (valid_for != i)
+            find_timestamp_in_playlist(c, pls, seek_timestamp, &pls->cur_seq_no);
     }
-    return ret;
+
+    return 0;
 }
 
 static int hls_probe(AVProbeData *p)