react-native-theoplayer 8.11.1 → 8.12.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 (49) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/android/proguard-rules.pro +4 -0
  3. package/android/src/main/java/com/theoplayer/PlayerConfigAdapter.kt +8 -1
  4. package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +1 -1
  5. package/ios/THEOplayerRCTBridge.m +0 -2
  6. package/ios/THEOplayerRCTPlayerAPI.swift +0 -10
  7. package/ios/THEOplayerRCTView.swift +29 -34
  8. package/ios/THEOplayerRCTViewManager.swift +0 -9
  9. package/ios/ads/THEOplayerRCTView+Ads.swift +4 -0
  10. package/ios/ads/THEOplayerRCTView+AdsConfig.swift +6 -1
  11. package/ios/backgroundAudio/THEOplayerRCTBackgroundAudioManager.swift +105 -0
  12. package/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +8 -79
  13. package/ios/pip/THEOplayerRCTPipManager.swift +34 -0
  14. package/ios/pip/THEOplayerRCTView+PipConfig.swift +3 -14
  15. package/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift +28 -10
  16. package/lib/commonjs/internal/THEOplayerView.js +0 -1
  17. package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
  18. package/lib/commonjs/internal/adapter/THEOplayerAdapter.js +0 -5
  19. package/lib/commonjs/internal/adapter/THEOplayerAdapter.js.map +1 -1
  20. package/lib/commonjs/internal/adapter/WebEventForwarder.js +3 -3
  21. package/lib/commonjs/internal/adapter/WebEventForwarder.js.map +1 -1
  22. package/lib/commonjs/internal/adapter/event/DefaultEventDispatcher.js +18 -2
  23. package/lib/commonjs/internal/adapter/event/DefaultEventDispatcher.js.map +1 -1
  24. package/lib/commonjs/manifest.json +1 -1
  25. package/lib/module/internal/THEOplayerView.js +0 -1
  26. package/lib/module/internal/THEOplayerView.js.map +1 -1
  27. package/lib/module/internal/adapter/THEOplayerAdapter.js +0 -5
  28. package/lib/module/internal/adapter/THEOplayerAdapter.js.map +1 -1
  29. package/lib/module/internal/adapter/WebEventForwarder.js +3 -3
  30. package/lib/module/internal/adapter/WebEventForwarder.js.map +1 -1
  31. package/lib/module/internal/adapter/event/DefaultEventDispatcher.js +18 -2
  32. package/lib/module/internal/adapter/event/DefaultEventDispatcher.js.map +1 -1
  33. package/lib/module/manifest.json +1 -1
  34. package/lib/typescript/api/ads/GoogleImaConfiguration.d.ts +5 -0
  35. package/lib/typescript/api/ads/GoogleImaConfiguration.d.ts.map +1 -1
  36. package/lib/typescript/api/player/THEOplayer.d.ts +0 -4
  37. package/lib/typescript/api/player/THEOplayer.d.ts.map +1 -1
  38. package/lib/typescript/internal/adapter/THEOplayerAdapter.d.ts +0 -1
  39. package/lib/typescript/internal/adapter/THEOplayerAdapter.d.ts.map +1 -1
  40. package/lib/typescript/internal/adapter/event/DefaultEventDispatcher.d.ts +6 -4
  41. package/lib/typescript/internal/adapter/event/DefaultEventDispatcher.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/src/api/ads/GoogleImaConfiguration.ts +6 -0
  44. package/src/api/player/THEOplayer.ts +0 -5
  45. package/src/internal/THEOplayerView.tsx +1 -1
  46. package/src/internal/adapter/THEOplayerAdapter.ts +0 -6
  47. package/src/internal/adapter/WebEventForwarder.ts +4 -4
  48. package/src/internal/adapter/event/DefaultEventDispatcher.ts +26 -8
  49. package/src/manifest.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ 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.12.0] - 25-01-09
