react-native-theoplayer 10.7.0 → 10.8.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 (29) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/android/src/main/java/com/theoplayer/PlayerEventEmitter.kt +3 -0
  3. package/android/src/main/java/com/theoplayer/ReactTHEOplayerView.kt +5 -2
  4. package/android/src/main/java/com/theoplayer/ReactTHEOplayerViewManager.kt +3 -1
  5. package/android/src/main/java/com/theoplayer/ads/AdAdapter.kt +4 -2
  6. package/android/src/main/java/com/theoplayer/drm/ContentProtectionAdapter.kt +4 -0
  7. package/android/src/main/java/com/theoplayer/player/PlayerModule.kt +8 -7
  8. package/android/src/main/java/com/theoplayer/presentation/FullscreenLayoutObserver.kt +3 -25
  9. package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +26 -39
  10. package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +2 -0
  11. package/android/src/main/java/com/theoplayer/util/ViewUtils.kt +67 -0
  12. package/ios/THEOplayerRCTDebug.swift +9 -2
  13. package/ios/THEOplayerRCTMainEventHandler.swift +4 -4
  14. package/ios/THEOplayerRCTPlayerStateBuilder.swift +100 -0
  15. package/ios/THEOplayerRCTSourceDescriptionAggregator.swift +12 -7
  16. package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +6 -4
  17. package/ios/THEOplayerRCTTrackMetadataAggregator.swift +1 -1
  18. package/ios/THEOplayerRCTView+AppState.swift +30 -1
  19. package/ios/THEOplayerRCTView.swift +37 -9
  20. package/ios/ads/THEOplayerRCTSourceDescriptionBuilder+Ads.swift +5 -5
  21. package/ios/backgroundAudio/THEOplayerRCTNowPlayingManager.swift +29 -54
  22. package/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift +36 -18
  23. package/lib/commonjs/manifest.json +1 -1
  24. package/lib/module/manifest.json +1 -1
  25. package/lib/typescript/api/source/drm/DRMConfiguration.d.ts +7 -0
  26. package/lib/typescript/api/source/drm/DRMConfiguration.d.ts.map +1 -1
  27. package/package.json +5 -5
  28. package/src/api/source/drm/DRMConfiguration.ts +8 -0
  29. package/src/manifest.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.1.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [10.8.0] - 26-01-22
