react-native-theoplayer 3.6.0 → 3.7.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 (34) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -0
  3. package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +41 -1
  4. package/ios/THEOplayerRCTMainEventHandler.swift +6 -24
  5. package/ios/THEOplayerRCTPlayerAPI.swift +3 -16
  6. package/ios/THEOplayerRCTTextTrackEventHandler.swift +0 -9
  7. package/ios/THEOplayerRCTTrackMetadataAggregator.swift +1 -1
  8. package/ios/THEOplayerRCTView.swift +12 -10
  9. package/ios/Theoplayer-Bridging-Header.h +1 -0
  10. package/ios/pip/THEOplayerRCTView+PipConfig.swift +2 -2
  11. package/ios/{THEOplayerRCTPresentationModeContext.swift → presentationMode/THEOplayerRCTPresentationModeContext.swift} +4 -8
  12. package/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift +159 -0
  13. package/ios/presentationMode/THEOplayerRCTView+PresentationMode.swift +11 -0
  14. package/ios/{THEOplayerRCTSideloadedMetadataTrackHandler.swift → sideloadedMetadata/THEOplayerRCTSideloadedMetadataProcessor.swift} +14 -4
  15. package/ios/sideloadedMetadata/THEOplayerRCTSideloadedMetadataTrackEventHandler.swift +28 -0
  16. package/lib/commonjs/internal/THEOplayerView.js +24 -3
  17. package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
  18. package/lib/commonjs/internal/adapter/THEOplayerAdapter.js +3 -0
  19. package/lib/commonjs/internal/adapter/THEOplayerAdapter.js.map +1 -1
  20. package/lib/commonjs/internal/utils/Dimensions.js +31 -0
  21. package/lib/commonjs/internal/utils/Dimensions.js.map +1 -0
  22. package/lib/module/internal/THEOplayerView.js +26 -5
  23. package/lib/module/internal/THEOplayerView.js.map +1 -1
  24. package/lib/module/internal/adapter/THEOplayerAdapter.js +5 -2
  25. package/lib/module/internal/adapter/THEOplayerAdapter.js.map +1 -1
  26. package/lib/module/internal/utils/Dimensions.js +26 -0
  27. package/lib/module/internal/utils/Dimensions.js.map +1 -0
  28. package/lib/typescript/internal/THEOplayerView.d.ts +7 -1
  29. package/lib/typescript/internal/utils/Dimensions.d.ts +5 -0
  30. package/package.json +1 -1
  31. package/react-native-theoplayer.podspec +5 -5
  32. package/src/internal/THEOplayerView.tsx +25 -5
  33. package/src/internal/adapter/THEOplayerAdapter.ts +11 -7
  34. package/src/internal/utils/Dimensions.ts +24 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ 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.0] - 24-02-09
9
+
10
+ ### Added
11
+
12
+ - Added fullscreen presentation mode support for iOS and Android. More info on the [documentation](./doc/fullscreen.md) page.
13
+
8
14
  ## [3.6.0] - 24-02-02
9
15
 
10
16
  ### 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
- // THEOplayerRCTView.swift
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 metadataTracksInfo: [[String:Any]] = []
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, presentationModeContext: THEOplayerRCTPresentationModeContext) {
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 setMetadataTracksInfo(metadataTracksInfo: [[String:Any]]) {
72
- self.metadataTracksInfo = metadataTracksInfo
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.metadataTracksInfo)
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 newPresentationMode: PresentationMode = THEOplayerRCTTypeUtils.presentationModeFromString(presentationMode)
139
- if newPresentationMode == .fullscreen {
140
- print(ERROR_MESSAGE_PLAYER_FULLSCREEN_UNSUPPORTED_FEATURE)
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
- THEOplayerRCTSideloadedMetadataTrackHandler.parseVtt(urlString) { cueArray in
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, presentationModeContext: self.presentationModeContext)
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
- if let trackDescriptions = metadataTrackDescriptions {
161
- THEOplayerRCTTrackMetadataAggregator.aggregatedMetadataTrackInfo(metadataTrackDescriptions: trackDescriptions) { tracksInfo in
162
- self.mainEventHandler.setMetadataTracksInfo(metadataTracksInfo: tracksInfo)
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.mainEventHandler.onNativePresentationModeChange = nativePresentationMode
319
+ self.presentationModeManager.onNativePresentationModeChange = nativePresentationMode
318
320
  if DEBUG_VIEW { PrintUtils.printLog(logText: "[NATIVE] nativePresentationMode prop set.") }
319
321
  }
320
322
 
@@ -6,3 +6,4 @@
6
6
  #import <React/RCTUIManager.h>
7
7
  #import <React/RCTBridgeModule.h>
8
8
  #import <React/RCTEventEmitter.h>
9
+ #import <React/RCTRootContentView.h>
@@ -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(_ newPresentationMode: PresentationMode) -> [String:Any] {
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(self.currentPresentationMode),
22
- "previousPresentationMode": THEOplayerRCTTypeUtils.presentationModeToString(previousPresentationMode),
17
+ "presentationMode": THEOplayerRCTTypeUtils.presentationModeToString(newPresentationMode),
18
+ "previousPresentationMode": THEOplayerRCTTypeUtils.presentationModeToString(oldPresentationMode),
23
19
  ]
24
- if previousPresentationMode == .pictureInPicture {
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
- // THEOplayerRCTMetadataAggregator.swift
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 THEOplayerRCTSideloadedMetadataTrackHandler {
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 = THEOplayerRCTSideloadedMetadataTrackHandler.parseVTTString(vttString)
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 = THEOplayerRCTSideloadedMetadataTrackHandler.separatorSequence(vttString)
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
+ }