9
+
10
+ ### Fixed
11
+
12
+ - Fixed a memory leak on iOS, where the presentationModeManager was holding a strong reference to the fullscreen's target and return views
13
+ - 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.
14
+ - Fixed an issue where, when requesting a text track's cues, the time properties would sometimes be in seconds instead of milliseconds.
15
+ - Fixed a rare crash on Android due to a `java.lang.NullPointerException` when creating the THEOplayerView.
16
+ - Fixed an issue on Android where R8 minification would obfuscate some API class names, which could lead to a crash.
17
+
18
+ ### Added
19
+
20
+ - 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.
21
+
8
22
  ## [8.11.1] - 24-12-18
9
23
 
10
24
  ### Fixed
@@ -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
  }
@@ -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),
@@ -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
  }
@@ -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
+ }
@@ -2,96 +2,25 @@
2
2
 
3
3
  import Foundation
4
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
- extension THEOplayerRCTView: BackgroundPlaybackDelegate {
15
5
 
6
+ extension THEOplayerRCTView {
16
7
  func initBackgroundAudio() {
17
- self.player?.backgroundPlaybackDelegate = self
18
- }
19
-
20
- func destroyBackgroundAudio() {
21
8
  guard let player = self.player else {
22
9
  return
23
10
  }
24
- player.backgroundPlaybackDelegate = DefaultBackgroundPlaybackDelegate()
25
- NotificationCenter.default.removeObserver(self,
26
- name: AVAudioSession.interruptionNotification,
27
- object: AVAudioSession.sharedInstance())
28
- }
29
-
30
- public func shouldContinueAudioPlaybackInBackground() -> Bool {
31
- // Make sure to go to the background with updated NowPlayingInfo
32
- self.nowPlayingManager.updateNowPlaying()
33
-
34
- return self.backgroundAudioConfig.enabled
35
- }
36
-
37
- func updateInterruptionNotifications() {
38
- // Get the default notification center instance.
39
- if self.backgroundAudioConfig.shouldResumeAfterInterruption {
40
- NotificationCenter.default.addObserver(self,
41
- selector: #selector(handleInterruption),
42
- name: AVAudioSession.interruptionNotification,
43
- object: AVAudioSession.sharedInstance())
44
- } else {
45
- NotificationCenter.default.removeObserver(self,
46
- name: AVAudioSession.interruptionNotification,
47
- object: AVAudioSession.sharedInstance())
48
- }
49
- }
50
-
51
- func updateAVAudioSessionMode() {
52
- do {
53
- THEOplayer.automaticallyManageAudioSession = (self.backgroundAudioConfig.audioSessionMode == .moviePlayback)
54
- try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: self.backgroundAudioConfig.audioSessionMode)
55
- if self.backgroundAudioConfig.audioSessionMode != .moviePlayback {
56
- print("[NATIVE] AVAudioSession mode updated to \(self.backgroundAudioConfig.audioSessionMode.rawValue)")
57
- }
58
- } catch {
59
- print("[NATIVE] Unable to update AVAudioSession mode to \(self.backgroundAudioConfig.audioSessionMode.rawValue): ", error)
60
- }
11
+ player.backgroundPlaybackDelegate = self.backgroundAudioManager
61
12
  }
62
13
 
63
- @objc func handleInterruption(notification: Notification) {
64
- guard let userInfo = notification.userInfo,
65
- let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
66
- let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
14
+ func destroyBackgroundAudio() {
15
+ guard let player = self.player else {
67
16
  return
68
17
  }
69
-
70
- // Switch over the interruption type.
71
- switch type {
72
- case .began:
73
- // An interruption began. Update the UI as necessary.
74
- if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] An interruption began")}
75
- case .ended:
76
- // An interruption ended. Resume playback, if appropriate.
77
- if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] An interruption ended")}
78
- guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
79
- let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
80
- if options.contains(.shouldResume) {
81
- // An interruption ended. Resume playback.
82
- if let player = self.player {
83
- if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] Ended interruption should resume playback => play()")}
84
- player.play()
85
- }
86
- } else {
87
- // An interruption ended. Don't resume playback.
88
- if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] Ended interruption should not resume playback.")}
89
- }
90
- default: ()
91
- }
18
+ player.backgroundPlaybackDelegate = DefaultBackgroundPlaybackDelegate()
92
19
  }
