>From c53a44281c8ea85564ed0b18df099597baa5b20e Mon Sep 17 00:00:00 2001
From: Bill C Riemers <docbill@hartnell.local>
Date: Tue, 17 Jun 2008 01:49:16 -0400
Subject: [PATCH] Enhancement: Use asynchronous reads and variable ranges for faster seeks.

---
 modules/access/http.c |  299 +++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 262 insertions(+), 37 deletions(-)

diff --git a/modules/access/http.c b/modules/access/http.c
index 0852e88..63d8a37 100644
--- a/modules/access/http.c
+++ b/modules/access/http.c
@@ -61,6 +61,14 @@
 static int  Open ( vlc_object_t * );
 static void Close( vlc_object_t * );
 
+/*
+ * The maximum amount of data to read instead of seeking.  This corresponds
+ * to about half a second of 480p divx video data.
+ */
+#define HTTP_MAX_SEEKREAD 0x20000L
+#define HTTP_MIN_READ_SIZE 0x1000L
+#define HTTP_MAX_READ_SIZE 0x200000L
+
 #define PROXY_TEXT N_("HTTP proxy")
 #define PROXY_LONGTEXT N_( \
     "HTTP proxy to be used It must be of the form " \
@@ -188,11 +196,17 @@ struct access_sys_t
     char       *psz_icy_genre;
     char       *psz_icy_title;
 
-    int i_remaining;
+    int64_t i_remaining;
+    int64_t i_request_itell1;
+    int64_t i_request_size1;
+    int64_t i_request_itell2;
+    int64_t i_request_size2;
+    int64_t i_readsize;
 
     bool b_seekable;
     bool b_reconnect;
     bool b_continuous;
+    bool b_persist;
     bool b_pace_control;
 
     vlc_array_t * cookies;
@@ -203,6 +217,7 @@ static int OpenWithCookies( vlc_object_t *p_this, vlc_array_t *cookies );
 
 /* */
 static ssize_t Read( access_t *, uint8_t *, size_t );
+static ssize_t ReadAvailable( access_t *, uint8_t *, size_t );
 static ssize_t ReadCompressed( access_t *, uint8_t *, size_t );
 static int Seek( access_t *, int64_t );
 static int Control( access_t *, int, va_list );
@@ -210,6 +225,8 @@ static int Control( access_t *, int, va_list );
 /* */
 static int Connect( access_t *, int64_t );
 static int Request( access_t *p_access, int64_t i_tell );
+static int SendRequest( access_t *, int64_t, int64_t );
+static int ReadResponse( access_t * );
 static void Disconnect( access_t * );
 
 /* Small Cookie utilities. Cookies support is partial. */
@@ -279,8 +296,14 @@ static int OpenWithCookies( vlc_object_t *p_this, vlc_array_t *cookies )
     p_sys->psz_icy_genre = NULL;
     p_sys->psz_icy_title = NULL;
     p_sys->i_remaining = 0;
-
+    p_sys->i_request_itell1 = 0;
+    p_sys->i_request_itell2 = 0;
+    p_sys->i_request_size1 = 0;
+    p_sys->i_request_size2 = 0;
+    p_sys->i_readsize = HTTP_MIN_READ_SIZE;
+    p_sys->b_persist = true;
     p_sys->cookies = saved_cookies;
+    p_access->info.i_size = 0;
 
     /* Parse URI - remove spaces */
     p = psz = strdup( p_access->psz_path );
