react-native-theoplayer 8.11.1 → 8.13.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 +28 -0
- package/android/build.gradle +10 -0
- package/android/proguard-rules.pro +4 -0
- package/android/src/main/java/com/theoplayer/PlayerConfigAdapter.kt +8 -1
- package/android/src/main/java/com/theoplayer/PlayerEventEmitter.kt +8 -4
- package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +1 -1
- package/android/src/main/java/com/theoplayer/ReactTHEOplayerView.kt +15 -4
- package/android/src/main/java/com/theoplayer/ReactTHEOplayerViewManager.kt +1 -1
- package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +7 -2
- package/android/src/main/java/com/theoplayer/util/ViewResolver.kt +10 -13
- package/ios/THEOplayerRCTBridge.m +0 -2
- package/ios/THEOplayerRCTPlayerAPI.swift +0 -10
- package/ios/THEOplayerRCTView.swift +29 -34
- package/ios/THEOplayerRCTViewManager.swift +0 -9
- package/ios/ads/THEOplayerRCTAdsAPI+DAI.swift +5 -0
- package/ios/ads/THEOplayerRCTView+Ads.swift +4 -0
- package/ios/ads/THEOplayerRCTView+AdsConfig.swift +6 -1
- package/ios/backgroundAudio/THEOplayerRCTBackgroundAudioManager.swift +105 -0
- package/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +8 -79
- package/ios/pip/THEOplayerRCTPipManager.swift +34 -0
- package/ios/pip/THEOplayerRCTView+PipConfig.swift +3 -14
- package/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift +28 -10
- package/lib/commonjs/internal/THEOplayerView.js +0 -1
- package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
- package/lib/commonjs/internal/adapter/THEOplayerAdapter.js +0 -5
- package/lib/commonjs/internal/adapter/THEOplayerAdapter.js.map +1 -1
- package/lib/commonjs/internal/adapter/WebEventForwarder.js +3 -3
- package/lib/commonjs/internal/adapter/WebEventForwarder.js.map +1 -1
- package/lib/commonjs/internal/adapter/event/DefaultEventDispatcher.js +18 -2
- package/lib/commonjs/internal/adapter/event/DefaultEventDispatcher.js.map +1 -1
- package/lib/commonjs/internal/adapter/web/WebPresentationModeManager.js +4 -12
- package/lib/commonjs/internal/adapter/web/WebPresentationModeManager.js.map +1 -1
- package/lib/commonjs/manifest.json +1 -1
- package/lib/module/internal/THEOplayerView.js +0 -1
- package/lib/module/internal/THEOplayerView.js.map +1 -1
- package/lib/module/internal/adapter/THEOplayerAdapter.js +0 -5
- package/lib/module/internal/adapter/THEOplayerAdapter.js.map +1 -1
- package/lib/module/internal/adapter/WebEventForwarder.js +3 -3
- package/lib/module/internal/adapter/WebEventForwarder.js.map +1 -1
- package/lib/module/internal/adapter/event/DefaultEventDispatcher.js +18 -2
- package/lib/module/internal/adapter/event/DefaultEventDispatcher.js.map +1 -1
- package/lib/module/internal/adapter/web/WebPresentationModeManager.js +4 -12
- package/lib/module/internal/adapter/web/WebPresentationModeManager.js.map +1 -1
- package/lib/module/manifest.json +1 -1
- package/lib/typescript/api/ads/GoogleImaConfiguration.d.ts +5 -0
- package/lib/typescript/api/ads/GoogleImaConfiguration.d.ts.map +1 -1
- package/lib/typescript/api/player/THEOplayer.d.ts +0 -4
- package/lib/typescript/api/player/THEOplayer.d.ts.map +1 -1
- package/lib/typescript/internal/adapter/THEOplayerAdapter.d.ts +0 -1
- package/lib/typescript/internal/adapter/THEOplayerAdapter.d.ts.map +1 -1
- package/lib/typescript/internal/adapter/event/DefaultEventDispatcher.d.ts +6 -4
- package/lib/typescript/internal/adapter/event/DefaultEventDispatcher.d.ts.map +1 -1
- package/lib/typescript/internal/adapter/web/WebPresentationModeManager.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/ads/GoogleImaConfiguration.ts +6 -0
- package/src/api/player/THEOplayer.ts +0 -5
- package/src/internal/THEOplayerView.tsx +1 -1
- package/src/internal/adapter/THEOplayerAdapter.ts +0 -6
- package/src/internal/adapter/WebEventForwarder.ts +4 -4
- package/src/internal/adapter/event/DefaultEventDispatcher.ts +26 -8
- package/src/internal/adapter/web/WebPresentationModeManager.ts +4 -12
- package/src/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ 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
|
+
## [8.13.0] - 25-01-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added support for New Architecture's through the Interop Layer. More info on the [React Native developer pages](https://reactnative.dev/architecture/landing-page).
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Fixed an issue on Web where picture-in-picture presentation mode would sometimes fail.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Upgraded the example app to use react-native-tvos@0.76.5-0.
|
|
21
|
+
|
|
22
|
+
## [8.12.0] - 25-01-09
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Fixed a memory leak on iOS, where the presentationModeManager was holding a strong reference to the fullscreen's target and return views
|
|
27
|
+
- Fixed an issue on iOS where the destruction of the THEOplayerView was not always propagated correctly over the iOS Bridge, resulting in an occasional memory leak.
|
|
28
|
+
- Fixed an issue where, when requesting a text track's cues, the time properties would sometimes be in seconds instead of milliseconds.
|
|
29
|
+
- Fixed a rare crash on Android due to a `java.lang.NullPointerException` when creating the THEOplayerView.
|
|
30
|
+
- Fixed an issue on Android where R8 minification would obfuscate some API class names, which could lead to a crash.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Added a `adLoadTimeout` property to `GoogleImaConfiguration` to control the amount of time that the SDK will wait before moving onto the next ad or main content.
|
|
35
|
+
|
|
8
36
|
## [8.11.1] - 24-12-18
|
|
9
37
|
|
|
10
38
|
### Fixed
|
package/android/build.gradle
CHANGED
|
@@ -25,6 +25,14 @@ static def versionString(version) {
|
|
|
25
25
|
return "${version == '+' ? 'latest' : version}"
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
def isNewArchitectureEnabled() {
|
|
29
|
+
// To opt-in for the New Architecture, you can either:
|
|
30
|
+
// - Set `newArchEnabled` to true inside the `gradle.properties` file
|
|
31
|
+
// - Invoke gradle with `-newArchEnabled=true`
|
|
32
|
+
// - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
|
|
33
|
+
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
// Extensions
|
|
29
37
|
def enabledGoogleIMA = safeExtGet("THEOplayer_extensionGoogleIMA", 'false').toBoolean()
|
|
30
38
|
def enabledGoogleDAI = safeExtGet("THEOplayer_extensionGoogleDAI", 'false').toBoolean()
|
|
@@ -69,6 +77,8 @@ android {
|
|
|
69
77
|
buildConfigField "boolean", "EXTENSION_MEDIASESSION", "${enabledMediaSession}"
|
|
70
78
|
buildConfigField "boolean", "EXTENSION_MEDIA3", "${enabledMedia3}"
|
|
71
79
|
|
|
80
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
81
|
+
|
|
72
82
|
consumerProguardFiles 'proguard-rules.pro'
|
|
73
83
|
}
|
|
74
84
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
# Do no warn if any of the API classes we resolve with compileOnly are missing because the feature
|
|
2
2
|
# is disabled: it is expected.
|
|
3
3
|
-dontwarn com.theoplayer.android.api.**
|
|
4
|
+
-dontwarn com.google.android.gms.cast.**
|
|
5
|
+
|
|
6
|
+
# We rely on gson to instantiate some source classes from json, so make sure they are not obfuscated.
|
|
7
|
+
-keep,includedescriptorclasses class com.theoplayer.android.api.source.** { *; }
|
|
@@ -28,6 +28,7 @@ private const val PROP_RETRY_MAX_BACKOFF = "maximumBackoff"
|
|
|
28
28
|
private const val PROP_CAST_CONFIGURATION = "cast"
|
|
29
29
|
private const val PROP_ADS_CONFIGURATION = "ads"
|
|
30
30
|
private const val PROP_IMA_CONFIGURATION = "ima"
|
|
31
|
+
private const val PROP_IMA_AD_LOAD_TIMEOUT = "adLoadTimeout"
|
|
31
32
|
private const val PROP_MEDIA_CONTROL = "mediaControl"
|
|
32
33
|
private const val PROP_PPID = "ppid"
|
|
33
34
|
private const val PROP_MAX_REDIRECTS = "maxRedirects"
|
|
@@ -169,11 +170,17 @@ class PlayerConfigAdapter(private val configProps: ReadableMap?) {
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
|
-
// bitrate
|
|
173
|
+
// bitrate and timeout are configured under the ima config
|
|
173
174
|
configProps?.getMap(PROP_ADS_CONFIGURATION)?.getMap(PROP_IMA_CONFIGURATION)?.run {
|
|
174
175
|
if (hasKey(PROP_BITRATE)) {
|
|
175
176
|
bitrateKbps = getInt(PROP_BITRATE)
|
|
176
177
|
}
|
|
178
|
+
|
|
179
|
+
// The time needs to be in milliseconds on android but seconds on ios.
|
|
180
|
+
// we unify the prop from javascript by multiplying it by 1000 here
|
|
181
|
+
if (hasKey(PROP_IMA_AD_LOAD_TIMEOUT)) {
|
|
182
|
+
setLoadVideoTimeout(getInt(PROP_IMA_AD_LOAD_TIMEOUT) * 1000)
|
|
183
|
+
}
|
|
177
184
|
}
|
|
178
185
|
}
|
|
179
186
|
}
|
|
@@ -8,7 +8,7 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
8
8
|
import com.facebook.react.bridge.WritableMap
|
|
9
9
|
import com.facebook.react.bridge.WritableNativeMap
|
|
10
10
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
11
|
-
import com.facebook.react.uimanager.common.
|
|
11
|
+
import com.facebook.react.uimanager.common.UIManagerType
|
|
12
12
|
import com.theoplayer.ads.AdEventAdapter
|
|
13
13
|
import com.theoplayer.ads.AdEventAdapter.AdEventEmitter
|
|
14
14
|
import com.theoplayer.android.api.THEOplayerGlobal
|
|
@@ -47,7 +47,6 @@ import com.theoplayer.track.*
|
|
|
47
47
|
import com.theoplayer.util.PayloadBuilder
|
|
48
48
|
import kotlin.math.floor
|
|
49
49
|
|
|
50
|
-
|
|
51
50
|
private val TAG = PlayerEventEmitter::class.java.name
|
|
52
51
|
|
|
53
52
|
private const val EVENT_PLAYER_READY = "onNativePlayerReady"
|
|
@@ -613,8 +612,13 @@ class PlayerEventEmitter internal constructor(
|
|
|
613
612
|
} catch (ignore: RuntimeException) {
|
|
614
613
|
}
|
|
615
614
|
}
|
|
616
|
-
|
|
617
|
-
|
|
615
|
+
UIManagerHelper.getUIManager(
|
|
616
|
+
reactContext,
|
|
617
|
+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
618
|
+
UIManagerType.FABRIC
|
|
619
|
+
} else {
|
|
620
|
+
UIManagerType.DEFAULT
|
|
621
|
+
})?.receiveEvent(UIManagerHelper.getSurfaceId(playerView), viewId, type, event)
|
|
618
622
|
}
|
|
619
623
|
|
|
620
624
|
private fun attachListeners(player: Player) {
|
|
@@ -205,7 +205,7 @@ class ReactTHEOplayerContext private constructor(
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
private fun initializePlayerView() {
|
|
208
|
-
playerView = object : THEOplayerView(reactContext
|
|
208
|
+
playerView = object : THEOplayerView(reactContext, configAdapter.playerConfig()) {
|
|
209
209
|
private fun measureAndLayout() {
|
|
210
210
|
measure(
|
|
211
211
|
MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY),
|
|
@@ -26,6 +26,7 @@ class ReactTHEOplayerView(private val reactContext: ThemedReactContext) :
|
|
|
26
26
|
var presentationManager: PresentationManager? = null
|
|
27
27
|
var playerContext: ReactTHEOplayerContext? = null
|
|
28
28
|
private var isInitialized: Boolean = false
|
|
29
|
+
private var config: PlayerConfigAdapter? = null
|
|
29
30
|
|
|
30
31
|
val adsApi: AdsApiWrapper
|
|
31
32
|
|
|
@@ -40,7 +41,7 @@ class ReactTHEOplayerView(private val reactContext: ThemedReactContext) :
|
|
|
40
41
|
adsApi = AdsApiWrapper()
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
fun initialize(
|
|
44
|
+
fun initialize(config: PlayerConfigAdapter) {
|
|
44
45
|
if (BuildConfig.LOG_VIEW_EVENTS) {
|
|
45
46
|
Log.d(TAG, "Initialize view")
|
|
46
47
|
}
|
|
@@ -48,10 +49,15 @@ class ReactTHEOplayerView(private val reactContext: ThemedReactContext) :
|
|
|
48
49
|
Log.w(TAG, "Already initialized view")
|
|
49
50
|
return
|
|
50
51
|
}
|
|
52
|
+
this.config = config
|
|
53
|
+
if (!isAttachedToWindow) {
|
|
54
|
+
// The view is not attached to the window yet, postpone the initialization.
|
|
55
|
+
return
|
|
56
|
+
}
|
|
51
57
|
isInitialized = true
|
|
52
58
|
playerContext = ReactTHEOplayerContext.create(
|
|
53
59
|
reactContext,
|
|
54
|
-
|
|
60
|
+
config
|
|
55
61
|
)
|
|
56
62
|
playerContext?.apply {
|
|
57
63
|
adsApi.initialize(player, imaIntegration, daiIntegration)
|
|
@@ -59,17 +65,22 @@ class ReactTHEOplayerView(private val reactContext: ThemedReactContext) :
|
|
|
59
65
|
playerView.layoutParams = layoutParams
|
|
60
66
|
(playerView.parent as? ViewGroup)?.removeView(playerView)
|
|
61
67
|
addView(playerView, 0, layoutParams)
|
|
62
|
-
|
|
63
68
|
presentationManager = PresentationManager(
|
|
64
69
|
this,
|
|
65
70
|
reactContext,
|
|
66
71
|
eventEmitter
|
|
67
72
|
)
|
|
68
|
-
|
|
69
73
|
eventEmitter.preparePlayer(player)
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
override fun onAttachedToWindow() {
|
|
78
|
+
super.onAttachedToWindow()
|
|
79
|
+
if (!isInitialized) {
|
|
80
|
+
config?.let { initialize(it) }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
73
84
|
override fun setId(id: Int) {
|
|
74
85
|
super.setId(id)
|
|
75
86
|
eventEmitter.setViewId(id)
|
|
@@ -31,7 +31,7 @@ class ReactTHEOplayerViewManager : ViewGroupManager<ReactTHEOplayerView>() {
|
|
|
31
31
|
|
|
32
32
|
@ReactProp(name = PROP_CONFIG)
|
|
33
33
|
fun setConfig(videoView: ReactTHEOplayerView, config: ReadableMap?) {
|
|
34
|
-
videoView.initialize(config)
|
|
34
|
+
videoView.initialize(PlayerConfigAdapter(config))
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
|
|
@@ -16,6 +16,7 @@ import androidx.core.view.WindowInsetsCompat
|
|
|
16
16
|
import androidx.core.view.WindowInsetsControllerCompat
|
|
17
17
|
import androidx.lifecycle.Lifecycle
|
|
18
18
|
import com.facebook.react.ReactRootView
|
|
19
|
+
import com.facebook.react.runtime.ReactSurfaceView
|
|
19
20
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
20
21
|
import com.facebook.react.views.view.ReactViewGroup
|
|
21
22
|
import com.theoplayer.BuildConfig
|
|
@@ -211,7 +212,7 @@ class PresentationManager(
|
|
|
211
212
|
// Get the player's ReactViewGroup parent, which contains THEOplayerView and its children (typically the UI).
|
|
212
213
|
val reactPlayerGroup: ReactViewGroup? = getClosestParentOfType(this.viewCtx.playerView)
|
|
213
214
|
|
|
214
|
-
// Get ReactNative's root node or the render
|
|
215
|
+
// Get ReactNative's root node or the render hierarchy
|
|
215
216
|
val root: ReactRootView? = getClosestParentOfType(reactPlayerGroup)
|
|
216
217
|
|
|
217
218
|
if (fullscreen) {
|
|
@@ -223,7 +224,11 @@ class PresentationManager(
|
|
|
223
224
|
if (!BuildConfig.REPARENT_ON_FULLSCREEN) {
|
|
224
225
|
return
|
|
225
226
|
}
|
|
226
|
-
playerGroupParentNode = (
|
|
227
|
+
playerGroupParentNode = if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
228
|
+
reactPlayerGroup?.parent as? ReactSurfaceView?
|
|
229
|
+
} else {
|
|
230
|
+
reactPlayerGroup?.parent as? ReactViewGroup?
|
|
231
|
+
}?.also { parent ->
|
|
227
232
|
playerGroupChildIndex = parent.indexOfChild(reactPlayerGroup)
|
|
228
233
|
// Re-parent the playerViewGroup to the root node
|
|
229
234
|
parent.removeView(reactPlayerGroup)
|
|
@@ -3,31 +3,28 @@ package com.theoplayer.util
|
|
|
3
3
|
import android.util.Log
|
|
4
4
|
import android.view.View
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
-
import com.facebook.react.uimanager.
|
|
6
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
7
7
|
|
|
8
8
|
private const val TAG = "ViewResolver"
|
|
9
9
|
private const val INVALID_TAG = -1
|
|
10
10
|
|
|
11
11
|
@Suppress("UNCHECKED_CAST")
|
|
12
12
|
class ViewResolver(private val reactContext: ReactApplicationContext) {
|
|
13
|
-
private var uiManager: UIManagerModule? = null
|
|
14
|
-
|
|
15
13
|
fun <T: View> resolveViewByTag(tag: Int, onResolved: (view: T?) -> Unit) {
|
|
16
14
|
if (tag == INVALID_TAG) {
|
|
17
15
|
// Don't bother trying to resolve an invalid tag.
|
|
18
16
|
onResolved(null)
|
|
19
17
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
onResolved(it.resolveView(tag) as? T?)
|
|
26
|
-
} catch (e: Exception) {
|
|
27
|
-
// The View instance could not be resolved: log but do not forward exception.
|
|
28
|
-
Log.e(TAG, "Failed to resolve View tag $tag: $e")
|
|
29
|
-
onResolved(null)
|
|
18
|
+
try {
|
|
19
|
+
reactContext.runOnUiQueueThread {
|
|
20
|
+
UIManagerHelper.getUIManagerForReactTag(reactContext, tag)?.let {
|
|
21
|
+
onResolved(it.resolveView(tag) as? T?)
|
|
22
|
+
}
|
|
30
23
|
}
|
|
24
|
+
} catch (e: Exception) {
|
|
25
|
+
// The ReactTHEOplayerView instance could not be resolved: log but do not forward exception.
|
|
26
|
+
Log.e(TAG, "Failed to resolve ReactTHEOplayerView tag $tag: $e")
|
|
27
|
+
onResolved(null)
|
|
31
28
|
}
|
|
32
29
|
}
|
|
33
30
|
}
|
|
@@ -112,8 +112,6 @@ RCT_EXTERN_METHOD(setPreload:(nonnull NSNumber *)node
|
|
|
112
112
|
RCT_EXTERN_METHOD(setTextTrackStyle:(nonnull NSNumber *)node
|
|
113
113
|
textTrackStyle:(NSDictionary)textTrackStyle)
|
|
114
114
|
|
|
115
|
-
RCT_EXTERN_METHOD(destroyPlayer:(nonnull NSNumber *)node);
|
|
116
|
-
|
|
117
115
|
@end
|
|
118
116
|
|
|
119
117
|
// ----------------------------------------------------------------------------
|
|
@@ -356,14 +356,4 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
|
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
|
-
|
|
360
|
-
@objc(destroyPlayer:)
|
|
361
|
-
func destroyPlayer(_ node: NSNumber) -> Void {
|
|
362
|
-
DispatchQueue.main.async {
|
|
363
|
-
if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView {
|
|
364
|
-
theView.destroyPlayer()
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
359
|
}
|
|
@@ -9,15 +9,16 @@ public class THEOplayerRCTView: UIView {
|
|
|
9
9
|
public private(set) var player: THEOplayer?
|
|
10
10
|
public private(set) var mainEventHandler: THEOplayerRCTMainEventHandler
|
|
11
11
|
public private(set) var broadcastEventHandler: THEOplayerRCTBroadcastEventHandler
|
|
12
|
-
let theoPlayerViewController = UIViewController()
|
|
13
12
|
var textTrackEventHandler: THEOplayerRCTTextTrackEventHandler
|
|
14
13
|
var mediaTrackEventHandler: THEOplayerRCTMediaTrackEventHandler
|
|
15
14
|
var metadataTrackEventHandler: THEOplayerRCTSideloadedMetadataTrackEventHandler
|
|
16
15
|
var adEventHandler: THEOplayerRCTAdsEventHandler
|
|
17
16
|
var castEventHandler: THEOplayerRCTCastEventHandler
|
|
18
17
|
var presentationModeManager: THEOplayerRCTPresentationModeManager
|
|
18
|
+
var backgroundAudioManager: THEOplayerRCTBackgroundAudioManager
|
|
19
19
|
var nowPlayingManager: THEOplayerRCTNowPlayingManager
|
|
20
20
|
var remoteCommandsManager: THEOplayerRCTRemoteCommandsManager
|
|
21
|
+
var pipManager: THEOplayerRCTPipManager
|
|
21
22
|
var pipControlsManager: THEOplayerRCTPipControlsManager
|
|
22
23
|
|
|
23
24
|
var adsConfig = AdsConfig()
|
|
@@ -36,8 +37,8 @@ public class THEOplayerRCTView: UIView {
|
|
|
36
37
|
}
|
|
37
38
|
var backgroundAudioConfig = BackgroundAudioConfig() {
|
|
38
39
|
didSet {
|
|
39
|
-
self.updateInterruptionNotifications()
|
|
40
|
-
self.updateAVAudioSessionMode()
|
|
40
|
+
self.backgroundAudioManager.updateInterruptionNotifications()
|
|
41
|
+
self.backgroundAudioManager.updateAVAudioSessionMode()
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -61,8 +62,10 @@ public class THEOplayerRCTView: UIView {
|
|
|
61
62
|
self.adEventHandler = THEOplayerRCTAdsEventHandler()
|
|
62
63
|
self.castEventHandler = THEOplayerRCTCastEventHandler()
|
|
63
64
|
self.presentationModeManager = THEOplayerRCTPresentationModeManager()
|
|
65
|
+
self.backgroundAudioManager = THEOplayerRCTBackgroundAudioManager()
|
|
64
66
|
self.nowPlayingManager = THEOplayerRCTNowPlayingManager()
|
|
65
67
|
self.remoteCommandsManager = THEOplayerRCTRemoteCommandsManager()
|
|
68
|
+
self.pipManager = THEOplayerRCTPipManager()
|
|
66
69
|
self.pipControlsManager = THEOplayerRCTPipControlsManager()
|
|
67
70
|
|
|
68
71
|
super.init(frame: .zero)
|
|
@@ -71,20 +74,32 @@ public class THEOplayerRCTView: UIView {
|
|
|
71
74
|
required init?(coder aDecoder: NSCoder) {
|
|
72
75
|
fatalError("[NATIVE] init(coder:) has not been implemented")
|
|
73
76
|
}
|
|
77
|
+
|
|
78
|
+
deinit {
|
|
79
|
+
self.mainEventHandler.destroy()
|
|
80
|
+
self.textTrackEventHandler.destroy()
|
|
81
|
+
self.mediaTrackEventHandler.destroy()
|
|
82
|
+
self.adEventHandler.destroy()
|
|
83
|
+
self.castEventHandler.destroy()
|
|
84
|
+
self.nowPlayingManager.destroy()
|
|
85
|
+
self.remoteCommandsManager.destroy()
|
|
86
|
+
self.pipManager.destroy()
|
|
87
|
+
self.pipControlsManager.destroy()
|
|
88
|
+
self.presentationModeManager.destroy()
|
|
89
|
+
self.backgroundAudioManager.destroy()
|
|
90
|
+
|
|
91
|
+
self.destroyBackgroundAudio()
|
|
92
|
+
self.player?.removeAllIntegrations()
|
|
93
|
+
self.player?.destroy()
|
|
94
|
+
self.player = nil
|
|
95
|
+
if DEBUG_THEOPLAYER_INTERACTION { PrintUtils.printLog(logText: "[NATIVE] THEOplayer instance destroyed.") }
|
|
96
|
+
}
|
|
74
97
|
|
|
75
98
|
override public func layoutSubviews() {
|
|
76
99
|
super.layoutSubviews()
|
|
77
100
|
if let player = self.player {
|
|
78
101
|
player.frame = self.frame
|
|
79
102
|
player.autoresizingMask = [.flexibleBottomMargin, .flexibleHeight, .flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleWidth]
|
|
80
|
-
|
|
81
|
-
// wrap theoPlayerViewController around the view
|
|
82
|
-
if theoPlayerViewController.parent == nil,
|
|
83
|
-
let parentViewController = self.findViewController() {
|
|
84
|
-
parentViewController.addChild(self.theoPlayerViewController)
|
|
85
|
-
self.theoPlayerViewController.didMove(toParent: parentViewController)
|
|
86
|
-
self.theoPlayerViewController.view = self
|
|
87
|
-
}
|
|
88
103
|
}
|
|
89
104
|
}
|
|
90
105
|
|
|
@@ -97,12 +112,14 @@ public class THEOplayerRCTView: UIView {
|
|
|
97
112
|
self.mainEventHandler.setPlayer(player)
|
|
98
113
|
self.textTrackEventHandler.setPlayer(player)
|
|
99
114
|
self.mediaTrackEventHandler.setPlayer(player)
|
|
100
|
-
self.presentationModeManager.setPlayer(player, view: self)
|
|
101
115
|
self.adEventHandler.setPlayer(player)
|
|
102
116
|
self.castEventHandler.setPlayer(player)
|
|
103
117
|
self.nowPlayingManager.setPlayer(player)
|
|
104
118
|
self.remoteCommandsManager.setPlayer(player)
|
|
105
119
|
self.pipControlsManager.setPlayer(player)
|
|
120
|
+
self.presentationModeManager.setPlayer(player, view: self)
|
|
121
|
+
self.backgroundAudioManager.setPlayer(player, view: self)
|
|
122
|
+
self.pipManager.setView(view: self)
|
|
106
123
|
// Attach player to view
|
|
107
124
|
player.addAsSubview(of: self)
|
|
108
125
|
}
|
|
@@ -137,28 +154,6 @@ public class THEOplayerRCTView: UIView {
|
|
|
137
154
|
return self.player
|
|
138
155
|
}
|
|
139
156
|
|
|
140
|
-
// MARK: - Destroy Player
|
|
141
|
-
|
|
142
|
-
public func destroyPlayer() {
|
|
143
|
-
self.mainEventHandler.destroy()
|
|
144
|
-
self.textTrackEventHandler.destroy()
|
|
145
|
-
self.mediaTrackEventHandler.destroy()
|
|
146
|
-
self.adEventHandler.destroy()
|
|
147
|
-
self.castEventHandler.destroy()
|
|
148
|
-
self.nowPlayingManager.destroy()
|
|
149
|
-
self.remoteCommandsManager.destroy()
|
|
150
|
-
self.pipControlsManager.destroy()
|
|
151
|
-
|
|
152
|
-
self.destroyBackgroundAudio()
|
|
153
|
-
self.player?.removeAllIntegrations()
|
|
154
|
-
self.player?.destroy()
|
|
155
|
-
self.player = nil
|
|
156
|
-
if DEBUG_THEOPLAYER_INTERACTION { PrintUtils.printLog(logText: "[NATIVE] THEOplayer instance destroyed.") }
|
|
157
|
-
|
|
158
|
-
self.theoPlayerViewController.view = nil
|
|
159
|
-
self.theoPlayerViewController.removeFromParent()
|
|
160
|
-
}
|
|
161
|
-
|
|
162
157
|
func processMetadataTracks(metadataTrackDescriptions: [TextTrackDescription]?) {
|
|
163
158
|
THEOplayerRCTSideloadedMetadataProcessor.loadTrackInfoFromTrackDescriptions(metadataTrackDescriptions) { tracksInfo in
|
|
164
159
|
self.mainEventHandler.setLoadedMetadataTracksInfo(tracksInfo)
|
|
@@ -16,13 +16,4 @@ class THEOplayerRCTViewManager: RCTViewManager {
|
|
|
16
16
|
override class func requiresMainQueueSetup() -> Bool {
|
|
17
17
|
return true
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
@objc func destroy(_ node: NSNumber) {
|
|
21
|
-
DispatchQueue.main.async {
|
|
22
|
-
let theView = self.bridge.uiManager.view(
|
|
23
|
-
forReactTag: node
|
|
24
|
-
) as! THEOplayerRCTView
|
|
25
|
-
theView.destroyPlayer()
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
19
|
}
|
|
@@ -79,6 +79,11 @@ extension THEOplayerRCTAdsAPI {
|
|
|
79
79
|
resolve(false)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
@objc(daiSetSnapback:enabled:)
|
|
83
|
+
func daiSetSnapback(_ node: NSNumber, enabled: Bool) -> Void {
|
|
84
|
+
if DEBUG_ADS_API { print(ERROR_MESSAGE_ADS_UNSUPPORTED_FEATURE) }
|
|
85
|
+
}
|
|
86
|
+
|
|
82
87
|
@objc(daiContentTimeForStreamTime:time:resolver:rejecter:)
|
|
83
88
|
func daiContentTimeForStreamTime(_ node: NSNumber, timeValue: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
84
89
|
if DEBUG_ADS_API { print(ERROR_MESSAGE_ADS_UNSUPPORTED_FEATURE) }
|
|
@@ -57,6 +57,10 @@ extension THEOplayerRCTView {
|
|
|
57
57
|
imaRenderSettings.mimeTypes = allowedMimeTypes
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
if let adLoadTimeout = self.adsConfig.adsImaConfig.adLoadTimeout {
|
|
61
|
+
imaRenderSettings.loadVideoTimeout = adLoadTimeout
|
|
62
|
+
}
|
|
63
|
+
|
|
60
64
|
// setup integration
|
|
61
65
|
let imaIntegration = GoogleIMAIntegrationFactory.createIntegration(on: player, with: imaSettings)
|
|
62
66
|
imaIntegration.renderingSettings = imaRenderSettings
|
|
@@ -21,14 +21,16 @@ struct AdsImaConfig {
|
|
|
21
21
|
var autoPlayAdBreaks: Bool?
|
|
22
22
|
var sessionID: String?
|
|
23
23
|
var bitrate: Int
|
|
24
|
+
var adLoadTimeout: TimeInterval?
|
|
24
25
|
|
|
25
|
-
init(maxRedirects: UInt, enableDebugMode: Bool, ppid: String? = nil, featureFlags: [String : String]? = nil, autoPlayAdBreaks: Bool? = nil, sessionID: String? = nil, bitrate: Int? = -1) {
|
|
26
|
+
init(maxRedirects: UInt, enableDebugMode: Bool, ppid: String? = nil, featureFlags: [String : String]? = nil, autoPlayAdBreaks: Bool? = nil, sessionID: String? = nil, bitrate: Int? = -1, adLoadTimeout: TimeInterval? = nil) {
|
|
26
27
|
self.maxRedirects = maxRedirects
|
|
27
28
|
self.enableDebugMode = enableDebugMode
|
|
28
29
|
self.ppid = ppid
|
|
29
30
|
self.featureFlags = featureFlags
|
|
30
31
|
self.autoPlayAdBreaks = autoPlayAdBreaks
|
|
31
32
|
self.sessionID = sessionID
|
|
33
|
+
self.adLoadTimeout = adLoadTimeout
|
|
32
34
|
#if canImport(THEOplayerGoogleIMAIntegration)
|
|
33
35
|
self.bitrate = bitrate ?? kIMAAutodetectBitrate
|
|
34
36
|
#else
|
|
@@ -67,6 +69,9 @@ extension THEOplayerRCTView {
|
|
|
67
69
|
if let bitrate = adsImaConfig["bitrate"] as? Int {
|
|
68
70
|
self.adsConfig.adsImaConfig.bitrate = bitrate
|
|
69
71
|
}
|
|
72
|
+
if let adLoadTimeout = adsImaConfig["adLoadTimeout"] as? TimeInterval {
|
|
73
|
+
self.adsConfig.adsImaConfig.adLoadTimeout = adLoadTimeout
|
|
74
|
+
}
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// TTHEOplayerRCTBackgroundAudioManager.swift
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
import THEOplayerSDK
|
|
5
|
+
import AVFAudio
|
|
6
|
+
import AVKit
|
|
7
|
+
|
|
8
|
+
struct BackgroundAudioConfig {
|
|
9
|
+
var enabled: Bool = false
|
|
10
|
+
var shouldResumeAfterInterruption: Bool = false
|
|
11
|
+
var audioSessionMode: AVAudioSession.Mode = .moviePlayback
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class THEOplayerRCTBackgroundAudioManager: NSObject, BackgroundPlaybackDelegate {
|
|
15
|
+
// MARK: Members
|
|
16
|
+
private weak var player: THEOplayer?
|
|
17
|
+
private weak var view: THEOplayerRCTView?
|
|
18
|
+
|
|
19
|
+
// MARK: - player setup / breakdown
|
|
20
|
+
func setPlayer(_ player: THEOplayer, view: THEOplayerRCTView?) {
|
|
21
|
+
self.player = player
|
|
22
|
+
self.view = view
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// MARK: - destruction
|
|
26
|
+
func destroy() {
|
|
27
|
+
self.cancelInterruptionNotifications()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// MARK: - logic
|
|
31
|
+
func shouldContinueAudioPlaybackInBackground() -> Bool {
|
|
32
|
+
if let view = self.view {
|
|
33
|
+
view.nowPlayingManager.updateNowPlaying()
|
|
34
|
+
return view.backgroundAudioConfig.enabled
|
|
35
|
+
}
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func cancelInterruptionNotifications() {
|
|
40
|
+
NotificationCenter.default.removeObserver(self,
|
|
41
|
+
name: AVAudioSession.interruptionNotification,
|
|
42
|
+
object: AVAudioSession.sharedInstance())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func updateInterruptionNotifications() {
|
|
46
|
+
guard let view = self.view else { return }
|
|
47
|
+
|
|
48
|
+
// Get the default notification center instance.
|
|
49
|
+
if view.backgroundAudioConfig.shouldResumeAfterInterruption {
|
|
50
|
+
NotificationCenter.default.addObserver(self,
|
|
51
|
+
selector: #selector(handleInterruption),
|
|
52
|
+
name: AVAudioSession.interruptionNotification,
|
|
53
|
+
object: AVAudioSession.sharedInstance())
|
|
54
|
+
} else {
|
|
55
|
+
NotificationCenter.default.removeObserver(self,
|
|
56
|
+
name: AVAudioSession.interruptionNotification,
|
|
57
|
+
object: AVAudioSession.sharedInstance())
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func updateAVAudioSessionMode() {
|
|
62
|
+
guard let view = self.view else { return }
|
|
63
|
+
|
|
64
|
+
do {
|
|
65
|
+
THEOplayer.automaticallyManageAudioSession = (view.backgroundAudioConfig.audioSessionMode == .moviePlayback)
|
|
66
|
+
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: view.backgroundAudioConfig.audioSessionMode)
|
|
67
|
+
if view.backgroundAudioConfig.audioSessionMode != .moviePlayback {
|
|
68
|
+
if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] AVAudioSession mode updated to \(view.backgroundAudioConfig.audioSessionMode.rawValue)") }
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] Unable to update AVAudioSession mode to \(view.backgroundAudioConfig.audioSessionMode.rawValue): \(error)") }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@objc func handleInterruption(notification: Notification) {
|
|
76
|
+
guard let userInfo = notification.userInfo,
|
|
77
|
+
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
78
|
+
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Switch over the interruption type.
|
|
83
|
+
switch type {
|
|
84
|
+
case .began:
|
|
85
|
+
// An interruption began. Update the UI as necessary.
|
|
86
|
+
if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] An interruption began")}
|
|
87
|
+
case .ended:
|
|
88
|
+
// An interruption ended. Resume playback, if appropriate.
|
|
89
|
+
if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] An interruption ended")}
|
|
90
|
+
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
|
|
91
|
+
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
|
92
|
+
if options.contains(.shouldResume) {
|
|
93
|
+
// An interruption ended. Resume playback.
|
|
94
|
+
if let player = self.player {
|
|
95
|
+
if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] Ended interruption should resume playback => play()")}
|
|
96
|
+
player.play()
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// An interruption ended. Don't resume playback.
|
|
100
|
+
if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] Ended interruption should not resume playback.")}
|
|
101
|
+
}
|
|
102
|
+
default: ()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|