Skip to content

Commit

Permalink
feat: add plugins management (#3909)
Browse files Browse the repository at this point in the history
  • Loading branch information
freeboub authored Jun 25, 2024
1 parent 3cfb96a commit 91d27a6
Show file tree
Hide file tree
Showing 28 changed files with 758 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
import com.brentvatne.common.toolbox.DebugLog;
import com.brentvatne.react.BuildConfig;
import com.brentvatne.react.R;
import com.brentvatne.react.ReactNativeVideoManager;
import com.brentvatne.receiver.AudioBecomingNoisyReceiver;
import com.brentvatne.receiver.BecomingNoisyListener;
import com.facebook.react.bridge.LifecycleEventListener;
Expand Down Expand Up @@ -258,6 +259,9 @@ public class ReactExoplayerView extends FrameLayout implements
private long lastDuration = -1;

private boolean viewHasDropped = false;

private String instanceId = String.valueOf(UUID.randomUUID());

private void updateProgress() {
if (player != null) {
if (playerControlView != null && isPlayingAd() && controls) {
Expand Down Expand Up @@ -756,6 +760,7 @@ private void initializePlayerCore(ReactExoplayerView self) {
.setLoadControl(loadControl)
.setMediaSourceFactory(mediaSourceFactory)
.build();
ReactNativeVideoManager.Companion.getInstance().onInstanceCreated(instanceId, player);
refreshDebugState();
player.addListener(self);
player.setVolume(muted ? 0.f : audioVolume * 1);
Expand Down Expand Up @@ -1150,6 +1155,7 @@ private void releasePlayer() {
player.removeListener(this);
trackSelector = null;

ReactNativeVideoManager.Companion.getInstance().onInstanceRemoved(instanceId, player);
player = null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.brentvatne.common.react.VideoEventEmitter;
import com.brentvatne.common.toolbox.DebugLog;
import com.brentvatne.common.toolbox.ReactBridgeUtils;
import com.brentvatne.react.ReactNativeVideoManager;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
Expand Down Expand Up @@ -97,12 +98,14 @@ public String getName() {
@NonNull
@Override
protected ReactExoplayerView createViewInstance(@NonNull ThemedReactContext themedReactContext) {
ReactNativeVideoManager.Companion.getInstance().registerView(this);
return new ReactExoplayerView(themedReactContext, config);
}

@Override
public void onDropViewInstance(ReactExoplayerView view) {
view.cleanUpResources();
ReactNativeVideoManager.Companion.getInstance().unregisterView(this);
}

@Override
Expand Down
22 changes: 22 additions & 0 deletions android/src/main/java/com/brentvatne/react/RNVPlugin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.brentvatne.react

/**
* Plugin interface definition
*/
interface RNVPlugin {
/**
* Function called when a new player is created
* @param id: a random string identifying the player
* @param player: the instantiated player reference
*/
fun onInstanceCreated(id: String, player: Any)

/**
* Function called when a player should be destroyed
* when this callback is called, the plugin shall free all
* resources and release all reference to Player object
* @param id: a random string identifying the player
* @param player: the player to release
*/
fun onInstanceRemoved(id: String, player: Any)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.brentvatne.react

import com.brentvatne.common.toolbox.DebugLog
import com.brentvatne.exoplayer.ReactExoplayerViewManager

/**
* ReactNativeVideoManager is a singleton class which allows to manipulate / the global state of the app
* It handles the list of <Video view instanced and registration of plugins
*/
class ReactNativeVideoManager : RNVPlugin {
companion object {
private const val TAG = "ReactNativeVideoManager"

@Volatile
private var instance: ReactNativeVideoManager? = null

/**
* Singleton accessor
*/
fun getInstance(): ReactNativeVideoManager =
instance ?: synchronized(this) {
instance ?: ReactNativeVideoManager().also { instance = it }
}
}

private var instanceList: ArrayList<ReactExoplayerViewManager> = ArrayList()
private var pluginList: ArrayList<RNVPlugin> = ArrayList()

/**
* register a new ReactExoplayerViewManager in the managed list
*/
fun registerView(newInstance: ReactExoplayerViewManager): () -> Boolean =
{
if (instanceList.size > 2) {
DebugLog.d(TAG, "multiple Video displayed ?")
}
instanceList.add(newInstance)
}

/**
* unregister existing ReactExoplayerViewManager in the managed list
*/
fun unregisterView(newInstance: ReactExoplayerViewManager): () -> Boolean =
{
instanceList.remove(newInstance)
}

/**
* register a new plugin in the managed list
*/
fun registerPlugin(plugin: RNVPlugin) {
pluginList.add(plugin)
return
}

/**
* unregister a plugin from the managed list
*/
fun unregisterPlugin(plugin: RNVPlugin) {
pluginList.remove(plugin)
return
}

override fun onInstanceCreated(id: String, player: Any) {
pluginList.forEach { it.onInstanceCreated(id, player) }
}

override fun onInstanceRemoved(id: String, player: Any) {
pluginList.forEach { it.onInstanceRemoved(id, player) }
}
}
3 changes: 2 additions & 1 deletion docs/pages/other/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"caching": "Caching",
"misc": "Misc",
"debug": "Debugging",
"new-arch": "New Architecture"
"new-arch": "New Architecture",
"plugin": "Plugin (experimental)"
}
125 changes: 125 additions & 0 deletions docs/pages/other/plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Plugin (experimental)

Since Version 6.4.0, it is possible to create plugins for analytics management and maybe much more.
A sample plugin is available in the repository in: example/react-native-video-plugin-sample. (important FIXME, put sample link)

## Concept

Most of the analytics system which tracks player information (bitrate, errors, ...) can be integrated directly with Exoplayer or AVPlayer handles.

This plugin system allows none intrusive integration of analytics in the react-native-package. It shall be done in native language (kotlin/swift).

The idea behind this system is to be able to plug an analytics package to react native video without doing any code change (ideally).

Following documentation will show on how to create a new plugin for react native video

## Warning and consideration
This is an experiental API, it is subject to change. The api with player is very simple but should be flexible enough to implement analytics system. If you need some metadata, you should implement setter in the new package you are creating.

As api is flexible, it makes possible to missuse the system. It is necessary to consider the player handle as read-only. If you modify player behavior, we cannot garanty the good behavior of react-native-video package.

## General

First you need to create a new react native package:
````shell
npx create-react-native-library@latest react-native-video-custom-analytics
````

Both android and iOS implementation expose an interface `RNVPlugin`.
Your `react-native-video-custom-analytics` shall implement this interface and register itself as a plugin for react native video.

## Android
There is no special requierement for gradle file.
You need two mandatory action to be able to receive player handle

### 1/ Create the plugin

First you should instanciate a class which extends `RNVPlugin`.

The proposed integration implement `RNVPlugin` directly inside the Module file (`VideoPluginSampleModule`).

The `RNVPlugin` interface only defines 2 functions, see description here under.

```kotlin
/**
* Function called when a new player is created
* @param id: a random string identifying the player
* @param player: the instantiated player reference
*/
fun onInstanceCreated(id: String, player: Any)
/**
* Function called when a player should be destroyed
* when this callback is called, the plugin shall free all
* resources and release all reference to Player object
* @param id: a random string identifying the player
* @param player: the player to release
*/
fun onInstanceRemoved(id: String, player: Any)
````

### 2/ register the plugin

To register this allocated class in the main react native video package you should call following function:

```kotlin
ReactNativeVideoManager.getInstance().registerPlugin(plugin)
```
The proposed integration register the instanciated class in `createNativeModules` entry point.

Your native module can now track Player updates directly from Player reference and report to backend.

## ios

### 1/ podspec integration

Your new module shall be able to access to react-native-video package, then we must declare it as a dependency of the new module you are creating.

```podfile
s.dependency "react-native-video"
````
### 2/ Create the plugin
First you should instanciate a class which extends `RNVPlugin`.
The proposed integration implement `RNVPlugin` directly inside the entry point of the module file (`VideoPluginSample`).
The `RNVPlugin` interface only defines 2 functions, see description here under.
```swift
/**
* Function called when a new player is created
* @param player: the instantiated player reference
*/
func onInstanceCreated(player: Any)
/**
* Function called when a player should be destroyed
* when this callback is called, the plugin shall free all
* resources and release all reference to Player object
* @param player: the player to release
*/
func onInstanceRemoved(player: Any)
```

### 3/ Register the plugin

To register this allocated class in the main react native video package you should register it by calling this function:

```swift
ReactNativeVideoManager.shared.registerPlugin(plugin: plugin)
```

The proposed integration register the instanciated class in file `VideoPluginSample` in the init function:

```swift
import react_native_video

...

override init() {
super.init()
ReactNativeVideoManager.shared.registerPlugin(plugin: self)
}
```

Your native module can now track Player updates directly from Player reference and report to backend.
1 change: 1 addition & 0 deletions examples/basic/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ dependencies {
}

implementation project(':react-native-video')
implementation project(':react-native-video-plugin-sample')

constraints {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader
import com.brentvatne.react.ReactVideoPackage
import com.videopluginsample.VideoPluginSamplePackage

class MainApplication : Application(), ReactApplication {

Expand All @@ -24,6 +25,7 @@ class MainApplication : Application(), ReactApplication {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
add(ReactVideoPackage())
add(VideoPluginSamplePackage())
}

override fun getJSMainModuleName(): String = "src/index"
Expand Down
3 changes: 3 additions & 0 deletions examples/basic/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ rootProject.name = 'videoplayer'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'

include ':react-native-video-plugin-sample'
project (':react-native-video-plugin-sample').projectDir = new File(rootProject.projectDir, '../../react-native-video-plugin-sample/android')

include ':react-native-video'
project (':react-native-video').projectDir = new File(rootProject.projectDir, '../../../android')

Expand Down
1 change: 1 addition & 0 deletions examples/basic/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ target 'videoplayer' do
)

pod 'react-native-video', path: '../../..'
pod 'react-native-video-plugin-sample', path: '../../react-native-video-plugin-sample'

target 'videoplayerTests' do
inherit! :complete
Expand Down
Loading

0 comments on commit 91d27a6

Please sign in to comment.