feat(android): implement asset folder playback (#3733)

* fix(ts): onPlaybackRateChangeData was not correctly typed

* fix: ensure tracks are well displayed in the sample

* feat(android): implement playback from asset folder

* chore(android): fix linter

* chore: move sample mp4 from package assets to exemple assets
This commit is contained in:
Olivier Bouillet 2024-05-06 21:51:17 +02:00 committed by GitHub
parent 51e22abfe3
commit e05da4e9fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 46 additions and 49 deletions

View File

@ -1,12 +1,18 @@
package com.brentvatne.exoplayer; package com.brentvatne.exoplayer;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.datasource.AssetDataSource;
import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.DefaultDataSource; import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.HttpDataSource; import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.okhttp.OkHttpDataSource; import androidx.media3.datasource.okhttp.OkHttpDataSource;
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
import com.brentvatne.common.toolbox.DebugLog;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.CookieJarContainer; import com.facebook.react.modules.network.CookieJarContainer;
import com.facebook.react.modules.network.ForwardingCookieHandler; import com.facebook.react.modules.network.ForwardingCookieHandler;
@ -23,7 +29,6 @@ public class DataSourceUtil {
private DataSourceUtil() { private DataSourceUtil() {
} }
private static DataSource.Factory rawDataSourceFactory = null;
private static DataSource.Factory defaultDataSourceFactory = null; private static DataSource.Factory defaultDataSourceFactory = null;
private static HttpDataSource.Factory defaultHttpDataSourceFactory = null; private static HttpDataSource.Factory defaultHttpDataSourceFactory = null;
private static String userAgent = null; private static String userAgent = null;
@ -39,18 +44,6 @@ public class DataSourceUtil {
return userAgent; return userAgent;
} }
public static DataSource.Factory getRawDataSourceFactory(ReactContext context) {
if (rawDataSourceFactory == null) {
rawDataSourceFactory = buildRawDataSourceFactory(context);
}
return rawDataSourceFactory;
}
public static void setRawDataSourceFactory(DataSource.Factory factory) {
DataSourceUtil.rawDataSourceFactory = factory;
}
public static DataSource.Factory getDefaultDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) { public static DataSource.Factory getDefaultDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {
if (defaultDataSourceFactory == null || (requestHeaders != null && !requestHeaders.isEmpty())) { if (defaultDataSourceFactory == null || (requestHeaders != null && !requestHeaders.isEmpty())) {
defaultDataSourceFactory = buildDataSourceFactory(context, bandwidthMeter, requestHeaders); defaultDataSourceFactory = buildDataSourceFactory(context, bandwidthMeter, requestHeaders);
@ -58,10 +51,6 @@ public class DataSourceUtil {
return defaultDataSourceFactory; return defaultDataSourceFactory;
} }
public static void setDefaultDataSourceFactory(DataSource.Factory factory) {
DataSourceUtil.defaultDataSourceFactory = factory;
}
public static HttpDataSource.Factory getDefaultHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) { public static HttpDataSource.Factory getDefaultHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {
if (defaultHttpDataSourceFactory == null || (requestHeaders != null && !requestHeaders.isEmpty())) { if (defaultHttpDataSourceFactory == null || (requestHeaders != null && !requestHeaders.isEmpty())) {
defaultHttpDataSourceFactory = buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders); defaultHttpDataSourceFactory = buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders);
@ -69,14 +58,6 @@ public class DataSourceUtil {
return defaultHttpDataSourceFactory; return defaultHttpDataSourceFactory;
} }
public static void setDefaultHttpDataSourceFactory(HttpDataSource.Factory factory) {
DataSourceUtil.defaultHttpDataSourceFactory = factory;
}
private static DataSource.Factory buildRawDataSourceFactory(ReactContext context) {
return new RawResourceDataSourceFactory(context.getApplicationContext());
}
private static DataSource.Factory buildDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) { private static DataSource.Factory buildDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {
return new DefaultDataSource.Factory(context, buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders)); return new DefaultDataSource.Factory(context, buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders));
} }
@ -100,4 +81,17 @@ public class DataSourceUtil {
return okHttpDataSourceFactory; return okHttpDataSourceFactory;
} }
public static DataSource.Factory buildAssetDataSourceFactory(ReactContext context, Uri srcUri) throws AssetDataSource.AssetDataSourceException {
DataSpec dataSpec = new DataSpec(srcUri);
final AssetDataSource rawResourceDataSource = new AssetDataSource(context);
rawResourceDataSource.open(dataSpec);
return new DataSource.Factory() {
@NonNull
@Override
public DataSource createDataSource() {
return rawResourceDataSource;
}
};
}
} }

