bitmovin-player-react-native 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/RNBitmovinPlayer.podspec +1 -1
  2. package/android/build.gradle +1 -2
  3. package/android/src/main/java/com/bitmovin/player/reactnative/AnalyticsModule.kt +43 -11
  4. package/android/src/main/java/com/bitmovin/player/reactnative/OfflineModule.kt +279 -0
  5. package/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +37 -1
  6. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt +1 -0
  7. package/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +123 -35
  8. package/android/src/main/java/com/bitmovin/player/reactnative/offline/OfflineContentManagerBridge.kt +216 -0
  9. package/android/src/main/java/com/bitmovin/player/reactnative/offline/OfflineDownloadRequest.kt +7 -0
  10. package/ios/DrmModule.swift +5 -5
  11. package/ios/OfflineContentManagerBridge.swift +141 -0
  12. package/ios/OfflineModule.m +19 -0
  13. package/ios/OfflineModule.swift +465 -0
  14. package/ios/PlayerModule.m +2 -0
  15. package/ios/PlayerModule.swift +40 -7
  16. package/ios/RCTConvert+BitmovinPlayer.swift +135 -1
  17. package/ios/RNBitmovinPlayer.h +1 -0
  18. package/lib/index.d.mts +382 -2
  19. package/lib/index.d.ts +382 -2
  20. package/lib/index.js +233 -0
  21. package/lib/index.mjs +231 -0
  22. package/package.json +1 -1
  23. package/src/adaptationConfig.ts +10 -0
  24. package/src/index.ts +2 -0
  25. package/src/offline/index.ts +7 -0
  26. package/src/offline/offlineContentConfig.ts +18 -0
  27. package/src/offline/offlineContentManager.ts +216 -0
  28. package/src/offline/offlineContentManagerListener.ts +187 -0
  29. package/src/offline/offlineContentOptions.ts +29 -0
  30. package/src/offline/offlineDownloadRequest.ts +20 -0
  31. package/src/offline/offlineSourceOptions.ts +11 -0
  32. package/src/offline/offlineState.ts +22 -0
  33. package/src/player.ts +30 -0
  34. package/src/source.ts +38 -0
@@ -19,7 +19,7 @@ Pod::Spec.new do |s|
19
19
  s.source_files = "ios/**/*.{h,m,mm,swift}"
20
20
 
21
21
  s.dependency "React-Core"
22
- s.dependency "BitmovinPlayer", "3.43.1"
22
+ s.dependency "BitmovinPlayer", "3.44.1"
23
23
  s.ios.dependency "GoogleAds-IMA-iOS-SDK", "3.18.4"
24
24
  s.tvos.dependency "GoogleAds-IMA-tvOS-SDK", "4.8.2"
25
25
  end
@@ -53,8 +53,7 @@ dependencies {
53
53
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
54
54
  implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.29.0'
55
55
  implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
56
- implementation 'com.bitmovin.analytics:collector-bitmovin-player:2.12.1'
57
- implementation 'com.bitmovin.player:player:3.40.0'
56
+ implementation 'com.bitmovin.player:player:3.42.0'
58
57
  //noinspection GradleDynamicVersion
59
58
  implementation 'com.facebook.react:react-native:+' // From node_modules
60
59
  }
@@ -1,6 +1,7 @@
1
1
  package com.bitmovin.player.reactnative
2
2
 
3
- import com.bitmovin.analytics.bitmovin.player.BitmovinPlayerCollector
3
+ import android.util.Log
4
+ import com.bitmovin.analytics.bitmovin.player.api.IBitmovinPlayerCollector
4
5
  import com.bitmovin.player.reactnative.converter.JsonConverter
5
6
  import com.facebook.react.bridge.*
6
7
  import com.facebook.react.module.annotations.ReactModule
@@ -13,7 +14,7 @@ class AnalyticsModule(private val context: ReactApplicationContext) : ReactConte
13
14
  /**
14
15
  * In-memory mapping from `nativeId`s to `BitmovinPlayerCollector` instances.
15
16
  */