93
20
  }
94
21
 
95
22
  struct DefaultBackgroundPlaybackDelegate: BackgroundPlaybackDelegate {
96
- func shouldContinueAudioPlaybackInBackground() -> Bool { false }
23
+ func shouldContinueAudioPlaybackInBackground() -> Bool {
24
+ return false
25
+ }
97
26
  }
@@ -0,0 +1,34 @@
1
+ // TTHEOplayerRCTPipManager.swift
2
+
3
+ import Foundation
4
+ import AVKit
5
+ import THEOplayerSDK
6
+
7
+ class THEOplayerRCTPipManager: NSObject, AVPictureInPictureControllerDelegate {
8
+
9
+ // MARK: Members
10
+ private weak var view: THEOplayerRCTView?
11
+
12
+ // MARK: - player setup / breakdown
13
+ func setView(view: THEOplayerRCTView?) {
14
+ self.view = view
15
+ }
16
+
17
+ func destroy() {}
18
+
19
+ // MARK: - AVPictureInPictureControllerDelegate
20
+ @available(tvOS 14.0, *)
21
+ public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
22
+ if let view = self.view {
23
+ view.presentationModeManager.presentationModeContext.pipContext = .PIP_CLOSED
24
+ view.pipControlsManager.willStartPip()
25
+ }
26
+ }
27
+
28
+ @available(tvOS 14.0, *)
29
+ public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
30
+ if let view = self.view {
31
+ view.presentationModeManager.presentationModeContext.pipContext = .PIP_RESTORED
32
+ }
33
+ }
34
+ }
@@ -8,7 +8,7 @@ struct PipConfig {
8
8
  var canStartPictureInPictureAutomaticallyFromInline: Bool = false
9
9
  }
10
10
 
11
- extension THEOplayerRCTView: AVPictureInPictureControllerDelegate {
11
+ extension THEOplayerRCTView {
12
12
 
13
13
  func playerPipConfiguration() -> PiPConfiguration {
14
14
  let builder = PiPConfigurationBuilder()
@@ -25,20 +25,9 @@ extension THEOplayerRCTView: AVPictureInPictureControllerDelegate {
25
25
  if let player = self.player,
26
26
  var pipController = player.pip {
27
27
  if #available(iOS 14.0, tvOS 14.0, *) {
28
- pipController.nativePictureInPictureDelegate = self
28
+ pipController.nativePictureInPictureDelegate = self.pipManager
29
29
  }
30
30
  }
31
31
  }
32
-
33
- // MARK: - AVPictureInPictureControllerDelegate
34
- @available(tvOS 14.0, *)
35
- public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
36
- self.presentationModeManager.presentationModeContext.pipContext = .PIP_CLOSED
37
- self.pipControlsManager.willStartPip()
38
- }
39
-
40
- @available(tvOS 14.0, *)
41
- public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
42
- self.presentationModeManager.presentationModeContext.pipContext = .PIP_RESTORED
43
- }
44
32
  }
