diff --git a/src/opusfile.c b/src/opusfile.c
index 992179abb9741eb8300ee9894902f39d2da9b8f3..7b819bc37a45ca841515e3de4c9badf5ab2c7cc3 100644
--- a/src/opusfile.c
+++ b/src/opusfile.c
@@ -2041,7 +2041,8 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
             op_granpos_add(&prev_packet_gp,prev_packet_gp,total_duration)
              should succeed and give prev_packet_gp==cur_page_gp.
             But we don't bother to check that, as there isn't much we can do
-             if it's not true.
+             if it's not true, and it actually will not be true on the first
+             page after a seek, if there was a continued packet.
             The only thing we guarantee is that the start and end granule
              positions of the packets are valid, and that they are monotonic
              within a page.
@@ -2143,6 +2144,20 @@ static ogg_int64_t op_get_granulepos(const OggOpusFile *_of,
   return -1;
 }
 
+/*A small helper to determine if an Ogg page contains data that continues onto
+   a subsequent page.*/
+static int op_page_continues(ogg_page *_og){
+  int header_len;
+  int nlacing;
+  header_len=_og->header_len;
+  OP_ASSERT(header_len>=27);
+  nlacing=_og->header[26];
+  OP_ASSERT(header_len>=27+nlacing);
+  /*This also correctly handles the (unlikely) case of nlacing==0, because
+     0!=255.*/
+  return _og->header[27+nlacing-1]==255;
+}
+
 /*This controls how close the target has to be to use the current stream
    position to subdivide the initial range.
   Two minutes seems to be a good default.*/
@@ -2174,11 +2189,13 @@ static int op_pcm_seek_page(OggOpusFile *_of,
   opus_int64         end;
   opus_int64         boundary;
   opus_int64         best;
+  opus_int64         best_start;
   opus_int64         page_offset;
   opus_int64         d0;
   opus_int64         d1;
   opus_int64         d2;
   int                force_bisect;
+  int                reset_needed;
   int                ret;
   _of->bytes_tracked=0;
   _of->samples_tracked=0;
@@ -2186,8 +2203,9 @@ static int op_pcm_seek_page(OggOpusFile *_of,
   best_gp=pcm_start=link->pcm_start;
   pcm_end=link->pcm_end;
   serialno=link->serialno;
-  best=begin=link->data_offset;
+  best=best_start=begin=link->data_offset;
   page_offset=-1;
+  reset_needed=1;
   /*We discard the first 80 ms of data after a seek, so seek back that much
      farther.
     If we can't, simply seek to the beginning of the link.*/
@@ -2231,8 +2249,11 @@ static int op_pcm_seek_page(OggOpusFile *_of,
           if(diff<0){
             OP_ASSERT(offset>=begin);
             if(offset-begin>=end-begin>>1||diff>-OP_CUR_TIME_THRESH){
-              best=begin=offset;
+              best=best_start=begin=offset;
               best_gp=pcm_start=gp;
+              /*Don't reset the Ogg stream state, or we'll dump any continued
+                 packets from previous pages.*/
+              reset_needed=0;
             }
           }
           else{
@@ -2328,7 +2349,10 @@ static int op_pcm_seek_page(OggOpusFile *_of,
            of the stream it came from or whether or not it has a timestamp.*/
         next_boundary=OP_MIN(page_offset,next_boundary);
         if(serialno!=(ogg_uint32_t)ogg_page_serialno(&og))continue;
-        gp=ogg_page_granulepos(&og);
+        /*Ignore the granule position on pages where no packets end.
+          Otherwise we wouldn't properly track continued packets through
+           them.*/
+        gp=ogg_page_packets(&og)>0?ogg_page_granulepos(&og):-1;
         if(gp==-1)continue;
         if(op_granpos_cmp(gp,_target_gp)<0){
           /*We found a page that ends before our target.
@@ -2343,6 +2367,15 @@ static int op_pcm_seek_page(OggOpusFile *_of,
           /*Save the byte offset of the end of the page with this granule
              position.*/
           best=begin;
+          /*If this page ends with a continued packet, remember the offset of
+             its start, so that we can prime the stream with the continued
+             packet data.
+            This will likely mean an extra seek backwards, since chances are
+             we will scan forward at least one more page to figure out where
+             we're stopping, but continued packets are rare enough it's not
+             worth the machinery to buffer two pages instead of one to avoid
+             that seek.*/
+          best_start=op_page_continues(&og)?page_offset:best;
           best_gp=pcm_start=gp;
           OP_ALWAYS_TRUE(!op_granpos_diff(&diff,_target_gp,pcm_start));
           /*If we're more than a second away from our target, break out and
@@ -2375,27 +2408,41 @@ static int op_pcm_seek_page(OggOpusFile *_of,
     }
   }
   /*Found our page.
-    Seek to the end of it and update prev_packet_gp.
-    Our caller will set cur_discard_count.
-    This is an easier case than op_raw_seek(), as we don't need to keep any
-     packets from the page we found.*/
+    The packets we want will end on pages that come after best, but the first
+     packet might be continued from data in the best page itself.
+    In that case, best_start will point to the start of the best page, rather
+     than the end, because that's where we need to start reading.
+    When there is no danger of a continued packet, best_start==best.*/
   /*Seek, if necessary.*/
-  if(best!=page_offset){
+  if(best_start!=page_offset){
     page_offset=-1;
-    ret=op_seek_helper(_of,best);
+    ret=op_seek_helper(_of,best_start);
     if(OP_UNLIKELY(ret<0))return ret;
   }
   OP_ASSERT(op_granpos_cmp(best_gp,pcm_start)>=0);
   _of->cur_link=_li;
   _of->ready_state=OP_STREAMSET;
+  /*Update prev_packet_gp to allow per-packet granule position assignment.
+    If best_start!=best, this is often wrong, but we'll be overwriting it
+     again shortly.*/
   _of->prev_packet_gp=best_gp;
-  ogg_stream_reset_serialno(&_of->os,serialno);
+  if(reset_needed)ogg_stream_reset_serialno(&_of->os,serialno);
   ret=op_fetch_and_process_page(_of,page_offset<0?NULL:&og,page_offset,1,0,1);
   if(OP_UNLIKELY(ret<=0))return OP_EBADLINK;
+  if(best_start<best){
+    /*The previous call merely primed the stream state to make sure we got the
+       data for a continued packet.
+      Now load the packets we actually wanted.*/
+    _of->prev_packet_gp=best_gp;
+    _of->op_pos=_of->op_count;
+    ret=op_fetch_and_process_page(_of,NULL,-1,1,0,1);
+    if(OP_UNLIKELY(ret<=0))return OP_EBADLINK;
+  }
   /*Verify result.*/
   if(OP_UNLIKELY(op_granpos_cmp(_of->prev_packet_gp,_target_gp)>0)){
     return OP_EBADLINK;
   }
+  /*Our caller will set cur_discard_count to handle pre-roll.*/
   return 0;
 }