It's a little bit late, but I hope this will be useful for somebody.
My solution is to create a custom DataSource
to use it as the source of ExtractorMediaSource. I've copied the AssetDataSource
implementation of ExoPlayer and modified it to get the data from the APK Expansion File.
OBBDataSource.java
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import com.android.vending.expansion.zipfile.APKExpansionSupport;
import com.android.vending.expansion.zipfile.ZipResourceFile;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* A {@link DataSource} for reading from the APK Expansion file
*/
public final class OBBDataSource implements DataSource {
/**
* Thrown when an {@link IOException} is encountered reading an OBB asset.
*/
public static final class OBBDataSourceException extends IOException {
public OBBDataSourceException(IOException cause) {
super(cause);
}
}
private final TransferListener<? super OBBDataSource> listener;
private Uri uri;
private InputStream inputStream;
private long bytesRemaining;
private boolean opened;
private Context context;
private String TAG = "OBB_DATA_SOURCE";
/**
* @param context A context.
*/
public OBBDataSource(Context context) {
this(context, null);
this.context = context;
}
/**
* @param context A context.
* @param listener An optional listener.
*/
public OBBDataSource(Context context, TransferListener<? super OBBDataSource> listener) {
this.context = context;
this.listener = listener;
}
@Override
public long open(DataSpec dataSpec) throws OBBDataSourceException {
try {
uri = dataSpec.uri;
String path = uri.getPath();
if (path.startsWith("/android_asset/")) {
path = path.substring(15);
} else if (path.startsWith("/")) {
path = path.substring(1);
}
inputStream = getInputStreamFromExpansionAPKFile(path);
long skipped = inputStream.skip(dataSpec.position);
if (skipped < dataSpec.position) {
// assetManager.open() returns an AssetInputStream, whose skip() implementation only skips
// fewer bytes than requested if the skip is beyond the end of the asset's data.
throw new EOFException();
}
if (dataSpec.length != C.LENGTH_UNSET) {
bytesRemaining = dataSpec.length;
} else {
bytesRemaining = inputStream.available();
if (bytesRemaining == Integer.MAX_VALUE) {
// assetManager.open() returns an AssetInputStream, whose available() implementation
// returns Integer.MAX_VALUE if the remaining length is greater than (or equal to)
// Integer.MAX_VALUE. We don't know the true length in this case, so treat as unbounded.
bytesRemaining = C.LENGTH_UNSET;
}
}
} catch (IOException e) {
throw new OBBDataSourceException(e);
}
opened = true;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
return bytesRemaining;
}
@Override
public int read(byte[] buffer, int offset, int readLength) throws OBBDataSourceException {
if (readLength == 0) {
return 0;
} else if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
}
int bytesRead;
try {
int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength
: (int) Math.min(bytesRemaining, readLength);
bytesRead = inputStream.read(buffer, offset, bytesToRead);
} catch (IOException e) {
throw new OBBDataSourceException(e);
}
if (bytesRead == -1) {
if (bytesRemaining != C.LENGTH_UNSET) {
// End of stream reached having not read sufficient data.
throw new OBBDataSourceException(new EOFException());
}
return C.RESULT_END_OF_INPUT;
}
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
}
return bytesRead;
}
@Override
public Uri getUri() {
return uri;
}
@Override
public void close() throws OBBDataSourceException {
uri = null;
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
throw new OBBDataSourceException(e);
} finally {
inputStream = null;
if (opened) {
opened = false;
if (listener != null) {
listener.onTransferEnd(this);
}
}
}
}
private InputStream getInputStreamFromExpansionAPKFile(String fileName){
// Get a ZipResourceFile representing a merger of both the main and patch files
try {
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(context, BuildConfig.OBB_FILE_VERSION , -1);
return expansionFile.getInputStream(fileName);
} catch (IOException e) {
Log.w(TAG, "Failed to find expansion file", e);
return null;
}
}
}
Example, How to use it (Kotlin code)
val obbDataSource = OBBDataSource(context)
val dataSource = ExtractorMediaSource.Factory{
obbDataSource
}
val videoPath = name + VIDEO_FILE_EXTENSION
val videoMediaSource : MediaSource
val mediaSource = dataSource.createMediaSource(Uri.parse(videoPath))