mirror of
https://github.com/samsonjs/media.git
synced 2026-03-28 09:55:48 +00:00
Add OkHttp extension to V2.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=118775995
This commit is contained in:
parent
b041b72bae
commit
e4d815d164
7 changed files with 508 additions and 0 deletions
9
extensions/okhttp/README.md
Normal file
9
extensions/okhttp/README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# ExoPlayer OkHttp Extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The OkHttp Extension is an [HttpDataSource][] implementation using Square's [OkHttp][].
|
||||
|
||||
[HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer/upstream/HttpDataSource.html
|
||||
[OkHttp]: https://square.github.io/okhttp/
|
||||
|
||||
42
extensions/okhttp/build.gradle
Normal file
42
extensions/okhttp/build.gradle
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (C) 2014 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 23
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':library')
|
||||
compile('com.squareup.okhttp3:okhttp:+') {
|
||||
exclude group: 'org.json'
|
||||
}
|
||||
}
|
||||
10
extensions/okhttp/src/main/.classpath
Normal file
10
extensions/okhttp/src/main/.classpath
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry combineaccessrules="false" exported="true" kind="src" path="/ExoPlayerLib"/>
|
||||
<classpathentry kind="src" path="java"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
||||
33
extensions/okhttp/src/main/.project
Normal file
33
extensions/okhttp/src/main/.project
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ExoPlayerExt-OkHttp</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
22
extensions/okhttp/src/main/AndroidManifest.xml
Normal file
22
extensions/okhttp/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer.ext.okhttp">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="23"/>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.okhttp;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
import com.google.android.exoplayer.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer.upstream.TransferListener;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.Predicate;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import okhttp3.CacheControl;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* An {@link HttpDataSource} that delegates to Square's {@link OkHttpClient}.
|
||||
*/
|
||||
public class OkHttpDataSource implements HttpDataSource {
|
||||
|
||||
private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>();
|
||||
|
||||
private final OkHttpClient okHttpClient;
|
||||
private final String userAgent;
|
||||
private final Predicate<String> contentTypePredicate;
|
||||
private final TransferListener listener;
|
||||
private final CacheControl cacheControl;
|
||||
private final HashMap<String, String> requestProperties;
|
||||
|
||||
private DataSpec dataSpec;
|
||||
private Response response;
|
||||
private InputStream responseByteStream;
|
||||
private boolean opened;
|
||||
|
||||
private long bytesToSkip;
|
||||
private long bytesToRead;
|
||||
|
||||
private long bytesSkipped;
|
||||
private long bytesRead;
|
||||
|
||||
/**
|
||||
* @param client An {@link OkHttpClient} for use by the source.
|
||||
* @param userAgent The User-Agent string that should be used.
|
||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
||||
* predicate then a
|
||||
* {@link com.google.android.exoplayer.upstream.HttpDataSource.InvalidContentTypeException} is
|
||||
* thrown from {@link #open(DataSpec)}.
|
||||
*/
|
||||
public OkHttpDataSource(OkHttpClient client, String userAgent,
|
||||
Predicate<String> contentTypePredicate) {
|
||||
this(client, userAgent, contentTypePredicate, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param client An {@link OkHttpClient} for use by the source.
|
||||
* @param userAgent The User-Agent string that should be used.
|
||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
||||
* predicate then a
|
||||
* {@link com.google.android.exoplayer.upstream.HttpDataSource.InvalidContentTypeException} is
|
||||
* thrown from {@link #open(DataSpec)}.
|
||||
* @param listener An optional listener.
|
||||
*/
|
||||
public OkHttpDataSource(OkHttpClient client, String userAgent,
|
||||
Predicate<String> contentTypePredicate, TransferListener listener) {
|
||||
this(client, userAgent, contentTypePredicate, listener, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param client An {@link OkHttpClient} for use by the source.
|
||||
* @param userAgent The User-Agent string that should be used.
|
||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
||||
* predicate then a
|
||||
* {@link com.google.android.exoplayer.upstream.HttpDataSource.InvalidContentTypeException} is
|
||||
* thrown from {@link #open(DataSpec)}.
|
||||
* @param listener An optional listener.
|
||||
* @param cacheControl An optional {@link CacheControl} which sets all requests' Cache-Control
|
||||
* header. For example, you could force the network response for all requests.
|
||||
*
|
||||
*/
|
||||
public OkHttpDataSource(OkHttpClient client, String userAgent,
|
||||
Predicate<String> contentTypePredicate, TransferListener listener,
|
||||
CacheControl cacheControl) {
|
||||
this.okHttpClient = Assertions.checkNotNull(client);
|
||||
this.userAgent = Assertions.checkNotEmpty(userAgent);
|
||||
this.contentTypePredicate = contentTypePredicate;
|
||||
this.listener = listener;
|
||||
this.cacheControl = cacheControl;
|
||||
this.requestProperties = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getUri() {
|
||||
return response == null ? null : Uri.parse(response.request().url().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getResponseHeaders() {
|
||||
return response == null ? null : response.headers().toMultimap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestProperty(String name, String value) {
|
||||
Assertions.checkNotNull(name);
|
||||
Assertions.checkNotNull(value);
|
||||
synchronized (requestProperties) {
|
||||
requestProperties.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearRequestProperty(String name) {
|
||||
Assertions.checkNotNull(name);
|
||||
synchronized (requestProperties) {
|
||||
requestProperties.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllRequestProperties() {
|
||||
synchronized (requestProperties) {
|
||||
requestProperties.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws HttpDataSourceException {
|
||||
this.dataSpec = dataSpec;
|
||||
this.bytesRead = 0;
|
||||
this.bytesSkipped = 0;
|
||||
Request request = makeRequest(dataSpec);
|
||||
try {
|
||||
response = okHttpClient.newCall(request).execute();
|
||||
responseByteStream = response.body().byteStream();
|
||||
} catch (IOException e) {
|
||||
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
|
||||
dataSpec);
|
||||
}
|
||||
|
||||
int responseCode = response.code();
|
||||
|
||||
// Check for a valid response code.
|
||||
if (!response.isSuccessful()) {
|
||||
Map<String, List<String>> headers = request.headers().toMultimap();
|
||||
closeConnectionQuietly();
|
||||
throw new InvalidResponseCodeException(responseCode, headers, dataSpec);
|
||||
}
|
||||
|
||||
// Check for a valid content type.
|
||||
MediaType mediaType = response.body().contentType();
|
||||
String contentType = mediaType != null ? mediaType.toString() : null;
|
||||
if (contentTypePredicate != null && !contentTypePredicate.evaluate(contentType)) {
|
||||
closeConnectionQuietly();
|
||||
throw new InvalidContentTypeException(contentType, dataSpec);
|
||||
}
|
||||
|
||||
// If we requested a range starting from a non-zero position and received a 200 rather than a
|
||||
// 206, then the server does not support partial requests. We'll need to manually skip to the
|
||||
// requested position.
|
||||
bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0;
|
||||
|
||||
// Determine the length of the data to be read, after skipping.
|
||||
long contentLength = response.body().contentLength();
|
||||
bytesToRead = dataSpec.length != C.LENGTH_UNBOUNDED ? dataSpec.length
|
||||
: contentLength != -1 ? contentLength - bytesToSkip
|
||||
: C.LENGTH_UNBOUNDED;
|
||||
|
||||
opened = true;
|
||||
if (listener != null) {
|
||||
listener.onTransferStart();
|
||||
}
|
||||
|
||||
return bytesToRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException {
|
||||
try {
|
||||
skipInternal();
|
||||
return readInternal(buffer, offset, readLength);
|
||||
} catch (IOException e) {
|
||||
throw new HttpDataSourceException(e, dataSpec);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws HttpDataSourceException {
|
||||
if (opened) {
|
||||
opened = false;
|
||||
if (listener != null) {
|
||||
listener.onTransferEnd();
|
||||
}
|
||||
closeConnectionQuietly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that have been skipped since the most recent call to
|
||||
* {@link #open(DataSpec)}.
|
||||
*
|
||||
* @return The number of bytes skipped.
|
||||
*/
|
||||
protected final long bytesSkipped() {
|
||||
return bytesSkipped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that have been read since the most recent call to
|
||||
* {@link #open(DataSpec)}.
|
||||
*
|
||||
* @return The number of bytes read.
|
||||
*/
|
||||
protected final long bytesRead() {
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that are still to be read for the current {@link DataSpec}.
|
||||
* <p>
|
||||
* If the total length of the data being read is known, then this length minus {@code bytesRead()}
|
||||
* is returned. If the total length is unknown, {@link C#LENGTH_UNBOUNDED} is returned.
|
||||
*
|
||||
* @return The remaining length, or {@link C#LENGTH_UNBOUNDED}.
|
||||
*/
|
||||
protected final long bytesRemaining() {
|
||||
return bytesToRead == C.LENGTH_UNBOUNDED ? bytesToRead : bytesToRead - bytesRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a connection.
|
||||
*/
|
||||
private Request makeRequest(DataSpec dataSpec) {
|
||||
long position = dataSpec.position;
|
||||
long length = dataSpec.length;
|
||||
boolean allowGzip = (dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) != 0;
|
||||
|
||||
HttpUrl url = HttpUrl.parse(dataSpec.uri.toString());
|
||||
Request.Builder builder = new Request.Builder().url(url);
|
||||
if (cacheControl != null) {
|
||||
builder.cacheControl(cacheControl);
|
||||
}
|
||||
synchronized (requestProperties) {
|
||||
for (Map.Entry<String, String> property : requestProperties.entrySet()) {
|
||||
builder.addHeader(property.getKey(), property.getValue());
|
||||
}
|
||||
}
|
||||
if (!(position == 0 && length == C.LENGTH_UNBOUNDED)) {
|
||||
String rangeRequest = "bytes=" + position + "-";
|
||||
if (length != C.LENGTH_UNBOUNDED) {
|
||||
rangeRequest += (position + length - 1);
|
||||
}
|
||||
builder.addHeader("Range", rangeRequest);
|
||||
}
|
||||
builder.addHeader("User-Agent", userAgent);
|
||||
if (!allowGzip) {
|
||||
builder.addHeader("Accept-Encoding", "identity");
|
||||
}
|
||||
if (dataSpec.postBody != null) {
|
||||
builder.post(RequestBody.create(null, dataSpec.postBody));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips any bytes that need skipping. Else does nothing.
|
||||
* <p>
|
||||
* This implementation is based roughly on {@code libcore.io.Streams.skipByReading()}.
|
||||
*
|
||||
* @throws InterruptedIOException If the thread is interrupted during the operation.
|
||||
* @throws EOFException If the end of the input stream is reached before the bytes are skipped.
|
||||
*/
|
||||
private void skipInternal() throws IOException {
|
||||
if (bytesSkipped == bytesToSkip) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Acquire the shared skip buffer.
|
||||
byte[] skipBuffer = skipBufferReference.getAndSet(null);
|
||||
if (skipBuffer == null) {
|
||||
skipBuffer = new byte[4096];
|
||||
}
|
||||
|
||||
while (bytesSkipped != bytesToSkip) {
|
||||
int readLength = (int) Math.min(bytesToSkip - bytesSkipped, skipBuffer.length);
|
||||
int read = responseByteStream.read(skipBuffer, 0, readLength);
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedIOException();
|
||||
}
|
||||
if (read == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
bytesSkipped += read;
|
||||
if (listener != null) {
|
||||
listener.onBytesTransferred(read);
|
||||
}
|
||||
}
|
||||
|
||||
// Release the shared skip buffer.
|
||||
skipBufferReference.set(skipBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at
|
||||
* index {@code offset}.
|
||||
* <p>
|
||||
* This method blocks until at least one byte of data can be read, the end of the opened range is
|
||||
* detected, or an exception is thrown.
|
||||
*
|
||||
* @param buffer The buffer into which the read data should be stored.
|
||||
* @param offset The start offset into {@code buffer} at which data should be written.
|
||||
* @param readLength The maximum number of bytes to read.
|
||||
* @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened
|
||||
* range is reached.
|
||||
* @throws IOException If an error occurs reading from the source.
|
||||
*/
|
||||
private int readInternal(byte[] buffer, int offset, int readLength) throws IOException {
|
||||
readLength = bytesToRead == C.LENGTH_UNBOUNDED ? readLength
|
||||
: (int) Math.min(readLength, bytesToRead - bytesRead);
|
||||
if (readLength == 0) {
|
||||
// We've read all of the requested data.
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
}
|
||||
|
||||
int read = responseByteStream.read(buffer, offset, readLength);
|
||||
if (read == -1) {
|
||||
if (bytesToRead != C.LENGTH_UNBOUNDED && bytesToRead != bytesRead) {
|
||||
// The server closed the connection having not sent sufficient data.
|
||||
throw new EOFException();
|
||||
}
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
}
|
||||
|
||||
bytesRead += read;
|
||||
if (listener != null) {
|
||||
listener.onBytesTransferred(read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current connection quietly, if there is one.
|
||||
*/
|
||||
private void closeConnectionQuietly() {
|
||||
response.body().close();
|
||||
response = null;
|
||||
responseByteStream = null;
|
||||
}
|
||||
|
||||
}
|
||||
16
extensions/okhttp/src/main/project.properties
Normal file
16
extensions/okhttp/src/main/project.properties
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-23
|
||||
android.library=true
|
||||
android.library.reference.1=../../../../library/src/main
|
||||
Loading…
Reference in a new issue