react-native-theoplayer 10.7.1 → 10.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LICENSE +29 -17
- package/android/src/main/java/com/theoplayer/PlayerEventEmitter.kt +25 -11
- 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/audio/BackgroundAudioConfigAdapter.kt +4 -8
- 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 +21 -39
- package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +2 -0
- package/android/src/main/java/com/theoplayer/util/ReadableExtension.kt +10 -0
- package/android/src/main/java/com/theoplayer/util/ViewUtils.kt +67 -0
- package/ios/THEOplayerRCTBridge.m +1 -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 +81 -17
- package/ios/ads/THEOplayerRCTSourceDescriptionBuilder+Ads.swift +5 -5
- package/ios/backgroundAudio/THEOplayerRCTNowPlayingManager.swift +29 -54
- package/ios/pip/THEOplayerRCTPipControlsManager.swift +1 -1
- package/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift +36 -18
- package/lib/commonjs/internal/THEOplayerView.js +11 -2
- package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
- package/lib/commonjs/internal/adapter/THEOplayerAdapter.js +5 -0
- package/lib/commonjs/internal/adapter/THEOplayerAdapter.js.map +1 -1
- package/lib/commonjs/manifest.json +1 -1
- package/lib/module/internal/THEOplayerView.js +13 -3
- package/lib/module/internal/THEOplayerView.js.map +1 -1
- package/lib/module/internal/adapter/THEOplayerAdapter.js +5 -0
- package/lib/module/internal/adapter/THEOplayerAdapter.js.map +1 -1
- package/lib/module/manifest.json +1 -1
- package/lib/typescript/internal/THEOplayerView.d.ts +1 -0
- package/lib/typescript/internal/THEOplayerView.d.ts.map +1 -1
- package/lib/typescript/internal/adapter/THEOplayerAdapter.d.ts +1 -0
- package/lib/typescript/internal/adapter/THEOplayerAdapter.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/internal/THEOplayerView.tsx +12 -3
- package/src/internal/adapter/THEOplayerAdapter.ts +6 -0
- package/src/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,41 @@ 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.9.0] - 26-01-29
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Fixed an issue on Android where the player could disrupt the layout by sending an unexpected dimension change event.
|
|
13
|
+
- Fixed an issue on Android where the default properties of the player's `backgroundAudioConfiguration` would not be properly applied.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Added functionality to synchronize the player state from the native mobile players to React Native.
|
|
18
|
+
- Added a bypass property on iOS to control the THEOplayerView's ability to detach from its superView.
|
|
19
|
+
- Trigger an update for the iOS control centers (nowPlayingInfo and remoteCommands) to reflect a player state sync.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Changed license to BSD 3-Cause Clear. See [LICENSE](./LICENSE) file for more information.
|
|
24
|
+
|
|
25
|
+
## [10.8.0] - 26-01-22
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Added support on iOS to push the initial state of the player from the iOS bridge to the React native adapter.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- 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.
|
|
34
|
+
- Fixed an issue on iOS where the player could crash when terminating the app while the player is backgrounded in fullscreen.
|
|
35
|
+
- Fixed an issue on Android where `player.ads.currentAds` would not return an array of ads due to a native error.
|
|
36
|
+
- Fixed an issue on Android where the player would sometimes have wrong dimensions after transitioning to fullscreen presentation mode.
|
|
37
|
+
- Fixed an issue on Android where the `useEMSG` property was not included when setting a TheoAds source description.
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- Added RNRepo support for Android to the example app. More info on the official [documentation](https://rnrepo.org/) page.
|
|
42
|
+
|
|
8
43
|
## [10.7.1] - 26-01-06
|
|
9
44
|
|
|
10
45
|
### Added
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
The Clear BSD License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 Dolby International AB
|
|
4
|
+
All rights reserved.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted (subject to the limitations in the disclaimer
|
|
8
|
+
below) provided that the following conditions are met:
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
1. Redistributions of source code must retain the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer.
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
14
|
+
notice, this list of conditions and the following disclaimer in
|
|
15
|
+
the documentation and/or other materials provided with the
|
|
16
|
+
distribution.
|
|
17
|
+
|
|
18
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
19
|
+
contributors may be used to endorse or promote products derived
|
|
20
|
+
from this software without specific prior written permission.
|
|
21
|
+
|
|
22
|
+
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED
|
|
23
|
+
BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
24
|
+
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
|
|
25
|
+
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
26
|
+
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
27
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
28
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
29
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
30
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
31
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
32
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
33
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -53,6 +53,7 @@ import kotlin.math.floor
|
|
|
53
53
|
private val TAG = PlayerEventEmitter::class.java.name
|
|
54
54
|
|
|
55
55
|
private const val EVENT_PLAYER_READY = "onNativePlayerReady"
|
|
56
|
+
private const val EVENT_PLAYER_STATE_SYNC = "onNativePlayerStateSync"
|
|
56
57
|
private const val EVENT_SOURCECHANGE = "onNativeSourceChange"
|
|
57
58
|
private const val EVENT_LOADSTART = "onNativeLoadStart"
|
|
58
59
|
private const val EVENT_LOADEDMETADATA = "onNativeLoadedMetadata"
|
|
@@ -98,6 +99,7 @@ class PlayerEventEmitter internal constructor(
|
|
|
98
99
|
@Retention(AnnotationRetention.SOURCE)
|
|
99
100
|
@StringDef(
|
|
100
101
|
EVENT_PLAYER_READY,
|
|
102
|
+
EVENT_PLAYER_STATE_SYNC,
|
|
101
103
|
EVENT_SOURCECHANGE,
|
|
102
104
|
EVENT_LOADSTART,
|
|
103
105
|
EVENT_LOADEDMETADATA,
|
|
@@ -135,6 +137,7 @@ class PlayerEventEmitter internal constructor(
|
|
|
135
137
|
companion object {
|
|
136
138
|
val Events = arrayOf(
|
|
137
139
|
EVENT_PLAYER_READY,
|
|
140
|
+
EVENT_PLAYER_STATE_SYNC,
|
|
138
141
|
EVENT_SOURCECHANGE,
|
|
139
142
|
EVENT_LOADSTART,
|
|
140
143
|
EVENT_LOADEDMETADATA,
|
|
@@ -291,10 +294,28 @@ class PlayerEventEmitter internal constructor(
|
|
|
291
294
|
|
|
292
295
|
fun preparePlayer(player: Player) {
|
|
293
296
|
attachListeners(player)
|
|
297
|
+
emitPlayerReady()
|
|
298
|
+
}
|
|
294
299
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
fun emitPlayerReady() {
|
|
301
|
+
// Notify the player is ready
|
|
302
|
+
receiveEvent(EVENT_PLAYER_READY, Arguments.createMap().apply {
|
|
303
|
+
putMap(EVENT_PROP_STATE, collectPlayerState())
|
|
304
|
+
putMap(EVENT_PROP_VERSION, WritableNativeMap().apply {
|
|
305
|
+
putString(EVENT_PROP_VERSION, THEOplayerGlobal.getVersion())
|
|
306
|
+
putString(EVENT_PROP_SUITE_VERSION, "")
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
fun emitPlayerStateSync() {
|
|
312
|
+
receiveEvent(EVENT_PLAYER_STATE_SYNC, Arguments.createMap().apply {
|
|
313
|
+
putMap(EVENT_PROP_STATE, collectPlayerState())
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private fun collectPlayerState(): WritableMap {
|
|
318
|
+
return playerView.player?.let { player ->
|
|
298
319
|
PayloadBuilder()
|
|
299
320
|
.source(player.source)
|
|
300
321
|
.currentTime(player.currentTime)
|
|
@@ -312,14 +333,7 @@ class PlayerEventEmitter internal constructor(
|
|
|
312
333
|
.selectedAudioTrack(getSelectedAudioTrack(player))
|
|
313
334
|
.selectedVideoTrack(getSelectedVideoTrack(player))
|
|
314
335
|
.build()
|
|
315
|
-
)
|
|
316
|
-
payload.putMap(EVENT_PROP_VERSION, WritableNativeMap().apply {
|
|
317
|
-
putString(EVENT_PROP_VERSION, THEOplayerGlobal.getVersion())
|
|
318
|
-
putString(EVENT_PROP_SUITE_VERSION, "")
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
// Notify the player is ready
|
|
322
|
-
receiveEvent(EVENT_PLAYER_READY, payload)
|
|
336
|
+
} ?: Arguments.createMap()
|
|
323
337
|
}
|
|
324
338
|
|
|
325
339
|
private fun emitError(code: String, message: String?) {
|
|
@@ -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.audio
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
import com.theoplayer.util.getBooleanOrNull
|
|
4
5
|
|
|
5
6
|
object BackgroundAudioConfigAdapter {
|
|
6
7
|
private const val PROP_ENABLED = "enabled"
|
|
@@ -10,15 +11,10 @@ object BackgroundAudioConfigAdapter {
|
|
|
10
11
|
private const val DEFAULT_STOP_ON_BACKGROUND = false
|
|
11
12
|
|
|
12
13
|
fun fromProps(props: ReadableMap?): BackgroundAudioConfig {
|
|
13
|
-
val enabled = props?.hasKey(PROP_ENABLED)?.let {
|
|
14
|
-
props.getBoolean(PROP_ENABLED)
|
|
15
|
-
} ?: DEFAULT_ENABLED
|
|
16
|
-
val destroy = props?.hasKey(PROP_STOP_ON_BACKGROUND)?.let {
|
|
17
|
-
props.getBoolean(PROP_STOP_ON_BACKGROUND)
|
|
18
|
-
} ?: DEFAULT_STOP_ON_BACKGROUND
|
|
19
14
|
return BackgroundAudioConfig(
|
|
20
|
-
enabled =
|
|
21
|
-
stopOnBackground =
|
|
15
|
+
enabled = props?.getBooleanOrNull(PROP_ENABLED) ?: DEFAULT_ENABLED,
|
|
16
|
+
stopOnBackground = props?.getBooleanOrNull(PROP_STOP_ON_BACKGROUND)
|
|
17
|
+
?: DEFAULT_STOP_ON_BACKGROUND,
|
|
22
18
|
)
|
|
23
19
|
}
|
|
24
20
|
}
|
|
@@ -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,13 @@ 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.ReactContext
|
|
20
20
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
21
21
|
import com.facebook.react.views.view.ReactViewGroup
|
|
22
22
|
import com.theoplayer.BuildConfig
|
|
@@ -26,11 +26,14 @@ import com.theoplayer.ReactTHEOplayerView
|
|
|
26
26
|
import com.theoplayer.android.api.error.ErrorCode
|
|
27
27
|
import com.theoplayer.android.api.error.THEOplayerException
|
|
28
28
|
import com.theoplayer.android.api.player.PresentationMode
|
|
29
|
+
import com.theoplayer.util.findReactRootView
|
|
30
|
+
import com.theoplayer.util.getClosestParentOfType
|
|
29
31
|
|
|
30
32
|
const val IS_TRANSITION_INTO_PIP = "isTransitioningToPip"
|
|
31
33
|
const val IS_IN_PIP_MODE = "isInPictureInPictureMode"
|
|
32
34
|
const val ON_USER_LEAVE_HINT = "onUserLeaveHint"
|
|
33
35
|
const val ON_PIP_MODE_CHANGED = "onPictureInPictureModeChanged"
|
|
36
|
+
const val ON_DID_UPDATE_DIMENSIONS = "_didUpdateDimensions"
|
|
34
37
|
|
|
35
38
|
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
|
36
39
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
|
@@ -52,6 +55,11 @@ class PresentationManager(
|
|
|
52
55
|
private val playerGroupRestoreOptions by lazy {
|
|
53
56
|
PlayerGroupRestoreOptions()
|
|
54
57
|
}
|
|
58
|
+
private val layoutChangeListener = View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
|
59
|
+
reactContext
|
|
60
|
+
.getJSModule(ReactContext.RCTDeviceEventEmitter::class.java)
|
|
61
|
+
.emit(ON_DID_UPDATE_DIMENSIONS, null)
|
|
62
|
+
}
|
|
55
63
|
var currentPresentationMode: PresentationMode = PresentationMode.INLINE
|
|
56
64
|
private set
|
|
57
65
|
var currentPresentationModeChangeContext: PresentationModeChangeContext? = null
|
|
@@ -71,6 +79,11 @@ class PresentationManager(
|
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
}
|
|
82
|
+
|
|
83
|
+
// Emit a private dimension update event when layout changes
|
|
84
|
+
// This makes sure a dimension update is sent after fullscreen immersive animations complete.
|
|
85
|
+
rootView?.addOnLayoutChangeListener(layoutChangeListener)
|
|
86
|
+
|
|
74
87
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
75
88
|
supportsPip =
|
|
76
89
|
reactContext.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
|
|
@@ -118,6 +131,7 @@ class PresentationManager(
|
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
fullScreenLayoutObserver.remove()
|
|
134
|
+
rootView?.removeOnLayoutChangeListener(layoutChangeListener)
|
|
121
135
|
pipUtils.destroy()
|
|
122
136
|
} catch (_: Exception) {
|
|
123
137
|
}
|
|
@@ -269,12 +283,7 @@ class PresentationManager(
|
|
|
269
283
|
get() = viewCtx.playerView.getClosestParentOfType()
|
|
270
284
|
|
|
271
285
|
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
|
-
}
|
|
286
|
+
get() = findReactRootView( reactContext, reactPlayerGroup)
|
|
278
287
|
|
|
279
288
|
private fun reparentPlayerToRoot() {
|
|
280
289
|
reactPlayerGroup?.let { playerGroup ->
|
|
@@ -283,10 +292,12 @@ class PresentationManager(
|
|
|
283
292
|
|
|
284
293
|
// Re-parent the playerViewGroup to the root node
|
|
285
294
|
parent.removeView(playerGroup)
|
|
286
|
-
rootView?.
|
|
295
|
+
rootView?.let { root ->
|
|
296
|
+
root.addView(playerGroup)
|
|
287
297
|
|
|
288
|
-
|
|
289
|
-
|
|
298
|
+
// Attach an observer that overrides the react-native lay-out and forces fullscreen.
|
|
299
|
+
fullScreenLayoutObserver.attach(playerGroup, root)
|
|
300
|
+
}
|
|
290
301
|
}
|
|
291
302
|
}
|
|
292
303
|
}
|
|
@@ -335,35 +346,6 @@ class PresentationManager(
|
|
|
335
346
|
}
|
|
336
347
|
}
|
|
337
348
|
|
|
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
349
|
private class PlayerGroupRestoreOptions {
|
|
368
350
|
var childIndex: Int? = null
|
|
369
351
|
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,10 @@
|
|
|
1
|
+
package com.theoplayer.util
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gets a boolean value from the map, or null if the key does not exist.
|
|
7
|
+
*/
|
|
8
|
+
fun ReadableMap.getBooleanOrNull(name: String): Boolean? {
|
|
9
|
+
return if (hasKey(name)) getBoolean(name) else null
|
|
10
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -52,6 +52,7 @@ RCT_EXPORT_VIEW_PROPERTY(onNativeAdEvent, RCTDirectEventBlock);
|
|
|
52
52
|
RCT_EXPORT_VIEW_PROPERTY(onNativeTHEOliveEvent, RCTDirectEventBlock);
|
|
53
53
|
RCT_EXPORT_VIEW_PROPERTY(onNativeTHEOadsEvent, RCTDirectEventBlock);
|
|
54
54
|
RCT_EXPORT_VIEW_PROPERTY(onNativeCastEvent, RCTDirectEventBlock);
|
|
55
|
+
RCT_EXPORT_VIEW_PROPERTY(onNativePlayerStateSync, RCTDirectEventBlock);
|
|
55
56
|
|
|
56
57
|
@end
|
|
57
58
|
|
|
@@ -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
|
}
|