feat(android): add asset management functions to plugin (#4494)
* feat: manage asset plugins * fix: continue when plugin is not exoplayer plugin * refactor: change functions plugin type * docs: introduce new plugins
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
package com.brentvatne.exoplayer
|
package com.brentvatne.exoplayer
|
||||||
|
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.datasource.DataSource
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import com.brentvatne.common.api.Source
|
||||||
import com.brentvatne.react.RNVPlugin
|
import com.brentvatne.react.RNVPlugin
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,6 +18,32 @@ interface RNVExoplayerPlugin : RNVPlugin {
|
|||||||
*/
|
*/
|
||||||
fun getDRMManager(): DRMManagerSpec? = null
|
fun getDRMManager(): DRMManagerSpec? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional function that allows the plugin to override the media data source factory,
|
||||||
|
* which is responsible for loading media data.
|
||||||
|
* @param source The media source being initialized.
|
||||||
|
* @param mediaDataSourceFactory The current default data source factory.
|
||||||
|
* @return A custom [DataSource.Factory] if override is needed, or null to use default.
|
||||||
|
*/
|
||||||
|
fun overrideMediaDataSourceFactory(source: Source, mediaDataSourceFactory: DataSource.Factory): DataSource.Factory? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional function that allows the plugin to modify the [MediaItem.Builder]
|
||||||
|
* before the final [MediaItem] is created.
|
||||||
|
* @param source The source from which the media item is being built.
|
||||||
|
* @param mediaItemBuilder The default [MediaItem.Builder] instance.
|
||||||
|
* @return A modified builder instance if override is needed, or null to use original.
|
||||||
|
*/
|
||||||
|
fun overrideMediaItemBuilder(source: Source, mediaItemBuilder: MediaItem.Builder): MediaItem.Builder? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional function that allows the plugin to control whether caching should be disabled
|
||||||
|
* for a given video source.
|
||||||
|
* @param source The video source being loaded.
|
||||||
|
* @return true to disable caching, false to keep it enabled.
|
||||||
|
*/
|
||||||
|
fun shouldDisableCache(source: Source): Boolean = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function called when a new player is created
|
* Function called when a new player is created
|
||||||
* @param id: a random string identifying the player
|
* @param id: a random string identifying the player
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
private Runnable mainRunnable;
|
private Runnable mainRunnable;
|
||||||
private Runnable pipListenerUnsubscribe;
|
private Runnable pipListenerUnsubscribe;
|
||||||
private boolean useCache = false;
|
private boolean useCache = false;
|
||||||
|
private boolean disableCache = false;
|
||||||
private ControlsConfig controlsConfig = new ControlsConfig();
|
private ControlsConfig controlsConfig = new ControlsConfig();
|
||||||
private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>();
|
private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>();
|
||||||
|
|
||||||
@@ -750,6 +751,8 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializePlayer() {
|
private void initializePlayer() {
|
||||||
|
disableCache = ReactNativeVideoManager.Companion.getInstance().shouldDisableCache(source);
|
||||||
|
|
||||||
ReactExoplayerView self = this;
|
ReactExoplayerView self = this;
|
||||||
Activity activity = themedReactContext.getCurrentActivity();
|
Activity activity = themedReactContext.getCurrentActivity();
|
||||||
// This ensures all props have been settled, to avoid async racing conditions.
|
// This ensures all props have been settled, to avoid async racing conditions.
|
||||||
@@ -853,7 +856,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
.forceEnableMediaCodecAsynchronousQueueing();
|
.forceEnableMediaCodecAsynchronousQueueing();
|
||||||
|
|
||||||
DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory);
|
DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory);
|
||||||
if (useCache) {
|
if (useCache && !disableCache) {
|
||||||
mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)));
|
mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1183,7 +1186,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
DataSource.Factory dataSourceFactory = mediaDataSourceFactory;
|
DataSource.Factory dataSourceFactory = mediaDataSourceFactory;
|
||||||
|
|
||||||
if (useCache) {
|
if (useCache && !disableCache) {
|
||||||
dataSourceFactory = RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true));
|
dataSourceFactory = RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1230,7 +1233,15 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaItem mediaItem = mediaItemBuilder.setStreamKeys(streamKeys).build();
|
mediaItemBuilder.setStreamKeys(streamKeys);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
final MediaItem.Builder overridenMediaItemBuilder = ReactNativeVideoManager.Companion.getInstance().overrideMediaItemBuilder(source, mediaItemBuilder);
|
||||||
|
|
||||||
|
MediaItem mediaItem = overridenMediaItemBuilder != null
|
||||||
|
? overridenMediaItemBuilder.build()
|
||||||
|
: mediaItemBuilder.build();
|
||||||
|
|
||||||
MediaSource mediaSource = mediaSourceFactory
|
MediaSource mediaSource = mediaSourceFactory
|
||||||
.setDrmSessionManagerProvider(drmProvider)
|
.setDrmSessionManagerProvider(drmProvider)
|
||||||
.setLoadErrorHandlingPolicy(
|
.setLoadErrorHandlingPolicy(
|
||||||
@@ -1921,10 +1932,15 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
boolean isSourceEqual = source.isEquals(this.source);
|
boolean isSourceEqual = source.isEquals(this.source);
|
||||||
hasDrmFailed = false;
|
hasDrmFailed = false;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.mediaDataSourceFactory =
|
final DataSource.Factory tmpMediaDataSourceFactory =
|
||||||
DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter,
|
DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter,
|
||||||
source.getHeaders());
|
source.getHeaders());
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
final DataSource.Factory overriddenMediaDataSourceFactory = ReactNativeVideoManager.Companion.getInstance().overrideMediaDataSourceFactory(source, tmpMediaDataSourceFactory);
|
||||||
|
|
||||||
|
this.mediaDataSourceFactory = Objects.requireNonNullElse(overriddenMediaDataSourceFactory, tmpMediaDataSourceFactory);
|
||||||
|
|
||||||
if (source.getCmcdProps() != null) {
|
if (source.getCmcdProps() != null) {
|
||||||
CMCDConfig cmcdConfig = new CMCDConfig(source.getCmcdProps());
|
CMCDConfig cmcdConfig = new CMCDConfig(source.getCmcdProps());
|
||||||
CmcdConfiguration.Factory factory = cmcdConfig.toCmcdConfigurationFactory();
|
CmcdConfiguration.Factory factory = cmcdConfig.toCmcdConfigurationFactory();
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.brentvatne.react
|
package com.brentvatne.react
|
||||||
|
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.datasource.DataSource
|
||||||
|
import com.brentvatne.common.api.Source
|
||||||
import com.brentvatne.common.toolbox.DebugLog
|
import com.brentvatne.common.toolbox.DebugLog
|
||||||
import com.brentvatne.exoplayer.DRMManagerSpec
|
import com.brentvatne.exoplayer.DRMManagerSpec
|
||||||
import com.brentvatne.exoplayer.RNVExoplayerPlugin
|
import com.brentvatne.exoplayer.RNVExoplayerPlugin
|
||||||
@@ -75,6 +78,35 @@ class ReactNativeVideoManager : RNVPlugin {
|
|||||||
// ----------------------- RNV Exoplayer plugin specific methods -----------------------
|
// ----------------------- RNV Exoplayer plugin specific methods -----------------------
|
||||||
fun getDRMManager(): DRMManagerSpec? = customDRMManager
|
fun getDRMManager(): DRMManagerSpec? = customDRMManager
|
||||||
|
|
||||||
|
fun overrideMediaDataSourceFactory(source: Source, mediaDataSourceFactory: DataSource.Factory): DataSource.Factory? {
|
||||||
|
for (plugin in pluginList) {
|
||||||
|
if (plugin !is RNVExoplayerPlugin) continue
|
||||||
|
|
||||||
|
val factory = plugin.overrideMediaDataSourceFactory(source, mediaDataSourceFactory)
|
||||||
|
if (factory != null) return factory
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun overrideMediaItemBuilder(source: Source, mediaItemBuilder: MediaItem.Builder): MediaItem.Builder? {
|
||||||
|
for (plugin in pluginList) {
|
||||||
|
if (plugin !is RNVExoplayerPlugin) continue
|
||||||
|
|
||||||
|
val builder = plugin.overrideMediaItemBuilder(source, mediaItemBuilder)
|
||||||
|
if (builder != null) return builder
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shouldDisableCache(source: Source): Boolean {
|
||||||
|
for (plugin in pluginList) {
|
||||||
|
if (plugin is RNVExoplayerPlugin && plugin.shouldDisableCache(source)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------- Custom Plugins Helpers -----------------------
|
// ----------------------- Custom Plugins Helpers -----------------------
|
||||||
private fun maybeRegisterExoplayerPlugin(plugin: RNVPlugin) {
|
private fun maybeRegisterExoplayerPlugin(plugin: RNVPlugin) {
|
||||||
if (plugin !is RNVExoplayerPlugin) {
|
if (plugin !is RNVExoplayerPlugin) {
|
||||||
|
|||||||
@@ -111,6 +111,65 @@ In the sample implementation, the plugin is registered in the `createNativeModul
|
|||||||
|
|
||||||
Once registered, your module can track player updates and report analytics data.
|
Once registered, your module can track player updates and report analytics data.
|
||||||
|
|
||||||
|
### Extending Core Functionality via Plugins
|
||||||
|
|
||||||
|
In addition to analytics, plugins can also be used to modify or override core behavior of `react-native-video`.
|
||||||
|
|
||||||
|
This allows native modules to deeply integrate with the playback system - for example:
|
||||||
|
- replacing the media source factory,
|
||||||
|
- modifying the media item before playback starts (e.g., injecting stream keys),
|
||||||
|
- disabling caching dynamically per source.
|
||||||
|
|
||||||
|
These capabilities are available through the advanced Android plugin interface: `RNVExoplayerPlugin`.
|
||||||
|
|
||||||
|
> ⚠️ These extension points are optional — if no plugin provides them, the player behaves exactly as it did before.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Plugin Extension Points (Android)
|
||||||
|
|
||||||
|
If your plugin implements `RNVExoplayerPlugin`, you can override the following methods:
|
||||||
|
|
||||||
|
##### 1. `overrideMediaItemBuilder`
|
||||||
|
|
||||||
|
Allows you to modify the `MediaItem.Builder` before it’s used. You can inject stream keys, cache keys, or override URIs.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
override fun overrideMediaItemBuilder(
|
||||||
|
source: Source,
|
||||||
|
mediaItemBuilder: MediaItem.Builder
|
||||||
|
): MediaItem.Builder? {
|
||||||
|
// Return modified builder or null to use default
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 2. `overrideMediaDataSourceFactory`
|
||||||
|
|
||||||
|
Lets you replace the data source used by ExoPlayer. Useful for implementing read-only cache or request interception.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
override fun overrideMediaDataSourceFactory(
|
||||||
|
source: Source,
|
||||||
|
mediaDataSourceFactory: DataSource.Factory
|
||||||
|
): DataSource.Factory? {
|
||||||
|
// Return your custom factory or null to use default
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 3. `shouldDisableCache`
|
||||||
|
|
||||||
|
Enables dynamic disabling of the caching system per source.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
override fun shouldDisableCache(source: Source): Boolean {
|
||||||
|
return true // your own logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Once implemented, `react-native-video` will automatically invoke these methods for each `<Video />` instance.
|
||||||
|
|
||||||
## iOS Implementation
|
## iOS Implementation
|
||||||
|
|
||||||
### 1. Podspec Integration
|
### 1. Podspec Integration
|
||||||
|
|||||||
Reference in New Issue
Block a user