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.
- package/CHANGELOG.md +24 -0
- package/android/src/main/java/com/theoplayer/PlayerEventEmitter.kt +3 -0
- package/android/src/main/java/com/theoplayer/ReactTHEOplayerView.kt +5 -2
- package/android/src/main/java/com/theoplayer/ReactTHEOplayerViewManager.kt +3 -1
- package/android/src/main/java/com/theoplayer/ads/AdAdapter.kt +4 -2
- package/android/src/main/java/com/theoplayer/drm/ContentProtectionAdapter.kt +4 -0
- package/android/src/main/java/com/theoplayer/player/PlayerModule.kt +8 -7
- package/android/src/main/java/com/theoplayer/presentation/FullscreenLayoutObserver.kt +3 -25
- package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +26 -39
- package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +2 -0
- package/android/src/main/java/com/theoplayer/util/ViewUtils.kt +67 -0
- package/ios/THEOplayerRCTDebug.swift +9 -2
- package/ios/THEOplayerRCTMainEventHandler.swift +4 -4
- package/ios/THEOplayerRCTPlayerStateBuilder.swift +100 -0
- package/ios/THEOplayerRCTSourceDescriptionAggregator.swift +12 -7
- package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +6 -4
- package/ios/THEOplayerRCTTrackMetadataAggregator.swift +1 -1
- package/ios/THEOplayerRCTView+AppState.swift +30 -1
- package/ios/THEOplayerRCTView.swift +37 -9
- package/ios/ads/THEOplayerRCTSourceDescriptionBuilder+Ads.swift +5 -5
- package/ios/backgroundAudio/THEOplayerRCTNowPlayingManager.swift +29 -54
- package/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift +36 -18
- package/lib/commonjs/manifest.json +1 -1
- package/lib/module/manifest.json +1 -1
- package/lib/typescript/api/source/drm/DRMConfiguration.d.ts +7 -0
- package/lib/typescript/api/source/drm/DRMConfiguration.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/api/source/drm/DRMConfiguration.ts +8 -0
- 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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
59
|
+
fun fromAds(ads: List<Ad?>): WritableArray {
|
|
60
60
|
val payload = Arguments.createArray()
|
|
61
61
|
for (ad in ads) {
|
|
62
|
-
|
|
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
|
|
291
|
-
reactApplicationContext.resources.displayMetrics.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
|
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?.
|
|
300
|
+
rootView?.let { root ->
|
|
301
|
+
root.addView(playerGroup)
|
|
287
302
|
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
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
|
|
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
|
|
278
|
-
let forwardedLoadedMetadataEvent = self
|
|
279
|
-
let metadata = THEOplayerRCTTrackMetadataAggregator.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|