bitmovin-player-react-native 0.3.1 → 0.5.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 (56) hide show
  1. package/README.md +486 -24
  2. package/RNBitmovinPlayer.podspec +5 -1
  3. package/android/build.gradle +8 -5
  4. package/android/src/main/java/com/bitmovin/player/reactnative/AnalyticsModule.kt +154 -0
  5. package/android/src/main/java/com/bitmovin/player/reactnative/DrmModule.kt +4 -5
  6. package/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +41 -5
  7. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +318 -2
  8. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +70 -9
  9. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt +3 -0
  10. package/android/src/main/java/com/bitmovin/player/reactnative/SourceModule.kt +4 -5
  11. package/android/src/main/java/com/bitmovin/player/reactnative/UuidModule.kt +3 -1
  12. package/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +389 -0
  13. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/Any.kt +27 -0
  14. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt +8 -0
  15. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReadableArray.kt +19 -2
  16. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/String.kt +8 -0
  17. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/WritableMap.kt +19 -0
  18. package/android/src/main/java/com/bitmovin/player/reactnative/ui/FullscreenHandlerBridge.kt +37 -0
  19. package/android/src/main/java/com/bitmovin/player/reactnative/ui/FullscreenHandlerModule.kt +73 -0
  20. package/android/src/main/java/com/bitmovin/player/reactnative/ui/RNPictureInPictureHandler.kt +191 -0
  21. package/ios/AnalyticsModule.m +14 -0
  22. package/ios/AnalyticsModule.swift +180 -0
  23. package/ios/AudioSessionModule.m +10 -0
  24. package/ios/AudioSessionModule.swift +65 -0
  25. package/ios/Event+JSON.swift +134 -0
  26. package/ios/FullscreenHandlerBridge.swift +33 -0
  27. package/ios/FullscreenHandlerModule.m +9 -0
  28. package/ios/FullscreenHandlerModule.swift +71 -0
  29. package/ios/PlayerModule.m +6 -0
  30. package/ios/PlayerModule.swift +44 -0
  31. package/ios/RCTConvert+BitmovinPlayer.swift +382 -0
  32. package/ios/RNPlayerView+PlayerListener.swift +56 -0
  33. package/ios/RNPlayerView+UserInterfaceListener.swift +35 -0
  34. package/ios/RNPlayerView.swift +22 -0
  35. package/ios/RNPlayerViewManager.m +24 -1
  36. package/ios/RNPlayerViewManager.swift +23 -1
  37. package/lib/index.d.ts +1013 -150
  38. package/lib/index.js +251 -48
  39. package/lib/index.mjs +231 -34
  40. package/package.json +1 -1
  41. package/src/advertising.ts +155 -0
  42. package/src/analytics/collector.ts +97 -0
  43. package/src/analytics/config.ts +218 -0
  44. package/src/analytics/index.ts +2 -0
  45. package/src/audioSession.ts +47 -0
  46. package/src/components/PlayerView/events.ts +49 -3
  47. package/src/components/PlayerView/index.tsx +68 -11
  48. package/src/components/PlayerView/native.ts +4 -1
  49. package/src/events.ts +255 -0
  50. package/src/index.ts +4 -0
  51. package/src/media.ts +33 -0
  52. package/src/player.ts +57 -1
  53. package/src/source.ts +4 -0
  54. package/src/styleConfig.ts +87 -0
  55. package/src/ui/fullscreenhandler.ts +19 -0
  56. package/src/ui/fullscreenhandlerbridge.ts +59 -0
@@ -4,29 +4,54 @@ import android.os.Handler
4
4
  import android.os.Looper
5
5
  import android.view.ViewGroup.LayoutParams
6
6
  import com.bitmovin.player.PlayerView
7
+ import com.bitmovin.player.reactnative.extensions.getModule
8
+ import com.bitmovin.player.reactnative.ui.FullscreenHandlerBridge
9
+ import com.bitmovin.player.reactnative.ui.FullscreenHandlerModule
10
+ import com.bitmovin.player.reactnative.ui.RNPictureInPictureHandler
7
11
  import com.facebook.react.bridge.*
12
+ import com.facebook.react.module.annotations.ReactModule
8
13
  import com.facebook.react.uimanager.SimpleViewManager
9
14
  import com.facebook.react.uimanager.ThemedReactContext
10
15
 
