react-native-theoplayer 10.7.1 → 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 +18 -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/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/package.json +5 -5
- package/src/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ 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
|
+
|
|
8
26
|
## [10.7.1] - 26-01-06
|
|
9
27
|
|
|
10
28
|
### 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
|
}
|
|
@@ -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
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// THEOplayerRCTView+AppState.swift
|
|
2
2
|
|
|
3
3
|
import Foundation
|
|
4
|
+
import THEOplayerSDK
|
|
5
|
+
import MediaPlayer
|
|
4
6
|
|
|
5
7
|
extension THEOplayerRCTView {
|
|
6
8
|
func setupAppStateObservers() {
|
|
@@ -17,6 +19,13 @@ extension THEOplayerRCTView {
|
|
|
17
19
|
name: UIApplication.willEnterForegroundNotification,
|
|
18
20
|
object: nil
|
|
19
21
|
)
|
|
22
|
+
|
|
23
|
+
NotificationCenter.default.addObserver(
|
|
24
|
+
self,
|
|
25
|
+
selector: #selector(appWillTerminate),
|
|
26
|
+
name: UIApplication.willTerminateNotification,
|
|
27
|
+
object: nil
|
|
28
|
+
)
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
func clearAppStateObservers() {
|
|
@@ -24,10 +33,30 @@ extension THEOplayerRCTView {
|
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
@objc private func applicationDidEnterBackground() {
|
|
36
|
+
// When player was fullscreen:
|
|
37
|
+
// - it either went to PiP before applicationDidEnterBackground was called. PiP always starts from fullscreen (startsAutomaticallyFromInline applies only to inline setup)
|
|
38
|
+
// - or we push the player back to inline to prevent view index issues on app termination (e.g. when the app is killed from the app switcher while in fullscreen)
|
|
39
|
+
if self.presentationModeManager.presentationMode == .fullscreen {
|
|
40
|
+
if DEBUG_APPSTATE { PrintUtils.printLog(logText: "[NATIVE] presentationMode is fullscreen while backgrounding => back to inline.") }
|
|
41
|
+
self.setPresentationMode(newPresentationMode: PresentationMode.inline)
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
self.isApplicationInBackground = true
|
|
28
45
|
}
|
|
29
46
|
|
|
30
|
-
@objc
|
|
47
|
+
@objc
|
|
48
|
+
private func applicationWillEnterForeground() {
|
|
31
49
|
self.isApplicationInBackground = false
|
|
32
50
|
}
|
|
51
|
+
|
|
52
|
+
@objc
|
|
53
|
+
private func appWillTerminate() {
|
|
54
|
+
if DEBUG_APPSTATE { PrintUtils.printLog(logText: "[NATIVE] App will terminate notification received.") }
|
|
55
|
+
|
|
56
|
+
// Clear any now playing info
|
|
57
|
+
if DEBUG_APPSTATE { PrintUtils.printLog(logText: "[NATIVE] Clearing nowPlayingInfo on app termination.") }
|
|
58
|
+
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
|
59
|
+
|
|
60
|
+
if DEBUG_APPSTATE { PrintUtils.printLog(logText: "[NATIVE] Player is prepared for app termination.") }
|
|
61
|
+
}
|
|
33
62
|
}
|
|
@@ -121,10 +121,11 @@ public class THEOplayerRCTView: UIView {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
func willUnmount() {
|
|
124
|
+
if DEBUG_PRESENTATIONMODES {PrintUtils.printLog(logText: "[NATIVE] willUnmount with presentationMode: \(self.presentationModeManager.presentationMode._rawValue)")}
|
|
125
|
+
|
|
124
126
|
// before destruction, make sure the view reparenting is reset when player was put in fullscreen
|
|
125
127
|
if self.presentationModeManager.presentationMode == .fullscreen {
|
|
126
|
-
if
|
|
127
|
-
|
|
128
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] presentationMode is fullscreen => back to inline for player unmount.") }
|
|
128
129
|
self.setPresentationMode(newPresentationMode: PresentationMode.inline)
|
|
129
130
|
}
|
|
130
131
|
}
|
|
@@ -209,16 +210,43 @@ public class THEOplayerRCTView: UIView {
|
|
|
209
210
|
}
|
|
210
211
|
}
|
|
211
212
|
|
|
212
|
-
|
|
213
|
+
public func notifyNativePlayerReady() {
|
|
213
214
|
DispatchQueue.main.async {
|
|
214
215
|
let versionString = THEOplayer.version
|
|
215
216
|
if let forwardedNativeReady = self.onNativePlayerReady {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
217
|
+
var payload: [String: Any] = [:]
|
|
218
|
+
|
|
219
|
+
// pass initial player state
|
|
220
|
+
if let player = self.player {
|
|
221
|
+
// collect stored track metadata
|
|
222
|
+
let trackInfo = THEOplayerRCTTrackMetadataAggregator.aggregateTrackInfo(
|
|
223
|
+
player: player,
|
|
224
|
+
metadataTracksInfo: self.mainEventHandler.loadedMetadataAndChapterTracksInfo
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
// build state
|
|
228
|
+
payload["state"] = THEOplayerRCTPlayerStateBuilder()
|
|
229
|
+
.source(player.source)
|
|
230
|
+
.currentTime(player.currentTime)
|
|
231
|
+
.currentProgramDateTime(player.currentProgramDateTime)
|
|
232
|
+
.paused(player.paused)
|
|
233
|
+
.playbackRate(player.playbackRate)
|
|
234
|
+
.duration(player.duration)
|
|
235
|
+
.volume(player.volume)
|
|
236
|
+
.muted(player.muted)
|
|
237
|
+
.seekable(player.seekable)
|
|
238
|
+
.buffered(player.buffered)
|
|
239
|
+
.trackInfo(trackInfo)
|
|
240
|
+
.build()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// pass version onfo
|
|
244
|
+
payload["version"] = [
|
|
245
|
+
"version": versionString,
|
|
246
|
+
"playerSuiteVersion": versionString
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
forwardedNativeReady(payload)
|
|
222
250
|
}
|
|
223
251
|
}
|
|
224
252
|
}
|
|
@@ -23,7 +23,7 @@ extension THEOplayerRCTSourceDescriptionBuilder {
|
|
|
23
23
|
if let adDescription = THEOplayerRCTSourceDescriptionBuilder.buildSingleAdDescription(adsData) {
|
|
24
24
|
adsDescriptions?.append(adDescription)
|
|
25
25
|
} else {
|
|
26
|
-
if
|
|
26
|
+
if DEBUG_SOURCE_DESCRIPTION_BUILDER {
|
|
27
27
|
PrintUtils.printLog(logText: "[NATIVE] Could not create THEOplayer GoogleImaAdDescription from adsData array")
|
|
28
28
|
}
|
|
29
29
|
return nil
|
|
@@ -35,7 +35,7 @@ extension THEOplayerRCTSourceDescriptionBuilder {
|
|
|
35
35
|
if let adDescription = THEOplayerRCTSourceDescriptionBuilder.buildSingleAdDescription(adsData) {
|
|
36
36
|
adsDescriptions?.append(adDescription)
|
|
37
37
|
} else {
|
|
38
|
-
if
|
|
38
|
+
if DEBUG_SOURCE_DESCRIPTION_BUILDER {
|
|
39
39
|
PrintUtils.printLog(logText: "[NATIVE] Could not create THEOplayer GoogleImaAdDescription from adsData")
|
|
40
40
|
}
|
|
41
41
|
return nil
|
|
@@ -58,7 +58,7 @@ extension THEOplayerRCTSourceDescriptionBuilder {
|
|
|
58
58
|
case "theoads":
|
|
59
59
|
return THEOplayerRCTSourceDescriptionBuilder.buildSingleTHEOadsDescription(adsData)
|
|
60
60
|
default:
|
|
61
|
-
if
|
|
61
|
+
if DEBUG_SOURCE_DESCRIPTION_BUILDER { PrintUtils.printLog(logText: "[NATIVE] We currently require and only support the 'google-ima' or 'sgai' integration in the 'ads' description.") }
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
return nil
|
|
@@ -81,7 +81,7 @@ extension THEOplayerRCTSourceDescriptionBuilder {
|
|
|
81
81
|
if let src = srcString {
|
|
82
82
|
return GoogleImaAdDescription(src: src, timeOffset: timeOffset)
|
|
83
83
|
} else {
|
|
84
|
-
if
|
|
84
|
+
if DEBUG_SOURCE_DESCRIPTION_BUILDER { PrintUtils.printLog(logText: "[NATIVE] AdDescription requires 'src' property in 'ads' description.") }
|
|
85
85
|
}
|
|
86
86
|
#endif
|
|
87
87
|
return nil
|
|
@@ -120,7 +120,7 @@ extension THEOplayerRCTSourceDescriptionBuilder {
|
|
|
120
120
|
adTagParameters: adTagParameters)
|
|
121
121
|
}
|
|
122
122
|
default:
|
|
123
|
-
if
|
|
123
|
+
if DEBUG_SOURCE_DESCRIPTION_BUILDER {
|
|
124
124
|
PrintUtils.printLog(logText: "[NATIVE] THEOplayer ssai 'availabilityType' must be 'live' or 'vod'")
|
|
125
125
|
}
|
|
126
126
|
return nil
|
|
@@ -16,7 +16,6 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
16
16
|
private var rateChangeListener: EventListener?
|
|
17
17
|
private var seekedListener: EventListener?
|
|
18
18
|
private var sourceChangeListener: EventListener?
|
|
19
|
-
private var appTerminationObserver: Any?
|
|
20
19
|
|
|
21
20
|
// MARK: - destruction
|
|
22
21
|
func destroy() {
|
|
@@ -33,7 +32,7 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
33
32
|
self.nowPlayingInfo = [:]
|
|
34
33
|
self.clearNowPlayingOnInfoCenter()
|
|
35
34
|
|
|
36
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
35
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] Destroy, nowPlayingInfo cleared on infoCenter.") }
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
// MARK: - player setup / breakdown
|
|
@@ -47,16 +46,16 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
47
46
|
func printCurrentNowPlayingInfo() {
|
|
48
47
|
Task { @MainActor [weak self] in
|
|
49
48
|
if let info = MPNowPlayingInfoCenter.default().nowPlayingInfo {
|
|
50
|
-
PrintUtils.printLog(logText: "[NATIVE
|
|
49
|
+
PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO CURRENT INFO] MPNowPlayingInfoCenter.default().nowPlayingInfo = ")
|
|
51
50
|
info.forEach { (key: String, value: Any) in
|
|
52
|
-
PrintUtils.printLog(logText: "[NATIVE
|
|
51
|
+
PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO CURRENT INFO] -> \(key): \(value)")
|
|
53
52
|
}
|
|
54
53
|
if let player = self?.player {
|
|
55
|
-
PrintUtils.printLog(logText: "[NATIVE
|
|
56
|
-
PrintUtils.printLog(logText: "[NATIVE
|
|
57
|
-
PrintUtils.printLog(logText: "[NATIVE
|
|
58
|
-
PrintUtils.printLog(logText: "[NATIVE
|
|
59
|
-
PrintUtils.printLog(logText: "[NATIVE
|
|
54
|
+
PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO CURRENT INFO] playerInfo = ")
|
|
55
|
+
PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO CURRENT INFO] -> currentTime: \(player.currentTime)")
|
|
56
|
+
PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO CURRENT INFO] -> playbackRate: \(player.playbackRate)")
|
|
57
|
+
PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO CURRENT INFO] -> paused: \(player.paused)")
|
|
58
|
+
PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO CURRENT INFO] -> duration: \(player.duration ?? -1)")
|
|
60
59
|
}
|
|
61
60
|
}
|
|
62
61
|
}
|
|
@@ -95,7 +94,7 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
95
94
|
if !nowPlayingInfo.isEmpty {
|
|
96
95
|
Task { @MainActor in
|
|
97
96
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
|
98
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
97
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] nowPlayingInfo processed to infoCenter.") }
|
|
99
98
|
|
|
100
99
|
if DEBUG_NOWINFO {
|
|
101
100
|
self.printCurrentNowPlayingInfo()
|
|
@@ -109,7 +108,7 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
109
108
|
private func clearNowPlayingOnInfoCenter() {
|
|
110
109
|
Task { @MainActor in
|
|
111
110
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
|
112
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
111
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] clearing nowPlayingInfo (to nil) on infoCenter.") }
|
|
113
112
|
|
|
114
113
|
if DEBUG_NOWINFO {
|
|
115
114
|
self.printCurrentNowPlayingInfo()
|
|
@@ -132,48 +131,48 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
132
131
|
private func updateTitle(_ metadataTitle: String?) {
|
|
133
132
|
if let title = metadataTitle {
|
|
134
133
|
self.nowPlayingInfo[MPMediaItemPropertyTitle] = title
|
|
135
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
134
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] title [\(title)] stored in nowPlayingInfo.") }
|
|
136
135
|
}
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
private func updateArtist(_ metadataArtist: String?) {
|
|
140
139
|
if let artist = metadataArtist {
|
|
141
140
|
self.nowPlayingInfo[MPMediaItemPropertyArtist] = artist
|
|
142
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
141
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] artist [\(artist)] stored in nowPlayingInfo.") }
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
144
|
|
|
146
145
|
private func updateAlbum(_ metadataAlbum: String?) {
|
|
147
146
|
if let album = metadataAlbum {
|
|
148
147
|
self.nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = album
|
|
149
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
148
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] album [\(album)] stored in nowPlayingInfo.") }
|
|
150
149
|
}
|
|
151
150
|
}
|
|
152
151
|
|
|
153
152
|
private func updateSubtitle(_ metadataSubtitle: String?) {
|
|
154
153
|
if let subtitle = metadataSubtitle {
|
|
155
154
|
self.nowPlayingInfo[MPMediaItemPropertyArtist] = subtitle
|
|
156
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
155
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] subtitle [\(subtitle)] stored in nowPlayingInfo.") }
|
|
157
156
|
}
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
private func updateServiceIdentifier(_ serviceId: String?) {
|
|
161
160
|
if let serviceId = serviceId {
|
|
162
161
|
self.nowPlayingInfo[MPNowPlayingInfoPropertyServiceIdentifier] = serviceId
|
|
163
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
162
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] serviceId [\(serviceId)] stored in nowPlayingInfo.") }
|
|
164
163
|
}
|
|
165
164
|
}
|
|
166
165
|
|
|
167
166
|
private func updateContentIdentifier(_ contentId: String?) {
|
|
168
167
|
if let contentId = contentId {
|
|
169
168
|
self.nowPlayingInfo[MPNowPlayingInfoPropertyExternalContentIdentifier] = contentId
|
|
170
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
169
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] contentId [\(contentId)] stored in nowPlayingInfo.") }
|
|
171
170
|
}
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
private func updateMediaType() {
|
|
175
174
|
self.nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = NSNumber(value: 2)
|
|
176
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
175
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] mediaType [hardcoded 2, for video] stored in nowPlayingInfo.") }
|
|
177
176
|
}
|
|
178
177
|
|
|
179
178
|
private func updateArtWork(_ urlString: String?, completion: (() -> Void)?) {
|
|
@@ -185,9 +184,9 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
185
184
|
self?.nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: displayIcon.size) { size in
|
|
186
185
|
return displayIcon
|
|
187
186
|
}
|
|
188
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
187
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] Artwork stored in nowPlayingInfo.") }
|
|
189
188
|
} else {
|
|
190
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
189
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] Failed to store artwork in nowPlayingInfo.") }
|
|
191
190
|
}
|
|
192
191
|
completion?()
|
|
193
192
|
}
|
|
@@ -199,49 +198,34 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
199
198
|
|
|
200
199
|
private func updatePlaybackRate(_ playerPlaybackRate: Double) {
|
|
201
200
|
self.nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = NSNumber(value: playerPlaybackRate)
|
|
202
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
201
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] playbackrate [\(playerPlaybackRate)] stored in nowPlayingInfo.") }
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
private func updateCurrentTime(_ currentTime: Double) {
|
|
206
205
|
self.nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = NSNumber(value: currentTime)
|
|
207
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
206
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] currentTime [\(currentTime)] stored in nowPlayingInfo.") }
|
|
208
207
|
}
|
|
209
208
|
|
|
210
209
|
private func updateDuration(_ duration: Double?) {
|
|
211
210
|
if let duration = duration {
|
|
212
211
|
let isLiveStream = duration.isInfinite
|
|
213
212
|
self.nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = isLiveStream
|
|
214
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
213
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] isLiveStream [\(isLiveStream)] stored in nowPlayingInfo.") }
|
|
215
214
|
if !isLiveStream {
|
|
216
215
|
self.nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = NSNumber(value: duration)
|
|
217
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
216
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO] duration [\(duration)] stored in nowPlayingInfo.") }
|
|
218
217
|
}
|
|
219
218
|
}
|
|
220
219
|
}
|
|
221
220
|
|
|
222
|
-
@objc
|
|
223
|
-
private func appWillTerminate() {
|
|
224
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE-NOWPLAYINGINFO] App will terminate notification received.") }
|
|
225
|
-
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
|
226
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE-NOWPLAYINGINFO] nowPlayingInfo cleared for app termination.") }
|
|
227
|
-
}
|
|
228
|
-
|
|
229
221
|
private func attachListeners() {
|
|
230
222
|
guard let player = self.player else {
|
|
231
223
|
return
|
|
232
224
|
}
|
|
233
225
|
|
|
234
|
-
// When app terminates, clear the nowPlayingInfo.
|
|
235
|
-
self.appTerminationObserver = NotificationCenter.default.addObserver(
|
|
236
|
-
self,
|
|
237
|
-
selector: #selector(appWillTerminate),
|
|
238
|
-
name: UIApplication.willTerminateNotification,
|
|
239
|
-
object: nil
|
|
240
|
-
)
|
|
241
|
-
|
|
242
226
|
// DURATION_CHANGE
|
|
243
227
|
self.durationChangeListener = player.addEventListener(type: PlayerEventTypes.DURATION_CHANGE) { [weak self, weak player] event in
|
|
244
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
228
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO-EVENT] DURATION_CHANGE") }
|
|
245
229
|
if let welf = self,
|
|
246
230
|
let wplayer = player,
|
|
247
231
|
let duration = wplayer.duration {
|
|
@@ -252,7 +236,7 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
252
236
|
|
|
253
237
|
// PLAYING
|
|
254
238
|
self.playingListener = player.addEventListener(type: PlayerEventTypes.PLAYING) { [weak self, weak player] event in
|
|
255
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
239
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO-EVENT] PLAYING") }
|
|
256
240
|
if let welf = self,
|
|
257
241
|
let wplayer = player {
|
|
258
242
|
welf.updatePlaybackRate(wplayer.playbackRate)
|
|
@@ -263,7 +247,7 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
263
247
|
|
|
264
248
|
// PAUSE
|
|
265
249
|
self.pauseListener = player.addEventListener(type: PlayerEventTypes.PAUSE) { [weak self, weak player] event in
|
|
266
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
250
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO-EVENT] PAUSE") }
|
|
267
251
|
if let welf = self,
|
|
268
252
|
let wplayer = player {
|
|
269
253
|
welf.updatePlaybackRate(0)
|
|
@@ -275,7 +259,7 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
275
259
|
|
|
276
260
|
// RATE_CHANGE
|
|
277
261
|
self.rateChangeListener = player.addEventListener(type: PlayerEventTypes.RATE_CHANGE) { [weak self, weak player] event in
|
|
278
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
262
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO-EVENT] RATE_CHANGE") }
|
|
279
263
|
if let welf = self,
|
|
280
264
|
let wplayer = player {
|
|
281
265
|
welf.updatePlaybackRate(wplayer.playbackRate)
|
|
@@ -286,7 +270,7 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
286
270
|
|
|
287
271
|
// SEEKED
|
|
288
272
|
self.seekedListener = player.addEventListener(type: PlayerEventTypes.SEEKED) { [weak self, weak player] event in
|
|
289
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
273
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO-EVENT] SEEKED") }
|
|
290
274
|
if let welf = self,
|
|
291
275
|
let wplayer = player {
|
|
292
276
|
welf.updatePlaybackRate(wplayer.playbackRate)
|
|
@@ -297,7 +281,7 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
297
281
|
|
|
298
282
|
// SOURCE_CHANGE
|
|
299
283
|
self.sourceChangeListener = player.addEventListener(type: PlayerEventTypes.SOURCE_CHANGE) { [weak self] event in
|
|
300
|
-
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE
|
|
284
|
+
if DEBUG_NOWINFO { PrintUtils.printLog(logText: "[NATIVE][NOWPLAYINGINFO-EVENT] SOURCE_CHANGE \(event.source == nil ? "to nil" : "")") }
|
|
301
285
|
self?.updateNowPlaying()
|
|
302
286
|
}
|
|
303
287
|
}
|
|
@@ -307,15 +291,6 @@ class THEOplayerRCTNowPlayingManager {
|
|
|
307
291
|
return
|
|
308
292
|
}
|
|
309
293
|
|
|
310
|
-
// When app terminates, clear the nowPlayingInfo.
|
|
311
|
-
if let appTerminationObserver = self.appTerminationObserver {
|
|
312
|
-
NotificationCenter.default.removeObserver(
|
|
313
|
-
appTerminationObserver,
|
|
314
|
-
name: UIApplication.willTerminateNotification,
|
|
315
|
-
object: nil)
|
|
316
|
-
self.appTerminationObserver = nil
|
|
317
|
-
}
|
|
318
|
-
|
|
319
294
|
// DURATION_CHANGE
|
|
320
295
|
if let durationChangeListener = self.durationChangeListener {
|
|
321
296
|
player.removeEventListener(type: PlayerEventTypes.DURATION_CHANGE, listener: durationChangeListener)
|
|
@@ -11,12 +11,12 @@ public class THEOplayerRCTPresentationModeManager {
|
|
|
11
11
|
var presentationModeContext = THEOplayerRCTPresentationModeContext()
|
|
12
12
|
private(set) var presentationMode: THEOplayerSDK.PresentationMode = .inline
|
|
13
13
|
private var rnInlineMode: THEOplayerSDK.PresentationMode = .inline // while native player is inline, RN player can be inline or fullsceen
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
|
|
15
|
+
|
|
16
16
|
private weak var containerView: UIView? // view containing the playerView and it's siblings (e.g. UI)
|
|
17
17
|
private weak var inlineParentView: UIView? // target view for inline representation
|
|
18
18
|
private var inlineParentViewIndex: Int?
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
// MARK: Events
|
|
21
21
|
var onNativePresentationModeChange: RCTDirectEventBlock?
|
|
22
22
|
|
|
@@ -58,7 +58,7 @@ public class THEOplayerRCTPresentationModeManager {
|
|
|
58
58
|
}
|
|
59
59
|
return viewControllers
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
/**
|
|
63
63
|
* Moves a view to a new parent.
|
|
64
64
|
* The view is inserted at the provided targetViewIndex or else at the front.
|
|
@@ -70,16 +70,31 @@ public class THEOplayerRCTPresentationModeManager {
|
|
|
70
70
|
movedVC.removeFromParent()
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
let parentView = movingView.superview
|
|
74
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] Before moving:") }
|
|
75
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] parentView:") }
|
|
76
|
+
self.logSubviews(of: parentView)
|
|
77
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] targetView:") }
|
|
78
|
+
self.logSubviews(of: targetView)
|
|
79
|
+
|
|
73
80
|
// move the actual view
|
|
74
81
|
movingView.removeFromSuperview()
|
|
75
82
|
if let viewIndex = targetViewIndex {
|
|
76
83
|
let safeIndex = min(viewIndex, targetView.subviews.count)
|
|
84
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] moveView: insertSubview at safeIndex \(safeIndex) (for targetViewIndex \(targetViewIndex ?? -1)) on targetView with \(targetView.subviews.count) subviews.") }
|
|
77
85
|
targetView.insertSubview(movingView, at: safeIndex)
|
|
78
86
|
} else {
|
|
87
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] moveView: addSubview on targetView with \(targetView.subviews.count) subviews.") }
|
|
79
88
|
targetView.addSubview(movingView)
|
|
80
89
|
targetView.bringSubviewToFront(movingView)
|
|
81
90
|
}
|
|
82
91
|
|
|
92
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] After moving:") }
|
|
93
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] updated parentView:") }
|
|
94
|
+
self.logSubviews(of: parentView)
|
|
95
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] updated targetView:") }
|
|
96
|
+
self.logSubviews(of: targetView)
|
|
97
|
+
|
|
83
98
|
// attach the moving viewControllers to their new parent
|
|
84
99
|
if let targetViewController = targetView.findViewController() {
|
|
85
100
|
movingViewControllers.forEach { movedVC in
|
|
@@ -98,6 +113,7 @@ public class THEOplayerRCTPresentationModeManager {
|
|
|
98
113
|
let inlineParentView = self.inlineParentView,
|
|
99
114
|
let fullscreenParentView = self.view?.findParentViewOfType(["RCTRootContentView", "RCTRootComponentView"]) {
|
|
100
115
|
self.inlineParentViewIndex = inlineParentView.subviews.firstIndex(of: containerView)
|
|
116
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] storing inlineParentViewIndex = \(self.inlineParentViewIndex ?? -1) of \(inlineParentView.subviews.count) subviews.") }
|
|
101
117
|
self.moveView(containerView, to: fullscreenParentView, targetViewIndex: nil)
|
|
102
118
|
|
|
103
119
|
// start hiding home indicator
|
|
@@ -109,7 +125,7 @@ public class THEOplayerRCTPresentationModeManager {
|
|
|
109
125
|
private func exitFullscreen() {
|
|
110
126
|
// stop hiding home indicator
|
|
111
127
|
setHomeIndicatorHidden(false)
|
|
112
|
-
|
|
128
|
+
|
|
113
129
|
// move the player
|
|
114
130
|
if let containerView = self.containerView,
|
|
115
131
|
let inlineParentView = self.inlineParentView {
|
|
@@ -118,22 +134,24 @@ public class THEOplayerRCTPresentationModeManager {
|
|
|
118
134
|
self.rnInlineMode = .inline
|
|
119
135
|
}
|
|
120
136
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
private func logSubviews(of viewToLog: UIView?) {
|
|
138
|
+
if let parentView = viewToLog {
|
|
139
|
+
for (index, subview) in parentView.subviews.enumerated() {
|
|
140
|
+
if DEBUG_PRESENTATIONMODES { PrintUtils.printLog(logText: "[NATIVE] Index \(index): \(type(of: subview)) - \(subview)") }
|
|
141
|
+
}
|
|
124
142
|
}
|
|
125
|
-
}
|
|
126
|
-
|
|
143
|
+
}
|
|
144
|
+
|
|
127
145
|
private func setHomeIndicatorHidden(_ hidden: Bool) {
|
|
128
146
|
#if os(iOS)
|
|
129
147
|
if let fullscreenParentView = self.view?.findParentViewOfType(["RCTRootContentView", "RCTRootComponentView"]),
|
|
130
148
|
let customRootViewController = fullscreenParentView.findViewController() as? HomeIndicatorViewController {
|
|
131
|
-
|
|
132
|
-
|
|
149
|
+
customRootViewController.prefersAutoHidden = hidden
|
|
150
|
+
customRootViewController.setNeedsUpdateOfHomeIndicatorAutoHidden()
|
|
133
151
|
}
|
|
134
152
|
#endif
|
|
135
153
|
}
|
|
136
|
-
|
|
154
|
+
|
|
137
155
|
func setPresentationModeFromRN(newPresentationMode: THEOplayerSDK.PresentationMode) {
|
|
138
156
|
guard newPresentationMode != self.presentationMode else { return }
|
|
139
157
|
|
|
@@ -170,7 +188,7 @@ public class THEOplayerRCTPresentationModeManager {
|
|
|
170
188
|
break;
|
|
171
189
|
}
|
|
172
190
|
}
|
|
173
|
-
|
|
191
|
+
|
|
174
192
|
func setPresentationModeFromNative(newPresentationMode: THEOplayerSDK.PresentationMode) {
|
|
175
193
|
// store old presentationMode
|
|
176
194
|
let oldPresentationMode = self.presentationMode
|
|
@@ -180,16 +198,16 @@ public class THEOplayerRCTPresentationModeManager {
|
|
|
180
198
|
if newPresentationMode == .inline {
|
|
181
199
|
self.presentationMode = self.rnInlineMode
|
|
182
200
|
}
|
|
183
|
-
|
|
201
|
+
|
|
184
202
|
// notify the presentationMode change
|
|
185
203
|
self.notifyPresentationModeChange(oldPresentationMode: oldPresentationMode, newPresentationMode: self.presentationMode)
|
|
186
204
|
}
|
|
187
|
-
|
|
205
|
+
|
|
188
206
|
private func setNativePresentationMode(_ presentationMode: THEOplayerSDK.PresentationMode) {
|
|
189
207
|
guard let player = self.player else { return }
|
|
190
208
|
player.presentationMode = presentationMode
|
|
191
209
|
}
|
|
192
|
-
|
|
210
|
+
|
|
193
211
|
private func notifyPresentationModeChange(oldPresentationMode: THEOplayerSDK.PresentationMode, newPresentationMode: THEOplayerSDK.PresentationMode) {
|
|
194
212
|
// update the current presentationMode
|
|
195
213
|
self.presentationMode = newPresentationMode
|
|
@@ -208,7 +226,7 @@ public class THEOplayerRCTPresentationModeManager {
|
|
|
208
226
|
// PRESENTATION_MODE_CHANGE
|
|
209
227
|
self.presentationModeChangeListener = player.addEventListener(type: PlayerEventTypes.PRESENTATION_MODE_CHANGE) { [weak self] event in
|
|
210
228
|
if let welf = self {
|
|
211
|
-
if DEBUG_THEOPLAYER_EVENTS
|
|
229
|
+
if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received PRESENTATION_MODE_CHANGE event from THEOplayer (to \(event.presentationMode._rawValue))") }
|
|
212
230
|
welf.setPresentationModeFromNative(newPresentationMode: event.presentationMode)
|
|
213
231
|
}
|
|
214
232
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"10.
|
|
1
|
+
{"version":"10.8.0","buildDate":"2026-01-22T15:21:17.281Z"}
|
package/lib/module/manifest.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"10.
|
|
1
|
+
{"version":"10.8.0","buildDate":"2026-01-22T15:21:17.281Z"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-theoplayer",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.8.0",
|
|
4
4
|
"description": "A THEOplayer video component for react-native.",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@eslint/js": "^9.13.0",
|
|
60
60
|
"@expo/config-plugins": "^10.0.2",
|
|
61
|
-
"@react-native/eslint-config": "^0.
|
|
62
|
-
"@types/react": "^19.
|
|
61
|
+
"@react-native/eslint-config": "^0.83.1",
|
|
62
|
+
"@types/react": "^19.2.0",
|
|
63
63
|
"eslint": "^8.57.1",
|
|
64
64
|
"eslint-config-prettier": "^9.1.0",
|
|
65
65
|
"eslint-plugin-react-hooks": "^7.0.0",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"lint-staged": "^15.5.1",
|
|
68
68
|
"pod-install": "^0.1.39",
|
|
69
69
|
"prettier": "^3.5.3",
|
|
70
|
-
"react": "^19.
|
|
71
|
-
"react-native": "^0.
|
|
70
|
+
"react": "^19.2.0",
|
|
71
|
+
"react-native": "^0.83.1",
|
|
72
72
|
"react-native-builder-bob": "^0.39.1",
|
|
73
73
|
"theoplayer": "^10",
|
|
74
74
|
"typedoc": "^0.25.13",
|
package/src/manifest.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"10.
|
|
1
|
+
{"version":"10.8.0","buildDate":"2026-01-22T15:21:17.281Z"}
|