]> git.sesse.net Git - vlc/blobdiff - modules/demux/oggseek.c
Qt: MIM initialize p_input through constructor parameter
[vlc] / modules / demux / oggseek.c
index 42d08ee2fc8c9c6f21ca6ffeeddb55b6725c7c0a..d5b6f490fc730531b105df98fd0e04fbf7d332e2 100644 (file)
@@ -7,19 +7,19 @@
  * adapted from: http://lives.svn.sourceforge.net/viewvc/lives/trunk/lives-plugins
  * /plugins/decoders/ogg_theora_decoder.c
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
 /*****************************************************************************
 #include <vlc_demux.h>
 
 #include <ogg/ogg.h>
+#include <limits.h>
+
+#include <assert.h>
 
 #include "ogg.h"
 #include "oggseek.h"
 
+/* Theora spec 7.1 */
+#define THEORA_FTYPE_NOTDATA       0x80
+#define THEORA_FTYPE_INTERFRAME    0x40
+
+#define SEGMENT_NOT_FOUND -1
 
+#define MAX_PAGE_SIZE 65307
+typedef struct packetStartCoordinates
+{
+    int64_t i_pos;
+    int64_t i_pageno;
+    int64_t i_skip;
+} packetStartCoordinates;
+
+//#define OGG_SEEK_DEBUG 1
+#ifdef OGG_SEEK_DEBUG
+  #define OggDebug(code) code
+  #define OggNoDebug(code)
+#else
+  #define OggDebug(code)
+  #define OggNoDebug(code) code
+#endif
 /************************************************************
 * index entries
 *************************************************************/
@@ -58,99 +82,53 @@ void oggseek_index_entries_free ( demux_index_entry_t *idx )
 }
 
 
-/* unlink and free idx. If idx is head of list, return new head */
-
-static demux_index_entry_t *index_entry_delete( demux_index_entry_t *idx )
-{
-    demux_index_entry_t *xidx = idx;
-
-    if ( idx->p_prev != NULL ) idx->p_prev->p_next = idx->p_next;
-    else xidx = idx->p_next;
-
-    if ( idx->p_next != NULL ) idx->p_next->p_prev = idx->p_prev;
-    free( idx );
-
-    return xidx;
-}
-
-
 /* internal function to create a new list member */
 
 static demux_index_entry_t *index_entry_new( void )
 {
     demux_index_entry_t *idx = xmalloc( sizeof( demux_index_entry_t ) );
+    if ( !idx ) return NULL;
     idx->p_next = idx->p_prev = NULL;
     idx->i_pagepos_end = -1;
     return idx;
 }
 
-
-
-/* add a theora entry to our list; format is highest granulepos -> page offset of
-   keyframe start */
-
-const demux_index_entry_t *oggseek_theora_index_entry_add ( logical_stream_t *p_stream,
-                                                            int64_t i_granule,
-                                                            int64_t i_pagepos)
+/* We insert into index, sorting by pagepos (as a page can match multiple
+   time stamps) */
+const demux_index_entry_t *OggSeek_IndexAdd ( logical_stream_t *p_stream,
+                                             int64_t i_timestamp,
+                                             int64_t i_pagepos )
 {
-    /* add or update entry for keyframe */
     demux_index_entry_t *idx;
     demux_index_entry_t *oidx;
     demux_index_entry_t *last_idx = NULL;
-    int64_t i_gpos;
-    int64_t i_frame;
-    int64_t i_kframe;
-    int64_t i_tframe;
-    int64_t i_tkframe;
 
     if ( p_stream == NULL ) return NULL;
 
     oidx = idx = p_stream->idx;
 
-    i_tkframe = i_granule >> p_stream->i_granule_shift;
-    i_tframe = i_tkframe + i_granule - ( i_tkframe << p_stream->i_granule_shift );
-
-    if ( i_tkframe < 1 ) return NULL;
+    if ( i_timestamp < 1 || i_pagepos < 1 ) return NULL;
 
     if ( idx == NULL )
     {
         demux_index_entry_t *ie = index_entry_new();
-        ie->i_value = i_granule;
+        if ( !ie ) return NULL;
+        ie->i_value = i_timestamp;
         ie->i_pagepos = i_pagepos;
         p_stream->idx = ie;
         return ie;
     }
 
-
     while ( idx != NULL )
     {
-        i_gpos = idx->i_value;
-
-        i_kframe = i_gpos >> p_stream->i_granule_shift;
-        if ( i_kframe > i_tframe ) break;
-
-        if ( i_kframe == i_tkframe )
-        {
-            /* entry exists, update it if applicable, and return it */
-            i_frame = i_kframe + i_gpos - ( i_kframe << p_stream->i_granule_shift );
-            if ( i_frame < i_tframe )
-            {
-                idx->i_value = i_granule;
-                idx->i_pagepos = i_pagepos;
-            }
-
-            return idx;
-        }
-
+        if ( idx->i_pagepos > i_pagepos ) break;
         last_idx = idx;
         idx = idx->p_next;
     }
 
-
     /* new entry; insert after last_idx */
-
     idx = index_entry_new();
-
+    if ( !idx ) return NULL;
     if ( last_idx != NULL )
     {
         idx->p_next = last_idx->p_next;
@@ -168,14 +146,38 @@ const demux_index_entry_t *oggseek_theora_index_entry_add ( logical_stream_t *p_
         idx->p_next->p_prev = idx;
     }
 
-    idx->i_value = i_granule;
+    idx->i_value = i_timestamp;
     idx->i_pagepos = i_pagepos;
 
     return idx;
 }
 
+static bool OggSeekIndexFind ( logical_stream_t *p_stream, int64_t i_timestamp,
+                               int64_t *pi_pos_lower, int64_t *pi_pos_upper )
+{
+    demux_index_entry_t *idx = p_stream->idx;
 
+    while ( idx != NULL )
+    {
+        if ( idx->i_value <= i_timestamp )
+        {
+            if ( !idx->p_next ) /* found on last index */
+            {
+                *pi_pos_lower = idx->i_pagepos;
+                return true;
+            }
+            if ( idx->p_next->i_value > i_timestamp )
+            {
+                *pi_pos_lower = idx->i_pagepos;
+                *pi_pos_upper = idx->p_next->i_pagepos;
+                return true;
+            }
+        }
+        idx = idx->p_next;
+    }
 
+    return false;
+}
 
 /*********************************************************************
  * private functions
@@ -218,31 +220,118 @@ static int64_t get_data( demux_t *p_demux, int64_t i_bytes_to_read )
         }
     }
 
+    i_bytes_to_read = __MIN( i_bytes_to_read, INT_MAX );
+
     seek_byte ( p_demux, p_sys->i_input_position );
 
     buf = ogg_sync_buffer( &p_sys->oy, i_bytes_to_read );
 
     i_result = stream_Read( p_demux->s, buf, i_bytes_to_read );
 
-    p_sys->b_page_waiting = false;
-
     ogg_sync_wrote( &p_sys->oy, i_result );
     return i_result;
 }
 
 
+void Oggseek_ProbeEnd( demux_t *p_demux )
+{
+    /* Temporary state */
+    ogg_stream_state os;
+    ogg_sync_state oy;
+    ogg_page page;
+    demux_sys_t *p_sys = p_demux->p_sys;
+    int64_t i_pos, i_startpos, i_result, i_granule, i_lowerbound;
+    int64_t i_length = 0;
+    int64_t i_backup_pos = stream_Tell( p_demux->s );
+    int64_t i_upperbound = stream_Size( p_demux->s );
+    unsigned int i_backoffset = OGGSEEK_BYTES_TO_READ;
+    assert( OGGSEEK_BYTES_TO_READ < UINT_MAX );
+
+    const char *buffer;
+
+    ogg_stream_init( &os, -1 );
+    ogg_sync_init( &oy );
+
+    /* Try to lookup last granule from each logical stream */
+    i_lowerbound = stream_Size( p_demux->s ) - p_sys->i_streams * MAX_PAGE_SIZE * 2;
+    i_lowerbound = __MAX( 0, i_lowerbound );
+
+    i_pos = i_startpos = __MAX( i_lowerbound, i_upperbound - i_backoffset );
+
+    if ( stream_Seek( p_demux->s, i_pos ) )
+    {
+        ogg_sync_clear( &oy );
+        ogg_stream_clear( &os );
+        return;
+    }
+
+    while( i_pos >= i_lowerbound )
+    {
+
+        while( i_pos < i_upperbound )
+        {
+            if ( oy.unsynced )
+                i_result = ogg_sync_pageseek( &oy, &page );
+
+            buffer = ogg_sync_buffer( &oy, OGGSEEK_BYTES_TO_READ );
+            if ( buffer == NULL ) goto clean;
+            i_result = stream_Read( p_demux->s, (void*) buffer, OGGSEEK_BYTES_TO_READ );
+            if ( i_result < 1 ) goto clean;
+            i_pos += i_result;
+            ogg_sync_wrote( &oy, i_result );
+
+            while( ogg_sync_pageout( &oy, &page ) == 1 )
+            {
+                i_granule = ogg_page_granulepos( &page );
+                if ( i_granule == -1 ) continue;
+
+                for ( int i=0; i< p_sys->i_streams; i++ )
+                {
+                    if ( p_sys->pp_stream[i]->i_serial_no != ogg_page_serialno( &page ) )
+                        continue;
+
+                    i_length = Oggseek_GranuleToAbsTimestamp( p_sys->pp_stream[i], i_granule, false );
+                    p_sys->i_length = __MAX( p_sys->i_length, i_length / 1000000 );
+                    break;
+                }
+            }
+            if ( i_length > 0 ) break;
+        }
 
+        /* We found at least a page with valid granule */
+        if ( i_length > 0 ) break;
 
+        /* Otherwise increase read size, starting earlier */
+        if ( i_backoffset <= ( UINT_MAX >> 1 ) )
+        {
+            i_backoffset <<= 1;
+            i_startpos = i_upperbound - i_backoffset;
+        }
+        else
+        {
+            i_startpos -= i_backoffset;
+        }
+        i_pos = i_startpos;
 
-/* Find the first first ogg page for p_stream between offsets i_pos1 and i_pos2,
-   return file offset in bytes; -1 is returned on failure */
+        if ( stream_Seek( p_demux->s, i_pos ) )
+            break;
+    }
+
+clean:
+    stream_Seek( p_demux->s, i_backup_pos );
+
+    ogg_sync_clear( &oy );
+    ogg_stream_clear( &os );
+}
 