16
+ private const val MODULE_NAME = "NativePlayerView"
17
+
18
+ @ReactModule(name = MODULE_NAME)
11
19
  class RNPlayerViewManager(private val context: ReactApplicationContext) : SimpleViewManager<RNPlayerView>() {
12
20
  /**
13
21
  * Native component functions.
14
22
  */
15
23
  enum class Commands {
16
24
  ATTACH_PLAYER,
25
+ ATTACH_FULLSCREEN_BRIDGE
17
26
  }
18
27
 
19
28
  /**
20
29
  * Exported module name to JS.
21
30
  */
22
- override fun getName() = "NativePlayerView"
31
+ override fun getName() = MODULE_NAME
32
+
33
+ /**
34
+ * React Native PiP handler instance. It can be subclassed, then set from other native
35
+ * modules in case a full-custom implementation is needed. A default implementation is provided
36
+ * out-of-the-box.
37
+ */
38
+ var pictureInPictureHandler = RNPictureInPictureHandler(context)
23
39
 
24
40
  /**
25
- * The component's native view factory. RN calls this method multiple times
41
+ * The component's native view factory. RN may call this method multiple times
26
42
  * for each component instance.
27
43
  */
28
44
  override fun createViewInstance(reactContext: ThemedReactContext) = RNPlayerView(context)
29
45
 
46
+ /**
47
+ * Called when the component's view gets detached from the view hierarchy. Useful to perform
48
+ * cleanups.
49
+ */
50
+ override fun onDropViewInstance(view: RNPlayerView) {
51
+ super.onDropViewInstance(view)
52
+ view.dispose()
53
+ }
54
+
30
55
  /**
31
56
  * A mapping between a event native identifier and its bubbled version that will
32
57
  * be accessed from React.
@@ -45,6 +70,8 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
45
70
  "playbackFinished" to "onPlaybackFinished",
46
71
  "seek" to "onSeek",
47
72
  "seeked" to "onSeeked",
73
+ "stallStarted" to "onStallStarted",
74
+ "stallEnded" to "onStallEnded",
48
75
  "timeChanged" to "onTimeChanged",
49
76
  "sourceLoad" to "onSourceLoad",
50
77
  "sourceLoaded" to "onSourceLoaded",
@@ -54,6 +81,25 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
54
81
  "subtitleAdded" to "onSubtitleAdded",
55
82
  "subtitleChanged" to "onSubtitleChanged",
56
83
  "subtitleRemoved" to "onSubtitleRemoved",
84
+ "pictureInPictureAvailabilityChanged" to "onPictureInPictureAvailabilityChanged",
85
+ "pictureInPictureEnter" to "onPictureInPictureEnter",
86
+ "pictureInPictureExit" to "onPictureInPictureExit",
87
+ "adBreakFinished" to "onAdBreakFinished",
88
+ "adBreakStarted" to "onAdBreakStarted",
89
+ "adClicked" to "onAdClicked",
90
+ "adError" to "onAdError",
91
+ "adFinished" to "onAdFinished",
92
+ "adManifestLoad" to "onAdManifestLoad",
93
+ "adManifestLoaded" to "onAdManifestLoaded",
94
+ "adQuartile" to "onAdQuartile",
95
+ "adScheduled" to "onAdScheduled",
96
+ "adSkipped" to "onAdSkipped",
97
+ "adStarted" to "onAdStarted",
98
+ "videoPlaybackQualityChanged" to "onVideoPlaybackQualityChanged",
99
+ "fullscreenEnabled" to "onFullscreenEnabled",
100
+ "fullscreenDisabled" to "onFullscreenDisabled",
101
+ "fullscreenEnter" to "onFullscreenEnter",
102
+ "fullscreenExit" to "onFullscreenExit",
57
103
  )
58
104
 
59
105
  /**
@@ -75,7 +121,8 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
75
121
  * @return map between names (used in js) and command ids (used in native code).
76
122
  */
77
123
  override fun getCommandsMap(): MutableMap<String, Int> = mutableMapOf(
78
- "attachPlayer" to Commands.ATTACH_PLAYER.ordinal
124
+ "attachPlayer" to Commands.ATTACH_PLAYER.ordinal,
125
+ "attachFullscreenBridge" to Commands.ATTACH_FULLSCREEN_BRIDGE.ordinal,
79
126
  )
80
127
 
81
128
  /**
@@ -88,36 +135,50 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
88
135
  super.receiveCommand(view, commandId, args)
89
136
  commandId?.toInt()?.let {
90
137
  when (it) {
91
- Commands.ATTACH_PLAYER.ordinal -> attachPlayer(view, args?.getString(1))
138
+ Commands.ATTACH_PLAYER.ordinal -> attachPlayer(view, args?.getString(1), args?.getMap(2))
139
+ Commands.ATTACH_FULLSCREEN_BRIDGE.ordinal -> args?.getString(1)?.let { fullscreenBridgeId ->
140
+ attachFullscreenBridge(view, fullscreenBridgeId)
141
+ }
92
142
  else -> {}
93
143
  }
94
144
  }
95
145
  }
96
146
 
147
+ private fun attachFullscreenBridge(view: RNPlayerView, fullscreenBridgeId: NativeId) {
148
+ Handler(Looper.getMainLooper()).post {
149
+ view.playerView?.setFullscreenHandler(
150
+ context.getModule<FullscreenHandlerModule>()?.getInstance(fullscreenBridgeId)
151
+ )
152
+ }
153
+ }
154
+
97
155
  /**
98
156
  * Set the `Player` instance for the target view using `playerId`.
99
157
  * @param view Target `RNPlayerView`.
100
158
  * @param playerId `Player` instance id inside `PlayerModule`'s registry.
101
159
  */
102
- private fun attachPlayer(view: RNPlayerView, playerId: NativeId?) {
160
+ private fun attachPlayer(view: RNPlayerView, playerId: NativeId?, playerConfig: ReadableMap?) {
103
161
  Handler(Looper.getMainLooper()).post {
104
162
  val player = getPlayerModule()?.getPlayer(playerId)
163
+ playerConfig?.getMap("playbackConfig")?.getBoolean("isPictureInPictureEnabled")?.let {
164
+ pictureInPictureHandler.isPictureInPictureEnabled = it
165
+ view.pictureInPictureHandler = pictureInPictureHandler
166
+ }
105
167
  if (view.playerView != null) {
106
168
  view.player = player
107
169
  } else {
108
170
  val playerView = PlayerView(context, player)
109
171
  playerView.layoutParams = LayoutParams(
110
172
  LayoutParams.MATCH_PARENT,
111
- LayoutParams.MATCH_PARENT)
173
+ LayoutParams.MATCH_PARENT
174
+ )
112
175
  view.addPlayerView(playerView)
113
176
  }
114
- view.startBubblingEvents()
115
177
  }
116
178
  }
117
179
 
118
180
  /**
119
181
  * Helper function that gets the instantiated `PlayerModule` from modules registry.
120
182
  */
121
- private fun getPlayerModule(): PlayerModule? =
122
- context.getNativeModule(PlayerModule::class.java)
183
+ private fun getPlayerModule(): PlayerModule? = context.getModule()
123
184
  }
