Skip to content Skip to sidebar Skip to footer

Why Is My Exoplayer2 Cachedatasource Not Writing The Downloaded Stream To The Designated Cache?

I have implemented a custom cache for Exoplayer2 - and am migrating from 2.11.x to 2.12.3. I have reviewed several posts here on SO (ex: here), as well as various parts from the la

Solution 1:

UPDATE May 2021

I have created a sample app on GitHub that shows this cached data source working, for any readers that are interested to see it.

Tapping the top button in the app launches a SAF-based chooser to select a local video (so no cache is necessary or used). The Uri of the selected file shows in the EditText, and you can tap "Play Video" to see it play.

However by default the sample Uri in the EditText is on the Internet, and as it is "not a local Uri" (the semantics of which are not anything really "official", just a simple delineation I created for the purposes of this test app) it uses the custom cache data source factory which causes Exoplayer's cache mechanics to kick into gear.

You can review the differences in LogCat while the app runs, and can see after playing the video streamed from the internet the first time, on the second run you start getting hits from the cache as evidenced by calls to the onCachedBytesRead() callback.

You can also open up your device explorer while running it on a hardware device connected to Android Studio, and you should see something along these lines:

enter image description here

As you can see, after the first time I played the video, it was cached in the location I specified, and thereafter Exoplayer2 grabs the video from there, not from the Internet again.

For streamed videos, make sure you use a Url like:

https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4.

and NOT something unique such as a YouTube "watch" one, i.e.

https://www.youtube.com/watch?v=Cn8PiqIXEjQ

Also, this sample uses the Exoplayer2 "Progressive" type media source to stream Uri from the Internet (which is now the default one since the Extractor type was deprecated a while back). If a url entered into this app points to a file requiring the DASH type, obviously it won't work.

The Exoplayer2 Source Code has an example app which does a much more sophisticated job of trying the various media source implementations against the target Uri until it finds the right onw.

There is a good basic Exoplayer2 tutorial here.

====

It turns out that this not caching is the default behavior, then in the normal course of DataSource management it is turned on in most cases. In my case it was not getting so because the Uri in question was a content:// Uri pointing to Dropbox that gets resolved by a custom SAF provider which uses Dropbox APIs to stream the video back. So the length of the file never gets recognized, thus the default behavior does not get changed.

Oliver Woodman of the Exoplayer team suggested two solutions. The first (quickest), was to change my custom DataSourceFactory implementation from returning a CacheDataSource, to returning a custom DataSource that "wraps" the CacheDataSource - forwarding the method calls from the DataSource interface to the CacheDataSource instance, except for the open() method. In that case it changes the DataSpec argument first, then passes it along:

/**
 * Class specifically to turn off the flag that would not cache streamed docs.
 */privateclassInTouchCacheDataSourceimplementsDataSource {
    private CacheDataSource cacheDataSource;
    InTouchCacheDataSource(CacheDataSource cacheDataSource) {
        this.cacheDataSource = cacheDataSource;
    }

    @OverridepublicvoidaddTransferListener(TransferListener transferListener) {
        cacheDataSource.addTransferListener(transferListener);
    }

    @Overridepubliclongopen(DataSpec dataSpec)throws IOException {
        return cacheDataSource.open(dataSpec
                .buildUpon()
                .setFlags(dataSpec.flags & ~DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN)
                .build());
    }

    @Nullable@Overridepublic Uri getUri() {
        return cacheDataSource.getUri();
    }

    @Overridepublicvoidclose()throws IOException {
        cacheDataSource.close();
    }

    @Overridepublicintread(byte[] target, int offset, int length)throws IOException {
        return cacheDataSource.read(target, offset, length);
    }
}

This class is then used by the custom factory like this:

@Overridepublic DataSource createDataSource() {
    CacheDataSourcedataSource=newCacheDataSource(InTouchUtils.getMediaCache(),
            defaultDatasourceFactory.createDataSource(),
            newFileDataSource(),
            newCacheDataSink(InTouchUtils.getMediaCache(), maxCacheFragmentSize),
            CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
            listener);
    returnnewInTouchCacheDataSource(dataSource);
}

This works perfectly, and Oliver assures me that Exoplayer will do the right thing even if the stream were to fill the cache (i.e. first envoke the evictor, then if that fails stop trying to write to the cache and just continue to stream).

The other option is to write a custom DataSource using ContentDataSource as a start, and incorporate the customized SAF into it (since in my case at least, I actually do know the size of the file but the standard SAF methods being used to afford the opportunity to pass this back in the standard SAF provider/resolver approach).

Post a Comment for "Why Is My Exoplayer2 Cachedatasource Not Writing The Downloaded Stream To The Designated Cache?"