diff --git a/android/src/main/java/com/brentvatne/exoplayer/RNVExoplayerPlugin.kt b/android/src/main/java/com/brentvatne/exoplayer/RNVExoplayerPlugin.kt index 49315beb..0df2d1de 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/RNVExoplayerPlugin.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/RNVExoplayerPlugin.kt @@ -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 diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 7f46d059..c3bbc95e 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -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 rootViewChildrenOriginalVisibility = new ArrayList(); @@ -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(); diff --git a/android/src/main/java/com/brentvatne/react/ReactNativeVideoManager.kt b/android/src/main/java/com/brentvatne/react/ReactNativeVideoManager.kt index 975ef4ee..3053ab6a 100644 --- a/android/src/main/java/com/brentvatne/react/ReactNativeVideoManager.kt +++ b/android/src/main/java/com/brentvatne/react/ReactNativeVideoManager.kt @@ -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) { diff --git a/docs/pages/other/plugin.md b/docs/pages/other/plugin.md index 36f3ec0a..68bcc6e9 100644 --- a/docs/pages/other/plugin.md +++ b/docs/pages/other/plugin.md @@ -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 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 `