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.
- package/CHANGELOG.md +14 -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/ReactTHEOplayerContext.kt +1 -1
- 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/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/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/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/package.json +1 -1
- 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/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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
64
|
-
guard let
|
|
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 {
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
66
|
+
// attach the moving viewControllers to their new parent
|
|
53
67
|
if let targetViewController = targetView.findViewController() {
|
|
54
|
-
|
|
55
|
-
|
|
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();
|