mirror of
https://github.com/zhigang1992/AndroidVideoCache.git
synced 2026-02-11 22:27:59 +08:00
@@ -22,7 +22,7 @@ public class ByteArraySource implements Source {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws ProxyCacheException {
|
||||
public int length() throws ProxyCacheException {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,9 +63,9 @@ class GetRequest {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GetRequest{" +
|
||||
"uri='" + uri + '\'' +
|
||||
", rangeOffset=" + rangeOffset +
|
||||
"rangeOffset=" + rangeOffset +
|
||||
", partial=" + partial +
|
||||
", uri='" + uri + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import static com.danikula.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE;
|
||||
|
||||
/**
|
||||
* {@link ProxyCache} that read http url and writes data to {@link Socket}
|
||||
*
|
||||
@@ -14,6 +16,8 @@ import java.net.Socket;
|
||||
*/
|
||||
class HttpProxyCache extends ProxyCache {
|
||||
|
||||
private static final float NO_CACHE_BARRIER = .2f;
|
||||
|
||||
private final HttpUrlSource source;
|
||||
private final FileCache cache;
|
||||
private CacheListener listener;
|
||||
@@ -30,27 +34,29 @@ class HttpProxyCache extends ProxyCache {
|
||||
|
||||
public void processRequest(GetRequest request, Socket socket) throws IOException, ProxyCacheException {
|
||||
OutputStream out = new BufferedOutputStream(socket.getOutputStream());
|
||||
byte[] buffer = new byte[ProxyCacheUtils.DEFAULT_BUFFER_SIZE];
|
||||
int readBytes;
|
||||
boolean headersWrote = false;
|
||||
String responseHeaders = newResponseHeaders(request);
|
||||
out.write(responseHeaders.getBytes("UTF-8"));
|
||||
|
||||
long offset = request.rangeOffset;
|
||||
while ((readBytes = read(buffer, offset, buffer.length)) != -1) {
|
||||
// tiny optimization: to prevent HEAD request in source for content-length. content-length 'll available after reading source
|
||||
if (!headersWrote) {
|
||||
String responseHeaders = newResponseHeaders(request);
|
||||
out.write(responseHeaders.getBytes("UTF-8"));
|
||||
headersWrote = true;
|
||||
}
|
||||
out.write(buffer, 0, readBytes);
|
||||
offset += readBytes;
|
||||
if (isUseCache(request)) {
|
||||
responseWithCache(out, offset);
|
||||
} else {
|
||||
responseWithoutCache(out, offset);
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private boolean isUseCache(GetRequest request) throws ProxyCacheException {
|
||||
int sourceLength = source.length();
|
||||
boolean sourceLengthKnown = sourceLength > 0;
|
||||
int cacheAvailable = cache.available();
|
||||
// do not use cache for partial requests which too far from available cache. It seems user seek video.
|
||||
return !sourceLengthKnown || !request.partial || request.rangeOffset <= cacheAvailable + sourceLength * NO_CACHE_BARRIER;
|
||||
}
|
||||
|
||||
private String newResponseHeaders(GetRequest request) throws IOException, ProxyCacheException {
|
||||
String mime = source.getMime();
|
||||
boolean mimeKnown = !TextUtils.isEmpty(mime);
|
||||
int length = cache.isCompleted() ? cache.available() : source.available();
|
||||
int length = cache.isCompleted() ? cache.available() : source.length();
|
||||
boolean lengthKnown = length >= 0;
|
||||
long contentLength = request.partial ? length - request.rangeOffset : length;
|
||||
boolean addRange = lengthKnown && request.partial;
|
||||
@@ -64,6 +70,32 @@ class HttpProxyCache extends ProxyCache {
|
||||
.toString();
|
||||
}
|
||||
|
||||
private void responseWithCache(OutputStream out, long offset) throws ProxyCacheException, IOException {
|
||||
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
int readBytes;
|
||||
while ((readBytes = read(buffer, offset, buffer.length)) != -1) {
|
||||
out.write(buffer, 0, readBytes);
|
||||
offset += readBytes;
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private void responseWithoutCache(OutputStream out, long offset) throws ProxyCacheException, IOException {
|
||||
try {
|
||||
HttpUrlSource source = new HttpUrlSource(this.source);
|
||||
source.open((int) offset);
|
||||
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
int readBytes;
|
||||
while ((readBytes = source.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, readBytes);
|
||||
offset += readBytes;
|
||||
}
|
||||
out.flush();
|
||||
} finally {
|
||||
source.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCachePercentsAvailableChanged(int percents) {
|
||||
if (listener != null) {
|
||||
|
||||
@@ -79,7 +79,7 @@ public class HttpProxyCacheServer {
|
||||
|
||||
private void makeSureServerWorks() {
|
||||
int maxPingAttempts = 3;
|
||||
int delay = 100;
|
||||
int delay = 200;
|
||||
int pingAttempts = 0;
|
||||
while (pingAttempts < maxPingAttempts) {
|
||||
try {
|
||||
@@ -92,13 +92,13 @@ public class HttpProxyCacheServer {
|
||||
SystemClock.sleep(delay);
|
||||
delay *= 2;
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
Log.e(LOG_TAG, "Error pinging server. Shutdown it... If you see this message, please, email me danikula@gmail.com", e);
|
||||
Log.e(LOG_TAG, "Error pinging server [attempt: " + pingAttempts + ", timeout: " + delay + "]. ", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pinged) {
|
||||
shutdown();
|
||||
}
|
||||
Log.e(LOG_TAG, "Shutdown server… Error pinging server [attempt: " + pingAttempts + ", timeout: " + delay + "]. " +
|
||||
"If you see this message, please, email me danikula@gmail.com");
|
||||
shutdown();
|
||||
}
|
||||
|
||||
private boolean pingServer() throws ProxyCacheException {
|
||||
@@ -212,7 +212,7 @@ public class HttpProxyCacheServer {
|
||||
} catch (SocketException e) {
|
||||
// There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
|
||||
// So just to prevent log flooding don't log stacktrace
|
||||
Log.d(LOG_TAG, "Client communication problem. It seems client closed connection");
|
||||
Log.d(LOG_TAG, "Closing socket… Socket is closed by client.");
|
||||
} catch (ProxyCacheException | IOException e) {
|
||||
onError(new ProxyCacheException("Error processing request", e));
|
||||
} finally {
|
||||
@@ -262,7 +262,7 @@ public class HttpProxyCacheServer {
|
||||
} catch (SocketException e) {
|
||||
// There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
|
||||
// So just to prevent log flooding don't log stacktrace
|
||||
Log.d(LOG_TAG, "Error closing client's input stream: it seems client closed connection");
|
||||
Log.d(LOG_TAG, "Releasing input stream… Socket is closed by client.");
|
||||
} catch (IOException e) {
|
||||
onError(new ProxyCacheException("Error closing socket input stream", e));
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class HttpUrlSource implements Source {
|
||||
public final String url;
|
||||
private HttpURLConnection connection;
|
||||
private InputStream inputStream;
|
||||
private volatile int available = Integer.MIN_VALUE;
|
||||
private volatile int length = Integer.MIN_VALUE;
|
||||
private volatile String mime;
|
||||
|
||||
public HttpUrlSource(String url) {
|
||||
@@ -41,12 +41,18 @@ public class HttpUrlSource implements Source {
|
||||
this.mime = mime;
|
||||
}
|
||||
|
||||
public HttpUrlSource(HttpUrlSource source) {
|
||||
this.url = source.url;
|
||||
this.mime = source.mime;
|
||||
this.length = source.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int available() throws ProxyCacheException {
|
||||
if (available == Integer.MIN_VALUE) {
|
||||
public synchronized int length() throws ProxyCacheException {
|
||||
if (length == Integer.MIN_VALUE) {
|
||||
fetchContentInfo();
|
||||
}
|
||||
return available;
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,7 +61,7 @@ public class HttpUrlSource implements Source {
|
||||
connection = openConnection(offset, "GET", -1);
|
||||
mime = connection.getContentType();
|
||||
inputStream = new BufferedInputStream(connection.getInputStream(), DEFAULT_BUFFER_SIZE);
|
||||
available = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
|
||||
length = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
|
||||
} catch (IOException e) {
|
||||
throw new ProxyCacheException("Error opening connection for " + url + " with offset " + offset, e);
|
||||
}
|
||||
@@ -64,7 +70,7 @@ public class HttpUrlSource implements Source {
|
||||
private int readSourceAvailableBytes(HttpURLConnection connection, int offset, int responseCode) throws IOException {
|
||||
int contentLength = connection.getContentLength();
|
||||
return responseCode == HTTP_OK ? contentLength
|
||||
: responseCode == HTTP_PARTIAL ? contentLength + offset : available;
|
||||
: responseCode == HTTP_PARTIAL ? contentLength + offset : length;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,10 +100,10 @@ public class HttpUrlSource implements Source {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
urlConnection = openConnection(0, "HEAD", 10000);
|
||||
available = urlConnection.getContentLength();
|
||||
length = urlConnection.getContentLength();
|
||||
mime = urlConnection.getContentType();
|
||||
inputStream = urlConnection.getInputStream();
|
||||
Log.i(LOG_TAG, "Content info for `" + url + "`: mime: " + mime + ", content-length: " + available);
|
||||
Log.i(LOG_TAG, "Content info for `" + url + "`: mime: " + mime + ", content-length: " + length);
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, "Error fetching info from " + url, e);
|
||||
} finally {
|
||||
|
||||
@@ -119,7 +119,7 @@ class ProxyCache {
|
||||
try {
|
||||
offset = cache.available();
|
||||
source.open(offset);
|
||||
sourceAvailable = source.available();
|
||||
sourceAvailable = source.length();
|
||||
byte[] buffer = new byte[ProxyCacheUtils.DEFAULT_BUFFER_SIZE];
|
||||
int readBytes;
|
||||
while ((readBytes = source.read(buffer)) != -1) {
|
||||
@@ -144,7 +144,7 @@ class ProxyCache {
|
||||
|
||||
private void tryComplete() throws ProxyCacheException {
|
||||
synchronized (stopLock) {
|
||||
if (!isStopped() && cache.available() == source.available()) {
|
||||
if (!isStopped() && cache.available() == source.length()) {
|
||||
cache.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ public interface Source {
|
||||
void open(int offset) throws ProxyCacheException;
|
||||
|
||||
/**
|
||||
* Returns available bytes or <b>negative value</b> if available bytes count is unknown.
|
||||
* Returns length bytes or <b>negative value</b> if length is unknown.
|
||||
*
|
||||
* @return bytes available
|
||||
* @return bytes length
|
||||
* @throws ProxyCacheException if error occur while fetching source data.
|
||||
*/
|
||||
int available() throws ProxyCacheException;
|
||||
int length() throws ProxyCacheException;
|
||||
|
||||
/**
|
||||
* Read data to byte buffer from source with current offset.
|
||||
|
||||
Reference in New Issue
Block a user