View File

@ -1,20 +0,0 @@
package com.brentvatne.exoplayer;
import android.content.Context;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.RawResourceDataSource;
class RawResourceDataSourceFactory implements DataSource.Factory {
private final Context context;
RawResourceDataSourceFactory(Context context) {
this.context = context;
}
@Override
public DataSource createDataSource() {
return new RawResourceDataSource(context);
}
}

View File

@ -7,6 +7,8 @@ import static androidx.media3.common.C.CONTENT_TYPE_RTSP;
import static androidx.media3.common.C.CONTENT_TYPE_SS; import static androidx.media3.common.C.CONTENT_TYPE_SS;
import static androidx.media3.common.C.TIME_END_OF_SOURCE; import static androidx.media3.common.C.TIME_END_OF_SOURCE;
import static com.brentvatne.exoplayer.DataSourceUtil.buildAssetDataSourceFactory;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager; import android.app.ActivityManager;
@ -841,7 +843,14 @@ public class ReactExoplayerView extends FrameLayout implements
); );
break; break;
case CONTENT_TYPE_OTHER: case CONTENT_TYPE_OTHER:
if (uri.toString().startsWith("file://") || if ("asset".equals(srcUri.getScheme())) {
try {
DataSource.Factory assetDataSourceFactory = buildAssetDataSourceFactory(themedReactContext, srcUri);
mediaSourceFactory = new ProgressiveMediaSource.Factory(assetDataSourceFactory);
} catch (Exception e) {
throw new IllegalStateException("cannot open input file" + srcUri);
}
} else if ("file".equals(srcUri.getScheme()) ||
cacheDataSourceFactory == null) { cacheDataSourceFactory == null) {
mediaSourceFactory = new ProgressiveMediaSource.Factory( mediaSourceFactory = new ProgressiveMediaSource.Factory(
mediaDataSourceFactory mediaDataSourceFactory

View File

@ -12,9 +12,7 @@ import com.facebook.react.uimanager.common.UIManagerType
import kotlin.math.roundToInt import kotlin.math.roundToInt
class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) { class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String { override fun getName(): String = REACT_CLASS
return REACT_CLASS
}
private fun performOnPlayerView(reactTag: Int, callback: (ReactExoplayerView?) -> Unit) { private fun performOnPlayerView(reactTag: Int, callback: (ReactExoplayerView?) -> Unit) {
UiThreadUtil.runOnUiThread { UiThreadUtil.runOnUiThread {

View File

@ -638,6 +638,18 @@ source={{ uri: 'file:///sdcard/Movies/sintel.mp4' }}
Note: Your app will need to request permission to read external storage if you're accessing a file outside your app. Note: Your app will need to request permission to read external storage if you're accessing a file outside your app.
##### File from asset folder (asset://)
<PlatformsList types={['Android']} />
Allows to play a video file from the asset folder from the application
Example:
```javascript
source={{ uri: 'asset:///sintel.mp4' }}
```
##### iPod Library (ipod-library://) ##### iPod Library (ipod-library://)
<PlatformsList types={['iOS']} /> <PlatformsList types={['iOS']} />

View File

@ -170,6 +170,10 @@ class VideoPlayer extends Component {
description: 'Another live sample', description: 'Another live sample',
uri: 'https://live.forstreet.cl/live/livestream.m3u8', uri: 'https://live.forstreet.cl/live/livestream.m3u8',
}, },
{
description: 'asset file',
uri: 'asset:///broadchurch.mp4',
},
{ {
description: '(dash) sintel subtitles', description: '(dash) sintel subtitles',
uri: 'https://bitmovin-a.akamaihd.net/content/sintel/sintel.mpd', uri: 'https://bitmovin-a.akamaihd.net/content/sintel/sintel.mpd',