Skip to content
This repository was archived by the owner on May 27, 2020. It is now read-only.

Commit 241d232

Browse files
committed
add seeking video support (danikula#21) and fix streaming while caching (danikula#17)
1 parent 9ffa983 commit 241d232

File tree

16 files changed

+208
-46
lines changed

16 files changed

+208
-46
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ repositories {
1212
maven { url 'https://dl.bintray.com/alexeydanilov/maven' }
1313
}
1414
dependencies {
15-
compile 'com.danikula:videocache:2.1.4'
15+
compile 'com.danikula:videocache:2.2.0'
1616
}
1717
```
1818

@@ -34,7 +34,7 @@ private HttpProxyCacheServer getProxy() {
3434
```
3535

3636
To guarantee normal work you should use **single** instance of `HttpProxyCacheServer` for whole app.
37-
For example you can store shared proxy on your `Application`:
37+
For example you can store shared proxy in your `Application`:
3838

3939
```java
4040
public class App extends Application {
@@ -59,6 +59,9 @@ More preferable way is use some dependency injector like [Dagger](http://square.
5959
See `sample` app for details.
6060

6161
## Whats new
62+
### 2.2.0
63+
- allow to [seek video](https://github.com/danikula/AndroidVideoCache/issues/21) in any position and [fix](https://github.com/danikula/AndroidVideoCache/issues/17) streaming while caching
64+
6265
### 2.1.4
6366
- [fix](https://github.com/danikula/AndroidVideoCache/issues/18) available cache percents callback
6467

library/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ publish {
2626
userOrg = 'alexeydanilov'
2727
groupId = 'com.danikula'
2828
artifactId = 'videocache'
29-
publishVersion = '2.1.4'
29+
publishVersion = '2.2.0'
3030
description = 'Cache support for android VideoView'
3131
website = 'https://github.com/danikula/AndroidVideoCache'
3232
}

library/src/main/java/com/danikula/videocache/ByteArraySource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public int read(byte[] buffer) throws ProxyCacheException {
2222
}
2323

2424
@Override
25-
public int available() throws ProxyCacheException {
25+
public int length() throws ProxyCacheException {
2626
return data.length;
2727
}
2828

library/src/main/java/com/danikula/videocache/GetRequest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ private String findUri(String request) {
6363
@Override
6464
public String toString() {
6565
return "GetRequest{" +
66-
"uri='" + uri + '\'' +
67-
", rangeOffset=" + rangeOffset +
66+
"rangeOffset=" + rangeOffset +
6867
", partial=" + partial +
68+
", uri='" + uri + '\'' +
6969
'}';
7070
}
7171
}

library/src/main/java/com/danikula/videocache/HttpProxyCache.java

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
import java.io.OutputStream;
88
import java.net.Socket;
99

10+
import static com.danikula.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE;
11+
1012
/**
1113
* {@link ProxyCache} that read http url and writes data to {@link Socket}
1214
*
1315
* @author Alexey Danilov ([email protected]).
1416
*/
1517
class HttpProxyCache extends ProxyCache {
1618

19+
private static final float NO_CACHE_BARRIER = .2f;
20+
1721
private final HttpUrlSource source;
1822
private final FileCache cache;
1923
private CacheListener listener;
@@ -30,27 +34,29 @@ public void registerCacheListener(CacheListener cacheListener) {
3034

3135
public void processRequest(GetRequest request, Socket socket) throws IOException, ProxyCacheException {
3236
OutputStream out = new BufferedOutputStream(socket.getOutputStream());
33-
byte[] buffer = new byte[ProxyCacheUtils.DEFAULT_BUFFER_SIZE];
34-
int readBytes;
35-
boolean headersWrote = false;
37+
String responseHeaders = newResponseHeaders(request);
38+
out.write(responseHeaders.getBytes("UTF-8"));
39+
3640
long offset = request.rangeOffset;
37-
while ((readBytes = read(buffer, offset, buffer.length)) != -1) {
38-
// tiny optimization: to prevent HEAD request in source for content-length. content-length 'll available after reading source
39-
if (!headersWrote) {
40-
String responseHeaders = newResponseHeaders(request);
41-
out.write(responseHeaders.getBytes("UTF-8"));
42-
headersWrote = true;
43-
}
44-
out.write(buffer, 0, readBytes);
45-
offset += readBytes;
41+
if (isUseCache(request)) {
42+
responseWithCache(out, offset);
43+
} else {
44+
responseWithoutCache(out, offset);
4645
}
47-
out.flush();
46+
}
47+
48+
private boolean isUseCache(GetRequest request) throws ProxyCacheException {
49+
int sourceLength = source.length();
50+
boolean sourceLengthKnown = sourceLength > 0;
51+
int cacheAvailable = cache.available();
52+
// do not use cache for partial requests which too far from available cache. It seems user seek video.
53+
return !sourceLengthKnown || !request.partial || request.rangeOffset <= cacheAvailable + sourceLength * NO_CACHE_BARRIER;
4854
}
4955

5056
private String newResponseHeaders(GetRequest request) throws IOException, ProxyCacheException {
5157
String mime = source.getMime();
5258
boolean mimeKnown = !TextUtils.isEmpty(mime);
53-
int length = cache.isCompleted() ? cache.available() : source.available();
59+
int length = cache.isCompleted() ? cache.available() : source.length();
5460
boolean lengthKnown = length >= 0;
5561
long contentLength = request.partial ? length - request.rangeOffset : length;
5662
boolean addRange = lengthKnown && request.partial;
@@ -64,6 +70,32 @@ private String newResponseHeaders(GetRequest request) throws IOException, ProxyC
6470
.toString();
6571
}
6672

73+
private void responseWithCache(OutputStream out, long offset) throws ProxyCacheException, IOException {
74+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
75+
int readBytes;
76+
while ((readBytes = read(buffer, offset, buffer.length)) != -1) {
77+
out.write(buffer, 0, readBytes);
78+
offset += readBytes;
79+
}
80+
out.flush();
81+
}
82+
83+
private void responseWithoutCache(OutputStream out, long offset) throws ProxyCacheException, IOException {
84+
try {
85+
HttpUrlSource source = new HttpUrlSource(this.source);
86+
source.open((int) offset);
87+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
88+
int readBytes;
89+
while ((readBytes = source.read(buffer)) != -1) {
90+
out.write(buffer, 0, readBytes);
91+
offset += readBytes;
92+
}
93+
out.flush();
94+
} finally {
95+
source.close();
96+
}
97+
}
98+
6799
@Override
68100
protected void onCachePercentsAvailableChanged(int percents) {
69101
if (listener != null) {

library/src/main/java/com/danikula/videocache/HttpProxyCacheServer.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public HttpProxyCacheServer(FileNameGenerator fileNameGenerator) {
7979

8080
private void makeSureServerWorks() {
8181
int maxPingAttempts = 3;
82-
int delay = 100;
82+
int delay = 200;
8383
int pingAttempts = 0;
8484
while (pingAttempts < maxPingAttempts) {
8585
try {
@@ -92,13 +92,13 @@ private void makeSureServerWorks() {
9292
SystemClock.sleep(delay);
9393
delay *= 2;
9494
} catch (InterruptedException | ExecutionException | TimeoutException e) {
95-
Log.e(LOG_TAG, "Error pinging server. Shutdown it... If you see this message, please, email me [email protected]", e);
95+
Log.e(LOG_TAG, "Error pinging server [attempt: " + pingAttempts + ", timeout: " + delay + "]. ", e);
9696
}
9797
}
9898

99-
if (!pinged) {
100-
shutdown();
101-
}
99+
Log.e(LOG_TAG, "Shutdown server… Error pinging server [attempt: " + pingAttempts + ", timeout: " + delay + "]. " +
100+
"If you see this message, please, email me [email protected]");
101+
shutdown();
102102
}
103103

104104
private boolean pingServer() throws ProxyCacheException {
@@ -212,7 +212,7 @@ private void processSocket(Socket socket) {
212212
} catch (SocketException e) {
213213
// There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
214214
// So just to prevent log flooding don't log stacktrace
215-
Log.d(LOG_TAG, "Client communication problem. It seems client closed connection");
215+
Log.d(LOG_TAG, "Closing socket… Socket is closed by client.");
216216
} catch (ProxyCacheException | IOException e) {
217217
onError(new ProxyCacheException("Error processing request", e));
218218
} finally {
@@ -262,7 +262,7 @@ private void closeSocketInput(Socket socket) {
262262
} catch (SocketException e) {
263263
// There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
264264
// So just to prevent log flooding don't log stacktrace
265-
Log.d(LOG_TAG, "Error closing client's input stream: it seems client closed connection");
265+
Log.d(LOG_TAG, "Releasing input stream… Socket is closed by client.");
266266
} catch (IOException e) {
267267
onError(new ProxyCacheException("Error closing socket input stream", e));
268268
}

library/src/main/java/com/danikula/videocache/HttpUrlSource.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class HttpUrlSource implements Source {
2929
public final String url;
3030
private HttpURLConnection connection;
3131
private InputStream inputStream;
32-
private volatile int available = Integer.MIN_VALUE;
32+
private volatile int length = Integer.MIN_VALUE;
3333
private volatile String mime;
3434

3535
public HttpUrlSource(String url) {
@@ -41,12 +41,18 @@ public HttpUrlSource(String url, String mime) {
4141
this.mime = mime;
4242
}
4343

44+
public HttpUrlSource(HttpUrlSource source) {
45+
this.url = source.url;
46+
this.mime = source.mime;
47+
this.length = source.length;
48+
}
49+
4450
@Override
45-
public synchronized int available() throws ProxyCacheException {
46-
if (available == Integer.MIN_VALUE) {
51+
public synchronized int length() throws ProxyCacheException {
52+
if (length == Integer.MIN_VALUE) {
4753
fetchContentInfo();
4854
}
49-
return available;
55+
return length;
5056
}
5157

5258
@Override
@@ -55,7 +61,7 @@ public void open(int offset) throws ProxyCacheException {
5561
connection = openConnection(offset, "GET", -1);
5662
mime = connection.getContentType();
5763
inputStream = new BufferedInputStream(connection.getInputStream(), DEFAULT_BUFFER_SIZE);
58-
available = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
64+
length = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
5965
} catch (IOException e) {
6066
throw new ProxyCacheException("Error opening connection for " + url + " with offset " + offset, e);
6167
}
@@ -64,7 +70,7 @@ public void open(int offset) throws ProxyCacheException {
6470
private int readSourceAvailableBytes(HttpURLConnection connection, int offset, int responseCode) throws IOException {
6571
int contentLength = connection.getContentLength();
6672
return responseCode == HTTP_OK ? contentLength
67-
: responseCode == HTTP_PARTIAL ? contentLength + offset : available;
73+
: responseCode == HTTP_PARTIAL ? contentLength + offset : length;
6874
}
6975

7076
@Override
@@ -94,10 +100,10 @@ private void fetchContentInfo() throws ProxyCacheException {
94100
InputStream inputStream = null;
95101
try {
96102
urlConnection = openConnection(0, "HEAD", 10000);
97-
available = urlConnection.getContentLength();
103+
length = urlConnection.getContentLength();
98104
mime = urlConnection.getContentType();
99105
inputStream = urlConnection.getInputStream();
100-
Log.i(LOG_TAG, "Content info for `" + url + "`: mime: " + mime + ", content-length: " + available);
106+
Log.i(LOG_TAG, "Content info for `" + url + "`: mime: " + mime + ", content-length: " + length);
101107
} catch (IOException e) {
102108
Log.e(LOG_TAG, "Error fetching info from " + url, e);
103109
} finally {

library/src/main/java/com/danikula/videocache/ProxyCache.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private void readSource() {
119119
try {
120120
offset = cache.available();
121121
source.open(offset);
122-
sourceAvailable = source.available();
122+
sourceAvailable = source.length();
123123
byte[] buffer = new byte[ProxyCacheUtils.DEFAULT_BUFFER_SIZE];
124124
int readBytes;
125125
while ((readBytes = source.read(buffer)) != -1) {
@@ -144,7 +144,7 @@ private void readSource() {
144144

145145
private void tryComplete() throws ProxyCacheException {
146146
synchronized (stopLock) {
147-
if (!isStopped() && cache.available() == source.available()) {
147+
if (!isStopped() && cache.available() == source.length()) {
148148
cache.complete();
149149
}
150150
}

library/src/main/java/com/danikula/videocache/Source.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ public interface Source {
1616
void open(int offset) throws ProxyCacheException;
1717

1818
/**
19-
* Returns available bytes or <b>negative value</b> if available bytes count is unknown.
19+
* Returns length bytes or <b>negative value</b> if length is unknown.
2020
*
21-
* @return bytes available
21+
* @return bytes length
2222
* @throws ProxyCacheException if error occur while fetching source data.
2323
*/
24-
int available() throws ProxyCacheException;
24+
int length() throws ProxyCacheException;
2525

2626
/**
2727
* Read data to byte buffer from source with current offset.

sample/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ dependencies {
3939
// compile project(':library')
4040
compile 'com.android.support:support-v4:23.0.1'
4141
compile 'org.androidannotations:androidannotations-api:3.3.2'
42-
compile 'com.danikula:videocache:2.1.4'
42+
compile 'com.danikula:videocache:2.2.0'
4343
compile 'com.viewpagerindicator:library:2.4.2-SNAPSHOT@aar'
4444
apt 'org.androidannotations:androidannotations:3.3.2'
4545
}

0 commit comments

Comments
 (0)