16
- private val collectors: Registry<BitmovinPlayerCollector> = mutableMapOf()
17
+ private val collectors: Registry<IBitmovinPlayerCollector> = mutableMapOf()
17
18
 
18
19
  /**
19
20
  * JS exported module name.
@@ -25,7 +26,7 @@ class AnalyticsModule(private val context: ReactApplicationContext) : ReactConte
25
26
  * @param nativeId `BitmovinPlayerCollector` instance ID.
26
27
  * @return The associated `BitmovinPlayerCollector` instance or `null`.
27
28
  */
28
- fun getCollector(nativeId: NativeId?): BitmovinPlayerCollector? {
29
+ fun getCollector(nativeId: NativeId?): IBitmovinPlayerCollector? {
29
30
  if (nativeId == null) {
30
31
  return null
31
32
  }
@@ -40,7 +41,7 @@ class AnalyticsModule(private val context: ReactApplicationContext) : ReactConte
40
41
  fun initWithConfig(nativeId: NativeId, config: ReadableMap?) {
41
42
  uiManager()?.addUIBlock { _ ->
42
43
  JsonConverter.toAnalyticsConfig(config)?.let {
43
- collectors[nativeId] = BitmovinPlayerCollector(it, context)
44
+ collectors[nativeId] = IBitmovinPlayerCollector.create(it, context)
44
45
  }
45
46
  }
46
47
  }
@@ -106,8 +107,20 @@ class AnalyticsModule(private val context: ReactApplicationContext) : ReactConte
106
107
  @ReactMethod
107
108
  fun setCustomData(nativeId: NativeId, playerId: NativeId?, json: ReadableMap?) {
108
109
  uiManager()?.addUIBlock { _ ->
109
- JsonConverter.toAnalyticsCustomData(json)?.let {
110
- collectors[nativeId]?.customData = it
110
+ val source = playerModule()?.getPlayer(playerId)?.source
111
+ val collector = collectors[nativeId]
112
+ val customData = JsonConverter.toAnalyticsCustomData(json)
113
+ when {
114
+ source == null -> Log.d(
115
+ "[AnalyticsModule]", "Could not find source for player ($playerId)"
116
+ )
117
+ collector == null -> Log.d(
118
+ "[AnalyticsModule]", "Could not find analytics collector ($nativeId)"
119
+ )
120
+ customData == null -> Log.d(
121
+ "[AnalyticsModule]", "Could not convert custom data, thus they are not applied to the active source for the player ($playerId) with the collector ($nativeId)"
122
+ )
123
+ else -> collector.setCustomData(source, customData)
111
124
  }
112
125
  }
113
126
  }
@@ -120,8 +133,16 @@ class AnalyticsModule(private val context: ReactApplicationContext) : ReactConte
120
133
  @ReactMethod
121
134
  fun getCustomData(nativeId: NativeId, playerId: NativeId?, promise: Promise) {
122
135
  uiManager()?.addUIBlock { _ ->
123
- collectors[nativeId]?.let {
124
- promise.resolve(JsonConverter.fromAnalyticsCustomData(it.customData))
136
+ val source = playerModule()?.getPlayer(playerId)?.source
137
+ val collector = collectors[nativeId]
138
+ when {
139
+ source == null -> promise.reject(
140
+ "[AnalyticsModule]", "Could not find source for player ($playerId)"
141
+ )
142
+ collector == null -> promise.reject(
143
+ "[AnalyticsModule]", "Could not find analytics collector ($nativeId)"
144
+ )
145
+ else -> promise.resolve(JsonConverter.fromAnalyticsCustomData(collector.getCustomData(source)))
125
146
  }
126
147
  }
127
148
  }
@@ -129,9 +150,20 @@ class AnalyticsModule(private val context: ReactApplicationContext) : ReactConte
129
150
  @ReactMethod
130
151
  fun addSourceMetadata(nativeId: NativeId, playerId: NativeId?, json: ReadableMap?) {
131
152
  uiManager()?.addUIBlock { _ ->
132
- val playerSource = playerModule()?.getPlayer(playerId)?.source ?: return@addUIBlock
133
- JsonConverter.toAnalyticsSourceMetadata(json)?.let { sourceMetadata ->
134
- collectors[nativeId]?.addSourceMetadata(playerSource, sourceMetadata)
153
+ val source = playerModule()?.getPlayer(playerId)?.source
154
+ val collector = collectors[nativeId]
155
+ val sourceMetadata = JsonConverter.toAnalyticsSourceMetadata(json)
156
+ when {
157
+ source == null -> Log.d(
158
+ "[AnalyticsModule]", "Could not find source for player ($playerId)"
159
+ )
160
+ collector == null -> Log.d(
161
+ "[AnalyticsModule]", "Could not find analytics collector ($nativeId)"
162
+ )
163
+ sourceMetadata == null -> Log.d(
164
+ "[AnalyticsModule]", "Could not convert source metadata, thus they are not applied to the collector ($nativeId)"
165
+ )
166
+ else -> collector.addSourceMetadata(source, sourceMetadata)
135
167
  }
136
168
  }
137
169
  }
@@ -0,0 +1,279 @@
1
+ package com.bitmovin.player.reactnative
2
+
3
+ import com.bitmovin.player.api.offline.options.OfflineOptionEntryState
4
+ import com.bitmovin.player.reactnative.converter.JsonConverter
5
+ import com.bitmovin.player.reactnative.extensions.toList
6
+ import com.bitmovin.player.reactnative.offline.OfflineDownloadRequest
7
+ import com.bitmovin.player.reactnative.offline.OfflineContentManagerBridge
8
+ import com.facebook.react.bridge.*
9
+ import com.facebook.react.module.annotations.ReactModule
10
+ import com.facebook.react.uimanager.UIManagerModule
11
+
12
+ private const val OFFLINE_MODULE = "BitmovinOfflineModule"
13
+
14
+ @ReactModule(name = OFFLINE_MODULE)
15
+ class OfflineModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
16
+
17
+ /**
18
+ * In-memory mapping from `nativeId`s to `OfflineManager` instances.
19
+ */
20
+ private val offlineContentManagerBridges: Registry<OfflineContentManagerBridge> = mutableMapOf()
21
+
22
+ /**
23
+ * JS exported module name.
24
+ */
25
+ override fun getName() = OFFLINE_MODULE
26
+
27
+ /**
28
+ * Fetches the `OfflineManager` instance associated with `nativeId` from the internal offline managers.
29
+ * @param nativeId `OfflineManager` instance ID.
30
+ * @return The associated `OfflineManager` instance or `null`.
31
+ */
32
+ fun getOfflineContentManagerBridge(nativeId: NativeId?): OfflineContentManagerBridge? {
33
+ if (nativeId == null) {
34
+ return null
35
+ }
36
+ return offlineContentManagerBridges[nativeId]
37
+ }
38
+
39
+ /**
40
+ * Callback when a new NativeEventEmitter is created from the Typescript layer.
41
+ */
42
+ @ReactMethod
43
+ fun addListener(eventName: String?) {
44
+ // NO-OP
45
+ }
46
+
47
+ /**
48
+ * Callback when a NativeEventEmitter is removed from the Typescript layer.
49
+ */
50
+ @ReactMethod
51
+ fun removeListeners(count: Int?) {
52
+ // NO-OP
53
+ }
54
+
55
+ /**
56
+ * Creates a new `OfflineManager` instance inside the internal offline managers using the provided `config` object.
57
+ * @param config `ReadableMap` object received from JS. Should contain a sourceConfig and location.
58
+ */
59
+ @ReactMethod
60
+ fun initWithConfig(nativeId: NativeId, config: ReadableMap?, drmNativeId: NativeId?, promise: Promise) {
61
+ uiManager()?.addUIBlock {
62
+ if (!offlineContentManagerBridges.containsKey(nativeId)) {
63
+ val identifier = config?.getString("identifier")
64
+ val sourceConfig = JsonConverter.toSourceConfig(config?.getMap("sourceConfig"))
65
+ sourceConfig?.drmConfig = drmModule()?.getConfig(drmNativeId)
66
+
67
+ if (identifier.isNullOrEmpty() || sourceConfig == null) {
68
+ promise.reject(IllegalArgumentException("Identifier and SourceConfig may not be null"))
69
+ return@addUIBlock
70
+ }
71
+
72
+ offlineContentManagerBridges[nativeId] = OfflineContentManagerBridge(nativeId, context, identifier, sourceConfig, context.cacheDir.path)
73
+ }
74
+ promise.resolve(null)
75
+ }
76
+ }
77
+
78
+ @ReactMethod
79
+ fun getState(nativeId: NativeId, promise: Promise) {
80
+ safeOfflineContentManager(nativeId, promise) {
81
+ promise.resolve(state.name)
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Starts the `OfflineContentManager`'s asynchronous process of fetching the `OfflineContentOptions`.
87
+ * When the options are loaded a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onOptionsAvailable`.
88
+ * @param nativeId Target offline manager.
89
+ */
90
+ @ReactMethod
91
+ fun getOptions(nativeId: NativeId, promise: Promise) {
92
+ safeOfflineContentManager(nativeId, promise) {
93
+ getOptions()
94
+ promise.resolve(null)
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Enqueues downloads according to the `OfflineDownloadRequest`.
100
+ * The promise will reject in the event of null or invalid request parameters.
101
+ * The promise will reject an `IllegalOperationException` when selecting an `OfflineOptionEntry` to download that is not compatible with the current state.
102
+ * @param nativeId Target offline manager.
103
+ * @param request `ReadableMap` that contains the `OfflineManager.OfflineOptionType`, id, and `OfflineOptionEntryAction` necessary to set the new action.
104
+ */
105
+ @ReactMethod
106
+ fun download(nativeId: NativeId, request: ReadableMap?, promise: Promise) {
107
+ if (request == null) {
108
+ promise.reject(IllegalArgumentException("Request may not be null"))
109
+ return
110
+ }
111
+
112
+ safeOfflineContentManager(nativeId, promise) {
113
+ try {
114
+ when (state) {
115
+ OfflineOptionEntryState.Downloaded -> {
116
+ promise.reject(IllegalStateException("Download already completed"))
117
+ return@safeOfflineContentManager
118
+ }
119
+ OfflineOptionEntryState.Downloading,
120
+ OfflineOptionEntryState.Failed -> {
121
+ promise.reject(IllegalStateException("Download already in progress"))
122
+ return@safeOfflineContentManager
123
+ }
124
+ OfflineOptionEntryState.Suspended -> {
125
+ promise.reject(IllegalStateException("Download is suspended"))
126
+ return@safeOfflineContentManager
127
+ }
128
+ else -> {}
129
+ }
130
+ val minimumBitRate = if(request.hasKey("minimumBitrate")) request.getInt("minimumBitrate") else null
131
+ if (minimumBitRate != null && minimumBitRate < 0) {
132
+ promise.reject(IllegalArgumentException("Invalid download request"))
133
+ return@safeOfflineContentManager
134
+ }
135
+
136
+ val audioOptionIds = request.getArray("audioOptionIds")?.toList<String>()?.filterNotNull()
137
+ val textOptionIds = request.getArray("textOptionIds")?.toList<String>()?.filterNotNull()
138
+
139
+ getOfflineContentManagerBridge(nativeId)?.process(OfflineDownloadRequest(minimumBitRate, audioOptionIds, textOptionIds))
140
+ promise.resolve(null)
141
+ } catch (e: Exception) {
142
+ promise.reject(e)
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Resumes all suspended actions.
149
+ * @param nativeId Target offline manager.
150
+ */
151
+ @ReactMethod
152
+ fun resume(nativeId: NativeId, promise: Promise) {
153
+ safeOfflineContentManager(nativeId, promise) {
154
+ resume()
155
+ promise.resolve(null)
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Suspends all active actions.
161
+ * @param nativeId Target offline manager.
162
+ */
163
+ @ReactMethod
164
+ fun suspend(nativeId: NativeId, promise: Promise) {
165
+ safeOfflineContentManager(nativeId, promise) {
166
+ suspend()
167
+ promise.resolve(null)
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Cancels and deletes the current download.
173
+ * @param nativeId Target offline manager.
174
+ */
175
+ @ReactMethod
176
+ fun cancelDownload(nativeId: NativeId, promise: Promise) {
177
+ safeOfflineContentManager(nativeId, promise) {
178
+ cancelDownload()
179
+ promise.resolve(null)
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Resolve `nativeId`'s current `usedStorage`.
185
+ * @param nativeId Target offline manager.
186
+ */
187
+ @ReactMethod
188
+ fun usedStorage(nativeId: NativeId, promise: Promise) {
189
+ safeOfflineContentManager(nativeId, promise) {
190
+ promise.resolve(offlineContentManager.usedStorage.toDouble())
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Deletes everything related to the related content ID.
196
+ * @param nativeId Target offline manager.
197
+ */
198
+ @ReactMethod
199
+ fun deleteAll(nativeId: NativeId, promise: Promise) {
200
+ safeOfflineContentManager(nativeId, promise) {
201
+ deleteAll()
202
+ promise.resolve(null)
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Downloads the offline license.
208
+ * When finished successfully a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onDrmLicenseUpdated`.
209
+ * Errors are transmitted by a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onError`.
210
+ * @param nativeId Target offline manager.
211
+ */
212
+ @ReactMethod
213
+ fun downloadLicense(nativeId: NativeId, promise: Promise) {
214
+ safeOfflineContentManager(nativeId, promise) {
215
+ downloadLicense()
216
+ promise.resolve(null)
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Releases the currently held offline license.
222
+ * When finished successfully a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onDrmLicenseUpdated`.
223
+ * Errors are transmitted by a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onError`.
224
+ * @param nativeId Target offline manager.
225
+ */
226
+ @ReactMethod
227
+ fun releaseLicense(nativeId: NativeId, promise: Promise) {
228
+ safeOfflineContentManager(nativeId, promise) {
229
+ releaseLicense()
230
+ promise.resolve(null)
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Renews the already downloaded DRM license.
236
+ * When finished successfully a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onDrmLicenseUpdated`.
237
+ * Errors are transmitted by a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onError`.
238
+ * @param nativeId Target offline manager.
239
+ */
240
+ @ReactMethod
241
+ fun renewOfflineLicense(nativeId: NativeId, promise: Promise) {
242
+ safeOfflineContentManager(nativeId, promise) {
243
+ renewOfflineLicense()
244
+ promise.resolve(null)
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Call `.release()` on `nativeId`'s offline manager.
250
+ * IMPORTANT: Call this when the component, in which it was created, is destroyed.
251
+ * The `OfflineManager` should not be used after calling this method.
252
+ * @param nativeId Target player Id.
253
+ */
254
+ @ReactMethod
255
+ fun release(nativeId: NativeId, promise: Promise) {
256
+ safeOfflineContentManager(nativeId, promise) {
257
+ release()
258
+ offlineContentManagerBridges.remove(nativeId)
259
+ promise.resolve(null)
260
+ }
261
+ }
262
+
263
+ private fun safeOfflineContentManager(nativeId: NativeId, promise: Promise, runBlock: OfflineContentManagerBridge.() -> Unit) {
264
+ getOfflineContentManagerBridge(nativeId)?.let(runBlock)
265
+ ?: promise.reject(IllegalArgumentException("Could not find the offline module instance"))
266
+ }
267
+
268
+ /**
269
+ * Helper function that returns the initialized `DrmModule` instance.
270
+ */
271
+ private fun drmModule(): DrmModule? =
272
+ context.getNativeModule(DrmModule::class.java)
273
+
274
+ /**
275
+ * Helper function that returns the initialized `UIManager` instance.
276
+ */
277
+ private fun uiManager(): UIManagerModule? =
278
+ context.getNativeModule(UIManagerModule::class.java)
279
+ }
@@ -50,7 +50,7 @@ class PlayerModule(private val context: ReactApplicationContext) : ReactContextB
50
50
  /**
51
51
  * Load the source of the given `nativeId` with `config` options from JS.
52
52
  * @param nativeId Target player.
53
- * @param config Source configuration options from JS.
53
+ * @param sourceNativeId Target source.
54
54
  */
55
55
  @ReactMethod
56
56
  fun loadSource(nativeId: NativeId, sourceNativeId: String) {
@@ -61,6 +61,24 @@ class PlayerModule(private val context: ReactApplicationContext) : ReactContextB
61
61
  }
62
62
  }
63
63
 
64
+ /**
65
+ * Load the `offlineSourceConfig` for the player with `nativeId` and offline source module with `offlineModuleNativeId`.
66
+ * @param nativeId Target player.
67
+ * @param offlineContentManagerBridgeId Target offline module.
68
+ * @param options Source configuration options from JS.
69
+ */
70
+ @ReactMethod
71
+ fun loadOfflineContent(nativeId: NativeId, offlineContentManagerBridgeId: String, options: ReadableMap?) {
72
+ uiManager()?.addUIBlock {
73
+ val offlineSourceConfig = offlineModule()?.getOfflineContentManagerBridge(offlineContentManagerBridgeId)
74
+ ?.offlineContentManager?.offlineSourceConfig
75
+
76
+ if (offlineSourceConfig != null) {
77
+ players[nativeId]?.load(offlineSourceConfig)
78
+ }
79
+ }
80
+ }
81
+
64
82
  /**
65
83
  * Call `.unload()` on `nativeId`'s player.
66
84
  * @param nativeId Target player Id.
@@ -398,6 +416,18 @@ class PlayerModule(private val context: ReactApplicationContext) : ReactContextB
398
416
  }
399
417
  }
400
418
 
419
+ /**
420
+ * Sets the max selectable bitrate for the player.
421
+ * @param nativeId Target player id.
422
+ * @param maxSelectableBitrate The desired max bitrate limit.
423
+ */
424
+ @ReactMethod
425
+ fun setMaxSelectableBitrate(nativeId: NativeId, maxSelectableBitrate: Int) {
426
+ uiManager()?.addUIBlock {
427
+ players[nativeId]?.setMaxSelectableVideoBitrate(maxSelectableBitrate.takeUnless { it == -1 } ?: Integer.MAX_VALUE)
428
+ }
429
+ }
430
+
401
431
  /**
402
432
  * Helper function that returns the initialized `UIManager` instance.
403
433
  */
@@ -409,4 +439,10 @@ class PlayerModule(private val context: ReactApplicationContext) : ReactContextB
409
439
  */
410
440
  private fun sourceModule(): SourceModule? =
411
441
  context.getNativeModule(SourceModule::class.java)
442
+
443
+ /**
444
+ * Helper function that returns the initialized `OfflineModule` instance.
445
+ */
446
+ private fun offlineModule(): OfflineModule? =
447
+ context.getNativeModule(OfflineModule::class.java)
412
448
  }
@@ -19,6 +19,7 @@ class RNPlayerViewPackage : ReactPackage {
19
19
  */
20
20
  override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> {
21
21
  return mutableListOf(
22
+ OfflineModule(reactContext),
22
23
  UuidModule(reactContext),
23
24
  PlayerModule(reactContext),
24
25
  SourceModule(reactContext),