@@ -1,6 +1,7 @@
1
1
  package com.bitmovin.player.reactnative
2
2
 
3
3
  import android.view.View
4
+ import com.bitmovin.player.reactnative.ui.FullscreenHandlerModule
4
5
  import com.facebook.react.ReactPackage
5
6
  import com.facebook.react.bridge.NativeModule
6
7
  import com.facebook.react.bridge.ReactApplicationContext
@@ -21,7 +22,9 @@ class RNPlayerViewPackage : ReactPackage {
21
22
  PlayerModule(reactContext),
22
23
  SourceModule(reactContext),
23
24
  DrmModule(reactContext),
25
+ AnalyticsModule(reactContext),
24
26
  RNPlayerViewManager(reactContext),
27
+ FullscreenHandlerModule(reactContext)
25
28
  )
26
29
  }
27
30
 
@@ -6,7 +6,9 @@ import com.facebook.react.bridge.*
6
6
  import com.facebook.react.module.annotations.ReactModule
7
7
  import com.facebook.react.uimanager.UIManagerModule
8
8
 
9
- @ReactModule(name = SourceModule.name)
9
+ private const val MODULE_NAME = "SourceModule"
10
+
11
+ @ReactModule(name = MODULE_NAME)
10
12
  class SourceModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
11
13
  /**
12
14
  * In-memory mapping from `nativeId`s to `Source` instances.
@@ -16,10 +18,7 @@ class SourceModule(private val context: ReactApplicationContext) : ReactContextB
16
18
  /**
17
19
  * JS exported module name.
18
20
  */
