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.
Files changed (62) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/android/build.gradle +10 -0
  3. package/android/proguard-rules.pro +4 -0
  4. package/android/src/main/java/com/theoplayer/PlayerConfigAdapter.kt +8 -1
  5. package/android/src/main/java/com/theoplayer/PlayerEventEmitter.kt +8 -4
  6. package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +1 -1
  7. package/android/src/main/java/com/theoplayer/ReactTHEOplayerView.kt +15 -4
  8. package/android/src/main/java/com/theoplayer/ReactTHEOplayerViewManager.kt +1 -1
  9. package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +7 -2
  10. package/android/src/main/java/com/theoplayer/util/ViewResolver.kt +10 -13
  11. package/ios/THEOplayerRCTBridge.m +0 -2
  12. package/ios/THEOplayerRCTPlayerAPI.swift +0 -10
  13. package/ios/THEOplayerRCTView.swift +29 -34
  14. package/ios/THEOplayerRCTViewManager.swift +0 -9
  15. package/ios/ads/THEOplayerRCTAdsAPI+DAI.swift +5 -0
  16. package/ios/ads/THEOplayerRCTView+Ads.swift +4 -0
  17. package/ios/ads/THEOplayerRCTView+AdsConfig.swift +6 -1
  18. package/ios/backgroundAudio/THEOplayerRCTBackgroundAudioManager.swift +105 -0
  19. package/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +8 -79
  20. package/ios/pip/THEOplayerRCTPipManager.swift +34 -0
  21. package/ios/pip/THEOplayerRCTView+PipConfig.swift +3 -14
  22. package/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift +28 -10
  23. package/lib/commonjs/internal/THEOplayerView.js +0 -1
  24. package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
  25. package/lib/commonjs/internal/adapter/THEOplayerAdapter.js +0 -5
  26. package/lib/commonjs/internal/adapter/THEOplayerAdapter.js.map +1 -1
  27. package/lib/commonjs/internal/adapter/WebEventForwarder.js +3 -3
  28. package/lib/commonjs/internal/adapter/WebEventForwarder.js.map +1 -1
  29. package/lib/commonjs/internal/adapter/event/DefaultEventDispatcher.js +18 -2
  30. package/lib/commonjs/internal/adapter/event/DefaultEventDispatcher.js.map +1 -1
  31. package/lib/commonjs/internal/adapter/web/WebPresentationModeManager.js +4 -12
  32. package/lib/commonjs/internal/adapter/web/WebPresentationModeManager.js.map +1 -1
  33. package/lib/commonjs/manifest.json +1 -1
  34. package/lib/module/internal/THEOplayerView.js +0 -1
  35. package/lib/module/internal/THEOplayerView.js.map +1 -1
  36. package/lib/module/internal/adapter/THEOplayerAdapter.js +0 -5
  37. package/lib/module/internal/adapter/THEOplayerAdapter.js.map +1 -1
  38. package/lib/module/internal/adapter/WebEventForwarder.js +3 -3
  39. package/lib/module/internal/adapter/WebEventForwarder.js.map +1 -1
  40. package/lib/module/internal/adapter/event/DefaultEventDispatcher.js +18 -2
  41. package/lib/module/internal/adapter/event/DefaultEventDispatcher.js.map +1 -1
  42. package/lib/module/internal/adapter/web/WebPresentationModeManager.js +4 -12
  43. package/lib/module/internal/adapter/web/WebPresentationModeManager.js.map +1 -1
  44. package/lib/module/manifest.json +1 -1
  45. package/lib/typescript/api/ads/GoogleImaConfiguration.d.ts +5 -0
  46. package/lib/typescript/api/ads/GoogleImaConfiguration.d.ts.map +1 -1
  47. package/lib/typescript/api/player/THEOplayer.d.ts +0 -4
  48. package/lib/typescript/api/player/THEOplayer.d.ts.map +1 -1
  49. package/lib/typescript/internal/adapter/THEOplayerAdapter.d.ts +0 -1
  50. package/lib/typescript/internal/adapter/THEOplayerAdapter.d.ts.map +1 -1
  51. package/lib/typescript/internal/adapter/event/DefaultEventDispatcher.d.ts +6 -4
  52. package/lib/typescript/internal/adapter/event/DefaultEventDispatcher.d.ts.map +1 -1
  53. package/lib/typescript/internal/adapter/web/WebPresentationModeManager.d.ts.map +1 -1
  54. package/package.json +4 -4
  55. package/src/api/ads/GoogleImaConfiguration.ts +6 -0
  56. package/src/api/player/THEOplayer.ts +0 -5
  57. package/src/internal/THEOplayerView.tsx +1 -1
  58. package/src/internal/adapter/THEOplayerAdapter.ts +0 -6
  59. package/src/internal/adapter/WebEventForwarder.ts +4 -4
  60. package/src/internal/adapter/event/DefaultEventDispatcher.ts +26 -8
  61. package/src/internal/adapter/web/WebPresentationModeManager.ts +4 -12
  62. 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
@@ -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 is configured under the ima config
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.ViewUtil
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
- val uiManager = UIManagerHelper.getUIManager(reactContext, ViewUtil.getUIManagerType(viewId))
617
- uiManager?.receiveEvent(UIManagerHelper.getSurfaceId(reactContext), viewId, type, event)
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.currentActivity!!, configAdapter.playerConfig()) {
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(configProps: ReadableMap?) {
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
- PlayerConfigAdapter(configProps)
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 hiearchy
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 = (reactPlayerGroup?.parent as ReactViewGroup?)?.also { parent ->
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.UIManagerModule
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
- if (uiManager == null) {
21
- uiManager = reactContext.getNativeModule(UIManagerModule::class.java)
22
- }
23
- uiManager?.addUIBlock {
24
- try {
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
+ }