2

I would like to load video and audio file to SimpleExoPlayer from an uncompressed zip APK Expansion file (ZipResourceFile - OBB). Generally I create a MediaSource object and pass it to the player but when I use a ZipResourceFile i can't get the Uri of the media files

MediaSource mediaSource = new ExtractorMediaSource(mediaUri, new DefaultDataSourceFactory(this, userAgent), new DefaultExtractorsFactory(), null, null);

mSimpleExoPlayer.prepare(mediaSource);
mSimpleExoPlayer.setPlayWhenReady(true);

Thanks

1 Answers1

1

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))
jmartinalonso
  • 2,324
  • 1
  • 19
  • 22