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:
Kamil Moskała
2025-03-28 13:41:13 +01:00
committed by GitHub
parent f188a7fd48
commit 697afd52f6
4 changed files with 141 additions and 5 deletions

View File

@@ -1,6 +1,9 @@
package com.brentvatne.exoplayer
import androidx.media3.common.MediaItem
import androidx.media3.datasource.DataSource
import androidx.media3.exoplayer.ExoPlayer
import com.brentvatne.common.api.Source
import com.brentvatne.react.RNVPlugin
/**
@@ -15,6 +18,32 @@ interface RNVExoplayerPlugin : RNVPlugin {
*/
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
* @param id: a random string identifying the player

View File

@@ -220,6 +220,7 @@ public class ReactExoplayerView extends FrameLayout implements
private Runnable mainRunnable;
private Runnable pipListenerUnsubscribe;
private boolean useCache = false;
private boolean disableCache = false;
private ControlsConfig controlsConfig = new ControlsConfig();
private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>();
@@ -750,6 +751,8 @@ public class ReactExoplayerView extends FrameLayout implements
}
private void initializePlayer() {
disableCache = ReactNativeVideoManager.Companion.getInstance().shouldDisableCache(source);
ReactExoplayerView self = this;
Activity activity = themedReactContext.getCurrentActivity();
// This ensures all props have been settled, to avoid async racing conditions.
@@ -853,7 +856,7 @@ public class ReactExoplayerView extends FrameLayout implements
.forceEnableMediaCodecAsynchronousQueueing();
DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory);
if (useCache) {
if (useCache && !disableCache) {
mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)));
}
@@ -1183,7 +1186,7 @@ public class ReactExoplayerView extends FrameLayout implements
DataSource.Factory dataSourceFactory = mediaDataSourceFactory;
if (useCache) {
if (useCache && !disableCache) {
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
.setDrmSessionManagerProvider(drmProvider)
.setLoadErrorHandlingPolicy(
@@ -1921,10 +1932,15 @@ public class ReactExoplayerView extends FrameLayout implements
boolean isSourceEqual = source.isEquals(this.source);
hasDrmFailed = false;
this.source = source;
this.mediaDataSourceFactory =
final DataSource.Factory tmpMediaDataSourceFactory =
DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter,
source.getHeaders());
@Nullable
final DataSource.Factory overriddenMediaDataSourceFactory = ReactNativeVideoManager.Companion.getInstance().overrideMediaDataSourceFactory(source, tmpMediaDataSourceFactory);
this.mediaDataSourceFactory = Objects.requireNonNullElse(overriddenMediaDataSourceFactory, tmpMediaDataSourceFactory);
if (source.getCmcdProps() != null) {
CMCDConfig cmcdConfig = new CMCDConfig(source.getCmcdProps());
CmcdConfiguration.Factory factory = cmcdConfig.toCmcdConfigurationFactory();

View File

@@ -1,5 +1,8 @@
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.exoplayer.DRMManagerSpec
import com.brentvatne.exoplayer.RNVExoplayerPlugin
@@ -75,6 +78,35 @@ class ReactNativeVideoManager : RNVPlugin {
// ----------------------- RNV Exoplayer plugin specific methods -----------------------
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 -----------------------
private fun maybeRegisterExoplayerPlugin(plugin: RNVPlugin) {
if (plugin !is RNVExoplayerPlugin) {

View File

@@ -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.
### 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 its 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
### 1. Podspec Integration
@@ -308,4 +367,4 @@ class CustomVideoPlugin: RNVAVPlayerPlugin {
- On Android, the default ExoPlayer DRM implementation will be used
4. The DRM manager must handle all DRM-related functionality:
- On iOS: key requests, license acquisition, and error handling through AVContentKeySession
- On Android: DRM session management and license acquisition through ExoPlayer's DrmSessionManager
- On Android: DRM session management and license acquisition through ExoPlayer's DrmSessionManager