33
+
@@ -11,9 +11,11 @@ public class THEOplayerRCTPresentationModeManager {
11
11
  var presentationModeContext = THEOplayerRCTPresentationModeContext()
12
12
  private var presentationMode: THEOplayerSDK.PresentationMode = .inline
13
13
  private var rnInlineMode: THEOplayerSDK.PresentationMode = .inline // while native player is inline, RN player can be inline or fullsceen
14
+ private var movingChildVCs: [UIViewController] = [] // list of playerView's child VCs that need to be reparented while moving the playerView
15
+
14
16
 
15
- private var containerView: UIView? // view containing the playerView and it's siblings (e.g. UI)
16
- private var inlineParentView: UIView? // target view for inline representation
17
+ private weak var containerView: UIView? // view containing the playerView and it's siblings (e.g. UI)
18
+ private weak var inlineParentView: UIView? // target view for inline representation
17
19
 
18
20
  // MARK: Events
19
21
  var onNativePresentationModeChange: RCTDirectEventBlock?
@@ -25,6 +27,7 @@ public class THEOplayerRCTPresentationModeManager {
25
27
  func destroy() {
26
28
  // dettach listeners
27
29
  self.dettachListeners()
30
+ self.clearMovingVCs()
28
31
  }
29
32
 
30
33
  // MARK: - player setup / breakdown
@@ -37,22 +40,35 @@ public class THEOplayerRCTPresentationModeManager {
37
40
  }
38
41
 
39
42
  // MARK: - logic
40
-
41
- private func moveView(_ movingView: UIView, to targetView: UIView) {
42
- guard let theoPlayerViewController = (self.view as? THEOplayerRCTView)?.theoPlayerViewController else { return }
43
+ private func storeMovingVCs(for view: UIView) {
44
+ if let viewController = view.findViewController() {
45
+ viewController.children.forEach { childVC in
46
+ self.movingChildVCs.append(childVC)
47
+ }
48
+ }
49
+ }
43
50
 
44
- // detach the viewController from its parent
45
- theoPlayerViewController.removeFromParent()
51
+ private func clearMovingVCs() {
52
+ self.movingChildVCs = []
53
+ }
54
+
55
+ private func moveView(_ movingView: UIView, to targetView: UIView) {
56
+ // detach the moving viewControllers from their parent
57
+ self.movingChildVCs.forEach { movedVC in
58
+ movedVC.removeFromParent()
59
+ }
46
60
 
47
61
  // move the actual view
48
62
  movingView.removeFromSuperview()
49
63
  targetView.addSubview(movingView)
50
64
  targetView.bringSubviewToFront(movingView)
51
65
 
52
- // attach the viewController to its new parent
66
+ // attach the moving viewControllers to their new parent
53
67
  if let targetViewController = targetView.findViewController() {
54
- targetViewController.addChild(theoPlayerViewController)
55
- theoPlayerViewController.didMove(toParent: targetViewController)
68
+ self.movingChildVCs.forEach { movedVC in
69
+ targetViewController.addChild(movedVC)
70
+ movedVC.didMove(toParent: targetViewController)
71
+ }
56
72
  }
57
73
  }
58
74
 
@@ -63,6 +79,7 @@ public class THEOplayerRCTPresentationModeManager {
63
79
  // move the player
64
80
  if let containerView = self.containerView,
65
81
  let fullscreenParentView = self.view?.findParentViewOfType(RCTRootContentView.self) {
82
+ self.storeMovingVCs(for: containerView)
66
83
  self.moveView(containerView, to: fullscreenParentView)
67
84
 
68
85
  // start hiding home indicator
@@ -79,6 +96,7 @@ public class THEOplayerRCTPresentationModeManager {
79
96
  if let containerView = self.containerView,
80
97
  let inlineParentView = self.inlineParentView {
81
98
  self.moveView(containerView, to: inlineParentView)
99
+ self.clearMovingVCs()
82
100
  }
83
101
  self.rnInlineMode = .inline
84
102
  }
@@ -45,7 +45,6 @@ class THEOplayerView extends _react.PureComponent {
45
45
  if (onPlayerDestroy) {
46
46
  onPlayerDestroy(this._facade);
47
47
  }
48
- this._facade.destroy();
49
48
  this._facade.dispatchEvent(new _BaseEvent.BaseEvent(_reactNativeTheoplayer.PlayerEventType.DESTROY));
50
49
  this._dimensionsHandler?.remove();
51
50
  this._facade.clearEventListeners();