mirror of
https://github.com/zhigang1992/AndroidVideoCache.git
synced 2026-06-17 11:01:44 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ffa983f58 | ||
|
|
6125478d27 | ||
|
|
e135bf0b42 | ||
|
|
edb12b574f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
|
||||
/.idea
|
||||
/build
|
||||
/publish.sh
|
||||
/local.properties
|
||||
/gradle.properties
|
||||
/library/build
|
||||
|
||||
13
README.md
13
README.md
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user