19
- companion object {
20
- const val name = "SourceModule"
21
- }
22
- override fun getName() = SourceModule.name
21
+ override fun getName() = MODULE_NAME
23
22
 
24
23
  /**
25
24
  * Fetches the `Source` instance associated with `nativeId` from internal sources.
@@ -5,11 +5,13 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
5
5
  import com.facebook.react.bridge.ReactMethod
6
6
  import java.util.*
7
7
 
8
+ private const val MODULE_NAME = "UuidModule"
9
+
8
10
  class UuidModule(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
9
11
  /**
10
12
  * Exported JS module name.
11
13
  */
12
- override fun getName() = "UuidModule"
14
+ override fun getName() = MODULE_NAME
13
15
 
14
16
  /**
15
17
  * Synchronously generate a random UUIDv4.
@@ -1,20 +1,32 @@
1
1
  package com.bitmovin.player.reactnative.converter
2
2
 
3
+ import com.bitmovin.analytics.BitmovinAnalyticsConfig
4
+ import com.bitmovin.analytics.data.CustomData
3
5
  import com.bitmovin.player.api.DeviceDescription.DeviceName
4
6
  import com.bitmovin.player.api.DeviceDescription.ModelName
5
7
  import com.bitmovin.player.api.PlaybackConfig
6
8
  import com.bitmovin.player.api.PlayerConfig
7
9
  import com.bitmovin.player.api.TweaksConfig
10
+ import com.bitmovin.player.api.advertising.*
8
11
  import com.bitmovin.player.api.drm.WidevineConfig
9
12
  import com.bitmovin.player.api.event.PlayerEvent
10
13
  import com.bitmovin.player.api.event.SourceEvent
11
14
  import com.bitmovin.player.api.event.data.SeekPosition
12
15
  import com.bitmovin.player.api.media.subtitle.SubtitleTrack
16
+ import com.bitmovin.player.api.media.thumbnail.ThumbnailTrack
17
+ import com.bitmovin.player.api.media.video.quality.VideoQuality
13
18
  import com.bitmovin.player.api.source.Source
14
19
  import com.bitmovin.player.api.source.SourceConfig
15
20
  import com.bitmovin.player.api.source.SourceType
21
+ import com.bitmovin.player.api.ui.ScalingMode
22
+ import com.bitmovin.player.api.ui.StyleConfig
16
23
  import com.bitmovin.player.reactnative.extensions.getName
24
+ import com.bitmovin.player.reactnative.extensions.putInt
25
+ import com.bitmovin.player.reactnative.extensions.putDouble
17
26
  import com.bitmovin.player.reactnative.extensions.toList
27
+ import com.bitmovin.player.reactnative.extensions.toReadableArray
28
+ import com.bitmovin.player.reactnative.extensions.getProperty
29
+ import com.bitmovin.player.reactnative.extensions.setProperty
18
30
  import com.facebook.react.bridge.*
19
31
  import java.util.UUID
20
32
 
@@ -41,11 +53,21 @@ class JsonConverter {
41
53
  playerConfig.playbackConfig = it
42
54
  }
43
55
  }
56
+ if (json.hasKey("styleConfig")) {
57
+ toStyleConfig(json.getMap("styleConfig"))?.let {
58
+ playerConfig.styleConfig = it
59
+ }
60
+ }
44
61
  if (json.hasKey("tweaksConfig")) {
45
62
  toTweaksConfig(json.getMap("tweaksConfig"))?.let {
46
63
  playerConfig.tweaksConfig = it
47
64
  }
48
65
  }
66
+ if (json.hasKey("advertisingConfig")) {
67
+ toAdvertisingConfig(json.getMap("advertisingConfig"))?.let {
68
+ playerConfig.advertisingConfig = it
69
+ }
70
+ }
49
71
  return playerConfig
50
72
  }
51
73
 
@@ -72,6 +94,47 @@ class JsonConverter {
72
94
  return playbackConfig
73
95
  }
74
96
 
97
+ /**
98
+ * Converts any JS object into a `StyleConfig` object.
99
+ * @param json JS object representing the `StyleConfig`.
100
+ * @return The generated `StyleConfig` if successful, `null` otherwise.
101
+ */
102
+ @JvmStatic
103
+ fun toStyleConfig(json: ReadableMap?): StyleConfig? {
104
+ if (json == null) {
105
+ return null
106
+ }
107
+ val styleConfig = StyleConfig()
108
+ if (json.hasKey("isUiEnabled")) {
109
+ styleConfig.isUiEnabled = json.getBoolean("isUiEnabled")
110
+ }
111
+ if (json.hasKey("playerUiCss")) {
112
+ val playerUiCss = json.getString("playerUiCss")
113
+ if (!playerUiCss.isNullOrEmpty()) {
114
+ styleConfig.playerUiCss = playerUiCss
115
+ }
116
+ }
117
+ if (json.hasKey("supplementalPlayerUiCss")) {
118
+ val supplementalPlayerUiCss = json.getString("supplementalPlayerUiCss")
119
+ if (!supplementalPlayerUiCss.isNullOrEmpty()) {
120
+ styleConfig.supplementalPlayerUiCss = supplementalPlayerUiCss
121
+ }
122
+ }
123
+ if (json.hasKey("playerUiJs")) {
124
+ val playerUiJs = json.getString("playerUiJs")
125
+ if (!playerUiJs.isNullOrEmpty()) {
126
+ styleConfig.playerUiJs = playerUiJs
127
+ }
128
+ }
129
+ if (json.hasKey("scalingMode")) {
130
+ val scalingMode = json.getString("scalingMode")
131
+ if (!scalingMode.isNullOrEmpty()) {
132
+ styleConfig.scalingMode = ScalingMode.valueOf(scalingMode)
133
+ }
134
+ }
135
+ return styleConfig
136
+ }
137
+
75
138
  /**
76
139
  * Converts any JS object into a `TweaksConfig` object.
77
140
  * @param json JS object representing the `TweaksConfig`.
@@ -124,6 +187,54 @@ class JsonConverter {
124
187
  return tweaksConfig
125
188
  }
126
189
 
190
+ /**
191
+ * Converts any JS object into an `AdvertisingConfig` object.
192
+ * @param json JS object representing the `AdvertisingConfig`.
193
+ * @return The generated `AdvertisingConfig` if successful, `null` otherwise.
194
+ */
195
+ @JvmStatic
196
+ fun toAdvertisingConfig(json: ReadableMap?): AdvertisingConfig? = json?.getArray("schedule")
197
+ ?.toList<ReadableMap>()
198
+ ?.mapNotNull(::toAdItem)
199
+ ?.let { AdvertisingConfig(it) }
200
+
201
+ /**
202
+ * Converts any JS object into an `AdItem` object.
203
+ * @param json JS object representing the `AdItem`.
204
+ * @return The generated `AdItem` if successful, `null` otherwise.
205
+ */
206
+ @JvmStatic
207
+ fun toAdItem(json: ReadableMap?): AdItem? {
208
+ val sources = json?.getArray("sources")
209
+ ?.toList<ReadableMap>()
210
+ ?.mapNotNull(::toAdSource)
211
+ ?.toTypedArray()
212
+ ?: return null
213
+ return AdItem(sources, json?.getString("position") ?: "pre")
214
+ }
215
+
216
+ /**
217
+ * Converts any JS object into an `AdSource` object.
218
+ * @param json JS object representing the `AdSource`.
219
+ * @return The generated `AdSource` if successful, `null` otherwise.
220
+ */
221
+ @JvmStatic
222
+ fun toAdSource(json: ReadableMap?): AdSource? = json?.getString("tag")?.let {
223
+ AdSource(toAdSourceType(json.getString("type")), it)
224
+ }
225
+
226
+ /**
227
+ * Converts any JS string into an `AdSourceType` enum value.
228
+ * @param json JS string representing the `AdSourceType`.
229
+ * @return The generated `AdSourceType`.
230
+ */
231
+ @JvmStatic
232
+ fun toAdSourceType(json: String?): AdSourceType = when (json) {
233
+ "ima" -> AdSourceType.Ima
234
+ "progressive" -> AdSourceType.Progressive
235
+ else -> AdSourceType.Unknown
236
+ }
237
+
127
238
  /**
128
239
  * Converts an arbitrary `json` to `SourceConfig`.
129
240
  * @param json JS object representing the `SourceConfig`.
@@ -150,6 +261,9 @@ class JsonConverter {
150
261
  }
151
262
  }
152
263
  }
264
+ if (json.hasKey("thumbnailTrack")) {
265
+ config.thumbnailTrack = toThumbnailTrack(json.getString("thumbnailTrack"))
266
+ }
153
267
  return config
154
268
  }
155
269
 
@@ -270,6 +384,59 @@ class JsonConverter {
270
384
  json.putMap("from", fromSeekPosition(event.from))
271
385
  json.putMap("to", fromSeekPosition(event.to))
272
386
  }
387
+ if (event is PlayerEvent.PictureInPictureAvailabilityChanged) {
388
+ json.putBoolean("isPictureInPictureAvailable", event.isPictureInPictureAvailable)
389
+ }
390
+ if (event is PlayerEvent.AdBreakFinished) {
391
+ json.putMap("adBreak", fromAdBreak(event.adBreak))
392
+ }
393
+ if (event is PlayerEvent.AdBreakStarted) {
394
+ json.putMap("adBreak", fromAdBreak(event.adBreak))
395
+ }
396
+ if (event is PlayerEvent.AdClicked) {
397
+ json.putString("clickThroughUrl", event.clickThroughUrl)
398
+ }
399
+ if (event is PlayerEvent.AdError) {
400
+ json.putInt("code", event.code)
401
+ json.putString("message", event.message)
402
+ json.putMap("adConfig", fromAdConfig(event.adConfig))
403
+ json.putMap("adItem", fromAdItem(event.adItem))
404
+ }
405
+ if (event is PlayerEvent.AdFinished) {
406
+ json.putMap("ad", fromAd(event.ad))
407
+ }
408
+ if (event is PlayerEvent.AdManifestLoad) {
409
+ json.putMap("adBreak", fromAdBreak(event.adBreak))
410
+ json.putMap("adConfig", fromAdConfig(event.adConfig))
411
+ }
412
+ if (event is PlayerEvent.AdManifestLoaded) {
413
+ json.putMap("adBreak", fromAdBreak(event.adBreak))
414
+ json.putMap("adConfig", fromAdConfig(event.adConfig))
415
+ json.putDouble("downloadTime", event.downloadTime.toDouble())
416
+ }
417
+ if (event is PlayerEvent.AdQuartile) {
418
+ json.putString("quartile", fromAdQuartile(event.quartile))
419
+ }
420
+ if (event is PlayerEvent.AdScheduled) {
421
+ json.putInt("numberOfAds", event.numberOfAds)
422
+ }
423
+ if (event is PlayerEvent.AdSkipped) {
424
+ json.putMap("ad", fromAd(event.ad))
425
+ }
426
+ if (event is PlayerEvent.AdStarted) {
427
+ json.putMap("ad", fromAd(event.ad))
428
+ json.putString("clickThroughUrl", event.clickThroughUrl)
429
+ json.putString("clientType", fromAdSourceType(event.clientType))
430
+ json.putDouble("duration", event.duration)
431
+ json.putInt("indexInQueue", event.indexInQueue)
432
+ json.putString("position", event.position)
433
+ json.putDouble("skipOffset", event.skipOffset)
434
+ json.putDouble("timeOffset", event.timeOffset)
435
+ }
436
+ if (event is PlayerEvent.VideoPlaybackQualityChanged) {
437
+ json.putMap("newVideoQuality", fromVideoQuality(event.newVideoQuality))
438
+ json.putMap("oldVideoQuality", fromVideoQuality(event.oldVideoQuality))
439
+ }
273
440
  return json
274
441
  }
275
442
 
@@ -287,6 +454,19 @@ class JsonConverter {
287
454
  widevineConfig
288
455
  }
289
456
 
457
+ /**
458
+ * Converts an `url` string into a `ThumbnailsTrack`.
459
+ * @param url JS object representing the `ThumbnailsTrack`.
460
+ * @return The generated `ThumbnailsTrack` if successful, `null` otherwise.
461
+ */
462
+ @JvmStatic
463
+ fun toThumbnailTrack(url: String?): ThumbnailTrack? {
464
+ if (url == null) {
465
+ return null
466
+ }
467
+ return ThumbnailTrack(url);
468
+ }
469
+
290
470
  /**
291
471
  * Converts an arbitrary `json` into a `SubtitleTrack`.
292
472
  * @param json JS object representing the `SubtitleTrack`.
@@ -378,5 +558,214 @@ class JsonConverter {
378
558
  }
379
559
  return mimeType.split("/").last()
380
560
  }
561
+
562
+ /**
563
+ * Converts any `AdBreak` object into its json representation.
564
+ * @param adBreak `AdBreak` object.
565
+ * @return The produced JS object.
566
+ */
567
+ @JvmStatic
568
+ fun fromAdBreak(adBreak: AdBreak?): WritableMap? = adBreak?.let {
569
+ Arguments.createMap().apply {
570
+ putArray("ads", it.ads.mapNotNull(::fromAd).toReadableArray())
571
+ putString("id", it.id)
572
+ putDouble("scheduleTime", it.scheduleTime)
573
+ }
574
+ }
575
+
576
+ /**
577
+ * Converts any `Ad` object into its json representation.
578
+ * @param ad `Ad` object.
579
+ * @return The produced JS object.
580
+ */
581
+ @JvmStatic
582
+ fun fromAd(ad: Ad?): WritableMap? = ad?.let {
583
+ Arguments.createMap().apply {
584
+ putString("clickThroughUrl", it.clickThroughUrl)
585
+ putMap("data", fromAdData(it.data))
586
+ putInt("height", it.height)
587
+ putString("id", it.id)
588
+ putBoolean("isLinear", it.isLinear)
589
+ putString("mediaFileUrl", it.mediaFileUrl)
590
+ putInt("width", it.width)
591
+ }
592
+ }
593
+
594
+ /**
595
+ * Converts any `AdData` object into its json representation.
596
+ * @param adData `AdData` object.
597
+ * @return The produced JS object.
598
+ */
599
+ @JvmStatic
600
+ fun fromAdData(adData: AdData?): WritableMap? = adData?.let {
601
+ Arguments.createMap().apply {
602
+ putInt("bitrate", it.bitrate)
603
+ putInt("maxBitrate", it.maxBitrate)
604
+ putString("mimeType", it.mimeType)
605
+ putInt("minBitrate", it.minBitrate)
606
+ }
607
+ }
608
+
609
+ /**
610
+ * Converts any `AdConfig` object into its json representation.
611
+ * @param adConfig `AdConfig` object.
612
+ * @return The produced JS object.
613
+ */
614
+ @JvmStatic
615
+ fun fromAdConfig(adConfig: AdConfig?): WritableMap? = adConfig?.let {
616
+ Arguments.createMap().apply {
617
+ putDouble("replaceContentDuration", it.replaceContentDuration)
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Converts any `AdItem` object into its json representation.
623
+ * @param adItem `AdItem` object.
624
+ * @return The produced JS object.
625
+ */
626
+ @JvmStatic
627
+ fun fromAdItem(adItem: AdItem?): WritableMap? = adItem?.let {
628
+ Arguments.createMap().apply {
629
+ putString("position", it.position)
630
+ putArray("sources", it.sources.mapNotNull(::fromAdSource).toReadableArray())
631
+ }
632
+ }
633
+
634
+ /**
635
+ * Converts any `AdSource` object into its json representation.
636
+ * @param adSource `AdSource` object.
637
+ * @return The produced JS object.
638
+ */
639
+ @JvmStatic
640
+ fun fromAdSource(adSource: AdSource?): WritableMap? = adSource?.let {
641
+ Arguments.createMap().apply {
642
+ putString("tag", it.tag)
643
+ putString("type", fromAdSourceType(it.type))
644
+ }
645
+ }
646
+
647
+ /**
648
+ * Converts any `AdSourceType` value into its json representation.
649
+ * @param adSourceType `AdSourceType` value.
650
+ * @return The produced JS string.
651
+ */
652
+ @JvmStatic
653
+ fun fromAdSourceType(adSourceType: AdSourceType?): String? = when (adSourceType) {
654
+ AdSourceType.Ima -> "ima"
655
+ AdSourceType.Unknown -> "unknown"
656
+ AdSourceType.Progressive -> "progressive"
657
+ else -> null
658
+ }
659
+
660
+ /**
661
+ * Converts any `AdQuartile` value into its json representation.
662
+ * @param adQuartile `AdQuartile` value.
663
+ * @return The produced JS string.
664
+ */
665
+ @JvmStatic
666
+ fun fromAdQuartile(adQuartile: AdQuartile?): String? = when (adQuartile) {
667
+ AdQuartile.FirstQuartile -> "first"
668
+ AdQuartile.MidPoint -> "mid_point"
669
+ AdQuartile.ThirdQuartile -> "third"
670
+ else -> null
671
+ }
672
+
673
+ /**
674
+ * Converts an arbitrary json object into a `BitmovinAnalyticsConfig`.
675
+ * @param json JS object representing the `BitmovinAnalyticsConfig`.
676
+ * @return The produced `BitmovinAnalyticsConfig` or null.
677
+ */
678
+ @JvmStatic
679
+ fun toAnalyticsConfig(json: ReadableMap?): BitmovinAnalyticsConfig? = json?.let {
680
+ var config: BitmovinAnalyticsConfig? = null
681
+ it.getString("key")?.let { key ->
682
+ config = it.getString("playerKey")
683
+ ?.let { playerKey -> BitmovinAnalyticsConfig(key, playerKey) }
684
+ ?: BitmovinAnalyticsConfig(key)
685
+ }
686
+ it.getString("cdnProvider")?.let { cdnProvider ->
687
+ config?.cdnProvider = cdnProvider
688
+ }
689
+ it.getString("customUserId")?.let { customUserId ->
690
+ config?.customUserId = customUserId
691
+ }
692
+ it.getString("experimentName")?.let { experimentName ->
693
+ config?.experimentName = experimentName
694
+ }
695
+ it.getString("videoId")?.let { videoId ->
696
+ config?.videoId = videoId
697
+ }
698
+ it.getString("title")?.let { title ->
699
+ config?.title = title
700
+ }
701
+ it.getString("path")?.let { path ->
702
+ config?.path = path
703
+ }
704
+ if (it.hasKey("isLive")) {
705
+ config?.isLive = it.getBoolean("isLive")
706
+ }
707
+ if (it.hasKey("ads")) {
708
+ config?.ads = it.getBoolean("ads")
709
+ }
710
+ if (it.hasKey("randomizeUserId")) {
711
+ config?.randomizeUserId = it.getBoolean("randomizeUserId")
712
+ }
713
+ for (n in 1..30) {
714
+ it.getString("customData${n}")?.let { customDataN ->
715
+ config?.setProperty("customData${n}", customDataN)
716
+ }
717
+ }
718
+ config
719
+ }
720
+
721
+ /**
722
+ * Converts an arbitrary json object into an analytics `CustomData`.
723
+ * @param json JS object representing the `CustomData`.
724
+ * @return The produced `CustomData` or null.
725
+ */
726
+ @JvmStatic
727
+ fun toAnalyticsCustomData(json: ReadableMap?): CustomData? = json?.let {
728
+ val customData = CustomData()
729
+ for (n in 1..30) {
730
+ it.getString("customData${n}")?.let { customDataN ->
731
+ customData.setProperty("customData${n}", customDataN)
732
+ }
733
+ }
734
+ customData
735
+ }
736
+
737
+ /**
738
+ * Converts an arbitrary analytics `CustomData` object into a JS value.
739
+ * @param customData `CustomData` to be converted.
740
+ * @return The produced JS value or null.
741
+ */
742
+ @JvmStatic
743
+ fun fromAnalyticsCustomData(customData: CustomData?): ReadableMap? = customData?.let {
744
+ val json = Arguments.createMap()
745
+ for (n in 1..30) {
746
+ it.getProperty<String>("customData${n}")?.let { customDataN ->
747
+ json.putString("customData${n}", customDataN)
748
+ }
749
+ }
750
+ json
751
+ }
752
+
753
+ /**
754
+ * Converts any `VideoQuality` value into its json representation.
755
+ * @param videoQuality `VideoQuality` value.
756
+ * @return The produced JS string.
757
+ */
758
+ @JvmStatic
759
+ fun fromVideoQuality(videoQuality: VideoQuality?): WritableMap? = videoQuality?.let {
760
+ Arguments.createMap().apply {
761
+ putString("id", videoQuality.id)
762
+ putString("label", videoQuality.label)
763
+ putInt("bitrate", videoQuality.bitrate)
764
+ putString("codec", videoQuality.codec)
765
+ putDouble("frameRate", videoQuality.frameRate.toDouble())
766
+ putInt("height", videoQuality.height)
767
+ putInt("width", videoQuality.width)
768
+ }
769
+ }
381
770
  }
382
771
  }