9
+
10
+ ### Added
11
+
12
+ - Added support on iOS to push the initial state of the player from the iOS bridge to the React native adapter.
13
+
14
+ ### Fixed
15
+
16
+ - Fixed an issue on iOS where the integrationParameters from the contentProtection section of the source were not processed correctly, resulting in failures for DRM connectors that depend on them.
17
+ - Fixed an issue on iOS where the player could crash when terminating the app while the player is backgrounded in fullscreen.
18
+ - Fixed an issue on Android where `player.ads.currentAds` would not return an array of ads due to a native error.
19
+ - Fixed an issue on Android where the player would sometimes have wrong dimensions after transitioning to fullscreen presentation mode.
20
+ - Fixed an issue on Android where the `useEMSG` property was not included when setting a TheoAds source description.
21
+
22
+ ### Added
23
+
24
+ - Added RNRepo support for Android to the example app. More info on the official [documentation](https://rnrepo.org/) page.
25
+
26
+ ## [10.7.1] - 26-01-06
27
+
28
+ ### Added
29
+
30
+ - Added a `multiSession` property to `DRMConfiguration` to enable multi-key content on Android platforms.
31
+
8
32
  ## [10.7.0] - 25-12-19
9
33
 
10
34
  ### Added
@@ -291,7 +291,10 @@ class PlayerEventEmitter internal constructor(
291
291
 
292
292
  fun preparePlayer(player: Player) {
293
293
  attachListeners(player)
294
+ emitPlayerReady(player)
295
+ }
294
296
 
297
+ fun emitPlayerReady(player: Player) {
295
298
  val payload = Arguments.createMap()
296
299
  payload.putMap(
297
300
  EVENT_PROP_STATE,
@@ -20,12 +20,15 @@ private const val TAG = "ReactTHEOplayerView"
20
20
  class ReactTHEOplayerView(private val reactContext: ThemedReactContext) :
21
21
  FrameLayout(reactContext), LifecycleEventListener {
22
22
 
23
- private val eventEmitter: PlayerEventEmitter =
23
+ // Set true to bypass ReactNative lifecycle, avoiding THEOplayerView being disposed by Fabric
24
+ var bypassDropInstanceOnReactLifecycle = false
25
+ val eventEmitter: PlayerEventEmitter =
24
26
  PlayerEventEmitter(reactContext.reactApplicationContext, this)
25
27
  val broadcast = EventBroadcastAdapter(this)
26
28
  var presentationManager: PresentationManager? = null
27
29
  var playerContext: ReactTHEOplayerContext? = null
28
- private var isInitialized: Boolean = false
30
+ var isInitialized: Boolean = false
31
+ private set
29
32
  private var config: PlayerConfigAdapter? = null
30
33
 
31
34
  val adsApi: AdsApiWrapper
@@ -23,7 +23,9 @@ class ReactTHEOplayerViewManager : ViewGroupManager<ReactTHEOplayerView>() {
23
23
  * {@link ViewManager} subclass.
24
24
  */
25
25
  override fun onDropViewInstance(view: ReactTHEOplayerView) {
26
- view.releasePlayer()
26
+ if (!view.bypassDropInstanceOnReactLifecycle) {
27
+ view.releasePlayer()
28
+ }
27
29
  }
28
30
 
29
31
  @ReactProp(name = PROP_CONFIG)
@@ -56,10 +56,12 @@ object AdAdapter {
56
56
  /**
57
57
  * Convert a list of native Ads to a ReactNative Ads.
58
58
  */
59
- fun fromAds(ads: List<Ad>): WritableArray {
59
+ fun fromAds(ads: List<Ad?>): WritableArray {
60
60
  val payload = Arguments.createArray()
61
61
  for (ad in ads) {
62
- payload.pushMap(fromAd(ad, true))
62
+ if (ad !== null) {
63
+ payload.pushMap(fromAd(ad, true))
64
+ }
63
65
  }
64
66
  return payload
65
67
  }
@@ -44,6 +44,7 @@ const val PROP_LICENSE_TYPE_TEMPORARY: String = "temporary"
44
44
  const val PROP_LICENSE_TYPE_PERSISTENT: String = "persistent"
45
45
  const val PROP_QUERY_PARAMETERS: String = "queryParameters"
46
46
  const val PROP_CERTIFICATE: String = "certificate"
47
+ const val PROP_MULTISESSION: String = "multiSession"
47
48
  const val PROP_KEYS: String = "keys"
48
49
  const val PROP_ID: String = "id"
49
50
  const val PROP_VALUE: String = "value"
@@ -104,6 +105,9 @@ object ContentProtectionAdapter {
104
105
  if (jsonConfig.has(PROP_INTEGRATION_PARAMETERS)) {
105
106
  integrationParameters(fromJSONObjectToMap(jsonConfig.getJSONObject(PROP_INTEGRATION_PARAMETERS)))
106
107
  }
108
+ if (jsonConfig.has(PROP_MULTISESSION)) {
109
+ multiSession(jsonConfig.getBoolean(PROP_MULTISESSION))
110
+ }
107
111
  queryParameters(fromJSONObjectToMap(jsonConfig.optJSONObject(PROP_QUERY_PARAMETERS)))
108
112
  }.build()
109
113
  }
@@ -1,6 +1,7 @@
1
1
  package com.theoplayer.player
2
2
 
3
3
  import android.os.Build
4
+ import android.util.Log
4
5
  import com.facebook.react.bridge.*
5
6
  import com.facebook.react.module.annotations.ReactModule
6
7
  import com.facebook.react.module.model.ReactModuleInfo
@@ -18,6 +19,8 @@ import com.theoplayer.audio.BackgroundAudioConfigAdapter
18
19
  import com.theoplayer.presentation.PipConfigAdapter
19
20
  import com.theoplayer.track.TextTrackStyleAdapter
20
21
  import com.theoplayer.util.ViewResolver
22
+ import com.theoplayer.util.findReactRootView
23
+ import com.theoplayer.util.getClosestParentOfType
21
24
 
22
25
  @Suppress("unused")
23
26
  @ReactModule(name = PlayerModule.NAME)
@@ -287,13 +290,11 @@ class PlayerModule(context: ReactApplicationContext) : ReactContextBaseJavaModul
287
290
  @ReactMethod(isBlockingSynchronousMethod = true)
288
291
  fun getUsableScreenDimensions(): WritableMap {
289
292
  // Pass the dimensions of the top-most View in the view hierarchy.
290
- val topView = reactApplicationContext.currentActivity?.window?.decorView?.rootView
291
- reactApplicationContext.resources.displayMetrics.also {
292
- val density = it.density
293
- return Arguments.createMap().apply {
294
- putDouble("width", (topView?.width ?: 0) / density.toDouble())
295
- putDouble("height", (topView?.height ?: 0) / density.toDouble())
296
- }
293
+ val topView = findReactRootView( reactApplicationContext)
294
+ val density = reactApplicationContext.resources.displayMetrics.density.toDouble()
295
+ return Arguments.createMap().apply {
296
+ putDouble("width", (topView?.width ?: 0) / density)
297
+ putDouble("height", (topView?.height ?: 0) / density)
297
298
  }
298
299
  }
299
300
  }
@@ -2,11 +2,11 @@ package com.theoplayer.presentation
2
2
 
3
3
  import android.util.Log
4
4
  import android.view.View
5
- import android.view.ViewGroup
6
5
  import android.view.ViewTreeObserver
7
- import androidx.core.view.children
6
+ import com.facebook.react.ReactRootView
8
7
  import com.facebook.react.views.view.ReactViewGroup
9
8
  import com.theoplayer.ReactTHEOplayerView
9
+ import com.theoplayer.util.applyOnViewTree
10
10
 
11
11
  private const val TAG = "FSLayoutObserver"
12
12
 
@@ -20,14 +20,13 @@ class FullScreenLayoutObserver {
20
20
  private var globalLayoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null
21
21
  private var attached: ReactViewGroup? = null
22
22
 
23
- fun attach(viewGroup: ReactViewGroup?) {
23
+ fun attach(viewGroup: ReactViewGroup?, root: ReactRootView) {
24
24
  if (attached != null) {
25
25
  Log.w(TAG, "A previously attached ViewGroup was not properly detached.")
26
26
  }
27
27
 
28
28
  viewGroup?.let { reactPlayerGroup ->
29
29
  globalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener {
30
- val root = getRootViewFrom(reactPlayerGroup)
31
30
  reactPlayerGroup.post {
32
31
  applyOnViewTree(reactPlayerGroup) { view ->
33
32
  if (view == reactPlayerGroup || view is ReactTHEOplayerView) {
@@ -51,24 +50,3 @@ class FullScreenLayoutObserver {
51
50
  globalLayoutListener = null
52
51
  }
53
52
  }
54
-
55
- /**
56
- * Find the view root, most likely the decorView
57
- */
58
- fun getRootViewFrom(view: View): View {
59
- var current = view
60
- while (current.parent is View) {
61
- current = current.parent as View
62
- }
63
- return current
64
- }
65
-
66
- /**
67
- * Conditionally apply an operation on each view in a hierarchy.
68
- */
69
- fun applyOnViewTree(view: View, doOp: (View) -> Unit) {
70
- doOp(view)
71
- if (view is ViewGroup) {
72
- view.children.forEach { ch -> applyOnViewTree(ch, doOp) }
73
- }
74
- }
@@ -10,13 +10,14 @@ import android.content.pm.PackageManager
10
10
  import android.os.Build
11
11
  import android.view.View
12
12
  import android.view.ViewGroup
13
- import android.view.ViewParent
14
13
  import androidx.activity.ComponentActivity
15
14
  import androidx.core.view.WindowInsetsCompat
16
15
  import androidx.core.view.WindowInsetsControllerCompat
17
16
  import androidx.core.view.children
18
17
  import androidx.lifecycle.Lifecycle
19
18
  import com.facebook.react.ReactRootView
19
+ import com.facebook.react.bridge.Arguments
20
+ import com.facebook.react.bridge.ReactContext
20
21
  import com.facebook.react.uimanager.ThemedReactContext
21
22
  import com.facebook.react.views.view.ReactViewGroup
22
23
  import com.theoplayer.BuildConfig
@@ -26,6 +27,8 @@ import com.theoplayer.ReactTHEOplayerView
26
27
  import com.theoplayer.android.api.error.ErrorCode
27
28
  import com.theoplayer.android.api.error.THEOplayerException
28
29
  import com.theoplayer.android.api.player.PresentationMode
30
+ import com.theoplayer.util.findReactRootView
31
+ import com.theoplayer.util.getClosestParentOfType
29
32
 
30
33
  const val IS_TRANSITION_INTO_PIP = "isTransitioningToPip"
31
34
  const val IS_IN_PIP_MODE = "isInPictureInPictureMode"
@@ -71,6 +74,22 @@ class PresentationManager(
71
74
  }
72
75
  }
73
76
  }
77
+
78
+ // Emit dimension updates when layout changes
79
+ // This makes sure a dimension update is sent after fullscreen immersive animations complete.
80
+ rootView?.addOnLayoutChangeListener { view, _, _, _, _, _, _, _, _ ->
81
+ val density = reactContext.resources.displayMetrics.density.toDouble()
82
+ val params = Arguments.createMap().apply {
83
+ putMap("window", Arguments.createMap().apply {
84
+ putDouble("width", (view.width) / density)
85
+ putDouble("height", (view.height) / density)
86
+ })
87
+ }
88
+ reactContext
89
+ .getJSModule(ReactContext.RCTDeviceEventEmitter::class.java)
90
+ .emit("didUpdateDimensions", params)
91
+ }
92
+
74
93
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
75
94
  supportsPip =
76
95
  reactContext.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
@@ -269,12 +288,7 @@ class PresentationManager(
269
288
  get() = viewCtx.playerView.getClosestParentOfType()
270
289
 
271
290
  private val rootView: ReactRootView?
272
- get() {
273
- val activity = reactContext.currentActivity ?: return null
274
- // Try to search in parents and as a fallback option from root to bottom using depth-first order
275
- return reactPlayerGroup?.getClosestParentOfType()
276
- ?: (activity.window.decorView.rootView as? ViewGroup)?.getClosestParentOfType(false)
277
- }
291
+ get() = findReactRootView( reactContext, reactPlayerGroup)
278
292
 
279
293
  private fun reparentPlayerToRoot() {
280
294
  reactPlayerGroup?.let { playerGroup ->
@@ -283,10 +297,12 @@ class PresentationManager(
283
297
 
284
298
  // Re-parent the playerViewGroup to the root node
285
299
  parent.removeView(playerGroup)
286
- rootView?.addView(playerGroup)
300
+ rootView?.let { root ->
301
+ root.addView(playerGroup)
287
302
 
288
- // Attach an observer that overrides the react-native lay-out and forces fullscreen.
289
- fullScreenLayoutObserver.attach(playerGroup)
303
+ // Attach an observer that overrides the react-native lay-out and forces fullscreen.
304
+ fullScreenLayoutObserver.attach(playerGroup, root)
305
+ }
290
306
  }
291
307
  }
292
308
  }
@@ -335,35 +351,6 @@ class PresentationManager(
335
351
  }
336
352
  }
337
353
 
338
- inline fun <reified T : View> ViewGroup.getClosestParentOfType(upward: Boolean = true): T? {
339
- if (upward) {
340
- // Search in the parent views of `this` view up to the root
341
- var parent: ViewParent? = parent
342
- while (parent != null && parent !is T) {
343
- parent = parent.parent
344
- }
345
- return parent as? T
346
- } else {
347
- // Search in the children collection.
348
- val viewStack = ArrayDeque(children.toList())
349
- // Use Stack/LIFO instead of recursion
350
- while (viewStack.isNotEmpty()) {
351
- when (val view = viewStack.removeAt(0)) {
352
- is T -> {
353
- return view
354
- }
355
-
356
- is ViewGroup -> {
357
- // Filling LIFO with all children of the ViewGroup: depth-first order
358
- viewStack.addAll(0, view.children.toList())
359
- }
360
- }
361
- }
362
- // Found nothing
363
- return null
364
- }
365
- }
366
-
367
354
  private class PlayerGroupRestoreOptions {
368
355
  var childIndex: Int? = null
369
356
  var parentNode: ViewGroup? = null
@@ -68,6 +68,7 @@ private const val PROP_CUSTOM_ASSET_KEY = "customAssetKey"
68
68
  private const val PROP_OVERRIDE_LAYOUT = "overrideLayout"
69
69
  private const val PROP_NETWORK_CODE = "networkCode"
70
70
  private const val PROP_USE_ID3 = "useId3"
71
+ private const val PROP_USE_EMSG = "useEMSG"
71
72
  private const val PROP_RETRIEVE_POD_ID_URI = "retrievePodIdURI"
72
73
  private const val PROP_INITIALIZATION_DELAY = "initializationDelay"
73
74
  private const val PROP_SSE_ENDPOINT = "sseEndpoint"
@@ -360,6 +361,7 @@ class SourceAdapter {
360
361
  networkCode = jsonAdDescription.optString(PROP_NETWORK_CODE).takeIf { it.isNotEmpty() },
361
362
  overrideLayout = parseOverrideLayout(jsonAdDescription.optString(PROP_OVERRIDE_LAYOUT)),
362
363
  useId3 = jsonAdDescription.optBoolean(PROP_USE_ID3, false),
364
+ useEMSG = jsonAdDescription.optBoolean(PROP_USE_EMSG, false),
363
365
  retrievePodIdURI = jsonAdDescription.optString(PROP_RETRIEVE_POD_ID_URI).takeIf { it.isNotEmpty() },
364
366
  initializationDelay = jsonAdDescription.optDouble(PROP_INITIALIZATION_DELAY).takeIf { it.isFinite() },
365
367
  sseEndpoint = jsonAdDescription.optString(PROP_SSE_ENDPOINT).takeIf { it.isNotEmpty() },
@@ -0,0 +1,67 @@
1
+ package com.theoplayer.util
2
+
3
+ import android.view.View
4
+ import android.view.ViewGroup
5
+ import android.view.ViewParent
6
+ import androidx.core.view.children
7
+ import com.facebook.react.ReactRootView
8
+ import com.facebook.react.bridge.ReactContext
9
+ import com.facebook.react.views.view.ReactViewGroup
10
+ import kotlin.sequences.forEach
11
+
12
+ /**
13
+ * Conditionally apply an operation on each view in a hierarchy.
14
+ */
15
+ fun applyOnViewTree(view: View, doOp: (View) -> Unit) {
16
+ doOp(view)
17
+ if (view is ViewGroup) {
18
+ view.children.forEach { ch -> applyOnViewTree(ch, doOp) }
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Finds the closest ReactRootView either by searching upwards from the given ReactViewGroup
24
+ * or, if that fails, by searching downwards from the root view of the current activity.
25
+ */
26
+ fun findReactRootView(reactContext: ReactContext, reactPlayerGroup: ReactViewGroup? = null): ReactRootView? {
27
+ val activity = reactContext.currentActivity ?: return null
28
+ // Try to search in parents and as a fallback option from root to bottom using depth-first order
29
+ return reactPlayerGroup?.getClosestParentOfType()
30
+ ?: (activity.window.decorView.rootView as? ViewGroup)?.getClosestParentOfType(false)
31
+ }
32
+
33
+ /**
34
+ * Finds the closest parent view of the specified type [T].
35
+ *
36
+ * @param upward If true, searches upwards in the view hierarchy (towards the root).
37
+ * If false, searches downwards in the view hierarchy (towards the children).
38
+ * @return The closest parent view of type [T], or null if none is found.
39
+ */
40
+ inline fun <reified T : View> ViewGroup.getClosestParentOfType(upward: Boolean = true): T? {
41
+ if (upward) {
42
+ // Search in the parent views of `this` view up to the root
43
+ var parent: ViewParent? = parent
44
+ while (parent != null && parent !is T) {
45
+ parent = parent.parent
46
+ }
47
+ return parent as? T
48
+ } else {
49
+ // Search in the children collection.
50
+ val viewStack = ArrayDeque(children.toList())
51
+ // Use Stack/LIFO instead of recursion
52
+ while (viewStack.isNotEmpty()) {
53
+ when (val view = viewStack.removeAt(0)) {
54
+ is T -> {
55
+ return view
56
+ }
57
+
58
+ is ViewGroup -> {
59
+ // Filling LIFO with all children of the ViewGroup: depth-first order
60
+ viewStack.addAll(0, view.children.toList())
61
+ }
62
+ }
63
+ }
64
+ // Found nothing
65
+ return null
66
+ }
67
+ }
@@ -19,7 +19,7 @@ let DEBUG_CONTENT_PROTECTION_API = DEBUG && false
19
19
  let DEBUG_VIEW = DEBUG && false
20
20
 
21
21
  // Debug flag to monitor correct SourceDescription buildup
22
- let DEBUG_SOURCE_DESCRIPTION_BUIDER = DEBUG && false
22
+ let DEBUG_SOURCE_DESCRIPTION_BUILDER = DEBUG && false
23
23
 
24
24
  // Debug flag to monitor ads API usage
25
25
  let DEBUG_ADS_API = DEBUG && false
@@ -51,5 +51,12 @@ let DEBUG_THEOLIVE_API = DEBUG && false
51
51
  // Debug flag to monitor THEOAds API usage
52
52
  let DEBUG_THEOADS_API = DEBUG && false
53
53
 
54
- // Debug flag to monitor AudioSession interruptions (e.g. phone cll)
54
+ // Debug flag to monitor AudioSession interruptions (e.g. phone call)
55
55
  let DEBUG_INTERRUPTIONS = DEBUG && false
56
+
57
+ // Debug flag to monitor Presentationmode changes
58
+ let DEBUG_PRESENTATIONMODES = DEBUG && false
59
+
60
+ // Debug flag to monitor App state changes
61
+ let DEBUG_APPSTATE = DEBUG && false
62
+
@@ -7,7 +7,7 @@ public class THEOplayerRCTMainEventHandler {
7
7
  // MARK: Members
8
8
  private weak var player: THEOplayer?
9
9
  private weak var presentationModeContext: THEOplayerRCTPresentationModeContext?
10
- private var loadedMetadataAndChapterTracksInfo: [[String:Any]] = []
10
+ private(set) var loadedMetadataAndChapterTracksInfo: [[String:Any]] = []
11
11
 
12
12
  // MARK: Events
13
13
  var onNativePlay: RCTDirectEventBlock?
@@ -274,9 +274,9 @@ public class THEOplayerRCTMainEventHandler {
274
274
  self.loadedMetadataListener = player.addEventListener(type: PlayerEventTypes.LOADED_META_DATA) { [weak self, weak player] event in
275
275
  if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received LOADED_META_DATA event from THEOplayer") }
276
276
  if let wplayer = player,
277
- let welf = self,
278
- let forwardedLoadedMetadataEvent = self?.onNativeLoadedMetadata {
279
- let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackMetadata(player: wplayer, metadataTracksInfo: welf.loadedMetadataAndChapterTracksInfo)
277
+ let self,
278
+ let forwardedLoadedMetadataEvent = self.onNativeLoadedMetadata {
279
+ let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackInfo(player: wplayer, metadataTracksInfo: self.loadedMetadataAndChapterTracksInfo)
280
280
  forwardedLoadedMetadataEvent(metadata)
281
281
  }
282
282
  }
@@ -0,0 +1,100 @@
1
+ import Foundation
2
+ import THEOplayerSDK
3
+
4
+ let STATE_SOURCE = "source"
5
+ let STATE_CURRENT_TIME = "currentTime"
6
+ let STATE_CURRENT_PROGRAM_DATE_TIME = "currentProgramDateTime"
7
+ let STATE_PAUSED = "paused"
8
+ let STATE_PLAYBACK_RATE = "playbackRate"
9
+ let STATE_DURATION = "duration"
10
+ let STATE_VOLUME = "volume"
11
+ let STATE_MUTED = "muted"
12
+ let STATE_SEEKABLE = "seekable"
13
+ let STATE_BUFFERED = "buffered"
14
+ let STATE_START = "start"
15
+ let STATE_END = "end"
16
+
17
+ class THEOplayerRCTPlayerStateBuilder {
18
+ private var playerState: [String: Any] = [:]
19
+
20
+ func build() -> [String: Any] {
21
+ return playerState
22
+ }
23
+
24
+ func source(_ sourceDescription: SourceDescription?) -> Self {
25
+ if let sourceDesc = sourceDescription {
26
+ playerState[STATE_SOURCE] = THEOplayerRCTSourceDescriptionAggregator.aggregateSourceDescription(sourceDescription: sourceDesc)
27
+ }
28
+ return self
29
+ }
30
+
31
+ func currentTime(_ timeInSec: Double) -> Self {
32
+ playerState[STATE_CURRENT_TIME] = timeInSec * 1000 // sec -> msec
33
+ return self
34
+ }
35
+
36
+ func currentProgramDateTime(_ programDataTime: Date?) -> Self {
37
+ if let date = programDataTime {
38
+ playerState[STATE_CURRENT_PROGRAM_DATE_TIME] = date.timeIntervalSince1970 * 1000 // sec -> msec
39
+ }
40
+ return self
41
+ }
42
+
43
+ func paused(_ paused: Bool) -> Self {
44
+ playerState[STATE_PAUSED] = paused
45
+ return self
46
+ }
47
+
48
+ func playbackRate(_ rate: Double) -> Self {
49
+ playerState[STATE_PLAYBACK_RATE] = rate
50
+ return self
51
+ }
52
+
53
+ func duration(_ durationInSec: Double?) -> Self {
54
+ if let durationInSec = durationInSec {
55
+ playerState[PROP_DURATION] = THEOplayerRCTTypeUtils.encodeInfNan(durationInSec * 1000) // sec -> msec
56
+ }
57
+ return self
58
+ }
59
+
60
+ func volume(_ volume: Float) -> Self {
61
+ playerState[STATE_VOLUME] = Double(volume)
62
+ return self
63
+ }
64
+
65
+ func muted(_ muted: Bool) -> Self {
66
+ playerState[STATE_MUTED] = muted
67
+ return self
68
+ }
69
+
70
+ func seekable(_ seekableRanges: [THEOplayerSDK.TimeRange]?) -> Self {
71
+ playerState[STATE_SEEKABLE] = fromTimeRanges(seekableRanges)
72
+ return self
73
+ }
74
+
75
+ func buffered(_ bufferedRanges: [THEOplayerSDK.TimeRange]?) -> Self {
76
+ playerState[STATE_BUFFERED] = fromTimeRanges(bufferedRanges)
77
+ return self
78
+ }
79
+
80
+ func trackInfo(_ trackInfo: [String: Any]) -> Self {
81
+ playerState.merge(trackInfo) { (current, _) in current }
82
+ return self
83
+ }
84
+
85
+ private func fromTimeRanges(_ ranges: [THEOplayerSDK.TimeRange]?) -> [[String: Double]] {
86
+ guard let inRanges = ranges else { return [] }
87
+
88
+ var outRanges: [[String:Double]] = []
89
+ inRanges.forEach({ timeRange in
90
+ outRanges.append(
91
+ [
92
+ STATE_START: timeRange.start * 1000, // sec -> msec
93
+ STATE_END: timeRange.end * 1000 // sec -> msec
94
+ ]
95
+ )
96
+ })
97
+ return outRanges
98
+ }
99
+ }
100
+
@@ -4,24 +4,30 @@ import Foundation
4
4
  import THEOplayerSDK
5
5
  import UIKit
6
6
 
7
- #if os(iOS)
8
7
  class THEOplayerRCTSourceDescriptionAggregator {
9
- class func aggregateCacheTaskSourceDescription(sourceDescription: SourceDescription, cachingTaskId: String) -> [String:Any]? {
8
+ class func aggregateSourceDescription(sourceDescription: SourceDescription) -> [String:Any]? {
10
9
  do {
11
10
  let jsonEncoder = JSONEncoder()
12
11
  let data = try jsonEncoder.encode(sourceDescription)
13
12
  if let result = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] {
14
- let srcDescription = THEOplayerRCTSourceDescriptionAggregator.sanitiseSourceDescriptionMetadata(input: result)
15
- let extendedSrcDescription = THEOplayerRCTSourceDescriptionAggregator.addCachingTaskIdToMetadata(input: srcDescription, cachingTaskId: cachingTaskId)
16
- return extendedSrcDescription
13
+ return THEOplayerRCTSourceDescriptionAggregator.sanitiseSourceDescriptionMetadata(input: result)
17
14
  }
18
15
  } catch {
19
- if DEBUG { PrintUtils.printLog(logText: "[NATIVE] Could not aggregate sourceDescription for caching task: \(error.localizedDescription)")}
16
+ if DEBUG { PrintUtils.printLog(logText: "[NATIVE] Could not aggregate sourceDescription: \(error.localizedDescription)")}
20
17
  return nil
21
18
  }
22
19
  return nil
23
20
  }
24
21
 
22
+ class func aggregateCacheTaskSourceDescription(sourceDescription: SourceDescription, cachingTaskId: String) -> [String:Any]? {
23
+ if let result = THEOplayerRCTSourceDescriptionAggregator.aggregateSourceDescription(sourceDescription: sourceDescription) {
24
+ let srcDescription = THEOplayerRCTSourceDescriptionAggregator.sanitiseSourceDescriptionMetadata(input: result)
25
+ let extendedSrcDescription = THEOplayerRCTSourceDescriptionAggregator.addCachingTaskIdToMetadata(input: srcDescription, cachingTaskId: cachingTaskId)
26
+ return extendedSrcDescription
27
+ }
28
+ return nil
29
+ }
30
+
25
31
  private class func sanitiseSourceDescriptionMetadata(input: [String:Any]) -> [String:Any] {
26
32
  var output: [String:Any] = input
27
33
  if let metadata = output[SD_PROP_METADATA] as? [String:Any],
@@ -53,4 +59,3 @@ class THEOplayerRCTSourceDescriptionAggregator {
53
59
  return output
54
60
  }
55
61
  }
56
- #endif
@@ -111,7 +111,7 @@ class THEOplayerRCTSourceDescriptionBuilder {
111
111
  if let typedSource = THEOplayerRCTSourceDescriptionBuilder.buildTypedSource(typedSourceData) {
112
112
  typedSources.append(typedSource)
113
113
  } else {
114
- if DEBUG_SOURCE_DESCRIPTION_BUIDER {
114
+ if DEBUG_SOURCE_DESCRIPTION_BUILDER {
115
115
  PrintUtils.printLog(logText: "[NATIVE] Could not create THEOplayer TypedSource from sourceData array")
116
116
  }
117
117
  return (nil, nil)
@@ -123,7 +123,7 @@ class THEOplayerRCTSourceDescriptionBuilder {
123
123
  if let typedSource = THEOplayerRCTSourceDescriptionBuilder.buildTypedSource(typedSourceData) {
124
124
  typedSources.append(typedSource)
125
125
  } else {
126
- if DEBUG_SOURCE_DESCRIPTION_BUIDER {
126
+ if DEBUG_SOURCE_DESCRIPTION_BUILDER {
127
127
  PrintUtils.printLog(logText: "[NATIVE] Could not create THEOplayer TypedSource from sourceData")
128
128
  }
129
129
  return (nil, nil)
@@ -147,7 +147,7 @@ class THEOplayerRCTSourceDescriptionBuilder {
147
147
  textTrackDescriptions?.append(textTrackDescription)
148
148
  }
149
149
  } else {
150
- if DEBUG_SOURCE_DESCRIPTION_BUIDER {
150
+ if DEBUG_SOURCE_DESCRIPTION_BUILDER {
151
151
  PrintUtils.printLog(logText: "[NATIVE] Could not create THEOplayer TextTrackDescription from textTrackData array")
152
152
  }
153
153
  return (nil, nil)
@@ -220,7 +220,7 @@ class THEOplayerRCTSourceDescriptionBuilder {
220
220
  return daiSource
221
221
  }
222
222
 
223
- if DEBUG_SOURCE_DESCRIPTION_BUIDER {
223
+ if DEBUG_SOURCE_DESCRIPTION_BUILDER {
224
224
  PrintUtils.printLog(logText: "[NATIVE] THEOplayer TypedSource requires 'src' property in 'sources' description")
225
225
  }
226
226
  return nil
@@ -392,9 +392,11 @@ class THEOplayerRCTSourceDescriptionBuilder {
392
392
 
393
393
  // global query parameters
394
394
  let queryParameters = contentProtectionData[SD_PROP_QUERY_PARAMETERS] as? [String:String]
395
+ let integrationParameters = contentProtectionData[SD_PROP_INTEGRATION_PARAMETERS] as? [String:Any] ?? [:]
395
396
 
396
397
  return MultiplatformDRMConfiguration(
397
398
  customIntegrationId: customIntegrationId,
399
+ integrationParameters: integrationParameters,
398
400
  keySystemConfigurations: KeySystemConfigurationCollection(fairplay: fairplayKeySystem, widevine: widevineKeySystem),
399
401
  queryParameters: queryParameters
400
402
  )
@@ -38,7 +38,7 @@ let PROP_END_ON_NEXT: String = "endOnNext"
38
38
 
39
39
  class THEOplayerRCTTrackMetadataAggregator {
40
40
 
41
- class func aggregateTrackMetadata(player: THEOplayer, metadataTracksInfo: [[String:Any]]) -> [String:Any] {
41
+ class func aggregateTrackInfo(player: THEOplayer, metadataTracksInfo: [[String:Any]]) -> [String:Any] {
42
42
  let textTracks: TextTrackList = player.textTracks
43
43
  let audioTracks: AudioTrackList = player.audioTracks
44
44
  let videoTracks: VideoTrackList = player.videoTracks