-static int64_t find_first_page( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2,
+
+static int64_t find_first_page_granule( demux_t *p_demux,
+                                int64_t i_pos1, int64_t i_pos2,
                                 logical_stream_t *p_stream,
-                                int64_t *pi_kframe, int64_t *pi_frame )
+                                int64_t *i_granulepos )
 {
     int64_t i_result;
-    int64_t i_granulepos;
+    *i_granulepos = -1;
     int64_t i_bytes_to_read = i_pos2 - i_pos1 + 1;
     int64_t i_bytes_read;
     int64_t i_pages_checked = 0;
@@ -255,14 +344,7 @@ static int64_t find_first_page( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2
     seek_byte( p_demux, i_pos1 );
 
     if ( i_pos1 == p_stream->i_data_start )
-    {
-        /* set a dummy granulepos at data_start */
-        *pi_kframe = p_stream->i_keyframe_offset;
-        *pi_frame = p_stream->i_keyframe_offset;
-
-        p_sys->b_page_waiting = true;
         return p_sys->i_input_position;
-    }
 
     if ( i_bytes_to_read > OGGSEEK_BYTES_TO_READ ) i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
 
@@ -272,7 +354,6 @@ static int64_t find_first_page( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2
         if ( p_sys->i_input_position >= i_pos2 )
         {
             /* we reached the end and found no pages */
-            *pi_frame=-1;
             return -1;
         }
 
@@ -280,7 +361,6 @@ static int64_t find_first_page( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2
         if ( ! ( i_bytes_read = get_data( p_demux, i_bytes_to_read ) ) )
         {
             /* EOF */
-            *pi_frame = -1;
             return -1;
         }
 
@@ -316,7 +396,6 @@ static int64_t find_first_page( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2
         if ( p_sys->i_input_position >= i_pos2 )
         {
             /* reached the end of the search region and nothing was found */
-            *pi_frame = -1;
             return p_sys->i_input_position;
         }
 
@@ -325,45 +404,37 @@ static int64_t find_first_page( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2
         if ( ! ( i_result = oggseek_read_page( p_demux ) ) )
         {
             /* EOF */
-            *pi_frame = -1;
             return p_sys->i_input_position;
         }
 
+        if ( ogg_page_granulepos( &p_sys->current_page ) <= 0 )
+        {
+            p_sys->i_input_position += i_result;
+            i_bytes_to_read += OGGSEEK_BYTES_TO_READ;
+            continue;
+        }
+
         // found a page
-        if ( p_stream->os.serialno != ogg_page_serialno( &p_sys->current_page ) )
+        if ( ogg_stream_pagein( &p_stream->os, &p_sys->current_page ) != 0 )
         {
-            /* page is not for this stream */
+            /* page is not for this stream or incomplete */
             p_sys->i_input_position += i_result;
             if ( ! i_pages_checked ) i_pos1 = p_sys->i_input_position;
             continue;
         }
 
-
-        ogg_stream_pagein( &p_stream->os, &p_sys->current_page );
-
         i_pages_checked++;
         i_packets_checked = 0;
 
-        if ( ogg_stream_packetout( &p_stream->os, &op ) > 0 )
+        while ( ogg_stream_packetout( &p_stream->os, &op ) > 0 )
         {
             i_packets_checked++;
         }
 
         if ( i_packets_checked )
         {
-            i_granulepos = ogg_page_granulepos( &p_sys->current_page );
-
-            oggseek_theora_index_entry_add( p_stream, i_granulepos, i_pos1 );
-
-            *pi_kframe =
-                i_granulepos >> p_stream->i_granule_shift;
-
-            *pi_frame = *pi_kframe +
-                i_granulepos - ( *pi_kframe << p_stream->i_granule_shift );
-
-            p_sys->b_page_waiting = true;
+            *i_granulepos = ogg_page_granulepos( &p_sys->current_page );
             return i_pos1;
-
         }
 
         /*  -> start of next page */
@@ -371,242 +442,394 @@ static int64_t find_first_page( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2
     }
 }
 
+/* Checks if current packet matches codec keyframe */
+bool Ogg_IsKeyFrame( logical_stream_t *p_stream, ogg_packet *p_packet )
+{
+    if ( p_stream->b_oggds )
+    {
+        return ( p_packet->bytes > 0 && p_packet->packet[0] & PACKET_IS_SYNCPOINT );
+    }
+    else switch ( p_stream->fmt.i_codec )
+    {
+    case VLC_CODEC_THEORA:
+    case VLC_CODEC_DAALA: /* Same convention used in daala */
+        if ( p_packet->bytes <= 0 || p_packet->packet[0] & THEORA_FTYPE_NOTDATA )
+            return false;
+        else
+            return !( p_packet->packet[0] & THEORA_FTYPE_INTERFRAME );
+    case VLC_CODEC_VP8:
+        return ( ( ( p_packet->granulepos >> 3 ) & 0x07FFFFFF ) == 0 );
+    case VLC_CODEC_DIRAC:
+        return ( p_packet->granulepos & 0xFF8000FF );
+    default:
+        return true;
+    }
+}
+
+int64_t Ogg_GetKeyframeGranule( logical_stream_t *p_stream, int64_t i_granule )
+{
+    if ( p_stream->b_oggds )
+    {
+           return -1; /* We have no way to know */
+    }
+    else if( p_stream->fmt.i_codec == VLC_CODEC_THEORA ||
+             p_stream->fmt.i_codec == VLC_CODEC_DAALA )
+    {
+        return ( i_granule >> p_stream->i_granule_shift ) << p_stream->i_granule_shift;
+    }
+    else if( p_stream->fmt.i_codec == VLC_CODEC_DIRAC )
+    {
+        return ( i_granule >> 31 ) << 31;
+    }
+    /* No change, that's keyframe or it can't be shifted out (oggds) */
+    return i_granule;
+}
 
+static bool OggSeekToPacket( demux_t *p_demux, logical_stream_t *p_stream,
+            int64_t i_granulepos, packetStartCoordinates *p_lastpacketcoords,
+            bool b_exact )
+{
+    ogg_packet op;
+    demux_sys_t *p_sys  = p_demux->p_sys;
+    if ( ogg_stream_pagein( &p_stream->os, &p_sys->current_page ) != 0 )
+        return false;
+    p_sys->b_page_waiting = true;
+    int i=0;
 
+    int64_t itarget_frame = Ogg_GetKeyframeGranule( p_stream, i_granulepos );
+    int64_t iframe = Ogg_GetKeyframeGranule( p_stream, ogg_page_granulepos( &p_sys->current_page ) );
 
+    if ( ! ogg_page_continued( &p_sys->current_page ) )
+    {
+        /* Start of frame, not continued page, but no packet. */
+        p_lastpacketcoords->i_pageno = ogg_page_pageno( &p_sys->current_page );
+        p_lastpacketcoords->i_pos = p_sys->i_input_position;
+        p_lastpacketcoords->i_skip = 0;
+    }
 
+    if ( b_exact && iframe > itarget_frame )
+    {
+        while( ogg_stream_packetout( &p_stream->os, &op ) > 0 ) {};
+        p_sys->b_page_waiting = false;
+        return false;
+    }
 
+    while( ogg_stream_packetpeek( &p_stream->os, &op ) > 0 )
+    {
+        if ( ( !b_exact || itarget_frame == iframe ) && Ogg_IsKeyFrame( p_stream, &op ) )
+        {
+            OggDebug(
+                msg_Dbg(p_demux, "** KEYFRAME **" );
+                msg_Dbg(p_demux, "** KEYFRAME PACKET START pageno %"PRId64" OFFSET %"PRId64" skip %"PRId64" **", p_lastpacketcoords->i_pageno, p_lastpacketcoords->i_pos, p_lastpacketcoords->i_skip );
+                msg_Dbg(p_demux, "KEYFRAME PACKET IS at pageno %"PRId64" OFFSET %"PRId64" with skip %d packet (%d / %d) ",
+                    ogg_page_pageno( &p_sys->current_page ), p_sys->i_input_position, i, i+1, ogg_page_packets( &p_sys->current_page ) );
+                DemuxDebug( p_sys->b_seeked = true; )
+            );
+
+            if ( i != 0 ) /* Not continued packet */
+            {
+                /* We need to handle the case when the packet spans onto N
+                       previous page(s). packetout() will be valid only when
+                       all segments are assembled.
+                       Keyframe flag is only available after assembling last part
+                       (when packetout() becomes valid). We have no way to guess
+                       keyframe at earlier time.
+                    */
+                p_lastpacketcoords->i_pageno = ogg_page_pageno( &p_sys->current_page );
+                p_lastpacketcoords->i_pos = p_sys->i_input_position;
+                p_lastpacketcoords->i_skip = i;
+            }
+            return true;
+        }
 
+        p_lastpacketcoords->i_pageno = ogg_page_pageno( &p_sys->current_page );
+        p_lastpacketcoords->i_pos = p_sys->i_input_position;
+        p_lastpacketcoords->i_skip = i + 1;
+        i++;
+        /* remove that packet and go sync to next */
+        ogg_stream_packetout( &p_stream->os, &op );
+    }
 
-/* Find the last frame for p_stream,
-   -1 is returned on failure */
+    return false;
+}
 
-static int64_t find_last_frame (demux_t *p_demux, logical_stream_t *p_stream)
+static int64_t OggForwardSeekToFrame( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2,
+                logical_stream_t *p_stream, int64_t i_granulepos, bool b_fastseek )
 {
-
-    int64_t i_page_pos;
-    int64_t i_start_pos;
-    int64_t i_frame = -1;
-    int64_t i_last_frame = -1;
-    int64_t i_kframe = 0;
-    int64_t i_pos1;
-    int64_t i_pos2;
-    int64_t i_serialno;
+    int64_t i_result;
+    int64_t i_bytes_to_read;
+    int64_t i_bytes_read;
 
     demux_sys_t *p_sys  = p_demux->p_sys;
 
-    i_pos1 = p_stream->i_data_start;
-    i_pos2 = p_sys->i_total_length;
-    i_serialno = p_stream->os.serialno;
-
-    i_start_pos = i_pos2 - OGGSEEK_BYTES_TO_READ;
+    i_bytes_to_read = i_pos2 - i_pos1 + 1;
+    seek_byte( p_demux, i_pos1 );
+    if ( i_bytes_to_read > OGGSEEK_BYTES_TO_READ ) i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
 
+    OggDebug(
+        msg_Dbg( p_demux, "Probing Fwd %"PRId64" %"PRId64" for granule %"PRId64,
+        i_pos1, i_pos2, i_granulepos );
+    );
 
-    while( 1 )
+    while ( 1 )
     {
-        if ( i_start_pos < i_pos1 ) i_start_pos = i_pos1;
 
-        i_page_pos = find_first_page( p_demux, i_start_pos, i_pos2, p_stream, &i_kframe, &i_frame );
+        if ( p_sys->i_input_position >= i_pos2 )
+            return SEGMENT_NOT_FOUND;
 
-        if ( i_frame == -1 )
-        {
-            /* no pages found in range */
-            if ( i_last_frame >= 0 )
-            {
-                /* No more pages in range -> return last one */
-                return i_last_frame;
-            }
-            if ( i_start_pos <= i_pos1 )
-            {
-                return -1;
-            }
+        /* read next chunk */
+        if ( ! ( i_bytes_read = get_data( p_demux, i_bytes_to_read ) ) )
+            return SEGMENT_NOT_FOUND;
 
-            /* Go back a bit */
-            i_pos2 -= i_start_pos;
-            i_start_pos -= OGGSEEK_BYTES_TO_READ;
-            if ( i_start_pos < i_pos1 ) i_start_pos = i_pos1;
-            i_pos2 += i_start_pos;
-        }
-        else
+        i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
+
+        i_result = ogg_sync_pageseek( &p_sys->oy, &p_sys->current_page );
+
+        if ( i_result < 0 )
         {
-            /* found a page, see if we can find another one */
-            i_last_frame = i_frame;
-            i_start_pos = i_page_pos + 1;
+            /* found a page, sync to page start */
+            p_sys->i_input_position -= i_result;
+            i_pos1 = p_sys->i_input_position;
+            continue;
         }
-    }
-    return -1;
-}
-
 
+        if ( i_result > 0 || ( i_result == 0 && p_sys->oy.fill > 3 &&
+                               ! strncmp( (char *)p_sys->oy.data, "OggS" , 4 ) ) )
+        {
+            i_pos1 = p_sys->i_input_position;
+            break;
+        }
 
+        p_sys->i_input_position += i_bytes_read;
+    };
 
+    seek_byte( p_demux, p_sys->i_input_position );
+    ogg_stream_reset( &p_stream->os );
 
+    ogg_packet op;
+    while( ogg_stream_packetout( &p_stream->os, &op ) > 0 ) {};
 
-/* convert a theora frame to a granulepos */
+    packetStartCoordinates lastpacket = { -1, -1, -1 };
 
-static inline int64_t frame_to_gpos( logical_stream_t *p_stream, int64_t i_kframe,
-                                     int64_t i_frame )
-{
-    if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
+    while( 1 )
     {
-        return ( i_kframe << p_stream->i_granule_shift ) + ( i_frame - i_kframe );
-    }
 
-    return i_kframe;
-}
+        if ( p_sys->i_input_position >= i_pos2 )
+        {
+            /* reached the end of the search region and nothing was found */
+            break;
+        }
 
+        p_sys->b_page_waiting = false;
 
+        if ( ! ( i_result = oggseek_read_page( p_demux ) ) )
+        {
+            /* EOF */
+            break;
+        }
 
+        // found a page
+        if ( p_stream->os.serialno != ogg_page_serialno( &p_sys->current_page ) )
+        {
+            /* page is not for this stream */
+            p_sys->i_input_position += i_result;
+            continue;
+        }
 
+        if ( OggSeekToPacket( p_demux, p_stream, i_granulepos, &lastpacket, b_fastseek ) )
+        {
+            p_sys->i_input_position = lastpacket.i_pos;
+            p_stream->i_skip_frames = 0;
+            return p_sys->i_input_position;
+        }
 
-/* seek to a suitable point to begin decoding for i_tframe. We can pre-set bounding positions
-   i_pos_lower and i_pos_higher to narrow the search domain. */
+        /*  -> start of next page */
+        p_sys->i_input_position += i_result;
+    }
 
+    return SEGMENT_NOT_FOUND;
+}
 
-static int64_t ogg_seek( demux_t *p_demux, logical_stream_t *p_stream, int64_t i_tframe,
-                         int64_t i_pos_lower, int64_t i_pos_upper, int64_t *pi_pagepos,
-                         bool b_exact )
+static int64_t OggBackwardSeekToFrame( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2,
+                               logical_stream_t *p_stream, int64_t i_granulepos )
 {
-    /* For theora:
-     * We do two passes here, first with b_exact set, then with b_exact unset.
-     *
-     * If b_exact is set, we find the highest granulepos <= the target granulepos
-     * from this we extract an estimate of the keyframe (note that there could be other
-     * "hidden" keyframes between the found granulepos and the target).
-     *
-     * On the second pass we find the highest granulepos < target. This places us just before or
-     * at the start of the target keyframe.
-     *
-     * When we come to decode, we start from this second position, discarding any completed
-     * packets on that page, and read pages discarding packets until we get to the target frame.
-     *
-     * The function returns the granulepos which is found,
-     * sets the page offset in pi_pagepos. -1 is returned on error.
-     *
-     * for dirac:
-     *
-     * we find the highest sync frame <= target frame, and return the sync_frame number
-     * b_exact should be set to true
-     *
-     *
-     * the method used is bi-sections:
-     *  - we check the lower keyframe
-     * if this is == target we return
-     * if > target, or we find no keyframes, we go to the lower segment
-     * if < target we divide the segment in two and check the upper half
-     *
-     * This is then repeated until the segment size is too small to hold a packet,
-     * at which point we return our best match
-     *
-     * Two optimisations are made: - anything we discover about keyframes is added to our index
-     * - before calling this function we get approximate bounds from the index
-     *
-     * therefore, subsequent searches become more rapid.
-     *
-     */
+    int64_t i_result;
+    int64_t i_offset = __MAX( 1 + ( (i_pos2 - i_pos1) >> 1 ), OGGSEEK_BYTES_TO_READ );
 
-    int64_t i_start_pos;
-    int64_t i_end_pos;
-    int64_t i_pagepos;
-    int64_t i_segsize;
-    int64_t i_frame;
-    int64_t i_kframe;
+restart:
 
-    int64_t i_best_kframe = -1;
-    int64_t i_best_frame = -1;
-    int64_t i_best_pagepos = -1;
+    OggDebug(
+        msg_Dbg( p_demux, "Probing Back %"PRId64" %"PRId64" for granule %"PRId64,
+        i_pos1, i_pos2, i_granulepos );
+    );
 
-    demux_sys_t *p_sys  = p_demux->p_sys;
+    i_result = OggForwardSeekToFrame( p_demux, i_pos1, i_pos2, p_stream,
+                                      i_granulepos, true );
 
-    if ( i_tframe < p_stream->i_keyframe_offset )
+    if ( i_result == SEGMENT_NOT_FOUND && i_pos1 > p_stream->i_data_start )
     {
-        *pi_pagepos = p_stream->i_data_start;
-
-        if ( ! b_exact ) {
-            seek_byte( p_demux, p_stream->i_data_start );
-            return frame_to_gpos( p_stream, p_stream->i_keyframe_offset, 1 );
-        }
-        return frame_to_gpos( p_stream, p_stream->i_keyframe_offset, 0 );
+        i_pos1 = __MAX( p_stream->i_data_start, i_pos1 - i_offset );
+        goto restart;
     }
 
-    if ( i_pos_lower < p_stream->i_data_start )
+    return i_result;
+}
+
+/* Dont use b_presentation with frames granules ! */
+int64_t Oggseek_GranuleToAbsTimestamp( logical_stream_t *p_stream,
+                                       int64_t i_granule, bool b_presentation )
+{
+    int64_t i_timestamp = -1;
+    if ( i_granule < 1 )
+        return -1;
+
+    if ( p_stream->b_oggds )
     {
-        i_pos_lower = p_stream->i_data_start;
+        if ( b_presentation ) i_granule--;
+        i_timestamp = i_granule * CLOCK_FREQ / p_stream->f_rate;
     }
-
-    if ( i_pos_upper < 0 )
+    else  switch( p_stream->fmt.i_codec )
+    {
+    case VLC_CODEC_THEORA:
+    case VLC_CODEC_DAALA:
+    case VLC_CODEC_KATE:
     {
-        i_pos_upper = p_sys->i_total_length;
+        ogg_int64_t iframe = i_granule >> p_stream->i_granule_shift;
+        ogg_int64_t pframe = i_granule - ( iframe << p_stream->i_granule_shift );
+        /* See Theora A.2.3 */
+        if ( b_presentation ) pframe -= p_stream->i_keyframe_offset;
+        i_timestamp = ( iframe + pframe ) * CLOCK_FREQ / p_stream->f_rate;
+        break;
     }
-
-    if ( i_pos_upper > p_sys->i_total_length )
+    case VLC_CODEC_VP8:
+    {
+        ogg_int64_t frame = i_granule >> p_stream->i_granule_shift;
+        if ( b_presentation ) frame--;
+        i_timestamp = frame * CLOCK_FREQ / p_stream->f_rate;
+        break;
+    }
+    case VLC_CODEC_DIRAC:
+    {
+        ogg_int64_t i_dts = i_granule >> 31;
+        ogg_int64_t delay = (i_granule >> 9) & 0x1fff;
+        /* NB, OggDirac granulepos values are in units of 2*picturerate */
+        double f_rate = p_stream->f_rate;
+        if ( !p_stream->special.dirac.b_interlaced ) f_rate *= 2;
+        if ( b_presentation ) i_dts += delay;
+        i_timestamp = i_dts * CLOCK_FREQ / f_rate;
+        break;
+    }
+    case VLC_CODEC_OPUS:
     {
-        i_pos_upper = p_sys->i_total_length;
+        if ( b_presentation ) return VLC_TS_INVALID;
+        i_timestamp = ( i_granule - p_stream->i_pre_skip ) * CLOCK_FREQ / 48000;
+        break;
     }
+    case VLC_CODEC_VORBIS:
+    case VLC_CODEC_FLAC:
+    {
+        if ( b_presentation ) return VLC_TS_INVALID;
+        i_timestamp = i_granule * CLOCK_FREQ / p_stream->f_rate;
+        break;
+    }
+    case VLC_CODEC_SPEEX:
+    {
+        if ( b_presentation )
+            i_granule -= p_stream->special.speex.i_framesize *
+                         p_stream->special.speex.i_framesperpacket;
+        i_timestamp = i_granule * CLOCK_FREQ / p_stream->f_rate;
+        break;
+    }
+    }
+
+    return i_timestamp;
+}
+
+/* returns pos */
+static int64_t OggBisectSearchByTime( demux_t *p_demux, logical_stream_t *p_stream,
+            int64_t i_targettime, int64_t i_pos_lower, int64_t i_pos_upper)
+{
+    int64_t i_start_pos;
+    int64_t i_end_pos;
+    int64_t i_segsize;
+
+    struct
+    {
+        int64_t i_pos;
+        int64_t i_timestamp;
+        int64_t i_granule;
+    } bestlower = { p_stream->i_data_start, -1, -1 },
+      current = { -1, -1, -1 },
+      lowestupper = { -1, -1, -1 };
+
+    demux_sys_t *p_sys  = p_demux->p_sys;
+
+    i_pos_lower = __MAX( i_pos_lower, p_stream->i_data_start );
+    i_pos_upper = __MIN( i_pos_upper, p_sys->i_total_length );
+    if ( i_pos_upper < 0 ) i_pos_upper = p_sys->i_total_length;
 
     i_start_pos = i_pos_lower;
     i_end_pos = i_pos_upper;
 
     i_segsize = ( i_end_pos - i_start_pos + 1 ) >> 1;
+    i_start_pos += i_segsize;
+
+    OggDebug( msg_Dbg(p_demux, "Bisecting for time=%"PRId64" between %"PRId64" and %"PRId64,
+            i_targettime, i_pos_lower, i_pos_upper ) );
 
     do
     {
         /* see if the frame lies in current segment */
-        if ( i_start_pos < i_pos_lower )
-        {
-            i_start_pos = i_pos_lower;
-        }
-        if ( i_end_pos > i_pos_upper )
-        {
-            i_end_pos = i_pos_upper;
-        }
+        i_start_pos = __MAX( i_start_pos, i_pos_lower );
+        i_end_pos = __MIN( i_end_pos, i_pos_upper );
 
         if ( i_start_pos >= i_end_pos )
         {
             if ( i_start_pos == i_pos_lower)
             {
-                if ( ! b_exact ) seek_byte( p_demux, i_start_pos );
-                *pi_pagepos = i_start_pos;
-                return frame_to_gpos( p_stream, p_stream->i_keyframe_offset, 1 );
+                return i_start_pos;
             }
-            break;
+            return -1;
         }
 
-        if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
+
+        current.i_pos = find_first_page_granule( p_demux,
+                                                 i_start_pos, i_end_pos,
+                                                 p_stream,
+                                                 &current.i_granule );
+
+        current.i_timestamp = Oggseek_GranuleToAbsTimestamp( p_stream,
+                                                             current.i_granule, false );
+
+        if ( current.i_timestamp == -1 && current.i_granule > 0 )
         {
-            i_pagepos = find_first_page( p_demux, i_start_pos, i_end_pos, p_stream,
-                                         &i_kframe, &i_frame );
+            msg_Err( p_demux, "Unmatched granule. New codec ?" );
+            return -1;
+        }
+        else if ( current.i_timestamp < -1 )  /* due to preskip with some codecs */
+        {
+            current.i_timestamp = 0;
         }
-        else return -1;
 
-        if ( i_pagepos != -1 && i_kframe != -1 )
+        if ( current.i_pos != -1 && current.i_granule != -1 )
         {
             /* found a page */
 
-            if ( b_exact && i_frame >= i_tframe && i_kframe <= i_tframe )
+            if ( current.i_timestamp <= i_targettime )
             {
-                /* got it ! */
-                *pi_pagepos = i_start_pos;
-                return frame_to_gpos( p_stream, i_kframe, i_frame );
+                /* set our lower bound */
+                if ( current.i_timestamp > bestlower.i_timestamp )
+                    bestlower = current;
+                i_start_pos = current.i_pos;
             }
-
-            if ( ( i_kframe < i_tframe || ( b_exact && i_kframe == i_tframe ) )
-                 && i_kframe > i_best_kframe )
-            {
-                i_best_kframe = i_kframe;
-                i_best_frame = i_frame;
-                i_best_pagepos = i_pagepos;
-            }
-
-            if ( i_frame >= i_tframe )
+            else if ( current.i_timestamp > i_targettime )
             {
+                if ( lowestupper.i_timestamp == -1 || current.i_timestamp < lowestupper.i_timestamp )
+                    lowestupper = current;
                 /* check lower half of segment */
                 i_start_pos -= i_segsize;
                 i_end_pos -= i_segsize;
             }
-
-            else i_start_pos = i_pagepos;
-
         }
         else
         {
@@ -615,247 +838,192 @@ static int64_t ogg_seek( demux_t *p_demux, logical_stream_t *p_stream, int64_t i
             i_start_pos -= i_segsize;
         }
 
+        OggDebug( msg_Dbg(p_demux, "Bisect restart i_segsize=%"PRId64" between %"PRId64
+                                   " and %"PRId64 " bl %"PRId64" lu %"PRId64,
+                i_segsize, i_start_pos, i_end_pos, bestlower.i_granule, lowestupper.i_granule  ) );
+
         i_segsize = ( i_end_pos - i_start_pos + 1 ) >> 1;
         i_start_pos += i_segsize;
 
     } while ( i_segsize > 64 );
 
-    if ( i_best_kframe >- 1 )
+    if ( bestlower.i_granule == -1 )
     {
-        if ( !b_exact )
-        {
-            seek_byte( p_demux, i_best_pagepos );
-        }
-        *pi_pagepos = i_best_pagepos;
-        return frame_to_gpos( p_stream, i_best_kframe, i_best_frame );
+        if ( lowestupper.i_granule == -1 )
+            return -1;
+        else
+            bestlower = lowestupper;
     }
 
-    return -1;
-}
-
-
-
-
-
-
-/* find upper and lower pagepos for i_tframe; if we find an exact match, we return it */
-
-static demux_index_entry_t *get_bounds_for ( logical_stream_t *p_stream, int64_t i_tframe,
-                                             int64_t *pi_pos_lower, int64_t *pi_pos_upper)
-{
-    int64_t i_kframe;
-    int64_t i_frame;
-    int64_t i_gpos;
-
-    demux_index_entry_t *idx = p_stream->idx;
-
-    *pi_pos_lower = *pi_pos_upper = -1;
-
-    while ( idx != NULL )
+    if ( p_stream->b_oggds )
     {
-
-        if ( idx-> i_pagepos < 0 )
-        {
-            /* kframe was found to be invalid */
-            idx = idx->p_next;
-            continue;
-        }
-
-        if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
-        {
-            i_gpos = idx->i_value;
-            i_kframe = i_gpos >> p_stream->i_granule_shift;
-            i_frame = i_kframe + i_gpos - ( i_kframe << p_stream->i_granule_shift );
-        }
-        else return NULL;
-
-
-        if ( i_kframe > i_tframe )
-        {
-            *pi_pos_upper = idx->i_pagepos;
-            return NULL;
-        }
-
-        if ( i_frame < i_tframe )
-        {
-            *pi_pos_lower = idx->i_pagepos;
-            idx = idx->p_next;
-            continue;
-        }
-
-        return idx;
+        int64_t a = OggBackwardSeekToFrame( p_demux,
+                __MAX ( bestlower.i_pos - OGGSEEK_BYTES_TO_READ, p_stream->i_data_start ),
+                bestlower.i_pos,
+                p_stream, bestlower.i_granule /* unused */ );
+        return a;
     }
+    /* If not each packet is usable as keyframe, query the codec for keyframe */
+    else if ( Ogg_GetKeyframeGranule( p_stream, bestlower.i_granule ) != bestlower.i_granule )
+    {
+        int64_t i_keyframegranule = Ogg_GetKeyframeGranule( p_stream, bestlower.i_granule );
 
-    return NULL;
-}
-
-
-/* get highest frame in theora and opus streams */
-
-static int64_t get_last_frame ( demux_t *p_demux, logical_stream_t *p_stream )
-{
-    demux_sys_t *p_sys  = p_demux->p_sys;
-    int64_t i_frame;
-
-    i_frame = find_last_frame ( p_demux, p_stream );
+        OggDebug( msg_Dbg( p_demux, "Need to reseek to keyframe (%"PRId64") granule (%"PRId64"!=%"PRId64") to t=%"PRId64,
+                           i_keyframegranule >> p_stream->i_granule_shift,
+                           bestlower.i_granule,
+                           i_pos_upper,
+                           Oggseek_GranuleToAbsTimestamp( p_stream, i_keyframegranule, false ) ) );
 
-    /* We need to reset back to the start here, otherwise packets cannot be decoded.
-     * I think this is due to the fact that we seek to the end and then we must reset
-     * all logical streams, which causes remaining headers not to be read correctly.
-     * Seeking to 0 is the only value which seems to work, and it appears to have no
-     * adverse effects. */
+        OggDebug( msg_Dbg( p_demux, "Seeking back to %"PRId64, __MAX ( bestlower.i_pos - OGGSEEK_BYTES_TO_READ, p_stream->i_data_start ) ) );
 
-    seek_byte( p_demux, 0 );
-    /* Reset stream states */
-    p_sys->i_streams = 0;
-    p_stream->i_serial_no = ogg_page_serialno( &p_sys->current_page );
-    ogg_stream_init( &p_stream->os, p_stream->i_serial_no );
-    ogg_stream_pagein( &p_stream->os, &p_sys->current_page );
+        int64_t a = OggBackwardSeekToFrame( p_demux,
+            __MAX ( bestlower.i_pos - OGGSEEK_BYTES_TO_READ, p_stream->i_data_start ),
+            stream_Size( p_demux->s ), p_stream, i_keyframegranule );
+        return a;
+    }
 
-    return i_frame;
+    return bestlower.i_pos;
 }
 
 
-
 /************************************************************************
  * public functions
  *************************************************************************/
 
-
-
-
-/* return highest frame number for p_stream (which must be a theora, dirac or opus stream) */
-
-int64_t oggseek_get_last_frame ( demux_t *p_demux, logical_stream_t *p_stream )
+int Oggseek_BlindSeektoAbsoluteTime( demux_t *p_demux, logical_stream_t *p_stream,
+                                     int64_t i_time, bool b_fastseek )
 {
-    int64_t i_frame = -1;
+    demux_sys_t *p_sys  = p_demux->p_sys;
+    int64_t i_lowerpos = -1;
+    int64_t i_upperpos = -1;
+    bool b_found = false;
 
-    if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA ||
-         p_stream->fmt.i_codec == VLC_CODEC_VORBIS ||
-         p_stream->fmt.i_codec == VLC_CODEC_OPUS )
-    {
-        i_frame = get_last_frame ( p_demux, p_stream );
+    /* Search in skeleton */
+    Ogg_GetBoundsUsingSkeletonIndex( p_stream, i_time, &i_lowerpos, &i_upperpos );
+    if ( i_lowerpos != -1 ) b_found = true;
 
-        if ( i_frame < 0 ) return -1;
-        return i_frame;
+    /* And also search in our own index */
+    if ( !b_found && OggSeekIndexFind( p_stream, i_time, &i_lowerpos, &i_upperpos ) )
+    {
+        b_found = true;
     }
 
-    /* unhandled video format */
-    return -1;
-}
-
+    /* Or try to be smart with audio fixed bitrate streams */
+    if ( !b_found && p_stream->fmt.i_cat == AUDIO_ES && p_sys->i_streams == 1
+         && p_sys->i_bitrate && Ogg_GetKeyframeGranule( p_stream, 0xFF00FF00 ) == 0xFF00FF00 )
+    {
+        /* But only if there's no keyframe/preload requirements */
+        /* FIXME: add function to get preload time by codec, ex: opus */
+        i_lowerpos = i_time * p_sys->i_bitrate / INT64_C(8000000);
+        b_found = true;
+    }
 
+    /* or search */
+    if ( !b_found && b_fastseek )
+    {
+        i_lowerpos = OggBisectSearchByTime( p_demux, p_stream, i_time,
+                                            p_stream->i_data_start, p_sys->i_total_length );
+        b_found = ( i_lowerpos != -1 );
+    }
 
+    if ( !b_found ) return -1;
 
+    if ( i_lowerpos < p_stream->i_data_start || i_upperpos > p_sys->i_total_length )
+        return -1;
 
+    /* And really do seek */
+    p_sys->i_input_position = i_lowerpos;
+    seek_byte( p_demux, p_sys->i_input_position );
+    ogg_stream_reset( &p_stream->os );
 
-/* seek to target frame in p_stream; actually we will probably end up just before it
- *   (so we set skip)
- *
- * range for i_tframe is 0 -> p_sys->i_total_frames - 1
- */
+    return i_lowerpos;
+}
 
-int oggseek_find_frame ( demux_t *p_demux, logical_stream_t *p_stream, int64_t i_tframe )
+int Oggseek_BlindSeektoPosition( demux_t *p_demux, logical_stream_t *p_stream,
+                                 double f, bool b_canfastseek )
 {
-
-    const demux_index_entry_t *fidx;
-
-    /* lower and upper bounds for search domain */
-    int64_t i_pos_lower;
-    int64_t i_pos_upper;
-
-    int64_t i_granulepos;
+    OggDebug( msg_Dbg( p_demux, "=================== Seeking To Blind Pos" ) );
+    int64_t i_size = stream_Size( p_demux->s );
+    int64_t i_granule;
     int64_t i_pagepos;
 
-    /* keyframe for i_tframe ( <= i_tframe ) */
-    int64_t i_kframe;
+    i_size = find_first_page_granule( p_demux,
+                                             i_size * f, i_size,
+                                             p_stream,
+                                             &i_granule );
 
-    /* keyframe for i_kframe ( <= i_kframe ) */
-    int64_t i_xkframe;
+    OggDebug( msg_Dbg( p_demux, "Seek start pos is %"PRId64" granule %"PRId64, i_size, i_granule ) );
 
-    /* next frame to be decoded ( >= i_xkframe ) */
-    int64_t i_cframe;
+    i_granule = Ogg_GetKeyframeGranule( p_stream, i_granule );
 
-    demux_sys_t *p_sys  = p_demux->p_sys;
-
-    i_tframe += p_stream->i_keyframe_offset;
-
-    i_cframe = i_tframe;
-    /* For Opus, seek back 80 ms before the target playback position. */
-    if ( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
+    if ( b_canfastseek )
     {
-        if ( i_tframe <= p_stream->i_pre_skip )
-            i_cframe = 0;
-        else if ( i_tframe < 80*48 )
-            i_cframe = 0;
-        else
-            i_cframe = i_tframe - 80*48;
+        /* Peek back until we meet a keyframe to start our decoding up to our
+         * final seek time */
+        i_pagepos = OggBackwardSeekToFrame( p_demux,
+                __MAX ( i_size - MAX_PAGE_SIZE, p_stream->i_data_start ),
+                __MIN ( i_size + MAX_PAGE_SIZE, p_demux->p_sys->i_total_length ),
+                p_stream, i_granule );
     }
-
-    /* reduce the search domain */
-    fidx = get_bounds_for( p_stream, i_cframe, &i_pos_lower, &i_pos_upper );
-
-    if ( fidx == NULL )
+    else
     {
-        /* no exact match found; search the domain for highest keyframe <= i_cframe */
-
-        i_granulepos = ogg_seek ( p_demux, p_stream, i_cframe, i_pos_lower, i_pos_upper,
-                                  &i_pagepos, true );
-        if ( i_granulepos == -1 )
-        {
-            return VLC_EGENERIC;
-        }
-
-    }
-    else {
-        i_granulepos = fidx->i_value;
+        /* Otherwise, we just sync to the next keyframe we meet */
+        i_pagepos = OggForwardSeekToFrame( p_demux,
+                __MAX ( i_size - MAX_PAGE_SIZE, p_stream->i_data_start ),
+                stream_Size( p_demux->s ),
+                p_stream, i_granule, false );
     }
 
-    if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
-    {
-        i_kframe = i_granulepos >> p_stream->i_granule_shift;
-        if ( i_kframe < p_stream->i_keyframe_offset )
-        {
-            i_kframe = p_stream->i_keyframe_offset;
-        }
-
-        /* we found a keyframe, but we don't know where its packet starts, so search for a
-           frame just before it */
-
-        /* reduce search domain */
-        get_bounds_for( p_stream, i_kframe-1, &i_pos_lower, &i_pos_upper );
+    OggDebug( msg_Dbg( p_demux, "=================== Seeked To %"PRId64" granule %"PRId64, i_pagepos, i_granule ) );
+    return i_pagepos;
+}
 
-        i_granulepos = ogg_seek( p_demux, p_stream, i_kframe-1, i_pos_lower, i_pos_upper,
-                                 &i_pagepos, false );
+int Oggseek_SeektoAbsolutetime( demux_t *p_demux, logical_stream_t *p_stream,
+                                int64_t i_time )
+{
+    demux_sys_t *p_sys  = p_demux->p_sys;
 
-        /* i_cframe will be the next frame we decode */
-        i_xkframe = i_granulepos >> p_stream->i_granule_shift;
-        i_cframe = i_xkframe + i_granulepos - ( i_xkframe << p_stream->i_granule_shift) + 1;
-
-        if ( p_sys->i_input_position == p_stream->i_data_start )
-        {
-            i_cframe = i_kframe = p_stream->i_keyframe_offset;
-        }
-        else
-        {
-            oggseek_theora_index_entry_add( p_stream, i_granulepos, p_sys->i_input_position );
-        }
+    OggDebug( msg_Dbg( p_demux, "=================== Seeking To Absolute Time %"PRId64, i_time ) );
+    int64_t i_offset_lower = -1;
+    int64_t i_offset_upper = -1;
 
+    if ( Ogg_GetBoundsUsingSkeletonIndex( p_stream, i_time, &i_offset_lower, &i_offset_upper ) )
+    {
+        /* Exact match */
+        OggDebug( msg_Dbg( p_demux, "Found keyframe at %"PRId64" using skeleton index", i_offset_lower ) );
+        if ( i_offset_lower == -1 ) i_offset_lower = p_stream->i_data_start;
+        p_sys->i_input_position = i_offset_lower;
+        seek_byte( p_demux, p_sys->i_input_position );
+        ogg_stream_reset( &p_stream->os );
+        return i_offset_lower;
     }
-    else return VLC_EGENERIC;
+    OggDebug( msg_Dbg( p_demux, "Search bounds set to %"PRId64" %"PRId64" using skeleton index", i_offset_lower, i_offset_upper ) );
 
-    p_stream->i_skip_frames = i_tframe - i_cframe;
+    OggNoDebug(
+        OggSeekIndexFind( p_stream, i_time, &i_offset_lower, &i_offset_upper )
+    );
 
-    ogg_stream_reset( &p_stream->os );
+    i_offset_lower = __MAX( i_offset_lower, p_stream->i_data_start );
+    i_offset_upper = __MIN( i_offset_upper, p_sys->i_total_length );
 
-    return VLC_SUCCESS;
+    int64_t i_pagepos = OggBisectSearchByTime( p_demux, p_stream, i_time,
+                                       i_offset_lower, i_offset_upper);
+    if ( i_pagepos >= 0 )
+    {
+        /* be sure to clear any state or read+pagein() will fail on same # */
+        ogg_stream_reset( &p_stream->os );
+        seek_byte( p_demux, p_sys->i_input_position );
+    }
+    /* Insert keyframe position into index */
+    OggNoDebug(
+    if ( i_pagepos >= p_stream->i_data_start )
+        OggSeek_IndexAdd( p_stream, i_time, i_pagepos )
+    );
+
+    OggDebug( msg_Dbg( p_demux, "=================== Seeked To %"PRId64" time %"PRId64, i_pagepos, i_time ) );
+    return i_pagepos;
 }
 
-
-
-
-
-
 /****************************************************************************
  * oggseek_read_page: Read a full Ogg page from the physical bitstream.
  ****************************************************************************
@@ -863,13 +1031,14 @@ int oggseek_find_frame ( demux_t *p_demux, logical_stream_t *p_stream, int64_t i
  * unless we are at the end of stream.
  *
  ****************************************************************************/
+
 int64_t oggseek_read_page( demux_t *p_demux )
 {
     demux_sys_t *p_ogg = p_demux->p_sys  ;
     uint8_t header[PAGE_HEADER_BYTES+255];
     int i_nsegs;
-    int i_in_pos;
     int i;
+    int64_t i_in_pos;
     int64_t i_result;
     int i_page_size;
     char *buf;
@@ -923,14 +1092,11 @@ int64_t oggseek_read_page( demux_t *p_demux )
 
     if ( ogg_sync_pageout( &p_ogg->oy, &p_ogg->current_page ) != 1 )
     {
-        msg_Err( p_demux , "Got invalid packet, read %"PRId64" of %i: %s",i_result,i_page_size,
-                 buf );
+        msg_Err( p_demux , "Got invalid packet, read %"PRId64" of %i: %s %"PRId64,
+                 i_result, i_page_size, buf, i_in_pos );
         return 0;
     }
 
-    p_sys->b_page_waiting = false;
-
     return i_result + PAGE_HEADER_BYTES + i_nsegs;
 }
 
-