@@ -334,7 +357,7 @@ static int OpenWithCookies( vlc_object_t *p_this, vlc_array_t *cookies )
                 if (proxies[0])
                 {
                     msg_Dbg(p_access, "libproxy suggest to use '%s'", proxies[0]);
-                    if(strcmp(proxies[0],"direct://") != 0) 
+                    if(strcmp(proxies[0],"direct://") != 0)
                     {
                         p_sys->b_proxy = true;
                         vlc_UrlParse( &p_sys->proxy, proxies[0], 0);
@@ -409,6 +432,7 @@ connect:
             msg_Dbg( p_access, "switching to HTTP version 1.0" );
             p_sys->i_version = 0;
             p_sys->b_seekable = false;
+            p_sys->b_persist = false;
 
             if( p_access->b_die || Connect( p_access, 0 ) )
                 goto error;
@@ -627,7 +651,8 @@ static void Close( vlc_object_t *p_this )
  * p_buffer. Return the actual number of bytes read
  *****************************************************************************/
 static int ReadICYMeta( access_t *p_access );
-static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
+
+static ssize_t ReadAvailable( access_t *p_access, uint8_t *p_buffer, size_t i_len )
 {
     access_sys_t *p_sys = p_access->p_sys;
     int i_read;
@@ -638,16 +663,47 @@ static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
         return 0;
     }
 
-    if( p_access->info.i_size > 0 &&
-        i_len + p_access->info.i_pos > p_access->info.i_size )
-    {
-        if( ( i_len = p_access->info.i_size - p_access->info.i_pos ) == 0 )
-        {
-            p_access->info.b_eof = true;
-            return 0;
+    if( i_len == 0) {
+        return 0;
+    }
+
+    if( p_access->info.i_size > 0 && p_sys->i_remaining <= 0 && p_access->info.i_pos < p_access->info.i_size) {
+        if ( p_access->info.i_pos == p_sys->i_request_itell1 ) {
+            if ( ReadResponse( p_access ) != VLC_SUCCESS ) {
+                int64_t i_pos = p_access->info.i_pos;
+                Disconnect( p_access );
+                Connect( p_access, i_pos );
+            }
+        }
+        else if ( p_sys->b_persist ) {
+            if ( Request ( p_access, p_access->info.i_pos ) != VLC_SUCCESS ) {
+               p_sys->b_persist = false;
+               return 0;
+            }
+        }
+        else {
+            int64_t i_pos = p_access->info.i_pos;
+            Disconnect( p_access );
+            Connect( p_access, i_pos );
         }
     }
 
+    if( p_access->info.i_size > 0 ) {
+        if ( (int64_t)i_len + (int64_t)p_access->info.i_pos > (int64_t)p_access->info.i_size ) {
+            if( ( i_len = (size_t)(p_access->info.i_size - p_access->info.i_pos) ) == 0 ) {
+                p_access->info.b_eof = true;
+                return 0;
+            }
+        }
+        if ( (int64_t)i_len > p_sys->i_remaining ) {
+            i_len = (size_t)(p_sys->i_remaining);
+        }
+    }
+
+    if( p_sys->b_persist && p_sys->i_request_size1 == 0  && p_access->info.i_pos+p_sys->i_remaining+1 < p_access->info.i_size) {
+        SendRequest( p_access, p_access->info.i_pos+p_sys->i_remaining, p_sys->i_readsize );
+    }
+
     if( p_sys->b_chunked )
     {
         if( p_sys->i_chunk < 0 )
@@ -677,23 +733,23 @@ static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
             }
         }
 
-        if( i_len > p_sys->i_chunk )
+        if( (int64_t)i_len > p_sys->i_chunk )
         {
-            i_len = p_sys->i_chunk;
+            i_len = (size_t)(p_sys->i_chunk);
         }
     }
 
-    if( p_sys->b_continuous && (ssize_t)i_len > p_sys->i_remaining )
+    if( p_sys->b_continuous && (int64_t)i_len > p_sys->i_remaining )
     {
         /* Only ask for the remaining length */
-        int i_new_len = p_sys->i_remaining;
+        int64_t i_new_len = p_sys->i_remaining;
         if( i_new_len == 0 )
         {
             Request( p_access, 0 );
             i_read = Read( p_access, p_buffer, i_len );
             return i_read;
         }
-        i_len = i_new_len;
+        i_len = (size_t)i_new_len;
     }
 
     if( p_sys->i_icy_meta > 0 && p_access->info.i_pos > 0 )
@@ -764,14 +820,24 @@ static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
         if( i_read == 0 ) p_access->info.b_eof = true;
     }
 
-    if( p_sys->b_continuous )
-    {
-        p_sys->i_remaining -= i_read;
-    }
+    p_sys->i_remaining -= i_read;
 
     return i_read;
 }
 
