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:
parent
51e22abfe3
commit
e05da4e9fe
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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']} />
|
||||||
|
BIN
examples/basic/android/app/src/main/assets/broadchurch.mp4
Normal file
BIN
examples/basic/android/app/src/main/assets/broadchurch.mp4
Normal file
Binary file not shown.
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user