bitmovin-player-react-native 0.7.2 → 0.9.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.
package/README.md CHANGED
@@ -11,11 +11,6 @@ Official React Native bindings for Bitmovin's mobile Player SDKs.
11
11
  [![npm dt](https://img.shields.io/npm/dt/bitmovin-player-react-native.svg)](https://www.npmjs.com/package/bitmovin-player-react-native)
12
12
 
13
13
  > As the library is under active development, this means certain features from our native SDKs are not yet exposed through these React Native bindings.
14
- > See [Feature Support](#feature-support) for an overview of the supported features.
15
- >
16
- > Not seeing the features you’re looking for?
17
- > We are accepting community pull requests to this open-source project so please feel free to contribute.
18
- > or let us know in [our community](https://community.bitmovin.com/c/requests/14) what features we should work on next.
19
14
 
20
15
  - [Bitmovin Player React Native](#bitmovin-player-react-native)
21
16
  - [Platform Support](#platform-support)
@@ -33,24 +28,14 @@ This library requires at least React Native 0.64+ and React 17+ to work properly
33
28
  - iOS 14.0+
34
29
  - tvOS 14.0+
35
30
  - Android API Level 21+
36
- - Android TV API Level 21+
31
+ - Android TV API Level 24+
37
32
  - Fire TV FireOS 5.0+
38
33
 
39
34
  Please note that browsers and other browser-like environments such as webOS and Tizen are not supported. For more details regarding Bitmovin Player SDK platform and device support, please refer to the [Supported Platforms & Devices](https://developer.bitmovin.com/playback/docs/supported-platforms-devices-player) page of our documentation.
40
35
 
41
36
  ## Feature Support
42
37
 
43
- Features of the native mobile Player SDKs are progressively being implemented in this React Native library. The table below summarizes the current state of the main Player SDK features.
44
-
45
- | Feature | State |
46
- | -------------------------------- | ----------------------------------------- |
47
- | Playback of DRM-protected assets | :white_check_mark: Available since v0.2.0 |
48
- | Subtitles & Captions | :white_check_mark: Available since v0.2.0 |
49
- | Advertising | :white_check_mark: Available since v0.4.0 |
50
- | Analytics | :white_check_mark: Available since v0.5.0 |
51
- | Playlist API | :x: Not available |
52
- | Casting | :x: Not available |
53
- | Offline Playback | :x: Not available |
38
+ Please refer to the [Features](https://developer.bitmovin.com/playback/docs/react-native-introduction#features) section of our documentation for an up-to-date list of supported Player features.
54
39
 
55
40
  ## Documentation
56
41
 
@@ -19,9 +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.41.0"
23
- s.dependency "BitmovinAnalyticsCollector/Core", "2.9.4"
24
- s.dependency "BitmovinAnalyticsCollector/BitmovinPlayer", "2.9.4"
25
- s.ios.dependency "GoogleAds-IMA-iOS-SDK", "3.17.0"
26
- s.tvos.dependency "GoogleAds-IMA-tvOS-SDK", "4.6.1"
22
+ s.dependency "BitmovinPlayer", "3.42.0"
23
+ s.ios.dependency "GoogleAds-IMA-iOS-SDK", "3.18.4"
24
+ s.tvos.dependency "GoogleAds-IMA-tvOS-SDK", "4.8.2"
27
25
  end
@@ -51,10 +51,10 @@ android {
51
51
 
52
52
  dependencies {
53
53
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
54
- implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.26.0'
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
56
  implementation 'com.bitmovin.analytics:collector-bitmovin-player:2.12.1'
57
- implementation 'com.bitmovin.player:player:3.39.0'
57
+ implementation 'com.bitmovin.player:player:3.40.0'
58
58
  //noinspection GradleDynamicVersion
59
59
  implementation 'com.facebook.react:react-native:+' // From node_modules
60
60
  }
package/android/gradlew CHANGED
@@ -85,9 +85,6 @@ done
85
85
  APP_BASE_NAME=${0##*/}
86
86
  APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87
87
 
88
- # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89
- DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90
-
91
88
  # Use the maximum available, or set MAX_FD != -1 to use that value.
92
89
  MAX_FD=maximum
93
90
 
@@ -133,10 +130,13 @@ location of your Java installation."
133
130
  fi
134
131
  else
135
132
  JAVACMD=java
136
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
133
+ if ! command -v java >/dev/null 2>&1
134
+ then
135
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137
136
 
138
137
  Please set the JAVA_HOME variable in your environment to match the
139
138
  location of your Java installation."
139
+ fi
140
140
  fi
141
141
 
142
142
  # Increase the maximum file descriptors if we can.
@@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then
197
197
  done
198
198
  fi
199
199
 
200
+
201
+ # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
202
+ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
203
+
200
204
  # Collect all arguments for the java command;
201
205
  # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
202
206
  # shell script including quotes and variable substitutions, so put them in
@@ -104,7 +104,7 @@ class AnalyticsModule(private val context: ReactApplicationContext) : ReactConte
104
104
  * @param json Custom data config json.
105
105
  */
106
106
  @ReactMethod
107
- fun setCustomData(nativeId: NativeId, json: ReadableMap?) {
107
+ fun setCustomData(nativeId: NativeId, playerId: NativeId?, json: ReadableMap?) {
108
108
  uiManager()?.addUIBlock { _ ->
109
109
  JsonConverter.toAnalyticsCustomData(json)?.let {
110
110
  collectors[nativeId]?.customData = it
@@ -118,7 +118,7 @@ class AnalyticsModule(private val context: ReactApplicationContext) : ReactConte
118
118
  * @param promise JS promise object.
119
119
  */
120
120
  @ReactMethod
121
- fun getCustomData(nativeId: NativeId, promise: Promise) {
121
+ fun getCustomData(nativeId: NativeId, playerId: NativeId?, promise: Promise) {
122
122
  uiManager()?.addUIBlock { _ ->
123
123
  collectors[nativeId]?.let {
124
124
  promise.resolve(JsonConverter.fromAnalyticsCustomData(it.customData))
@@ -126,6 +126,16 @@ class AnalyticsModule(private val context: ReactApplicationContext) : ReactConte
126
126
  }
127
127
  }
128
128
 
129
+ @ReactMethod
130
+ fun addSourceMetadata(nativeId: NativeId, playerId: NativeId?, json: ReadableMap?) {
131
+ uiManager()?.addUIBlock { _ ->
132
+ val playerSource = playerModule()?.getPlayer(playerId)?.source ?: return@addUIBlock
133
+ JsonConverter.toAnalyticsSourceMetadata(json)?.let { sourceMetadata ->
134
+ collectors[nativeId]?.addSourceMetadata(playerSource, sourceMetadata)
135
+ }
136
+ }
137
+ }
138
+
129
139
  /**
130
140
  * Gets the current user Id for a `BitmovinPlayerCollector` instance.
131
141
  * @param nativeId Native Id of the the collector instance.
@@ -82,6 +82,16 @@ private val EVENT_CLASS_TO_REACT_NATIVE_NAME_MAPPING_UI = mapOf<KClass<out Event
82
82
  @SuppressLint("ViewConstructor")
83
83
  class RNPlayerView(val context: ReactApplicationContext) : LinearLayout(context),
84
84
  LifecycleEventListener, View.OnLayoutChangeListener, RNPictureInPictureDelegate {
85
+
86
+ init {
87
+ // React Native has a bug that dynamically added views sometimes aren't laid out again properly.
88
+ // Since we dynamically add and remove SurfaceView under the hood this caused the player
89
+ // to suddenly not show the video anymore because SurfaceView was not laid out properly.
90
+ // Bitmovin player issue: https://github.com/bitmovin/bitmovin-player-react-native/issues/180
91
+ // React Native layout issue: https://github.com/facebook/react-native/issues/17968
92
+ getViewTreeObserver().addOnGlobalLayoutListener { requestLayout() }
93
+ }
94
+
85
95
  /**
86
96
  * Relays the provided set of events, emitted by the player, together with the associated name
87
97
  * to the `eventOutput` callback.
@@ -24,10 +24,11 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
24
24
  /**
25
25
  * Native component functions.
26
26
  */
27
- enum class Commands {
28
- ATTACH_PLAYER,
29
- ATTACH_FULLSCREEN_BRIDGE,
30
- SET_CUSTOM_MESSAGE_HANDLER_BRIDGE_ID,
27
+ enum class Commands(val command: String) {
28
+ ATTACH_PLAYER("attachPlayer"),
29
+ ATTACH_FULLSCREEN_BRIDGE("attachFullscreenBridge"),
30
+ SET_CUSTOM_MESSAGE_HANDLER_BRIDGE_ID("setCustomMessageHandlerBridgeId"),
31
+ SET_FULLSCREEN("setFullscreen");
31
32
  }
32
33
 
33
34
  /**
@@ -132,11 +133,9 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
132
133
  * to call 'functions' on them.
133
134
  * @return map between names (used in js) and command ids (used in native code).
134
135
  */
135
- override fun getCommandsMap(): MutableMap<String, Int> = mutableMapOf(
136
- "attachPlayer" to Commands.ATTACH_PLAYER.ordinal,
137
- "attachFullscreenBridge" to Commands.ATTACH_FULLSCREEN_BRIDGE.ordinal,
138
- "setCustomMessageHandlerBridgeId" to Commands.SET_CUSTOM_MESSAGE_HANDLER_BRIDGE_ID.ordinal,
139
- )
136
+ override fun getCommandsMap(): Map<String, Int> = Commands.values().associate {
137
+ it.command to it.ordinal
138
+ }
140
139
 
141
140
  /**
142
141
  * Callback triggered in response to command dispatches from the js side.
@@ -145,19 +144,23 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
145
144
  * @param args Arguments list sent from the js side.
146
145
  */
147
146
  override fun receiveCommand(view: RNPlayerView, commandId: String?, args: ReadableArray?) {
148
- super.receiveCommand(view, commandId, args)
149
- commandId?.toInt()?.let {
150
- when (it) {
151
- Commands.ATTACH_PLAYER.ordinal -> attachPlayer(view, args?.getString(1), args?.getMap(2))
152
- Commands.ATTACH_FULLSCREEN_BRIDGE.ordinal -> args?.getString(1)?.let { fullscreenBridgeId ->
153
- attachFullscreenBridge(view, fullscreenBridgeId)
147
+ val command = commandId?.toInt()?.toCommand() ?: throw IllegalArgumentException(
148
+ "The received command is not supported by the Bitmovin Player View"
149
+ )
150
+ when (command) {
151
+ Commands.ATTACH_PLAYER -> attachPlayer(view, args?.getString(1), args?.getMap(2))
152
+ Commands.ATTACH_FULLSCREEN_BRIDGE -> args?.getString(1)?.let { fullscreenBridgeId ->
153
+ attachFullscreenBridge(view, fullscreenBridgeId)
154
+ }
155
+ Commands.SET_CUSTOM_MESSAGE_HANDLER_BRIDGE_ID -> {
156
+ args?.getString(1)?.let { customMessageHandlerBridgeId ->
157
+ setCustomMessageHandlerBridgeId(view, customMessageHandlerBridgeId)
154
158
  }
155
- Commands.SET_CUSTOM_MESSAGE_HANDLER_BRIDGE_ID.ordinal -> {
156
- args?.getString(1)?.let { customMessageHandlerBridgeId ->
157
- setCustomMessageHandlerBridgeId(view, customMessageHandlerBridgeId)
158
- }
159
+ }
160
+ Commands.SET_FULLSCREEN -> {
161
+ args?.getBoolean(1)?.let { isFullscreen ->
162
+ setFullscreen(view, isFullscreen)
159
163
  }
160
- else -> {}
161
164
  }
162
165
  }
163
166
  }
@@ -170,6 +173,20 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
170
173
  }
171
174
  }
172
175
 
176
+ private fun setFullscreen(view: RNPlayerView, isFullscreen: Boolean) {
177
+ if (view.playerView?.isFullscreen == isFullscreen) return
178
+
179
+ Handler(Looper.getMainLooper()).post {
180
+ with(view.playerView ?: return@post) {
181
+ if (isFullscreen) {
182
+ enterFullscreen()
183
+ } else {
184
+ exitFullscreen()
185
+ }
186
+ }
187
+ }
188
+ }
189
+
173
190
  private fun setCustomMessageHandlerBridgeId(view: RNPlayerView, customMessageHandlerBridgeId: NativeId) {
174
191
  this.customMessageHandlerBridgeId = customMessageHandlerBridgeId
175
192
  attachCustomMessageHandlerBridge(view)
@@ -223,3 +240,5 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
223
240
  */
224
241
  private fun getPlayerModule(): PlayerModule? = context.getModule()
225
242
  }
243
+
244
+ private fun Int.toCommand(): RNPlayerViewManager.Commands? = RNPlayerViewManager.Commands.values().getOrNull(this)
@@ -1,6 +1,7 @@
1
1
  package com.bitmovin.player.reactnative.converter
2
2
 
3
3
  import com.bitmovin.analytics.BitmovinAnalyticsConfig
4
+ import com.bitmovin.analytics.config.SourceMetadata
4
5
  import com.bitmovin.analytics.data.CustomData
5
6
  import com.bitmovin.player.api.DeviceDescription.DeviceName
6
7
  import com.bitmovin.player.api.DeviceDescription.ModelName
@@ -784,6 +785,9 @@ class JsonConverter {
784
785
  customData.setProperty("customData${n}", customDataN)
785
786
  }
786
787
  }
788
+ it.getString("experimentName")?.let { experimentName ->
789
+ customData.experimentName = experimentName
790
+ }
787
791
  customData
788
792
  }
789
793
 
@@ -800,9 +804,33 @@ class JsonConverter {
800
804
  json.putString("customData${n}", customDataN)
801
805
  }
802
806
  }
807
+ it.experimentName?.let { experimentName ->
808
+ json.putString("experimentName", experimentName)
809
+ }
803
810
  json
804
811
  }
805
812
 
813
+ @JvmStatic
814
+ fun toAnalyticsSourceMetadata(json: ReadableMap?): SourceMetadata? = json?.let {
815
+ val sourceMetadata = SourceMetadata(
816
+ title = it.getString("title"),
817
+ videoId = it.getString("videoId"),
818
+ cdnProvider = it.getString("cdnProvider"),
819
+ path = it.getString("path"),
820
+ isLive = it.getBoolean("isLive")
821
+ )
822
+
823
+ for (n in 1..30) {
824
+ it.getString("customData${n}")?.let { customDataN ->
825
+ sourceMetadata.setProperty("customData${n}", customDataN)
826
+ }
827
+ }
828
+ it.getString("experimentName")?.let { experimentName ->
829
+ sourceMetadata.experimentName = experimentName
830
+ }
831
+ sourceMetadata
832
+ }
833
+
806
834
  /**
807
835
  * Converts any `VideoQuality` value into its json representation.
808
836
  * @param videoQuality `VideoQuality` value.
@@ -66,6 +66,11 @@ class FullscreenHandlerModule(private val context: ReactApplicationContext) : Re
66
66
  this.fullscreenHandler[nativeId] = fullscreenHandler
67
67
  }
68
68
 
69
+ @ReactMethod
70
+ fun setIsFullscreenActive(nativeId: NativeId, isFullscreenActive: Boolean) {
71
+ fullscreenHandler[nativeId]?.isFullscreen = isFullscreenActive
72
+ }
73
+
69
74
  @ReactMethod
70
75
  fun destroy(nativeId: NativeId) {
71
76
  fullscreenHandler.remove(nativeId)
@@ -7,8 +7,9 @@ RCT_EXTERN_METHOD(destroy:(NSString *)nativeId)
7
7
  RCT_EXTERN_METHOD(attach:(NSString *)nativeId playerId:(NSString *)playerId)
8
8
  RCT_EXTERN_METHOD(detach:(NSString *)nativeId)
9
9
  RCT_EXTERN_METHOD(setCustomDataOnce:(NSString *)nativeId json:(nullable id)json)
10
- RCT_EXTERN_METHOD(setCustomData:(NSString *)nativeId json:(nullable id)json)
11
- RCT_EXTERN_METHOD(getCustomData:(NSString *)nativeId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
10
+ RCT_EXTERN_METHOD(setCustomData:(NSString *)nativeId playerId:(nullable NSString *)playerId json:(nullable id)json)
11
+ RCT_EXTERN_METHOD(getCustomData:(NSString *)nativeId playerId:(nullable NSString *)playerId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
12
12
  RCT_EXTERN_METHOD(getUserId:(NSString *)nativeId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
13
+ RCT_EXTERN_METHOD(addSourceMetadata:(NSString *)nativeId playerId:(nullable NSString *)playerId json:(nullable id)json)
13
14
 
14
15
  @end
@@ -1,5 +1,5 @@
1
1
  import BitmovinPlayer
2
- import BitmovinAnalyticsCollector
2
+ import BitmovinCollector
3
3
 
4
4
  @objc(AnalyticsModule)
5
5
  class AnalyticsModule: NSObject, RCTBridgeModule {
@@ -118,37 +118,50 @@ class AnalyticsModule: NSObject, RCTBridgeModule {
118
118
  /**
119
119
  Sets the custom data config for a `BitmovinPlayerCollector` instance.
120
120
  - Parameter nativeId: Native Id of the collector instance.
121
+ - Parameter playerId: Native Id of the player instance.
121
122
  - Parameter json: Custom data config json.
122
123
  */
123
- @objc(setCustomData:json:)
124
- func setCustomData(_ nativeId: NativeId, json: Any?) {
124
+ @objc(setCustomData:playerId:json:)
125
+ func setCustomData(
126
+ _ nativeId: NativeId,
127
+ playerId: NativeId?,
128
+ json: Any?
129
+ ) {
125
130
  bridge.uiManager.addUIBlock { [weak self] _, _ in
126
131
  guard
127
132
  let collector = self?.collectors[nativeId],
128
- let customData = RCTConvert.analyticsCustomData(json)
133
+ let customData = RCTConvert.analyticsCustomData(json),
134
+ let playerId = playerId,
135
+ let player = self?.bridge[PlayerModule.self]?.retrieve(playerId),
136
+ let source = player.source
129
137
  else {
130
138
  return
131
139
  }
132
- collector.setCustomData(customData: customData)
140
+ collector.apply(customData: customData, for: source)
133
141
  }
134
142
  }
135
143
 
136
144
  /**
137
145
  Gets the current custom data config for a `BitmovinPlayerCollector` instance.
138
146
  - Parameter nativeId: Native Id of the the collector instance.
147
+ - Parameter playerId: Native Id of the player instance.
139
148
  - Parameter resolver: JS promise resolver.
140
149
  - Parameter rejecter: JS promise rejecter.
141
150
  */
142
- @objc(getCustomData:resolver:rejecter:)
151
+ @objc(getCustomData:playerId:resolver:rejecter:)
143
152
  func getCustomData(
144
153
  _ nativeId: NativeId,
154
+ playerId: NativeId?,
145
155
  resolver resolve: @escaping RCTPromiseResolveBlock,
146
156
  rejecter reject: @escaping RCTPromiseRejectBlock
147
157
  ) {
148
158
  bridge.uiManager.addUIBlock { [weak self] _, _ in
149
159
  guard
150
160
  let collector = self?.collectors[nativeId],
151
- let customData = RCTConvert.toJson(analyticsCustomData: collector.getCustomData())
161
+ let playerId = playerId,
162
+ let player = self?.bridge[PlayerModule.self]?.retrieve(playerId),
163
+ let source = player.source,
164
+ let customData = RCTConvert.toJson(analyticsCustomData: collector.customData(for: source))
152
165
  else {
153
166
  reject("[AnalyticsModule]", "Could not find analytics collector with ID (\(nativeId))", nil)
154
167
  return
@@ -177,4 +190,30 @@ class AnalyticsModule: NSObject, RCTBridgeModule {
177
190
  resolve(collector.getUserId())
178
191
  }
179
192
  }
193
+
194
+ /**
195
+ Applies the source metadata for the current source via the `BitmovinPlayerCollector` instance.
196
+ - Parameter nativeId: Native Id of the collector instance.
197
+ - Parameter playerId: Native Id of the player instance.
198
+ - Parameter json: Custom data config json.
199
+ */
200
+ @objc(addSourceMetadata:playerId:json:)
201
+ func addSourceMetadata(
202
+ _ nativeId: NativeId,
203
+ playerId: NativeId?,
204
+ json: Any?
205
+ ) {
206
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
207
+ guard
208
+ let collector = self?.collectors[nativeId],
209
+ let sourceMetadata = RCTConvert.analyticsSourceMetadata(json),
210
+ let playerId = playerId,
211
+ let player = self?.bridge[PlayerModule.self]?.retrieve(playerId),
212
+ let source = player.source
213
+ else {
214
+ return
215
+ }
216
+ collector.apply(sourceMetadata: sourceMetadata, for: source)
217
+ }
218
+ }
180
219
  }
@@ -6,4 +6,5 @@ RCT_EXTERN_METHOD(destroy:(NSString *)nativeId)
6
6
 
7
7
  RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(onFullscreenChanged:(NSString *)nativeId isFullscreenEnabled:(BOOL)isFullscreenEnabled)
8
8
  RCT_EXTERN_METHOD(registerHandler:(NSString *)nativeId)
9
+ RCT_EXTERN_METHOD(setIsFullscreenActive:(NSString *)nativeId isFullscreen:(BOOL)isFullscreen)
9
10
  @end
@@ -57,6 +57,11 @@ class FullscreenHandlerModule: NSObject, RCTBridgeModule {
57
57
  fullscreenHandlers[nativeId] = FullscreenHandlerBridge(nativeId, bridge: bridge)
58
58
  }
59
59
 
60
+ @objc(setIsFullscreenActive:isFullscreen:)
61
+ func setIsFullscreenActive(_ nativeId: NativeId, isFullscreen: Bool) {
62
+ fullscreenHandlers[nativeId]?.isFullscreen = isFullscreen
63
+ }
64
+
60
65
  func onFullscreenRequested(nativeId: NativeId) {
61
66
  fullscreenChangeDispatchGroup.enter()
62
67
  bridge.enqueueJSCall("FullscreenBridge-\(nativeId)", method: "enterFullscreen", args: []) {}
@@ -1,6 +1,6 @@
1
1
  import Foundation
2
2
  import BitmovinPlayer
3
- import BitmovinAnalyticsCollector
3
+ import BitmovinCollector
4
4
 
5
5
  extension RCTConvert {
6
6
  /**
@@ -72,6 +72,9 @@ extension RCTConvert {
72
72
  if let isUiEnabled = json["isUiEnabled"] as? Bool {
73
73
  styleConfig.isUiEnabled = isUiEnabled
74
74
  }
75
+ if let userInterfaceType = userInterfaceType(json["userInterfaceType"]) {
76
+ styleConfig.userInterfaceType = userInterfaceType
77
+ }
75
78
  #if !os(tvOS)
76
79
  if let playerUiCss = json["playerUiCss"] as? String {
77
80
  styleConfig.playerUiCss = RCTConvert.nsurl(playerUiCss)
@@ -614,11 +617,37 @@ extension RCTConvert {
614
617
  if let randomizeUserId = json["randomizeUserId"] as? Bool {
615
618
  config.randomizeUserId = randomizeUserId
616
619
  }
617
- for n in 1..<30 {
618
- if let customDataN = json["customData\(n)"] as? String {
619
- config.setValue(customDataN, forKey: "customData\(n)")
620
- }
621
- }
620
+ config.customData1 = json["customData1"] as? String
621
+ config.customData2 = json["customData2"] as? String
622
+ config.customData3 = json["customData3"] as? String
623
+ config.customData4 = json["customData4"] as? String
624
+ config.customData5 = json["customData5"] as? String
625
+ config.customData6 = json["customData6"] as? String
626
+ config.customData7 = json["customData7"] as? String
627
+ config.customData8 = json["customData8"] as? String
628
+ config.customData9 = json["customData9"] as? String
629
+ config.customData10 = json["customData10"] as? String
630
+ config.customData11 = json["customData11"] as? String
631
+ config.customData12 = json["customData12"] as? String
632
+ config.customData13 = json["customData13"] as? String
633
+ config.customData14 = json["customData14"] as? String
634
+ config.customData15 = json["customData15"] as? String
635
+ config.customData16 = json["customData16"] as? String
636
+ config.customData17 = json["customData17"] as? String
637
+ config.customData18 = json["customData18"] as? String
638
+ config.customData19 = json["customData19"] as? String
639
+ config.customData20 = json["customData20"] as? String
640
+ config.customData21 = json["customData21"] as? String
641
+ config.customData22 = json["customData22"] as? String
642
+ config.customData23 = json["customData23"] as? String
643
+ config.customData24 = json["customData24"] as? String
644
+ config.customData25 = json["customData25"] as? String
645
+ config.customData26 = json["customData26"] as? String
646
+ config.customData27 = json["customData27"] as? String
647
+ config.customData28 = json["customData28"] as? String
648
+ config.customData29 = json["customData29"] as? String
649
+ config.customData30 = json["customData30"] as? String
650
+ config.experimentName = json["experimentName"] as? String
622
651
  return config
623
652
  }
624
653
 
@@ -631,13 +660,39 @@ extension RCTConvert {
631
660
  guard let json = json as? [String: Any?] else {
632
661
  return nil
633
662
  }
634
- let customData = CustomData()
635
- for n in 1..<30 {
636
- if let customDataN = json["customData\(n)"] as? String {
637
- customData.setValue(customDataN, forKey: "customData\(n)")
638
- }
639
- }
640
- return customData
663
+ return CustomData(
664
+ customData1: json["customData1"] as? String,
665
+ customData2: json["customData2"] as? String,
666
+ customData3: json["customData3"] as? String,
667
+ customData4: json["customData4"] as? String,
668
+ customData5: json["customData5"] as? String,
669
+ customData6: json["customData6"] as? String,
670
+ customData7: json["customData7"] as? String,
671
+ customData8: json["customData8"] as? String,
672
+ customData9: json["customData9"] as? String,
673
+ customData10: json["customData10"] as? String,
674
+ customData11: json["customData11"] as? String,
675
+ customData12: json["customData12"] as? String,
676
+ customData13: json["customData13"] as? String,
677
+ customData14: json["customData14"] as? String,
678
+ customData15: json["customData15"] as? String,
679
+ customData16: json["customData16"] as? String,
680
+ customData17: json["customData17"] as? String,
681
+ customData18: json["customData18"] as? String,
682
+ customData19: json["customData19"] as? String,
683
+ customData20: json["customData20"] as? String,
684
+ customData21: json["customData21"] as? String,
685
+ customData22: json["customData22"] as? String,
686
+ customData23: json["customData23"] as? String,
687
+ customData24: json["customData24"] as? String,
688
+ customData25: json["customData25"] as? String,
689
+ customData26: json["customData26"] as? String,
690
+ customData27: json["customData27"] as? String,
691
+ customData28: json["customData28"] as? String,
692
+ customData29: json["customData29"] as? String,
693
+ customData30: json["customData30"] as? String,
694
+ experimentName: json["experimentName"] as? String
695
+ )
641
696
  }
642
697
 
643
698
  /**
@@ -650,13 +705,62 @@ extension RCTConvert {
650
705
  return nil
651
706
  }
652
707
  var json: [String: Any?] = [:]
653
- for n in 1..<30 {
654
- if let customDataN = analyticsCustomData.value(forKey: "customData\(n)") {
655
- json["customData\(n)"] = customDataN
656
- }
657
- }
708
+ json["customData1"] = analyticsCustomData.customData1
709
+ json["customData2"] = analyticsCustomData.customData2
710
+ json["customData3"] = analyticsCustomData.customData3
711
+ json["customData4"] = analyticsCustomData.customData4
712
+ json["customData5"] = analyticsCustomData.customData5
713
+ json["customData6"] = analyticsCustomData.customData6
714
+ json["customData7"] = analyticsCustomData.customData7
715
+ json["customData8"] = analyticsCustomData.customData8
716
+ json["customData9"] = analyticsCustomData.customData9
717
+ json["customData10"] = analyticsCustomData.customData10
718
+ json["customData11"] = analyticsCustomData.customData11
719
+ json["customData12"] = analyticsCustomData.customData12
720
+ json["customData13"] = analyticsCustomData.customData13
721
+ json["customData14"] = analyticsCustomData.customData14
722
+ json["customData15"] = analyticsCustomData.customData15
723
+ json["customData16"] = analyticsCustomData.customData16
724
+ json["customData17"] = analyticsCustomData.customData17
725
+ json["customData18"] = analyticsCustomData.customData18
726
+ json["customData19"] = analyticsCustomData.customData19
727
+ json["customData20"] = analyticsCustomData.customData20
728
+ json["customData21"] = analyticsCustomData.customData21
729
+ json["customData22"] = analyticsCustomData.customData22
730
+ json["customData23"] = analyticsCustomData.customData23
731
+ json["customData24"] = analyticsCustomData.customData24
732
+ json["customData25"] = analyticsCustomData.customData25
733
+ json["customData26"] = analyticsCustomData.customData26
734
+ json["customData27"] = analyticsCustomData.customData27
735
+ json["customData28"] = analyticsCustomData.customData28
736
+ json["customData29"] = analyticsCustomData.customData29
737
+ json["customData30"] = analyticsCustomData.customData30
738
+ json["experimentName"] = analyticsCustomData.experimentName
658
739
  return json
659
740
  }
741
+
742
+ /**
743
+ Utility method to get an analytics `SourceMetadata` value from a JS object.
744
+ - Parameter json: JS object.
745
+ - Returns: The associated `SourceMetadata` value or nil.
746
+ */
747
+ static func analyticsSourceMetadata(_ json: Any?) -> SourceMetadata? {
748
+ guard let json = json as? [String: Any?] else {
749
+ return nil
750
+ }
751
+
752
+ let customData = analyticsCustomData(json)
753
+
754
+ return SourceMetadata(
755
+ videoId: json["videoId"] as? String,
756
+ title: json["title"] as? String,
757
+ path: json["path"] as? String,
758
+ isLive: json["isLive"] as? Bool,
759
+ cdnProvider: json["cdnProvider"] as? String,
760
+ customData: customData ?? CustomData()
761
+ )
762
+ }
763
+
660
764
  /**
661
765
  Utility method to compute a JS value from a `VideoQuality` object.
662
766
  - Parameter videoQuality `VideoQuality` object to be converted.
@@ -675,4 +779,23 @@ extension RCTConvert {
675
779
  "bitrate": videoQuality.bitrate,
676
780
  ]
677
781
  }
782
+
783
+ /**
784
+ Utility method to get a `UserInterfaceType` from a JS object.
785
+ - Parameter json: JS object
786
+ - Returns: The associated `UserInterfaceType` value or `nil`
787
+ */
788
+ static func userInterfaceType(_ json: Any?) -> UserInterfaceType? {
789
+ guard let json = json as? String else {
790
+ return .none
791
+ }
792
+ switch json {
793
+ #if os(iOS)
794
+ case "Bitmovin": return .bitmovin
795
+ #endif
796
+ case "System": return .system
797
+ case "Subtitle": return .subtitle
798
+ default: return nil
799
+ }
800
+ }
678
801
  }
@@ -56,5 +56,6 @@ RCT_EXPORT_VIEW_PROPERTY(onFullscreenExit, RCTBubblingEventBlock)
56
56
  RCT_EXTERN_METHOD(attachPlayer:(nonnull NSNumber *)viewId playerId:(NSString *)playerId playerConfig:(nullable NSDictionary *)playerConfig)
57
57
  RCT_EXTERN_METHOD(attachFullscreenBridge:(nonnull NSNumber *)viewId fullscreenBridgeId:(NSString *)fullscreenBridgeId)
58
58
  RCT_EXTERN_METHOD(setCustomMessageHandlerBridgeId:(nonnull NSNumber *)viewId customMessageHandlerBridgeId:(NSString *)fullscreenBridgeId)
59
+ RCT_EXTERN_METHOD(setFullscreen:(nonnull NSNumber *)viewId isFullscreen:(BOOL)isFullscreen)
59
60
 
60
61
  @end