4 Commits

Author SHA1 Message Date
Alexey Danilov
9ffa983f58 hide 'publish.sh' 2015-09-28 11:39:06 +06:00
Alexey Danilov
6125478d27 fix 🐛 available cache percents callback 2015-09-28 11:31:02 +06:00
Alexey Danilov
e135bf0b42 ping proxy after starting to make sure it works fine 2015-09-25 20:46:02 +03:00
Alexey Danilov
edb12b574f fix readme 2015-09-25 17:08:50 +03:00
9 changed files with 151 additions and 25 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
/.idea
/build
/publish.sh
/local.properties
/gradle.properties
/library/build

View File

@@ -12,7 +12,7 @@ repositories {
maven { url 'https://dl.bintray.com/alexeydanilov/maven' }
}
dependencies {
compile 'com.danikula:videocache:2.1.2'
compile 'com.danikula:videocache:2.1.4'
}
```
@@ -59,10 +59,17 @@ More preferable way is use some dependency injector like [Dagger](http://square.
See `sample` app for details.
## Whats new
### 2.1.4
- [fix](https://github.com/danikula/AndroidVideoCache/issues/18) available cache percents callback
### 2.1.3
- ping proxy after starting to make sure it works fine
### 2.1.2
- fix offline work
- [fix](https://github.com/danikula/AndroidVideoCache/issues/13) offline work
### 2.1.1
- fix for too long cache file name
- [fix](https://github.com/danikula/AndroidVideoCache/issues/14) for too long cache file name
- url redirects support (thanks [ongakuer](https://github.com/ongakuer) for [PR](https://github.com/danikula/AndroidVideoCache/pull/12))
### 2.0

View File

@@ -26,7 +26,7 @@ publish {
userOrg = 'alexeydanilov'
groupId = 'com.danikula'
artifactId = 'videocache'
publishVersion = '2.1.2'
publishVersion = '2.1.4'
description = 'Cache support for android VideoView'
website = 'https://github.com/danikula/AndroidVideoCache'
}

View File

@@ -43,9 +43,6 @@ class HttpProxyCache extends ProxyCache {
}
out.write(buffer, 0, readBytes);
offset += readBytes;
if (cache.isCompleted()) {
onCacheAvailable(100);
}
}
out.flush();
}
@@ -68,7 +65,7 @@ class HttpProxyCache extends ProxyCache {
}
@Override
protected void onCacheAvailable(int percents) {
protected void onCachePercentsAvailableChanged(int percents) {
if (listener != null) {
listener.onCacheAvailable(cache.file, source.url, percents);
}

View File

@@ -1,17 +1,25 @@
package com.danikula.videocache;
import android.os.SystemClock;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static com.danikula.videocache.Preconditions.checkAllNotNull;
import static com.danikula.videocache.Preconditions.checkNotNull;
@@ -39,6 +47,8 @@ import static com.danikula.videocache.ProxyCacheUtils.LOG_TAG;
public class HttpProxyCacheServer {
private static final String PROXY_HOST = "127.0.0.1";
private static final String PING_REQUEST = "ping";
private static final String PING_RESPONSE = "ping ok";
private final Object clientsLock = new Object();
private final ExecutorService socketProcessor = Executors.newFixedThreadPool(8);
@@ -47,6 +57,7 @@ public class HttpProxyCacheServer {
private final int port;
private final Thread waitConnectionThread;
private final FileNameGenerator fileNameGenerator;
private boolean pinged;
public HttpProxyCacheServer(FileNameGenerator fileNameGenerator) {
this.fileNameGenerator = checkNotNull(fileNameGenerator);
@@ -58,13 +69,65 @@ public class HttpProxyCacheServer {
this.waitConnectionThread = new Thread(new WaitRequestsRunnable(startSignal));
this.waitConnectionThread.start();
startSignal.await(); // freeze thread, wait for server starts
Log.i(LOG_TAG, "Proxy cache server started. Ping it...");
makeSureServerWorks();
} catch (IOException | InterruptedException e) {
socketProcessor.shutdown();
throw new IllegalStateException("Error starting local proxy server", e);
}
}
private void makeSureServerWorks() {
int maxPingAttempts = 3;
int delay = 100;
int pingAttempts = 0;
while (pingAttempts < maxPingAttempts) {
try {
Future<Boolean> pingFuture = socketProcessor.submit(new PingCallable());
pinged = pingFuture.get(delay, TimeUnit.MILLISECONDS);
if (pinged) {
return;
}
pingAttempts++;
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);
}
}
if (!pinged) {
shutdown();
}
}
private boolean pingServer() throws ProxyCacheException {
String pingUrl = appendToProxyUrl(PING_REQUEST);
HttpUrlSource source = new HttpUrlSource(pingUrl);
try {
byte[] expectedResponse = PING_RESPONSE.getBytes();
source.open(0);
byte[] response = new byte[expectedResponse.length];
source.read(response);
boolean pingOk = Arrays.equals(expectedResponse, response);
Log.d(LOG_TAG, "Ping response: `" + new String(response) + "`, pinged? " + pingOk);
return pingOk;
} catch (ProxyCacheException e) {
Log.e(LOG_TAG, "Error reading ping response", e);
return false;
} finally {
source.close();
}
}
public String getProxyUrl(String url) {
if (!pinged) {
Log.e(LOG_TAG, "Proxy server isn't pinged. Caching doesn't work. If you see this message, please, email me danikula@gmail.com");
}
return pinged ? appendToProxyUrl(url) : url;
}
private String appendToProxyUrl(String url) {
return String.format("http://%s:%d/%s", PROXY_HOST, port, ProxyCacheUtils.encode(url));
}
@@ -140,8 +203,12 @@ public class HttpProxyCacheServer {
GetRequest request = GetRequest.read(socket.getInputStream());
Log.i(LOG_TAG, "Request to cache proxy:" + request);
String url = ProxyCacheUtils.decode(request.uri);
HttpProxyCacheServerClients clients = getClients(url);
clients.processRequest(request, socket);
if (PING_REQUEST.equals(url)) {
responseToPing(socket);
} else {
HttpProxyCacheServerClients clients = getClients(url);
clients.processRequest(request, socket);
}
} 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
@@ -154,6 +221,12 @@ public class HttpProxyCacheServer {
}
}
private void responseToPing(Socket socket) throws IOException {
OutputStream out = socket.getOutputStream();
out.write("HTTP/1.1 200 OK\n\n".getBytes());
out.write(PING_RESPONSE.getBytes());
}
private HttpProxyCacheServerClients getClients(String url) throws ProxyCacheException {
synchronized (clientsLock) {
HttpProxyCacheServerClients clients = clientsMap.get(url);
@@ -248,4 +321,11 @@ public class HttpProxyCacheServer {
}
}
private class PingCallable implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
return pingServer();
}
}
}

View File

@@ -24,9 +24,10 @@ class ProxyCache {
private final Cache cache;
private final Object wc = new Object();
private final Object stopLock = new Object();
private final AtomicInteger readSourceErrorsCount;
private volatile Thread sourceReaderThread;
private volatile boolean stopped;
private final AtomicInteger readSourceErrorsCount;
private volatile int percentsAvailable = -1;
public ProxyCache(Source source, Cache cache) {
this.source = checkNotNull(source);
@@ -42,7 +43,12 @@ class ProxyCache {
waitForSourceData();
checkReadSourceErrorsCount();
}
return cache.read(buffer, offset, length);
int read = cache.read(buffer, offset, length);
if (cache.isCompleted() && percentsAvailable != 100) {
percentsAvailable = 100;
onCachePercentsAvailableChanged(100);
}
return read;
}
private void checkReadSourceErrorsCount() throws ProxyCacheException {
@@ -86,22 +92,34 @@ class ProxyCache {
}
}
private void notifyNewCacheDataAvailable(int cachePercentage) {
onCacheAvailable(cachePercentage);
private void notifyNewCacheDataAvailable(long cacheAvailable, long sourceAvailable) {
onCacheAvailable(cacheAvailable, sourceAvailable);
synchronized (wc) {
wc.notifyAll();
}
}
protected void onCacheAvailable(int percents) {
protected void onCacheAvailable(long cacheAvailable, long sourceAvailable) {
int percents = (int) (cacheAvailable * 100 / sourceAvailable);
boolean percentsChanged = percents != percentsAvailable;
boolean sourceLengthKnown = sourceAvailable >= 0;
if (sourceLengthKnown && percentsChanged) {
onCachePercentsAvailableChanged(percents);
}
percentsAvailable = percents;
}
protected void onCachePercentsAvailableChanged(int percentsAvailable) {
}
private void readSource() {
int cachePercentage = 0;
int sourceAvailable = -1;
int offset = 0;
try {
int offset = cache.available();
offset = cache.available();
source.open(offset);
sourceAvailable = source.available();
byte[] buffer = new byte[ProxyCacheUtils.DEFAULT_BUFFER_SIZE];
int readBytes;
while ((readBytes = source.read(buffer)) != -1) {
@@ -112,9 +130,7 @@ class ProxyCache {
cache.append(buffer, readBytes);
}
offset += readBytes;
cachePercentage = offset * 100 / source.available();
notifyNewCacheDataAvailable(cachePercentage);
notifyNewCacheDataAvailable(offset, sourceAvailable);
}
tryComplete();
} catch (Throwable e) {
@@ -122,7 +138,7 @@ class ProxyCache {
onError(e);
} finally {
closeSource();
notifyNewCacheDataAvailable(cachePercentage);
notifyNewCacheDataAvailable(offset, sourceAvailable);
}
}

View File

@@ -7,11 +7,34 @@ package com.danikula.videocache;
*/
public interface Source {
int available() throws ProxyCacheException;
/**
* Opens source. Source should be open before using {@link #read(byte[])}
*
* @param offset offset in bytes for source.
* @throws ProxyCacheException if error occur while opening source.
*/
void open(int offset) throws ProxyCacheException;
void close() throws ProxyCacheException;
/**
* Returns available bytes or <b>negative value</b> if available bytes count is unknown.
*
* @return bytes available
* @throws ProxyCacheException if error occur while fetching source data.
*/
int available() throws ProxyCacheException;
/**
* Read data to byte buffer from source with current offset.
*
* @param buffer a buffer to be used for reading data.
* @throws ProxyCacheException if error occur while reading source.
*/
int read(byte[] buffer) throws ProxyCacheException;
/**
* Closes source and release resources. Every opened source should be closed.
*
* @throws ProxyCacheException if error occur while closing source.
*/
void close() throws ProxyCacheException;
}

View File

@@ -39,7 +39,7 @@ dependencies {
// compile project(':library')
compile 'com.android.support:support-v4:23.0.1'
compile 'org.androidannotations:androidannotations-api:3.3.2'
compile 'com.danikula:videocache:2.1.2'
compile 'com.danikula:videocache:2.1.4'
compile 'com.viewpagerindicator:library:2.4.2-SNAPSHOT@aar'
apt 'org.androidannotations:androidannotations:3.3.2'
}

View File

@@ -4,6 +4,7 @@ import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.widget.ProgressBar;
import android.widget.VideoView;
@@ -77,6 +78,7 @@ public class VideoFragment extends Fragment implements CacheListener {
@Override
public void onCacheAvailable(File file, String url, int percentsAvailable) {
progressBar.setSecondaryProgress(percentsAvailable);
Log.d(LOG_TAG, String.format("onCacheAvailable. percents: %d, file: %s, url: %s", percentsAvailable, file, url));
}
private void updateVideoProgress() {