Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd0b4111a4 | ||
|
|
eb67640212 | ||
|
|
1d50807fe0 | ||
|
|
6da9650030 | ||
|
|
a1d00fec7b | ||
|
|
f5a779266d | ||
|
|
ca6f36ada3 | ||
|
|
33b7e5e9d3 | ||
|
|
0aad13f118 | ||
|
|
a270460c8d |
28
README.md
@@ -14,14 +14,14 @@ Because there is no sense to download video a lot of times while streaming!
|
|||||||
|
|
||||||
Note `AndroidVideoCache` works only with **direct urls** to media file, it [**doesn't support**](https://github.com/danikula/AndroidVideoCache/issues/19) any streaming technology like DASH, SmoothStreaming, HLS.
|
Note `AndroidVideoCache` works only with **direct urls** to media file, it [**doesn't support**](https://github.com/danikula/AndroidVideoCache/issues/19) any streaming technology like DASH, SmoothStreaming, HLS.
|
||||||
|
|
||||||
## How to use?
|
## Get started
|
||||||
Just add dependency (`AndroidVideoCache` is available in jcenter):
|
Just add dependency (`AndroidVideoCache` is available in jcenter):
|
||||||
```
|
```
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.danikula:videocache:2.3.2'
|
compile 'com.danikula:videocache:2.4.0'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -64,6 +64,8 @@ public class App extends Application {
|
|||||||
or use [simple factory](http://pastebin.com/s2fafSYS).
|
or use [simple factory](http://pastebin.com/s2fafSYS).
|
||||||
More preferable way is use some dependency injector like [Dagger](http://square.github.io/dagger/).
|
More preferable way is use some dependency injector like [Dagger](http://square.github.io/dagger/).
|
||||||
|
|
||||||
|
## Recipes
|
||||||
|
### Disk cache limit
|
||||||
By default `HttpProxyCacheServer` uses 512Mb for caching files. You can change this value:
|
By default `HttpProxyCacheServer` uses 512Mb for caching files. You can change this value:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@@ -82,9 +84,17 @@ private HttpProxyCacheServer newProxy() {
|
|||||||
.maxCacheFilesCount(20)
|
.maxCacheFilesCount(20)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
See `sample` app for details.
|
### Listen caching progress
|
||||||
|
Use `HttpProxyCacheServer.registerCacheListener(CacheListener listener)` method to set listener with callback `onCacheAvailable(File cacheFile, String url, int percentsAvailable)` to be aware of caching progress. Do not forget to to unsubscribe listener with help of `HttpProxyCacheServer.unregisterCacheListener(CacheListener listener)` method to avoid memory leaks.
|
||||||
|
|
||||||
|
Use `HttpProxyCacheServer.isCached(String url)` method to check was url's content fully cached to file or not.
|
||||||
|
|
||||||
|
See `sample` app for more details.
|
||||||
|
|
||||||
|
### Sample
|
||||||
|
See `sample` app.
|
||||||
|
|
||||||
## Whats new
|
## Whats new
|
||||||
See Release Notes [here](https://github.com/danikula/AndroidVideoCache/releases)
|
See Release Notes [here](https://github.com/danikula/AndroidVideoCache/releases)
|
||||||
@@ -94,9 +104,11 @@ If it's a feature that you think would need to be discussed please open an issue
|
|||||||
|
|
||||||
1. [Fork the project](http://help.github.com/fork-a-repo/)
|
1. [Fork the project](http://help.github.com/fork-a-repo/)
|
||||||
2. Create a feature branch (git checkout -b my_branch)
|
2. Create a feature branch (git checkout -b my_branch)
|
||||||
3. Push your changes to your new branch (git push origin my_branch)
|
3. Fix a problem. Your code **must** contain test for reproducing problem. Your tests **must be passed** with help of your fix
|
||||||
4. Initiate a [pull request](http://help.github.com/send-pull-requests/) on github
|
4. Push your changes to your new branch (git push origin my_branch)
|
||||||
5. Your pull request will be reviewed and hopefully merged :)
|
5. Initiate a [pull request](http://help.github.com/send-pull-requests/) on github
|
||||||
|
6. Rebase [master branch](https://github.com/danikula/AndroidVideoCache) if your local branch is not actual. Merging is not acceptable, only rebase
|
||||||
|
6. Your pull request will be reviewed and hopefully merged :)
|
||||||
|
|
||||||
## Where published?
|
## Where published?
|
||||||
[Here](https://bintray.com/alexeydanilov/maven/videocache/view)
|
[Here](https://bintray.com/alexeydanilov/maven/videocache/view)
|
||||||
@@ -106,7 +118,7 @@ If it's a feature that you think would need to be discussed please open an issue
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright 2014-2015 Alexey Danilov
|
Copyright 2014-2016 Alexey Danilov
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:1.3.0'
|
classpath 'com.android.tools.build:gradle:2.1.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
files/android
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
files/android.jpg
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
files/devbytes.mp4
Normal file
0
files/empty.txt
Normal file
BIN
files/orange1.mp4
Normal file
BIN
files/orange2.mp4
Normal file
BIN
files/orange3.mp4
Normal file
BIN
files/orange4.mp4
Normal file
BIN
files/orange5.mp4
Normal file
BIN
files/phones.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.novoda:bintray-release:0.2.10'
|
classpath 'com.novoda:bintray-release:0.3.4'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +18,9 @@ idea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targetCompatibility = '1.7'
|
||||||
|
sourceCompatibility = '1.7'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.google.android:android:1.6_r2'
|
compile 'com.google.android:android:1.6_r2'
|
||||||
}
|
}
|
||||||
@@ -26,7 +29,7 @@ publish {
|
|||||||
userOrg = 'alexeydanilov'
|
userOrg = 'alexeydanilov'
|
||||||
groupId = 'com.danikula'
|
groupId = 'com.danikula'
|
||||||
artifactId = 'videocache'
|
artifactId = 'videocache'
|
||||||
publishVersion = '2.3.2'
|
publishVersion = '2.4.0'
|
||||||
description = 'Cache support for android VideoView'
|
description = 'Cache support for android VideoView'
|
||||||
website = 'https://github.com/danikula/AndroidVideoCache'
|
website = 'https://github.com/danikula/AndroidVideoCache'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class HttpProxyCacheServer {
|
|||||||
|
|
||||||
private void makeSureServerWorks() {
|
private void makeSureServerWorks() {
|
||||||
int maxPingAttempts = 3;
|
int maxPingAttempts = 3;
|
||||||
int delay = 200;
|
int delay = 300;
|
||||||
int pingAttempts = 0;
|
int pingAttempts = 0;
|
||||||
while (pingAttempts < maxPingAttempts) {
|
while (pingAttempts < maxPingAttempts) {
|
||||||
try {
|
try {
|
||||||
@@ -107,7 +107,7 @@ public class HttpProxyCacheServer {
|
|||||||
pingAttempts++;
|
pingAttempts++;
|
||||||
delay *= 2;
|
delay *= 2;
|
||||||
}
|
}
|
||||||
Log.e(LOG_TAG, "Shutdown server… Error pinging server [attempt: " + pingAttempts + ", timeout: " + delay + "]. " +
|
Log.e(LOG_TAG, "Shutdown server… Error pinging server [attempts: " + pingAttempts + ", max timeout: " + delay / 2 + "]. " +
|
||||||
"If you see this message, please, email me danikula@gmail.com");
|
"If you see this message, please, email me danikula@gmail.com");
|
||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
@@ -173,6 +173,20 @@ public class HttpProxyCacheServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks is cache contains fully cached file for particular url.
|
||||||
|
*
|
||||||
|
* @param url an url cache file will be checked for.
|
||||||
|
* @return {@code true} if cache contains fully cached file for passed in parameters url.
|
||||||
|
*/
|
||||||
|
public boolean isCached(String url) {
|
||||||
|
checkNotNull(url, "Url can't be null!");
|
||||||
|
File cacheDir = config.cacheRoot;
|
||||||
|
String fileName = config.fileNameGenerator.generate(url);
|
||||||
|
File cacheFile = new File(cacheDir, fileName);
|
||||||
|
return cacheFile.exists();
|
||||||
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
Log.i(LOG_TAG, "Shutdown proxy server");
|
Log.i(LOG_TAG, "Shutdown proxy server");
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class HttpUrlSource implements Source {
|
|||||||
@Override
|
@Override
|
||||||
public void open(int offset) throws ProxyCacheException {
|
public void open(int offset) throws ProxyCacheException {
|
||||||
try {
|
try {
|
||||||
connection = openConnection(offset, "GET", -1);
|
connection = openConnection(offset, -1);
|
||||||
mime = connection.getContentType();
|
mime = connection.getContentType();
|
||||||
inputStream = new BufferedInputStream(connection.getInputStream(), DEFAULT_BUFFER_SIZE);
|
inputStream = new BufferedInputStream(connection.getInputStream(), DEFAULT_BUFFER_SIZE);
|
||||||
length = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
|
length = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
|
||||||
@@ -76,7 +76,13 @@ public class HttpUrlSource implements Source {
|
|||||||
@Override
|
@Override
|
||||||
public void close() throws ProxyCacheException {
|
public void close() throws ProxyCacheException {
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
connection.disconnect();
|
try {
|
||||||
|
connection.disconnect();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
// https://github.com/danikula/AndroidVideoCache/issues/32
|
||||||
|
// https://github.com/danikula/AndroidVideoCache/issues/29
|
||||||
|
throw new ProxyCacheException("Error disconnecting HttpUrlConnection", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +105,7 @@ public class HttpUrlSource implements Source {
|
|||||||
HttpURLConnection urlConnection = null;
|
HttpURLConnection urlConnection = null;
|
||||||
InputStream inputStream = null;
|
InputStream inputStream = null;
|
||||||
try {
|
try {
|
||||||
urlConnection = openConnection(0, "HEAD", 10000);
|
urlConnection = openConnection(0, 10000);
|
||||||
length = urlConnection.getContentLength();
|
length = urlConnection.getContentLength();
|
||||||
mime = urlConnection.getContentType();
|
mime = urlConnection.getContentType();
|
||||||
inputStream = urlConnection.getInputStream();
|
inputStream = urlConnection.getInputStream();
|
||||||
@@ -114,7 +120,7 @@ public class HttpUrlSource implements Source {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpURLConnection openConnection(int offset, String method, int timeout) throws IOException, ProxyCacheException {
|
private HttpURLConnection openConnection(int offset, int timeout) throws IOException, ProxyCacheException {
|
||||||
HttpURLConnection connection;
|
HttpURLConnection connection;
|
||||||
boolean redirected;
|
boolean redirected;
|
||||||
int redirectCount = 0;
|
int redirectCount = 0;
|
||||||
@@ -122,7 +128,6 @@ public class HttpUrlSource implements Source {
|
|||||||
do {
|
do {
|
||||||
Log.d(LOG_TAG, "Open connection " + (offset > 0 ? " with offset " + offset : "") + " to " + url);
|
Log.d(LOG_TAG, "Open connection " + (offset > 0 ? " with offset " + offset : "") + " to " + url);
|
||||||
connection = (HttpURLConnection) new URL(url).openConnection();
|
connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
connection.setRequestMethod(method);
|
|
||||||
if (offset > 0) {
|
if (offset > 0) {
|
||||||
connection.setRequestProperty("Range", "bytes=" + offset + "-");
|
connection.setRequestProperty("Range", "bytes=" + offset + "-");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,10 +100,11 @@ class ProxyCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onCacheAvailable(long cacheAvailable, long sourceAvailable) {
|
protected void onCacheAvailable(long cacheAvailable, long sourceLength) {
|
||||||
int percents = (int) (cacheAvailable * 100 / sourceAvailable);
|
boolean zeroLengthSource = sourceLength == 0;
|
||||||
|
int percents = zeroLengthSource ? 100 : (int) (cacheAvailable * 100 / sourceLength);
|
||||||
boolean percentsChanged = percents != percentsAvailable;
|
boolean percentsChanged = percents != percentsAvailable;
|
||||||
boolean sourceLengthKnown = sourceAvailable >= 0;
|
boolean sourceLengthKnown = sourceLength >= 0;
|
||||||
if (sourceLengthKnown && percentsChanged) {
|
if (sourceLengthKnown && percentsChanged) {
|
||||||
onCachePercentsAvailableChanged(percents);
|
onCachePercentsAvailableChanged(percents);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ apply plugin: 'com.neenbedankt.android-apt'
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 23
|
compileSdkVersion 23
|
||||||
buildToolsVersion '23.0.1'
|
buildToolsVersion '24'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.danikula.videocache.sample"
|
applicationId 'com.danikula.videocache.sample'
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
versionCode 1
|
versionCode 1
|
||||||
@@ -38,7 +38,7 @@ dependencies {
|
|||||||
// compile project(':library')
|
// compile project(':library')
|
||||||
compile 'com.android.support:support-v4:23.1.0'
|
compile 'com.android.support:support-v4:23.1.0'
|
||||||
compile 'org.androidannotations:androidannotations-api:3.3.2'
|
compile 'org.androidannotations:androidannotations-api:3.3.2'
|
||||||
compile 'com.danikula:videocache:2.3.2'
|
compile 'com.danikula:videocache:2.4.0'
|
||||||
compile 'com.viewpagerindicator:library:2.4.2-SNAPSHOT@aar'
|
compile 'com.viewpagerindicator:library:2.4.2-SNAPSHOT@aar'
|
||||||
apt 'org.androidannotations:androidannotations:3.3.2'
|
apt 'org.androidannotations:androidannotations:3.3.2'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import java.io.File;
|
|||||||
|
|
||||||
public enum Video {
|
public enum Video {
|
||||||
|
|
||||||
ORANGE_1("https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/orange1.mp4"),
|
ORANGE_1(Config.ROOT + "orange1.mp4"),
|
||||||
ORANGE_2("https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/orange2.mp4"),
|
ORANGE_2(Config.ROOT + "orange2.mp4"),
|
||||||
ORANGE_3("https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/orange3.mp4"),
|
ORANGE_3(Config.ROOT + "orange3.mp4"),
|
||||||
ORANGE_4("https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/orange4.mp4"),
|
ORANGE_4(Config.ROOT + "orange4.mp4"),
|
||||||
ORANGE_5("https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/orange5.mp4");
|
ORANGE_5(Config.ROOT + "orange5.mp4");
|
||||||
|
|
||||||
public final String url;
|
public final String url;
|
||||||
|
|
||||||
@@ -21,4 +21,8 @@ public enum Video {
|
|||||||
public File getCacheFile(Context context) {
|
public File getCacheFile(Context context) {
|
||||||
return new File(context.getExternalCacheDir(), name());
|
return new File(context.getExternalCacheDir(), name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Config {
|
||||||
|
private static final String ROOT = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.os.Handler;
|
|||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.VideoView;
|
import android.widget.VideoView;
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ public class VideoFragment extends Fragment implements CacheListener {
|
|||||||
@FragmentArg String url;
|
@FragmentArg String url;
|
||||||
@FragmentArg String cachePath;
|
@FragmentArg String cachePath;
|
||||||
|
|
||||||
|
@ViewById ImageView cacheStatusImageView;
|
||||||
@ViewById VideoView videoView;
|
@ViewById VideoView videoView;
|
||||||
@ViewById ProgressBar progressBar;
|
@ViewById ProgressBar progressBar;
|
||||||
|
|
||||||
@@ -45,9 +47,16 @@ public class VideoFragment extends Fragment implements CacheListener {
|
|||||||
|
|
||||||
@AfterViews
|
@AfterViews
|
||||||
void afterViewInjected() {
|
void afterViewInjected() {
|
||||||
|
checkCachedState();
|
||||||
startVideo();
|
startVideo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkCachedState() {
|
||||||
|
HttpProxyCacheServer proxy = App.getProxy(getActivity());
|
||||||
|
boolean fullyCached = proxy.isCached(url);
|
||||||
|
setCachedState(fullyCached);
|
||||||
|
}
|
||||||
|
|
||||||
private void startVideo() {
|
private void startVideo() {
|
||||||
HttpProxyCacheServer proxy = App.getProxy(getActivity());
|
HttpProxyCacheServer proxy = App.getProxy(getActivity());
|
||||||
proxy.registerCacheListener(this, url);
|
proxy.registerCacheListener(this, url);
|
||||||
@@ -78,6 +87,7 @@ public class VideoFragment extends Fragment implements CacheListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onCacheAvailable(File file, String url, int percentsAvailable) {
|
public void onCacheAvailable(File file, String url, int percentsAvailable) {
|
||||||
progressBar.setSecondaryProgress(percentsAvailable);
|
progressBar.setSecondaryProgress(percentsAvailable);
|
||||||
|
setCachedState(percentsAvailable == 100);
|
||||||
Log.d(LOG_TAG, String.format("onCacheAvailable. percents: %d, file: %s, url: %s", percentsAvailable, file, url));
|
Log.d(LOG_TAG, String.format("onCacheAvailable. percents: %d, file: %s, url: %s", percentsAvailable, file, url));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +102,11 @@ public class VideoFragment extends Fragment implements CacheListener {
|
|||||||
videoView.seekTo(videoPosition);
|
videoView.seekTo(videoPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setCachedState(boolean cached) {
|
||||||
|
int statusIconId = cached ? R.drawable.ic_cloud_done : R.drawable.ic_cloud_download;
|
||||||
|
cacheStatusImageView.setImageResource(statusIconId);
|
||||||
|
}
|
||||||
|
|
||||||
private final class VideoProgressUpdater extends Handler {
|
private final class VideoProgressUpdater extends Handler {
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
|
|||||||
BIN
sample/src/main/res/drawable-hdpi/ic_cloud_done.png
Normal file
|
After Width: | Height: | Size: 379 B |
BIN
sample/src/main/res/drawable-hdpi/ic_cloud_download.png
Normal file
|
After Width: | Height: | Size: 353 B |
BIN
sample/src/main/res/drawable-mdpi/ic_cloud_done.png
Normal file
|
After Width: | Height: | Size: 265 B |
BIN
sample/src/main/res/drawable-mdpi/ic_cloud_download.png
Normal file
|
After Width: | Height: | Size: 242 B |
BIN
sample/src/main/res/drawable-xhdpi/ic_cloud_done.png
Normal file
|
After Width: | Height: | Size: 449 B |
BIN
sample/src/main/res/drawable-xhdpi/ic_cloud_download.png
Normal file
|
After Width: | Height: | Size: 417 B |
BIN
sample/src/main/res/drawable-xxhdpi/ic_cloud_done.png
Normal file
|
After Width: | Height: | Size: 672 B |
BIN
sample/src/main/res/drawable-xxhdpi/ic_cloud_download.png
Normal file
|
After Width: | Height: | Size: 610 B |
BIN
sample/src/main/res/drawable-xxxhdpi/ic_cloud_done.png
Normal file
|
After Width: | Height: | Size: 855 B |
BIN
sample/src/main/res/drawable-xxxhdpi/ic_cloud_download.png
Normal file
|
After Width: | Height: | Size: 789 B |
@@ -10,6 +10,13 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/cacheStatusImageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:src="@drawable/ic_cloud_download" />
|
||||||
|
|
||||||
<SeekBar
|
<SeekBar
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
style="@android:style/Widget.Holo.ProgressBar.Horizontal"
|
style="@android:style/Widget.Holo.ProgressBar.Horizontal"
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public class HttpProxyCacheServerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMimeFromResponse() throws Exception {
|
public void testMimeFromResponse() throws Exception {
|
||||||
Pair<File, Response> response = readProxyData("https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/android");
|
Pair<File, Response> response = readProxyData("https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/android");
|
||||||
assertThat(response.second.contentType).isEqualTo("application/octet-stream");
|
assertThat(response.second.contentType).isEqualTo("application/octet-stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,10 +210,55 @@ public class HttpProxyCacheServerTest {
|
|||||||
assertThat(file(cacheFolder, HTTP_DATA_URL_6_REDIRECTS)).exists();
|
assertThat(file(cacheFolder, HTTP_DATA_URL_6_REDIRECTS)).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckFileExistForNotCachedUrl() throws Exception {
|
||||||
|
HttpProxyCacheServer proxy = newProxy(cacheFolder);
|
||||||
|
proxy.shutdown();
|
||||||
|
assertThat(proxy.isCached(HTTP_DATA_URL)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckFileExistForFullyCachedUrl() throws Exception {
|
||||||
|
HttpProxyCacheServer proxy = newProxy(cacheFolder);
|
||||||
|
readProxyResponse(proxy, HTTP_DATA_URL, 0);
|
||||||
|
proxy.shutdown();
|
||||||
|
|
||||||
|
assertThat(proxy.isCached(HTTP_DATA_URL)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckFileExistForPartiallyCachedUrl() throws Exception {
|
||||||
|
File cacheDir = RuntimeEnvironment.application.getExternalCacheDir();
|
||||||
|
File file = file(cacheDir, HTTP_DATA_URL);
|
||||||
|
int partialCacheSize = 1000;
|
||||||
|
byte[] partialData = ProxyCacheTestUtils.generate(partialCacheSize);
|
||||||
|
File partialCacheFile = ProxyCacheTestUtils.getTempFile(file);
|
||||||
|
IoUtils.saveToFile(partialData, partialCacheFile);
|
||||||
|
|
||||||
|
HttpProxyCacheServer proxy = newProxy(cacheDir);
|
||||||
|
assertThat(proxy.isCached(HTTP_DATA_URL)).isFalse();
|
||||||
|
|
||||||
|
readProxyResponse(proxy, HTTP_DATA_URL);
|
||||||
|
proxy.shutdown();
|
||||||
|
|
||||||
|
assertThat(proxy.isCached(HTTP_DATA_URL)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckFileExistForDeletedCacheFile() throws Exception {
|
||||||
|
HttpProxyCacheServer proxy = newProxy(cacheFolder);
|
||||||
|
readProxyResponse(proxy, HTTP_DATA_URL, 0);
|
||||||
|
proxy.shutdown();
|
||||||
|
File cacheFile = file(cacheFolder, HTTP_DATA_URL);
|
||||||
|
boolean deleted = cacheFile.delete();
|
||||||
|
|
||||||
|
assertThat(deleted).isTrue();
|
||||||
|
assertThat(proxy.isCached(HTTP_DATA_URL)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
private Pair<File, Response> readProxyData(String url, int offset) throws IOException {
|
private Pair<File, Response> readProxyData(String url, int offset) throws IOException {
|
||||||
File externalCacheDir = RuntimeEnvironment.application.getExternalCacheDir();
|
File file = file(cacheFolder, url);
|
||||||
File file = file(externalCacheDir, url);
|
HttpProxyCacheServer proxy = newProxy(cacheFolder);
|
||||||
HttpProxyCacheServer proxy = newProxy(externalCacheDir);
|
|
||||||
|
|
||||||
Response response = readProxyResponse(proxy, url, offset);
|
Response response = readProxyResponse(proxy, url, offset);
|
||||||
proxy.shutdown();
|
proxy.shutdown();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.robolectric.RobolectricGradleTestRunner;
|
|||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
|
||||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL;
|
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL;
|
||||||
@@ -20,6 +21,7 @@ import static org.fest.assertions.api.Assertions.assertThat;
|
|||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Matchers.anyInt;
|
import static org.mockito.Matchers.anyInt;
|
||||||
import static org.mockito.Matchers.anyLong;
|
import static org.mockito.Matchers.anyLong;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -73,4 +75,24 @@ public class HttpProxyCacheTest {
|
|||||||
assertThat(response.data).isEqualTo(partialData);
|
assertThat(response.data).isEqualTo(partialData);
|
||||||
assertThat(response.code).isEqualTo(206);
|
assertThat(response.code).isEqualTo(206);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadEmptyFile() throws Exception {
|
||||||
|
String zeroSizeUrl = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/empty.txt";
|
||||||
|
HttpUrlSource source = new HttpUrlSource(zeroSizeUrl);
|
||||||
|
HttpProxyCache proxyCache = new HttpProxyCache(source, new FileCache(ProxyCacheTestUtils.newCacheFile()));
|
||||||
|
GetRequest request = new GetRequest("GET /" + HTTP_DATA_URL + " HTTP/1.1");
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
Socket socket = mock(Socket.class);
|
||||||
|
when(socket.getOutputStream()).thenReturn(out);
|
||||||
|
|
||||||
|
CacheListener listener = Mockito.mock(CacheListener.class);
|
||||||
|
proxyCache.registerCacheListener(listener);
|
||||||
|
proxyCache.processRequest(request, socket);
|
||||||
|
proxyCache.registerCacheListener(null);
|
||||||
|
Response response = new Response(out.toByteArray());
|
||||||
|
|
||||||
|
Mockito.verify(listener).onCacheAvailable(Mockito.<File>any(), eq(zeroSizeUrl), eq(100));
|
||||||
|
assertThat(response.data).isEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.robolectric.annotation.Config;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static com.danikula.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE;
|
||||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_BIG_NAME;
|
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_BIG_NAME;
|
||||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_NAME;
|
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_NAME;
|
||||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_SIZE;
|
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_SIZE;
|
||||||
@@ -83,7 +84,7 @@ public class HttpUrlSourceTest {
|
|||||||
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_ONE_REDIRECT);
|
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_ONE_REDIRECT);
|
||||||
source.open(0);
|
source.open(0);
|
||||||
byte[] readData = new byte[HTTP_DATA_SIZE];
|
byte[] readData = new byte[HTTP_DATA_SIZE];
|
||||||
source.read(readData);
|
readSource(source, readData);
|
||||||
source.close();
|
source.close();
|
||||||
|
|
||||||
byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_NAME), 0, HTTP_DATA_SIZE);
|
byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_NAME), 0, HTTP_DATA_SIZE);
|
||||||
@@ -96,7 +97,7 @@ public class HttpUrlSourceTest {
|
|||||||
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_ONE_REDIRECT);
|
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_ONE_REDIRECT);
|
||||||
source.open(offset);
|
source.open(offset);
|
||||||
byte[] readData = new byte[HTTP_DATA_SIZE - offset];
|
byte[] readData = new byte[HTTP_DATA_SIZE - offset];
|
||||||
source.read(readData);
|
readSource(source, readData);
|
||||||
source.close();
|
source.close();
|
||||||
|
|
||||||
byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_NAME), offset, HTTP_DATA_SIZE);
|
byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_NAME), offset, HTTP_DATA_SIZE);
|
||||||
@@ -109,7 +110,7 @@ public class HttpUrlSourceTest {
|
|||||||
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_3_REDIRECTS);
|
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_3_REDIRECTS);
|
||||||
source.open(offset);
|
source.open(offset);
|
||||||
byte[] readData = new byte[HTTP_DATA_SIZE - offset];
|
byte[] readData = new byte[HTTP_DATA_SIZE - offset];
|
||||||
source.read(readData);
|
readSource(source, readData);
|
||||||
source.close();
|
source.close();
|
||||||
|
|
||||||
byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_NAME), offset, HTTP_DATA_SIZE);
|
byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_NAME), offset, HTTP_DATA_SIZE);
|
||||||
@@ -130,4 +131,14 @@ public class HttpUrlSourceTest {
|
|||||||
assertThat(new HttpUrlSource("http://mysite.by/video.mp4").getMime()).isEqualTo("video/mp4");
|
assertThat(new HttpUrlSource("http://mysite.by/video.mp4").getMime()).isEqualTo("video/mp4");
|
||||||
assertThat(new HttpUrlSource(HTTP_DATA_URL).getMime()).isEqualTo("image/jpeg");
|
assertThat(new HttpUrlSource(HTTP_DATA_URL).getMime()).isEqualTo("image/jpeg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void readSource(Source source, byte[] target) throws ProxyCacheException {
|
||||||
|
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||||
|
int totalRead = 0;
|
||||||
|
int readBytes;
|
||||||
|
while ((readBytes = source.read(buffer)) != -1) {
|
||||||
|
System.arraycopy(buffer, 0, target, totalRead, readBytes);
|
||||||
|
totalRead += readBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ import java.util.UUID;
|
|||||||
*/
|
*/
|
||||||
public class ProxyCacheTestUtils {
|
public class ProxyCacheTestUtils {
|
||||||
|
|
||||||
public static final String HTTP_DATA_URL = "https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/android.jpg";
|
public static final String HTTP_DATA_URL = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/android.jpg";
|
||||||
public static final String HTTP_DATA_URL_ONE_REDIRECT = "http://bit.ly/1V5PeY5";
|
public static final String HTTP_DATA_URL_ONE_REDIRECT = "http://bit.ly/1LAJKAy";
|
||||||
public static final String HTTP_DATA_URL_3_REDIRECTS = "http://bit.ly/1KvVmgZ";
|
public static final String HTTP_DATA_URL_3_REDIRECTS = "http://bit.ly/1QtKJiB";
|
||||||
public static final String HTTP_DATA_URL_6_REDIRECTS = "http://ow.ly/SugRH";
|
public static final String HTTP_DATA_URL_6_REDIRECTS = "http://ow.ly/Z17wz";
|
||||||
public static final String HTTP_DATA_BIG_URL = "https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/phones.jpg";
|
public static final String HTTP_DATA_BIG_URL = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/phones.jpg";
|
||||||
public static final String HTTP_DATA_BIG_URL_ONE_REDIRECT = "http://bit.ly/1iJ69yA";
|
public static final String HTTP_DATA_BIG_URL_ONE_REDIRECT = "http://bit.ly/24DdZ06";
|
||||||
public static final String ASSETS_DATA_NAME = "android.jpg";
|
public static final String ASSETS_DATA_NAME = "android.jpg";
|
||||||
public static final String ASSETS_DATA_BIG_NAME = "phones.jpg";
|
public static final String ASSETS_DATA_BIG_NAME = "phones.jpg";
|
||||||
public static final int HTTP_DATA_SIZE = 4768;
|
public static final int HTTP_DATA_SIZE = 4768;
|
||||||
@@ -40,7 +40,11 @@ public class ProxyCacheTestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Response readProxyResponse(HttpProxyCacheServer proxy, String url, int offset) throws IOException {
|
public static Response readProxyResponse(HttpProxyCacheServer proxy, String url, int offset) throws IOException {
|
||||||
URL proxiedUrl = new URL(proxy.getProxyUrl(url));
|
String proxyUrl = proxy.getProxyUrl(url);
|
||||||
|
if (!proxyUrl.startsWith("http://127.0.0.1")) {
|
||||||
|
throw new IllegalStateException("Url " + url + " is not proxied!");
|
||||||
|
}
|
||||||
|
URL proxiedUrl = new URL(proxyUrl);
|
||||||
HttpURLConnection connection = (HttpURLConnection) proxiedUrl.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) proxiedUrl.openConnection();
|
||||||
try {
|
try {
|
||||||
if (offset >= 0) {
|
if (offset >= 0) {
|
||||||
|
|||||||