react-native-theoplayer 3.6.0 → 3.7.1
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 +12 -0
- package/README.md +1 -0
- package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +41 -1
- package/ios/THEOplayerRCTMainEventHandler.swift +6 -24
- package/ios/THEOplayerRCTPlayerAPI.swift +3 -16
- package/ios/THEOplayerRCTTextTrackEventHandler.swift +0 -9
- package/ios/THEOplayerRCTTrackMetadataAggregator.swift +1 -1
- package/ios/THEOplayerRCTView.swift +12 -10
- package/ios/Theoplayer-Bridging-Header.h +1 -0
- package/ios/pip/THEOplayerRCTView+PipConfig.swift +2 -2
- package/ios/{THEOplayerRCTPresentationModeContext.swift → presentationMode/THEOplayerRCTPresentationModeContext.swift} +4 -8
- package/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift +159 -0
- package/ios/presentationMode/THEOplayerRCTView+PresentationMode.swift +11 -0
- package/ios/{THEOplayerRCTSideloadedMetadataTrackHandler.swift → sideloadedMetadata/THEOplayerRCTSideloadedMetadataProcessor.swift} +14 -4
- package/ios/sideloadedMetadata/THEOplayerRCTSideloadedMetadataTrackEventHandler.swift +28 -0
- package/lib/commonjs/internal/THEOplayerView.js +24 -3
- package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
- package/lib/commonjs/internal/adapter/THEOplayerAdapter.js +3 -0
- package/lib/commonjs/internal/adapter/THEOplayerAdapter.js.map +1 -1
- package/lib/commonjs/internal/utils/Dimensions.js +31 -0
- package/lib/commonjs/internal/utils/Dimensions.js.map +1 -0
- package/lib/module/internal/THEOplayerView.js +26 -5
- package/lib/module/internal/THEOplayerView.js.map +1 -1
- package/lib/module/internal/adapter/THEOplayerAdapter.js +5 -2
- package/lib/module/internal/adapter/THEOplayerAdapter.js.map +1 -1
- package/lib/module/internal/utils/Dimensions.js +26 -0
- package/lib/module/internal/utils/Dimensions.js.map +1 -0
- package/lib/typescript/internal/THEOplayerView.d.ts +7 -1
- package/lib/typescript/internal/utils/Dimensions.d.ts +5 -0
- package/package.json +1 -1
- package/react-native-theoplayer.podspec +7 -5
- package/src/internal/THEOplayerView.tsx +25 -5
- package/src/internal/adapter/THEOplayerAdapter.ts +11 -7
- package/src/internal/utils/Dimensions.ts +24 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ 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
|
+
## [3.7.1] - 24-02-09
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Fixed a dependency issue on iOS when using chromecast or google ima features.
|
|
13
|
+
|
|
14
|
+
## [3.7.0] - 24-02-09
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Added fullscreen presentation mode support for iOS and Android. More info on the [documentation](./doc/fullscreen.md) page.
|
|
19
|
+
|
|
8
20
|
## [3.6.0] - 24-02-02
|
|
9
21
|
|
|
10
22
|
### Fixed
|
package/README.md
CHANGED
|
@@ -101,6 +101,7 @@ and discussed in the next section. Finally, an overview of features, limitations
|
|
|
101
101
|
- [Casting with Chromecast and Airplay](./doc/cast.md)
|
|
102
102
|
- [Custom iOS framework](./doc/custom-ios-framework.md)
|
|
103
103
|
- [Digital Rights Management (DRM)](./doc/drm.md)
|
|
104
|
+
- [Fullscreen presentation](./doc/fullscreen.md)
|
|
104
105
|
- [Media Caching](./doc/media_caching.md)
|
|
105
106
|
- [Migrating to `react-native-theoplayer` v2.x](./doc/migrating_v2.md)
|
|
106
107
|
- [Picture-in-Picture (PiP)](./doc/pip.md)
|
|
@@ -8,11 +8,20 @@ import android.content.Intent
|
|
|
8
8
|
import android.content.IntentFilter
|
|
9
9
|
import android.content.pm.PackageManager
|
|
10
10
|
import android.os.Build
|
|
11
|
+
import android.view.View
|
|
12
|
+
import android.view.View.OnLayoutChangeListener
|
|
13
|
+
import android.view.ViewGroup
|
|
14
|
+
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
|
15
|
+
import android.view.ViewParent
|
|
11
16
|
import androidx.activity.ComponentActivity
|
|
12
17
|
import androidx.core.view.WindowInsetsCompat
|
|
13
18
|
import androidx.core.view.WindowInsetsControllerCompat
|
|
19
|
+
import androidx.core.view.children
|
|
20
|
+
import androidx.core.view.updateLayoutParams
|
|
14
21
|
import androidx.lifecycle.Lifecycle
|
|
22
|
+
import com.facebook.react.ReactRootView
|
|
15
23
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
24
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
16
25
|
import com.theoplayer.PlayerEventEmitter
|
|
17
26
|
import com.theoplayer.ReactTHEOplayerContext
|
|
18
27
|
import com.theoplayer.android.api.error.ErrorCode
|
|
@@ -28,7 +37,8 @@ class PresentationManager(
|
|
|
28
37
|
private var supportsPip = false
|
|
29
38
|
private var onUserLeaveHintReceiver: BroadcastReceiver? = null
|
|
30
39
|
private var onPictureInPictureModeChanged: BroadcastReceiver? = null
|
|
31
|
-
|
|
40
|
+
private var playerGroupParentNode: ViewGroup? = null
|
|
41
|
+
private var playerGroupChildIndex: Int? = null
|
|
32
42
|
private val pipUtils: PipUtils = PipUtils(viewCtx, reactContext)
|
|
33
43
|
|
|
34
44
|
var currentPresentationMode: PresentationMode = PresentationMode.INLINE
|
|
@@ -189,16 +199,38 @@ class PresentationManager(
|
|
|
189
199
|
}
|
|
190
200
|
val activity = reactContext.currentActivity ?: return
|
|
191
201
|
val window = activity.window
|
|
202
|
+
|
|
203
|
+
// Get the player's ReactViewGroup parent, which contains THEOplayerView and its children (typically the UI).
|
|
204
|
+
val reactPlayerGroup: ReactViewGroup? = getClosestParentOfType(this.viewCtx.playerView)
|
|
205
|
+
|
|
206
|
+
// Get ReactNative's root node or the render hiearchy
|
|
207
|
+
val root: ReactRootView? = getClosestParentOfType(reactPlayerGroup)
|
|
208
|
+
|
|
192
209
|
if (fullscreen) {
|
|
193
210
|
WindowInsetsControllerCompat(window, window.decorView).apply {
|
|
194
211
|
systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
195
212
|
}.hide(WindowInsetsCompat.Type.systemBars())
|
|
196
213
|
updatePresentationMode(PresentationMode.FULLSCREEN)
|
|
214
|
+
|
|
215
|
+
playerGroupParentNode = (reactPlayerGroup?.parent as ReactViewGroup?)?.also { parent ->
|
|
216
|
+
playerGroupChildIndex = parent.indexOfChild(reactPlayerGroup)
|
|
217
|
+
// Re-parent the playerViewGroup to the root node
|
|
218
|
+
parent.removeView(reactPlayerGroup)
|
|
219
|
+
root?.addView(reactPlayerGroup)
|
|
220
|
+
}
|
|
197
221
|
} else {
|
|
198
222
|
WindowInsetsControllerCompat(window, window.decorView).show(
|
|
199
223
|
WindowInsetsCompat.Type.systemBars()
|
|
200
224
|
)
|
|
201
225
|
updatePresentationMode(PresentationMode.INLINE)
|
|
226
|
+
|
|
227
|
+
root?.run {
|
|
228
|
+
// Re-parent the playerViewGroup from the root node to its original parent
|
|
229
|
+
removeView(reactPlayerGroup)
|
|
230
|
+
playerGroupParentNode?.addView(reactPlayerGroup, playerGroupChildIndex ?: 0)
|
|
231
|
+
playerGroupParentNode = null
|
|
232
|
+
playerGroupChildIndex = null
|
|
233
|
+
}
|
|
202
234
|
}
|
|
203
235
|
}
|
|
204
236
|
|
|
@@ -224,3 +256,11 @@ class PresentationManager(
|
|
|
224
256
|
}
|
|
225
257
|
}
|
|
226
258
|
}
|
|
259
|
+
|
|
260
|
+
inline fun <reified T : View> getClosestParentOfType(view: View?): T? {
|
|
261
|
+
var parent: ViewParent? = view?.parent
|
|
262
|
+
while (parent != null && parent !is T) {
|
|
263
|
+
parent = parent.parent
|
|
264
|
+
}
|
|
265
|
+
return parent as? T
|
|
266
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// THEOplayerRCTMainEventHandler.swift
|
|
2
2
|
|
|
3
3
|
import Foundation
|
|
4
4
|
import THEOplayerSDK
|
|
@@ -7,7 +7,7 @@ public class THEOplayerRCTMainEventHandler {
|
|
|
7
7
|
// MARK: Members
|
|
8
8
|
private weak var player: THEOplayer?
|
|
9
9
|
private weak var presentationModeContext: THEOplayerRCTPresentationModeContext?
|
|
10
|
-
private var
|
|
10
|
+
private var loadedMetadataTracksInfo: [[String:Any]] = []
|
|
11
11
|
|
|
12
12
|
// MARK: Events
|
|
13
13
|
var onNativePlay: RCTDirectEventBlock?
|
|
@@ -29,7 +29,6 @@ public class THEOplayerRCTMainEventHandler {
|
|
|
29
29
|
var onNativeRateChange: RCTDirectEventBlock?
|
|
30
30
|
var onNativeWaiting: RCTDirectEventBlock?
|
|
31
31
|
var onNativeCanPlay: RCTDirectEventBlock?
|
|
32
|
-
var onNativePresentationModeChange: RCTDirectEventBlock?
|
|
33
32
|
|
|
34
33
|
// MARK: player Listeners
|
|
35
34
|
private var playListener: EventListener?
|
|
@@ -60,16 +59,15 @@ public class THEOplayerRCTMainEventHandler {
|
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
// MARK: - player setup / breakdown
|
|
63
|
-
func setPlayer(_ player: THEOplayer
|
|
62
|
+
func setPlayer(_ player: THEOplayer) {
|
|
64
63
|
self.player = player
|
|
65
|
-
self.presentationModeContext = presentationModeContext
|
|
66
64
|
|
|
67
65
|
// attach listeners
|
|
68
66
|
self.attachListeners()
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
func
|
|
72
|
-
self.
|
|
69
|
+
func setLoadedMetadataTracksInfo(_ metadataTracksInfo: [[String:Any]]) {
|
|
70
|
+
self.loadedMetadataTracksInfo = metadataTracksInfo
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
// MARK: - attach/dettach main player Listeners
|
|
@@ -273,7 +271,7 @@ public class THEOplayerRCTMainEventHandler {
|
|
|
273
271
|
if let wplayer = player,
|
|
274
272
|
let welf = self,
|
|
275
273
|
let forwardedLoadedMetadataEvent = self?.onNativeLoadedMetadata {
|
|
276
|
-
let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackMetadata(player: wplayer, metadataTracksInfo: welf.
|
|
274
|
+
let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackMetadata(player: wplayer, metadataTracksInfo: welf.loadedMetadataTracksInfo)
|
|
277
275
|
print(metadata)
|
|
278
276
|
forwardedLoadedMetadataEvent(metadata)
|
|
279
277
|
}
|
|
@@ -306,16 +304,6 @@ public class THEOplayerRCTMainEventHandler {
|
|
|
306
304
|
}
|
|
307
305
|
}
|
|
308
306
|
if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] Waiting listener attached to THEOplayer") }
|
|
309
|
-
|
|
310
|
-
// PRESENTATION_MODE_CHANGE
|
|
311
|
-
self.presentationModeChangeListener = player.addEventListener(type: PlayerEventTypes.PRESENTATION_MODE_CHANGE) { [weak self] event in
|
|
312
|
-
if DEBUG_THEOPLAYER_EVENTS || true { PrintUtils.printLog(logText: "[NATIVE] Received PRESENTATION_MODE_CHANGE event from THEOplayer (to \(event.presentationMode._rawValue))") }
|
|
313
|
-
if let forwardedPresentationModeChangeEvent = self?.onNativePresentationModeChange,
|
|
314
|
-
let presentationModeContext = self?.presentationModeContext {
|
|
315
|
-
forwardedPresentationModeChangeEvent(presentationModeContext.eventContextForNewPresentationMode(event.presentationMode))
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] PresentationModeChange listener attached to THEOplayer") }
|
|
319
307
|
}
|
|
320
308
|
|
|
321
309
|
private func dettachListeners() {
|
|
@@ -430,11 +418,5 @@ public class THEOplayerRCTMainEventHandler {
|
|
|
430
418
|
player.removeEventListener(type: PlayerEventTypes.CAN_PLAY, listener: canPlayListener)
|
|
431
419
|
if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] CanPlay listener dettached from THEOplayer") }
|
|
432
420
|
}
|
|
433
|
-
|
|
434
|
-
// PRESENTATION_MODE_CHANGE
|
|
435
|
-
if let presentationModeChangeListener = self.presentationModeChangeListener {
|
|
436
|
-
player.removeEventListener(type: PlayerEventTypes.PRESENTATION_MODE_CHANGE, listener: presentationModeChangeListener)
|
|
437
|
-
if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] PresentationModeChange listener dettached from THEOplayer") }
|
|
438
|
-
}
|
|
439
421
|
}
|
|
440
422
|
}
|
|
@@ -12,7 +12,6 @@ import THEOplayerConnectorSideloadedSubtitle
|
|
|
12
12
|
|
|
13
13
|
let ERROR_MESSAGE_PLAYER_ABR_UNSUPPORTED_FEATURE: String = "Setting an ABRconfig is not supported on iOS/tvOS."
|
|
14
14
|
let ERROR_MESSAGE_PLAYER_QUALITY_UNSUPPORTED_FEATURE: String = "Setting a target video quality is not supported on iOS/tvOS."
|
|
15
|
-
let ERROR_MESSAGE_PLAYER_FULLSCREEN_UNSUPPORTED_FEATURE: String = "Fullscreen presentationmode should be implemented on the RN level for iOS/tvOS."
|
|
16
15
|
|
|
17
16
|
let TTS_PROP_BACKGROUND_COLOR = "backgroundColor"
|
|
18
17
|
let TTS_PROP_EDGE_STYLE = "edgeStyle"
|
|
@@ -85,7 +84,6 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
|
|
|
85
84
|
@objc(setABRConfig:abrConfig:)
|
|
86
85
|
func setABRConfig(_ node: NSNumber, setABRConfig: NSDictionary) -> Void {
|
|
87
86
|
if DEBUG_PLAYER_API { print(ERROR_MESSAGE_PLAYER_ABR_UNSUPPORTED_FEATURE) }
|
|
88
|
-
return
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
@objc(setCurrentTime:time:)
|
|
@@ -135,19 +133,11 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
|
|
|
135
133
|
@objc(setPresentationMode:presentationMode:)
|
|
136
134
|
func setPresentationMode(_ node: NSNumber, presentationMode: String) -> Void {
|
|
137
135
|
DispatchQueue.main.async {
|
|
138
|
-
let
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
else if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView,
|
|
143
|
-
let player = theView.player {
|
|
144
|
-
if player.presentationMode != newPresentationMode {
|
|
145
|
-
if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Changing TheoPlayer to \(presentationMode)") }
|
|
146
|
-
player.presentationMode = newPresentationMode
|
|
147
|
-
}
|
|
136
|
+
if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView {
|
|
137
|
+
let newPresentationMode: PresentationMode = THEOplayerRCTTypeUtils.presentationModeFromString(presentationMode)
|
|
138
|
+
theView.setPresentationMode(newPresentationMode: newPresentationMode)
|
|
148
139
|
}
|
|
149
140
|
}
|
|
150
|
-
return
|
|
151
141
|
}
|
|
152
142
|
|
|
153
143
|
@objc(setAspectRatio:ratio:)
|
|
@@ -162,7 +152,6 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
|
|
|
162
152
|
}
|
|
163
153
|
}
|
|
164
154
|
}
|
|
165
|
-
return
|
|
166
155
|
}
|
|
167
156
|
|
|
168
157
|
@objc(setPipConfig:pipConfig:)
|
|
@@ -173,7 +162,6 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
|
|
|
173
162
|
theView.pipConfig = pipConfig
|
|
174
163
|
}
|
|
175
164
|
}
|
|
176
|
-
return
|
|
177
165
|
}
|
|
178
166
|
|
|
179
167
|
private func parsePipConfig(configDict: NSDictionary) -> PipConfig {
|
|
@@ -190,7 +178,6 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
|
|
|
190
178
|
theView.backgroundAudioConfig = newBackgroundAudioConfig
|
|
191
179
|
}
|
|
192
180
|
}
|
|
193
|
-
return
|
|
194
181
|
}
|
|
195
182
|
|
|
196
183
|
private func parseBackgroundAudioConfig(configDict: NSDictionary) -> BackgroundAudioConfig {
|
|
@@ -37,15 +37,6 @@ class THEOplayerRCTTextTrackEventHandler {
|
|
|
37
37
|
self.attachListeners()
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
func triggerAddMetadataTrackEvent(metadataTrackInfo: [String:Any]) {
|
|
41
|
-
if let addTrackEvent = self.onNativeTextTrackListEvent {
|
|
42
|
-
addTrackEvent([
|
|
43
|
-
"track" : metadataTrackInfo,
|
|
44
|
-
"type" : TrackListEventType.ADD_TRACK.rawValue
|
|
45
|
-
])
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
40
|
// MARK: - attach/dettach textTrackList Listeners
|
|
50
41
|
private func attachListeners() {
|
|
51
42
|
guard let player = self.player else {
|
|
@@ -247,7 +247,7 @@ class THEOplayerRCTTrackMetadataAggregator {
|
|
|
247
247
|
guard trackDescription.kind == .metadata, trackDescription.format == .WebVTT else { continue }
|
|
248
248
|
|
|
249
249
|
let urlString = trackDescription.src.absoluteString
|
|
250
|
-
|
|
250
|
+
THEOplayerRCTSideloadedMetadataProcessor.parseVtt(urlString) { cueArray in
|
|
251
251
|
if let cues = cueArray {
|
|
252
252
|
var track: [String:Any] = [:]
|
|
253
253
|
let trackUid = 1000 + trackIndex
|
|
@@ -11,12 +11,13 @@ public class THEOplayerRCTView: UIView {
|
|
|
11
11
|
public private(set) var broadcastEventHandler: THEOplayerRCTBroadcastEventHandler
|
|
12
12
|
var textTrackEventHandler: THEOplayerRCTTextTrackEventHandler
|
|
13
13
|
var mediaTrackEventHandler: THEOplayerRCTMediaTrackEventHandler
|
|
14
|
+
var metadataTrackEventHandler: THEOplayerRCTSideloadedMetadataTrackEventHandler
|
|
14
15
|
var adEventHandler: THEOplayerRCTAdsEventHandler
|
|
15
16
|
var castEventHandler: THEOplayerRCTCastEventHandler
|
|
17
|
+
var presentationModeManager: THEOplayerRCTPresentationModeManager
|
|
16
18
|
var nowPlayingManager: THEOplayerRCTNowPlayingManager
|
|
17
19
|
var remoteCommandsManager: THEOplayerRCTRemoteCommandsManager
|
|
18
20
|
var pipControlsManager: THEOplayerRCTPipControlsManager
|
|
19
|
-
var presentationModeContext = THEOplayerRCTPresentationModeContext()
|
|
20
21
|
var adsConfig = AdsConfig()
|
|
21
22
|
var castConfig = CastConfig()
|
|
22
23
|
var uiConfig = UIConfig()
|
|
@@ -48,8 +49,10 @@ public class THEOplayerRCTView: UIView {
|
|
|
48
49
|
self.broadcastEventHandler = THEOplayerRCTBroadcastEventHandler()
|
|
49
50
|
self.textTrackEventHandler = THEOplayerRCTTextTrackEventHandler()
|
|
50
51
|
self.mediaTrackEventHandler = THEOplayerRCTMediaTrackEventHandler()
|
|
52
|
+
self.metadataTrackEventHandler = THEOplayerRCTSideloadedMetadataTrackEventHandler()
|
|
51
53
|
self.adEventHandler = THEOplayerRCTAdsEventHandler()
|
|
52
54
|
self.castEventHandler = THEOplayerRCTCastEventHandler()
|
|
55
|
+
self.presentationModeManager = THEOplayerRCTPresentationModeManager()
|
|
53
56
|
self.nowPlayingManager = THEOplayerRCTNowPlayingManager()
|
|
54
57
|
self.remoteCommandsManager = THEOplayerRCTRemoteCommandsManager()
|
|
55
58
|
self.pipControlsManager = THEOplayerRCTPipControlsManager()
|
|
@@ -75,9 +78,10 @@ public class THEOplayerRCTView: UIView {
|
|
|
75
78
|
// Create new player instance
|
|
76
79
|
if let player = self.initPlayer() {
|
|
77
80
|
// Attach player instance to event handlers
|
|
78
|
-
self.mainEventHandler.setPlayer(player
|
|
81
|
+
self.mainEventHandler.setPlayer(player)
|
|
79
82
|
self.textTrackEventHandler.setPlayer(player)
|
|
80
83
|
self.mediaTrackEventHandler.setPlayer(player)
|
|
84
|
+
self.presentationModeManager.setPlayer(player, view: self)
|
|
81
85
|
self.adEventHandler.setPlayer(player)
|
|
82
86
|
self.castEventHandler.setPlayer(player)
|
|
83
87
|
self.nowPlayingManager.setPlayer(player)
|
|
@@ -157,13 +161,9 @@ public class THEOplayerRCTView: UIView {
|
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
func processMetadataTracks(metadataTrackDescriptions: [TextTrackDescription]?) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
for trackInfo in tracksInfo {
|
|
164
|
-
self.textTrackEventHandler.triggerAddMetadataTrackEvent(metadataTrackInfo: trackInfo)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
164
|
+
THEOplayerRCTSideloadedMetadataProcessor.loadTrackInfoFromTrackDescriptions(metadataTrackDescriptions) { tracksInfo in
|
|
165
|
+
self.mainEventHandler.setLoadedMetadataTracksInfo(tracksInfo)
|
|
166
|
+
self.metadataTrackEventHandler.setLoadedMetadataTracksInfo(tracksInfo)
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
@@ -312,9 +312,11 @@ public class THEOplayerRCTView: UIView {
|
|
|
312
312
|
if DEBUG_VIEW { PrintUtils.printLog(logText: "[NATIVE] nativeCanPlay prop set.") }
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
// MARK: - Listener based PRESENTATIONMODE event bridging
|
|
316
|
+
|
|
315
317
|
@objc(setOnNativePresentationModeChange:)
|
|
316
318
|
func setOnNativePresentationModeChange(nativePresentationMode: @escaping RCTDirectEventBlock) {
|
|
317
|
-
self.
|
|
319
|
+
self.presentationModeManager.onNativePresentationModeChange = nativePresentationMode
|
|
318
320
|
if DEBUG_VIEW { PrintUtils.printLog(logText: "[NATIVE] nativePresentationMode prop set.") }
|
|
319
321
|
}
|
|
320
322
|
|
|
@@ -40,12 +40,12 @@ extension THEOplayerRCTView: AVPictureInPictureControllerDelegate {
|
|
|
40
40
|
// MARK: - AVPictureInPictureControllerDelegate
|
|
41
41
|
@available(tvOS 14.0, *)
|
|
42
42
|
public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
|
43
|
-
self.presentationModeContext.pipContext = .PIP_CLOSED
|
|
43
|
+
self.presentationModeManager.presentationModeContext.pipContext = .PIP_CLOSED
|
|
44
44
|
self.pipControlsManager.willStartPip()
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
@available(tvOS 14.0, *)
|
|
48
48
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
|
|
49
|
-
self.presentationModeContext.pipContext = .PIP_RESTORED
|
|
49
|
+
self.presentationModeManager.presentationModeContext.pipContext = .PIP_RESTORED
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -10,18 +10,14 @@ enum PipContext: String {
|
|
|
10
10
|
|
|
11
11
|
class THEOplayerRCTPresentationModeContext {
|
|
12
12
|
// MARK: Members
|
|
13
|
-
var currentPresentationMode: THEOplayerSDK.PresentationMode = .inline // TheoPlayer's initial presentationMode
|
|
14
13
|
var pipContext: PipContext = .PIP_CLOSED
|
|
15
14
|
|
|
16
|
-
func eventContextForNewPresentationMode(
|
|
17
|
-
let previousPresentationMode = self.currentPresentationMode
|
|
18
|
-
self.currentPresentationMode = newPresentationMode
|
|
19
|
-
|
|
15
|
+
func eventContextForNewPresentationMode(oldPresentationMode: PresentationMode, newPresentationMode: PresentationMode) -> [String:Any] {
|
|
20
16
|
var eventContext: [String:Any] = [
|
|
21
|
-
"presentationMode": THEOplayerRCTTypeUtils.presentationModeToString(
|
|
22
|
-
"previousPresentationMode": THEOplayerRCTTypeUtils.presentationModeToString(
|
|
17
|
+
"presentationMode": THEOplayerRCTTypeUtils.presentationModeToString(newPresentationMode),
|
|
18
|
+
"previousPresentationMode": THEOplayerRCTTypeUtils.presentationModeToString(oldPresentationMode),
|
|
23
19
|
]
|
|
24
|
-
if
|
|
20
|
+
if oldPresentationMode == .pictureInPicture {
|
|
25
21
|
eventContext["context"] = ["pip" : self.pipContext.rawValue]
|
|
26
22
|
}
|
|
27
23
|
return eventContext
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// THEOplayerRCTPresentationModeEventHandler.swift
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
import THEOplayerSDK
|
|
5
|
+
|
|
6
|
+
public class THEOplayerRCTPresentationModeManager {
|
|
7
|
+
// MARK: Members
|
|
8
|
+
private weak var player: THEOplayer?
|
|
9
|
+
private weak var view: UIView?
|
|
10
|
+
var presentationModeContext = THEOplayerRCTPresentationModeContext()
|
|
11
|
+
var presentationMode: THEOplayerSDK.PresentationMode = .inline
|
|
12
|
+
|
|
13
|
+
private var containerView: UIView? // view containing the playerView and it's siblings (e.g. UI)
|
|
14
|
+
private var fullscreenParentView: UIView? // target view for fulllscreen representation
|
|
15
|
+
private var inlineParentView: UIView? // target view for inline representation
|
|
16
|
+
|
|
17
|
+
// MARK: Events
|
|
18
|
+
var onNativePresentationModeChange: RCTDirectEventBlock?
|
|
19
|
+
|
|
20
|
+
// MARK: player Listeners
|
|
21
|
+
private var presentationModeChangeListener: EventListener?
|
|
22
|
+
|
|
23
|
+
// MARK: - destruction
|
|
24
|
+
func destroy() {
|
|
25
|
+
// dettach listeners
|
|
26
|
+
self.dettachListeners()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// MARK: - player setup / breakdown
|
|
30
|
+
func setPlayer(_ player: THEOplayer, view: UIView?) {
|
|
31
|
+
self.player = player
|
|
32
|
+
self.view = view
|
|
33
|
+
|
|
34
|
+
// attach listeners
|
|
35
|
+
self.attachListeners()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// MARK: - logic
|
|
39
|
+
|
|
40
|
+
private func enterFullscreen() {
|
|
41
|
+
self.containerView = self.view?.findParentViewOfType(RCTView.self)
|
|
42
|
+
self.fullscreenParentView = self.view?.findParentViewOfType(RCTRootContentView.self)
|
|
43
|
+
self.inlineParentView = self.containerView?.findParentViewOfType(RCTView.self)
|
|
44
|
+
|
|
45
|
+
if let containerView = self.containerView,
|
|
46
|
+
let fullscreenParentView = self.fullscreenParentView {
|
|
47
|
+
containerView.removeFromSuperview()
|
|
48
|
+
fullscreenParentView.addSubview(containerView)
|
|
49
|
+
fullscreenParentView.bringSubviewToFront(containerView)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private func exitFullscreen() {
|
|
54
|
+
if let containerView = self.containerView,
|
|
55
|
+
let inlineParentView = self.inlineParentView {
|
|
56
|
+
containerView.removeFromSuperview()
|
|
57
|
+
inlineParentView.addSubview(containerView)
|
|
58
|
+
inlineParentView.bringSubviewToFront(containerView)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func setPresentationMode(newPresentationMode: THEOplayerSDK.PresentationMode) {
|
|
63
|
+
guard newPresentationMode != self.presentationMode, let player = self.player else { return }
|
|
64
|
+
|
|
65
|
+
// store old presentationMode
|
|
66
|
+
let oldPresentationMode = self.presentationMode
|
|
67
|
+
|
|
68
|
+
// set new presentationMode
|
|
69
|
+
self.presentationMode = newPresentationMode
|
|
70
|
+
|
|
71
|
+
// change prensentationMode
|
|
72
|
+
switch oldPresentationMode {
|
|
73
|
+
case .fullscreen:
|
|
74
|
+
if newPresentationMode == .inline {
|
|
75
|
+
// get out of fullscreen via view reparenting
|
|
76
|
+
self.exitFullscreen();
|
|
77
|
+
} else if newPresentationMode == .pictureInPicture {
|
|
78
|
+
// get out of fullscreen via view reparenting
|
|
79
|
+
self.exitFullscreen();
|
|
80
|
+
// get into pip
|
|
81
|
+
player.presentationMode = .pictureInPicture
|
|
82
|
+
}
|
|
83
|
+
case .inline:
|
|
84
|
+
if newPresentationMode == .fullscreen {
|
|
85
|
+
// get into fullscreen via view reparenting
|
|
86
|
+
self.enterFullscreen();
|
|
87
|
+
} else if newPresentationMode == .pictureInPicture {
|
|
88
|
+
// get into pip
|
|
89
|
+
player.presentationMode = .pictureInPicture
|
|
90
|
+
}
|
|
91
|
+
case .pictureInPicture:
|
|
92
|
+
if newPresentationMode == .fullscreen {
|
|
93
|
+
// get out of pip
|
|
94
|
+
player.presentationMode = .inline
|
|
95
|
+
// get into fullscreen via view reparenting
|
|
96
|
+
self.enterFullscreen();
|
|
97
|
+
} else if newPresentationMode == .inline {
|
|
98
|
+
// get into pip
|
|
99
|
+
player.presentationMode = .inline
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// notify the presentationMode change
|
|
106
|
+
self.notifyPresentationModeChange(oldPresentationMode: oldPresentationMode, newPresentationMode: newPresentationMode)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private func notifyPresentationModeChange(oldPresentationMode: THEOplayerSDK.PresentationMode, newPresentationMode: THEOplayerSDK.PresentationMode) {
|
|
110
|
+
// update the current presentationMode
|
|
111
|
+
self.presentationMode = newPresentationMode
|
|
112
|
+
|
|
113
|
+
if let forwardedPresentationModeChangeEvent = self.onNativePresentationModeChange {
|
|
114
|
+
forwardedPresentationModeChangeEvent(presentationModeContext.eventContextForNewPresentationMode(oldPresentationMode: oldPresentationMode, newPresentationMode: newPresentationMode))
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// MARK: - attach/dettach main player Listeners
|
|
119
|
+
private func attachListeners() {
|
|
120
|
+
guard let player = self.player else {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// PRESENTATION_MODE_CHANGE
|
|
125
|
+
self.presentationModeChangeListener = player.addEventListener(type: PlayerEventTypes.PRESENTATION_MODE_CHANGE) { [weak self] event in
|
|
126
|
+
if let welf = self {
|
|
127
|
+
if DEBUG_THEOPLAYER_EVENTS || true { PrintUtils.printLog(logText: "[NATIVE] Received PRESENTATION_MODE_CHANGE event from THEOplayer (to \(event.presentationMode._rawValue))") }
|
|
128
|
+
welf.setPresentationMode(newPresentationMode: event.presentationMode)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] PresentationModeChange listener attached to THEOplayer") }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private func dettachListeners() {
|
|
135
|
+
guard let player = self.player else {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// PRESENTATION_MODE_CHANGE
|
|
140
|
+
if let presentationModeChangeListener = self.presentationModeChangeListener {
|
|
141
|
+
player.removeEventListener(type: PlayerEventTypes.PRESENTATION_MODE_CHANGE, listener: presentationModeChangeListener)
|
|
142
|
+
if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] PresentationModeChange listener dettached from THEOplayer") }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// UIView extension to look for parent views
|
|
148
|
+
extension UIView {
|
|
149
|
+
func findParentViewOfType<T: UIView>(_ viewType: T.Type) -> T? {
|
|
150
|
+
var currentView: UIView? = self
|
|
151
|
+
while let view = currentView {
|
|
152
|
+
if let parentView = view.superview as? T {
|
|
153
|
+
return parentView
|
|
154
|
+
}
|
|
155
|
+
currentView = view.superview
|
|
156
|
+
}
|
|
157
|
+
return nil
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// THEOplayerRCTView+PresentationMode.swift
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
import THEOplayerSDK
|
|
5
|
+
|
|
6
|
+
extension THEOplayerRCTView {
|
|
7
|
+
|
|
8
|
+
func setPresentationMode(newPresentationMode: THEOplayerSDK.PresentationMode) {
|
|
9
|
+
self.presentationModeManager.setPresentationMode(newPresentationMode: newPresentationMode)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// THEOplayerRCTSideloadedMetadataProcessor.swift
|
|
2
2
|
|
|
3
3
|
import Foundation
|
|
4
4
|
import THEOplayerSDK
|
|
@@ -9,7 +9,17 @@ struct Cue {
|
|
|
9
9
|
var cueContent: String
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class THEOplayerRCTSideloadedMetadataProcessor {
|
|
13
|
+
|
|
14
|
+
class func loadTrackInfoFromTrackDescriptions(_ metadataTrackDescriptions: [TextTrackDescription]?, completed: (([[String:Any]]) -> Void)?) {
|
|
15
|
+
if let trackDescriptions = metadataTrackDescriptions {
|
|
16
|
+
THEOplayerRCTTrackMetadataAggregator.aggregatedMetadataTrackInfo(metadataTrackDescriptions: trackDescriptions) { tracksInfo in
|
|
17
|
+
completed?(tracksInfo)
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
completed?([])
|
|
21
|
+
}
|
|
22
|
+
}
|
|
13
23
|
|
|
14
24
|
class func parseVtt(_ urlString: String, completion: @escaping ([Cue]?) -> Void) {
|
|
15
25
|
guard let url = URL(string: urlString) else {
|
|
@@ -24,7 +34,7 @@ class THEOplayerRCTSideloadedMetadataTrackHandler {
|
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
if let vttString = String(data: data, encoding: .utf8) {
|
|
27
|
-
let cues =
|
|
37
|
+
let cues = THEOplayerRCTSideloadedMetadataProcessor.parseVTTString(vttString)
|
|
28
38
|
completion(cues)
|
|
29
39
|
} else {
|
|
30
40
|
completion(nil)
|
|
@@ -38,7 +48,7 @@ class THEOplayerRCTSideloadedMetadataTrackHandler {
|
|
|
38
48
|
var cues: [Cue] = []
|
|
39
49
|
var currentCue: Cue?
|
|
40
50
|
|
|
41
|
-
let separator =
|
|
51
|
+
let separator = THEOplayerRCTSideloadedMetadataProcessor.separatorSequence(vttString)
|
|
42
52
|
let lines = vttString.components(separatedBy: separator)
|
|
43
53
|
for line in lines {
|
|
44
54
|
if line.isEmpty { // process unprocessed cue to list
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// THEOplayerRCTSideloadedMetadataTrackEventHandler.swift
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
import THEOplayerSDK
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class THEOplayerRCTSideloadedMetadataTrackEventHandler {
|
|
8
|
+
private var loadedMetadataTracksInfo: [[String:Any]] = []
|
|
9
|
+
// MARK: Events
|
|
10
|
+
var onNativeTextTrackListEvent: RCTDirectEventBlock?
|
|
11
|
+
var onNativeTextTrackEvent: RCTDirectEventBlock?
|
|
12
|
+
|
|
13
|
+
func setLoadedMetadataTracksInfo(_ metadataTracksInfo: [[String:Any]]) {
|
|
14
|
+
self.loadedMetadataTracksInfo = metadataTracksInfo
|
|
15
|
+
self.triggerAddMetadataTrackEvent(metadataTracksInfo)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func triggerAddMetadataTrackEvent(_ metadataTrackInfo: [[String:Any]]) {
|
|
19
|
+
for trackInfo in metadataTrackInfo {
|
|
20
|
+
if let addTrackEvent = self.onNativeTextTrackListEvent {
|
|
21
|
+
addTrackEvent([
|
|
22
|
+
"track" : trackInfo,
|
|
23
|
+
"type" : TrackListEventType.ADD_TRACK.rawValue
|
|
24
|
+
])
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|