Support android.resource URI scheme

Issue: #7866
PiperOrigin-RevId: 330736774
This commit is contained in:
kimvde 2020-09-09 17:23:05 +01:00 committed by Oliver Woodman
parent 8955cd3a61
commit 97a0df77f6
3 changed files with 78 additions and 28 deletions

View File

@ -1,5 +1,11 @@
# Release notes
### 2.12.1 ###
* Data sources:
* Add support for `android.resource` URI scheme in `RawResourceDataSource`
([#7866](https://github.com/google/ExoPlayer/issues/7866)).
### 2.12.0 (2020-09-11) ###
To learn more about what's new in 2.12, read the corresponding

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.upstream;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.Nullable;
@ -39,6 +40,9 @@ import java.util.Map;
* <li>rawresource: For fetching data from a raw resource in the application's apk (e.g.
* rawresource:///resourceId, where rawResourceId is the integer identifier of the raw
* resource).
* <li>android.resource: For fetching data in the application's apk (e.g.
* android.resource:///resourceId or android.resource://resourceType/resourceName). See {@link
* RawResourceDataSource} for more information about the URI form.
* <li>content: For fetching data from a content URI (e.g. content://authority/path/123).
* <li>rtmp: For fetching data over RTMP. Only supported if the project using ExoPlayer has an
* explicit dependency on ExoPlayer's RTMP extension.
@ -58,7 +62,9 @@ public final class DefaultDataSource implements DataSource {
private static final String SCHEME_CONTENT = "content";
private static final String SCHEME_RTMP = "rtmp";
private static final String SCHEME_UDP = "udp";
private static final String SCHEME_DATA = DataSchemeDataSource.SCHEME_DATA;
private static final String SCHEME_RAW = RawResourceDataSource.RAW_RESOURCE_SCHEME;
private static final String SCHEME_ANDROID_RESOURCE = ContentResolver.SCHEME_ANDROID_RESOURCE;
private final Context context;
private final List<TransferListener> transferListeners;
@ -182,9 +188,9 @@ public final class DefaultDataSource implements DataSource {
dataSource = getRtmpDataSource();
} else if (SCHEME_UDP.equals(scheme)) {
dataSource = getUdpDataSource();
} else if (DataSchemeDataSource.SCHEME_DATA.equals(scheme)) {
} else if (SCHEME_DATA.equals(scheme)) {
dataSource = getDataSchemeDataSource();
} else if (SCHEME_RAW.equals(scheme)) {
} else if (SCHEME_RAW.equals(scheme) || SCHEME_ANDROID_RESOURCE.equals(scheme)) {
dataSource = getRawResourceDataSource();
} else {
dataSource = baseDataSource;

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.upstream;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.min;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
@ -34,9 +35,20 @@ import java.io.InputStream;
/**
* A {@link DataSource} for reading a raw resource inside the APK.
*
* <p>URIs supported by this source are of the form {@code rawresource:///rawResourceId}, where
* rawResourceId is the integer identifier of a raw resource. {@link #buildRawResourceUri(int)} can
* be used to build {@link Uri}s in this format.
* <p>URIs supported by this source are of one of the forms:
*
* <ul>
* <li>{@code rawresource:///id}, where {@code id} is the integer identifier of a raw resource.
* <li>{@code android.resource:///id}, where {@code id} is the integer identifier of a raw
* resource.
* <li>{@code android.resource://[package]/[type/]name}, where {@code package} is the name of the
* package in which the resource is located, {@code type} is the resource type and {@code
* name} is the resource name. The package and the type are optional. Their default value is
* the package of this application and "raw", respectively. Using the two other forms is more
* efficient.
* </ul>
*
* <p>{@link #buildRawResourceUri(int)} can be used to build supported {@link Uri}s.
*/
public final class RawResourceDataSource extends BaseDataSource {
@ -67,6 +79,7 @@ public final class RawResourceDataSource extends BaseDataSource {
public static final String RAW_RESOURCE_SCHEME = "rawresource";
private final Resources resources;
private final String packageName;
@Nullable private Uri uri;
@Nullable private AssetFileDescriptor assetFileDescriptor;
@ -80,33 +93,55 @@ public final class RawResourceDataSource extends BaseDataSource {
public RawResourceDataSource(Context context) {
super(/* isNetwork= */ false);
this.resources = context.getResources();
this.packageName = context.getPackageName();
}
@Override
public long open(DataSpec dataSpec) throws RawResourceDataSourceException {
try {
Uri uri = dataSpec.uri;
this.uri = uri;
if (!TextUtils.equals(RAW_RESOURCE_SCHEME, uri.getScheme())) {
throw new RawResourceDataSourceException("URI must use scheme " + RAW_RESOURCE_SCHEME);
}
Uri uri = dataSpec.uri;
this.uri = uri;
int resourceId;
int resourceId;
if (TextUtils.equals(RAW_RESOURCE_SCHEME, uri.getScheme())
|| (TextUtils.equals(ContentResolver.SCHEME_ANDROID_RESOURCE, uri.getScheme())
&& uri.getPathSegments().size() == 1
&& Assertions.checkNotNull(uri.getLastPathSegment()).matches("\\d+"))) {
try {
resourceId = Integer.parseInt(Assertions.checkNotNull(uri.getLastPathSegment()));
} catch (NumberFormatException e) {
throw new RawResourceDataSourceException("Resource identifier must be an integer.");
}
transferInitializing(dataSpec);
AssetFileDescriptor assetFileDescriptor = resources.openRawResourceFd(resourceId);
this.assetFileDescriptor = assetFileDescriptor;
if (assetFileDescriptor == null) {
throw new RawResourceDataSourceException("Resource is compressed: " + uri);
} else if (TextUtils.equals(ContentResolver.SCHEME_ANDROID_RESOURCE, uri.getScheme())) {
String path = Assertions.checkNotNull(uri.getPath());
if (path.startsWith("/")) {
path = path.substring(1);
}
FileInputStream inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor());
this.inputStream = inputStream;
@Nullable String host = uri.getHost();
String resourceName = (TextUtils.isEmpty(host) ? "" : (host + ":")) + path;
resourceId =
resources.getIdentifier(
resourceName, /* defType= */ "raw", /* defPackage= */ packageName);
if (resourceId == 0) {
throw new RawResourceDataSourceException("Resource not found.");
}
} else {
throw new RawResourceDataSourceException(
"URI must either use scheme "
+ RAW_RESOURCE_SCHEME
+ " or "
+ ContentResolver.SCHEME_ANDROID_RESOURCE);
}
transferInitializing(dataSpec);
AssetFileDescriptor assetFileDescriptor = resources.openRawResourceFd(resourceId);
this.assetFileDescriptor = assetFileDescriptor;
if (assetFileDescriptor == null) {
throw new RawResourceDataSourceException("Resource is compressed: " + uri);
}
FileInputStream inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor());
this.inputStream = inputStream;
try {
inputStream.skip(assetFileDescriptor.getStartOffset());
long skipped = inputStream.skip(dataSpec.position);
if (skipped < dataSpec.position) {
@ -114,18 +149,21 @@ public final class RawResourceDataSource extends BaseDataSource {
// skip beyond the end of the data.
throw new EOFException();
}
if (dataSpec.length != C.LENGTH_UNSET) {
bytesRemaining = dataSpec.length;
} else {
long assetFileDescriptorLength = assetFileDescriptor.getLength();
// If the length is UNKNOWN_LENGTH then the asset extends to the end of the file.
bytesRemaining = assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH
? C.LENGTH_UNSET : (assetFileDescriptorLength - dataSpec.position);
}
} catch (IOException e) {
throw new RawResourceDataSourceException(e);
}
if (dataSpec.length != C.LENGTH_UNSET) {
bytesRemaining = dataSpec.length;
} else {
long assetFileDescriptorLength = assetFileDescriptor.getLength();
// If the length is UNKNOWN_LENGTH then the asset extends to the end of the file.
bytesRemaining =
assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH
? C.LENGTH_UNSET
: (assetFileDescriptorLength - dataSpec.position);
}
opened = true;
transferStarted(dataSpec);