+static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
+{
+   size_t i_total = 0;
+   while ( i_len > i_total ) {
+       ssize_t i_read = ReadAvailable (p_access, p_buffer+i_total, i_len-i_total );
+       if(i_read <= 0 ) {
+           return i_total;
+       }
+       i_total += (size_t)i_read;
+   }
+   return i_total;
+}
+
 static int ReadICYMeta( access_t *p_access )
 {
     access_sys_t *p_sys = p_access->p_sys;
@@ -879,22 +945,52 @@ static int Seek( access_t *p_access, int64_t i_pos )
 {
     msg_Dbg( p_access, "trying to seek to %"PRId64, i_pos );
 
-    Disconnect( p_access );
-
     if( i_pos < 0 ) {
         msg_Err( p_access, "seek to soon" );
         i_pos = 0;
     }
-    if(p_access->info.i_size > 0 && i_pos >= p_access->info.i_size ) {
-        msg_Err( p_access, "seek to far" );
-        int retval = Seek( p_access, p_access->info.i_size - 1 );
-        if( retval == VLC_SUCCESS ) {
-            uint8_t p_buffer[2];
-            Read( p_access, p_buffer, 1);
-            p_access->info.b_eof  = false;
+    if ( i_pos == p_access->info.i_pos ) {
+        return VLC_SUCCESS;
+    }
+    access_sys_t *p_sys = p_access->p_sys;
+    if (p_access->info.i_size > 0) {
+        if(i_pos >= p_access->info.i_size ) {
+            msg_Err( p_access, "seek to far" );
+            int retval = Seek( p_access, p_access->info.i_size - 1 );
+            if( retval == VLC_SUCCESS ) {
+                uint8_t p_buffer[2];
+                Read( p_access, p_buffer, 1);
+                p_access->info.b_eof  = false;
+            }
+            return retval;
+        }
+        if ( p_sys->i_request_size2 <= 0) {
+            /* contained in the current request */
+            if ( i_pos >= p_access->info.i_pos && i_pos <= p_access->info.i_pos + p_sys->i_remaining ) {
+                if ( i_pos <= p_access->info.i_pos + HTTP_MAX_SEEKREAD ) {
+                    uint8_t *p_buffer = (uint8_t *)malloc(i_pos - p_access->info.i_pos);
+                    Read(p_access, p_buffer, i_pos - p_access->info.i_pos);
+                    free(p_buffer);
+                    if ( i_pos == p_access->info.i_pos ) {
+                        return VLC_SUCCESS;
+                    }
+                    msg_Dbg( p_access, "failed to skip data" );
+                }
+            }
+            /** contained in the next request */
+            else if ( i_pos >= p_sys->i_request_itell1 && i_pos < p_sys->i_request_itell1 + p_sys->i_request_size1 ) {
+                if ( i_pos + p_sys->i_remaining - p_sys->i_request_itell1 <= HTTP_MAX_SEEKREAD ) {
+                    msg_Dbg( p_access, "seek to next response");
+                    ReadResponse( p_access );
+                    return Seek( p_access, i_pos );
+                }
+            }
+            else if ( p_sys->b_persist && p_sys->i_remaining + p_sys->i_request_size1 <= HTTP_MAX_SEEKREAD ) {
+                return Request( p_access, i_pos );
+            }
         }
-        return retval;
     }
+    Disconnect( p_access );
     if( Connect( p_access, i_pos ) )
     {
         msg_Err( p_access, "seek failed" );
@@ -1010,6 +1106,13 @@ static int Connect( access_t *p_access, int64_t i_tell )
     p_sys->psz_icy_name = NULL;
     p_sys->psz_icy_genre = NULL;
     p_sys->psz_icy_title = NULL;
+    p_sys->i_remaining = 0;
+    p_sys->i_request_itell1 = 0;
+    p_sys->i_request_itell2 = 0;
+    p_sys->i_request_size1 = 0;
+    p_sys->i_request_size2 = 0;
+    p_sys->i_readsize = HTTP_MIN_READ_SIZE;
+    p_sys->b_persist = true;
 
     p_access->info.i_size = 0;
     p_access->info.i_pos  = i_tell;
@@ -1104,12 +1207,32 @@ static int Connect( access_t *p_access, int64_t i_tell )
 }
 
 
-static int Request( access_t *p_access, int64_t i_tell )
+/*****************************************************************************
+ * SendRequest:
+ * Send a request but do not wait for the response.
+ *****************************************************************************/
+static int SendRequest( access_t *p_access, int64_t i_tell, int64_t i_readsize )
 {
     access_sys_t   *p_sys = p_access->p_sys;
-    char           *psz ;
     v_socket_t     *pvs = p_sys->p_vs;
 
+    if ( p_sys->i_request_size2 > 0 ) {
+        msg_Err( p_access, "only 2 pending requests are allowed");
+        return VLC_EGENERIC;
+    }
+    if ( p_sys->i_request_size1 <= 0 ) {
+        msg_Dbg( p_access, "sending request 1 pos=%"PRId64" size=%"PRId64,i_tell,i_readsize);
+        p_sys->i_request_itell1 = i_tell;
+        p_sys->i_request_size1 = i_readsize;
+        p_sys->i_request_itell2 = 0;
+        p_sys->i_request_size2 = 0;
+    }
+    else {
+        msg_Dbg( p_access, "sending request 2 pos=%"PRId64" size=%"PRId64,i_tell,i_readsize);
+        p_sys->i_request_itell2 = i_tell;
+        p_sys->i_request_size2 = i_readsize;
+    }
+
     if( p_sys->b_proxy )
     {
         if( p_sys->url.psz_path )
@@ -1154,8 +1277,12 @@ static int Request( access_t *p_access, int64_t i_tell )
     /* Offset */
     if( p_sys->i_version == 1 )
     {
+        int64_t i_end = i_tell + i_readsize - 1;
+        if (( p_access->info.i_size > 0) && ( i_end > p_access->info.i_size )) {
+           i_end = p_access->info.i_size - 1;
+        }
         net_Printf( VLC_OBJECT(p_access), p_sys->fd, pvs,
-                    "Range: bytes=%"PRId64"-\r\n", i_tell );
+                    "Range: bytes=%"PRId64"-%"PRId64"\r\n", i_tell, i_end );
     }
 
     /* Cookies */
@@ -1195,17 +1322,65 @@ static int Request( access_t *p_access, int64_t i_tell )
     /* ICY meta data request */
     net_Printf( VLC_OBJECT(p_access), p_sys->fd, pvs, "Icy-MetaData: 1\r\n" );
 
-
-    net_Printf( VLC_OBJECT( p_access ), p_sys->fd, pvs,
+    if(p_sys->b_persist ) {
+        net_Printf( VLC_OBJECT( p_access ), p_sys->fd, pvs,
                 "Connection: Keep-Alive\r\n" );
+    }
 
     if( net_Printf( VLC_OBJECT(p_access), p_sys->fd, pvs, "\r\n" ) < 0 )
     {
         msg_Err( p_access, "failed to send request" );
-        Disconnect( p_access );
+        p_sys->b_persist = false;
         return VLC_EGENERIC;
     }
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * ReadResponse:
+ * Read the response from a pending request.
+ *****************************************************************************/
+static int ReadResponse( access_t *p_access )
+{
+    access_sys_t   *p_sys = p_access->p_sys;
+    char           *psz ;
+    v_socket_t     *pvs = p_sys->p_vs;
 
+    if ( p_sys->i_request_size1 <= 0) {
+        msg_Err( p_access, "no pending requests" );
+        goto error;
+    }
+
+    /* Read any remaining data. */
+    if ( p_sys->i_remaining > 0 ) {
+        msg_Dbg( p_access, "reading remaining data %"PRId64,p_sys->i_remaining);
+        uint8_t *p_buffer = (uint8_t *)malloc( HTTP_MAX_SEEKREAD );
+        do {
+            size_t i_len = p_sys->i_remaining;
+            if (i_len > HTTP_MAX_SEEKREAD ) {
+                i_len = HTTP_MAX_SEEKREAD;
+            }
+            size_t i_read = Read( p_access, p_buffer, i_len );
+            if(i_read <= 0) {
+                msg_Err( p_access, "failed to read data" );
+                free( p_buffer );
+                goto error;
+            }
+        } while ( p_sys->i_remaining > 0 );
+        free( p_buffer );
+    }
+    int64_t i_tell = p_sys->i_request_itell1;
+    p_sys->i_readsize = p_sys->i_remaining = p_sys->i_request_size1;
+    p_sys->i_request_itell1 = p_sys->i_request_itell2;
+    p_sys->i_request_size1 = p_sys->i_request_size2;
+    p_sys->i_request_itell2 = 0;
+    p_sys->i_request_size2 = 0;
+    if (p_sys->i_readsize + p_sys->i_readsize > HTTP_MAX_READ_SIZE ) {
+        p_sys->i_readsize = HTTP_MAX_READ_SIZE;
+    }
+    else {
+        p_sys->i_readsize *= 2;
+    }
     /* Read Answer */
     if( ( psz = net_Gets( VLC_OBJECT(p_access), p_sys->fd, pvs ) ) == NULL )
     {
@@ -1296,10 +1471,31 @@ static int Request( access_t *p_access, int64_t i_tell )
             }
             else
             {
-                p_access->info.i_size = i_tell + atoll( p );
+                p_sys->i_remaining = atoll( p );
+                if( p_access->info.i_size < i_tell + p_sys->i_remaining ) {
+                    p_access->info.i_size = i_tell + p_sys->i_remaining;
+                }
                 msg_Dbg( p_access, "stream size=%"PRId64, p_access->info.i_size );
             }
         }
+        else if( !strcasecmp( psz, "Content-Range" ) ) {
+            int64_t i_ntell = i_tell;
+            int64_t i_nend = ((p_access->info.i_size <= i_tell ) ? i_tell + p_sys->i_remaining : p_access->info.i_size) - 1;
+            int64_t i_nsize = p_access->info.i_size;
+            sscanf(p,"bytes %"PRId64"-%"PRId64"/%"PRId64,&i_ntell,&i_nend,&i_nsize);
+            p_access->info.i_size = i_nsize;
+            p_access->info.i_pos = i_ntell;
+            p_sys->i_remaining = i_nend+1-i_ntell;
+            msg_Dbg( p_access, "stream size=%"PRId64",pos=%"PRId64",remaining=%"PRId64,i_nsize,i_ntell,p_sys->i_remaining);
+        }
+        else if( !strcasecmp( psz, "Connection" ) ) {
+            msg_Dbg( p_access, "Connection: %s",p );
+            int i = -1;
+            sscanf(p, "close%n",&i);
+            if( i >= 0 ) {
+                p_sys->b_persist = false;
+            }
+        }
         else if( !strcasecmp( psz, "Location" ) )
         {
             char * psz_new_loc;
@@ -1459,6 +1655,30 @@ error:
 }
 
 /*****************************************************************************
+ * Request:
+ * Send a request and wait for the response.
+ *****************************************************************************/
+static int Request( access_t *p_access, int64_t i_tell )
+{
+    msg_Dbg( p_access, "synchronous request %"PRId64,i_tell);
+    access_sys_t *p_sys = p_access->p_sys;
+    int retval;
+    retval = SendRequest(p_access, i_tell, HTTP_MIN_READ_SIZE );
+    if ( retval != VLC_SUCCESS ) {
+        Disconnect( p_access );
+        return VLC_EGENERIC;
+    }
+    if ( p_sys->i_request_size2 > 0) {
+        msg_Dbg( p_access, "skip response");
+        retval = ReadResponse( p_access );
+        if( retval != VLC_SUCCESS) {
+            return retval;
+        }
+    }
+    return ReadResponse(p_access );
+}
+
+/*****************************************************************************
  * Disconnect:
  *****************************************************************************/
 static void Disconnect( access_t *p_access )
@@ -1476,6 +1696,11 @@ static void Disconnect( access_t *p_access )
         net_Close(p_sys->fd);
         p_sys->fd = -1;
     }
+    p_sys->b_persist = false;
+    p_sys->i_request_size1 = 0;
+    p_sys->i_request_itell1 = 0;
+    p_sys->i_request_size2 = 0;
+    p_sys->i_request_itell2 = 0;
 
 }
 
-- 
1.5.5.1

