react-native-theoplayer 10.4.0 → 10.5.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 +26 -2
- package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +6 -0
- package/android/src/main/java/com/theoplayer/audio/BackgroundAudioConfig.kt +7 -0
- package/android/src/main/java/com/theoplayer/audio/BackgroundAudioConfigAdapter.kt +12 -3
- package/android/src/main/java/com/theoplayer/drm/ContentProtectionAdapter.kt +1 -0
- package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +8 -4
- package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +3 -0
- package/android/src/main/java/com/theoplayer/theolive/EndpointAdapter.kt +16 -0
- package/ios/THEOplayerRCTDebug.swift +1 -1
- package/ios/THEOplayerRCTMediaTrackEventHandler.swift +3 -38
- package/ios/THEOplayerRCTPlayerAPI.swift +1 -0
- package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +51 -63
- package/ios/THEOplayerRCTTrackMetadataAggregator.swift +58 -6
- package/ios/THEOplayerRCTView+AppState.swift +33 -0
- package/ios/THEOplayerRCTView.swift +29 -1
- package/ios/backgroundAudio/THEOplayerRCTBackgroundAudioManager.swift +19 -17
- package/ios/backgroundAudio/THEOplayerRCTNowPlayingManager.swift +3 -3
- package/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +8 -0
- package/ios/cache/THEOplayerRCTCacheAPI.swift +1 -2
- package/ios/pip/THEOplayerRCTPipControlsManager.swift +1 -1
- package/ios/pip/THEOplayerRCTPipManager.swift +38 -26
- package/ios/theolive/THEOplayerRCTSourceDescriptionBuilder+Theolive.swift +2 -2
- package/ios/theolive/THEOplayerRCTTHEOliveEventAdapter.swift +4 -0
- package/lib/commonjs/api/backgroundAudio/BackgroundAudioConfiguration.js.map +1 -1
- package/lib/commonjs/internal/adapter/THEOplayerWebAdapter.js +8 -3
- package/lib/commonjs/internal/adapter/THEOplayerWebAdapter.js.map +1 -1
- package/lib/commonjs/manifest.json +1 -1
- package/lib/module/api/backgroundAudio/BackgroundAudioConfiguration.js.map +1 -1
- package/lib/module/internal/adapter/THEOplayerWebAdapter.js +8 -3
- package/lib/module/internal/adapter/THEOplayerWebAdapter.js.map +1 -1
- package/lib/module/manifest.json +1 -1
- package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts +9 -0
- package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts.map +1 -1
- package/lib/typescript/api/media/MediaControlConfiguration.d.ts +1 -1
- package/lib/typescript/api/source/drm/DRMConfiguration.d.ts +10 -0
- package/lib/typescript/api/source/drm/DRMConfiguration.d.ts.map +1 -1
- package/lib/typescript/api/theolive/TheoLiveEndpoint.d.ts +7 -0
- package/lib/typescript/api/theolive/TheoLiveEndpoint.d.ts.map +1 -1
- package/lib/typescript/internal/adapter/THEOplayerWebAdapter.d.ts.map +1 -1
- package/package.json +1 -1
- package/react-native-theoplayer.podspec +7 -7
- package/src/api/backgroundAudio/BackgroundAudioConfiguration.ts +10 -0
- package/src/api/media/MediaControlConfiguration.ts +1 -1
- package/src/api/source/drm/DRMConfiguration.ts +9 -0
- package/src/api/theolive/TheoLiveEndpoint.ts +8 -0
- package/src/internal/adapter/THEOplayerWebAdapter.ts +8 -3
- package/src/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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
|
+
## [10.5.1] - 25-11-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Support for contentProtection query parameters on THEOlive sources.
|
|
13
|
+
|
|
14
|
+
## [10.5.0] - 25-11-24
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Fixed an issue on iOS where the scrim of an IMA ad was in a wrong position due to incorrect `safeAreaInsets`.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Added `stopOnBackground` property to `BackgroundAudioConfiguration` to control whether playback should stop when the app goes to the background.
|
|
23
|
+
- Added `millicastSrc` to `TheoLiveEndpoint` for Web and Android.
|
|
24
|
+
- Added support for configuring query parameters that are common to multiple key system configurations. The parameters defined in `contentProtection.queryParameters` will be merged with any query parameters that are explicitly defined on a key system configuration, whereby the latter takes precedence.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Updated the active quality info extraction on iOS to use the activeQualityChange event data instead of player API.
|
|
29
|
+
- On iOS, we now also pass all available qualities and the current active quality when bridging video- and audio track data.
|
|
30
|
+
|
|
8
31
|
## [10.4.0] - 25-11-13
|
|
9
32
|
|
|
10
33
|
### Fixed
|
|
@@ -15,6 +38,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
15
38
|
### Added
|
|
16
39
|
|
|
17
40
|
- Pass `streamActivityMonitorId` property for `THEOAdDescription` on iOS and Android.
|
|
41
|
+
- Added `allowLivePlayPause` and `seekToLiveOnResume` properties to `PlayerConfiguration.mediaControl` to control pausing and resuming behavior on live streams from the lockscreen controls.
|
|
18
42
|
|
|
19
43
|
### Changed
|
|
20
44
|
|
|
@@ -138,11 +162,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
138
162
|
|
|
139
163
|
- Deprecated the `BaseSource.integration` property in favor of `TypedSource.type`.
|
|
140
164
|
|
|
141
|
-
|
|
165
|
+
### Added
|
|
142
166
|
|
|
143
167
|
- Added support for `THEOlive` events.
|
|
144
168
|
|
|
145
|
-
|
|
169
|
+
### Fixed
|
|
146
170
|
|
|
147
171
|
- Fixed an issue on Android where, depending on the project structure, the Maven repository list would be incorrect.
|
|
148
172
|
|
|
@@ -458,6 +458,12 @@ class ReactTHEOplayerContext private constructor(
|
|
|
458
458
|
// The player pauses and goes to the background, we can abandon audio focus.
|
|
459
459
|
audioFocusManager?.abandonAudioFocus()
|
|
460
460
|
}
|
|
461
|
+
|
|
462
|
+
// Optionally fully stop play-out, unless PiP mode is active.
|
|
463
|
+
if (backgroundAudioConfig.stopOnBackground
|
|
464
|
+
&& reactContext.currentActivity?.isInPictureInPictureMode != true) {
|
|
465
|
+
player.stop()
|
|
466
|
+
}
|
|
461
467
|
}
|
|
462
468
|
|
|
463
469
|
/**
|
|
@@ -7,4 +7,11 @@ data class BackgroundAudioConfig(
|
|
|
7
7
|
* @defaultValue `false`
|
|
8
8
|
*/
|
|
9
9
|
val enabled: Boolean,
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Set whether the player should stop play-out when the app goes to background.
|
|
13
|
+
*
|
|
14
|
+
* @defaultValue `false`
|
|
15
|
+
*/
|
|
16
|
+
val stopOnBackground: Boolean = false
|
|
10
17
|
)
|
|
@@ -4,12 +4,21 @@ import com.facebook.react.bridge.ReadableMap
|
|
|
4
4
|
|
|
5
5
|
object BackgroundAudioConfigAdapter {
|
|
6
6
|
private const val PROP_ENABLED = "enabled"
|
|
7
|
+
private const val PROP_STOP_ON_BACKGROUND = "stopOnBackground"
|
|
7
8
|
|
|
8
9
|
private const val DEFAULT_ENABLED = false
|
|
10
|
+
private const val DEFAULT_STOP_ON_BACKGROUND = false
|
|
9
11
|
|
|
10
12
|
fun fromProps(props: ReadableMap?): BackgroundAudioConfig {
|
|
11
|
-
val enabled =
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
val enabled = props?.hasKey(PROP_ENABLED)?.let {
|
|
14
|
+
props.getBoolean(PROP_ENABLED)
|
|
15
|
+
} ?: DEFAULT_ENABLED
|
|
16
|
+
val destroy = props?.hasKey(PROP_STOP_ON_BACKGROUND)?.let {
|
|
17
|
+
props.getBoolean(PROP_STOP_ON_BACKGROUND)
|
|
18
|
+
} ?: DEFAULT_STOP_ON_BACKGROUND
|
|
19
|
+
return BackgroundAudioConfig(
|
|
20
|
+
enabled = enabled,
|
|
21
|
+
stopOnBackground = destroy
|
|
22
|
+
)
|
|
14
23
|
}
|
|
15
24
|
}
|
|
@@ -104,6 +104,7 @@ object ContentProtectionAdapter {
|
|
|
104
104
|
if (jsonConfig.has(PROP_INTEGRATION_PARAMETERS)) {
|
|
105
105
|
integrationParameters(fromJSONObjectToMap(jsonConfig.getJSONObject(PROP_INTEGRATION_PARAMETERS)))
|
|
106
106
|
}
|
|
107
|
+
queryParameters(fromJSONObjectToMap(jsonConfig.optJSONObject(PROP_QUERY_PARAMETERS)))
|
|
107
108
|
}.build()
|
|
108
109
|
}
|
|
109
110
|
|
|
@@ -319,14 +319,18 @@ class PresentationManager(
|
|
|
319
319
|
currentPresentationModeChangeContext = context
|
|
320
320
|
eventEmitter.emitPresentationModeChange(presentationMode, prevPresentationMode, context)
|
|
321
321
|
|
|
322
|
-
// Resume
|
|
322
|
+
// Resume play-out when going to PiP and player was playing
|
|
323
323
|
if (presentationMode == PresentationMode.PICTURE_IN_PICTURE && viewCtx.wasPlayingOnHostPause) {
|
|
324
324
|
viewCtx.player.play()
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
//
|
|
328
|
-
if (context?.pip == PresentationModeChangePipContext.CLOSED
|
|
329
|
-
|
|
327
|
+
// When closing PiP window
|
|
328
|
+
if (context?.pip == PresentationModeChangePipContext.CLOSED) {
|
|
329
|
+
// Pause if background audio is not enabled
|
|
330
|
+
if (!viewCtx.backgroundAudioConfig.enabled) viewCtx.player.pause()
|
|
331
|
+
|
|
332
|
+
// Optionally fully stop play-out
|
|
333
|
+
if (viewCtx.backgroundAudioConfig.stopOnBackground) viewCtx.player.stop()
|
|
330
334
|
}
|
|
331
335
|
}
|
|
332
336
|
}
|
|
@@ -178,6 +178,9 @@ class SourceAdapter {
|
|
|
178
178
|
},
|
|
179
179
|
latencyConfiguration=jsonTypedSource.optJSONObject(PROP_LATENCY_CONFIGURATION)?.let {
|
|
180
180
|
parseLatencyConfiguration(it)
|
|
181
|
+
},
|
|
182
|
+
drm=jsonTypedSource.optJSONObject(PROP_CONTENT_PROTECTION)?.let {
|
|
183
|
+
ContentProtectionAdapter.drmConfigurationFromJson(it)
|
|
181
184
|
}
|
|
182
185
|
)
|
|
183
186
|
}
|
|
@@ -4,9 +4,15 @@ import com.facebook.react.bridge.Arguments
|
|
|
4
4
|
import com.facebook.react.bridge.WritableMap
|
|
5
5
|
import com.theoplayer.android.api.theolive.ContentProtectionConfiguration
|
|
6
6
|
import com.theoplayer.android.api.theolive.Endpoint
|
|
7
|
+
import com.theoplayer.android.api.theolive.EndpointMillicastSource
|
|
7
8
|
import com.theoplayer.android.api.theolive.FairPlayConfiguration
|
|
8
9
|
import com.theoplayer.android.api.theolive.KeySystemConfiguration
|
|
9
10
|
|
|
11
|
+
private const val PROP_MILLICAST_SRC = "millicastSrc"
|
|
12
|
+
private const val PROP_MILLICAST_NAME = "name"
|
|
13
|
+
private const val PROP_MILLICAST_ACCOUNTID = "accountId"
|
|
14
|
+
private const val PROP_MILLICAST_SUBSCRIBER_TOKEN ="subscriberToken"
|
|
15
|
+
private const val PROP_MILLICAST_DIRECTOR_URL = "directorUrl"
|
|
10
16
|
private const val PROP_HESP_SRC = "hespSrc"
|
|
11
17
|
private const val PROP_HLS_SRC = "hlsSrc"
|
|
12
18
|
private const val PROP_AD_SRC = "adSrc"
|
|
@@ -24,8 +30,18 @@ private const val PROP_CERTIFICATE_URL = "certificateUrl"
|
|
|
24
30
|
|
|
25
31
|
object EndpointAdapter {
|
|
26
32
|
|
|
33
|
+
fun fromEndPointMillicastSource(millicastSource: EndpointMillicastSource): WritableMap {
|
|
34
|
+
return Arguments.createMap().apply {
|
|
35
|
+
putString(PROP_MILLICAST_NAME , millicastSource.name)
|
|
36
|
+
putString(PROP_MILLICAST_ACCOUNTID, millicastSource.accountId)
|
|
37
|
+
millicastSource.directorUrl?.let { putString(PROP_MILLICAST_DIRECTOR_URL, it) }
|
|
38
|
+
millicastSource.subscriberToken?.let { putString(PROP_MILLICAST_SUBSCRIBER_TOKEN, it) }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
fun fromEndpoint(endPoint: Endpoint): WritableMap {
|
|
28
43
|
return Arguments.createMap().apply {
|
|
44
|
+
endPoint.millicastSrc?.let { putMap(PROP_MILLICAST_SRC, fromEndPointMillicastSource(it)) }
|
|
29
45
|
endPoint.hespSrc?.let { putString(PROP_HESP_SRC, it) }
|
|
30
46
|
endPoint.hlsSrc?.let { putString(PROP_HLS_SRC, it) }
|
|
31
47
|
endPoint.adSrc?.let { putString(PROP_AD_SRC, it) }
|
|
@@ -15,7 +15,7 @@ let DEBUG_THEOPLAYER_INTERACTION = DEBUG && false
|
|
|
15
15
|
// Debug flag to monitor contentProtection integration handling
|
|
16
16
|
let DEBUG_CONTENT_PROTECTION_API = DEBUG && false
|
|
17
17
|
|
|
18
|
-
// Debug flag to monitor all updates made
|
|
18
|
+
// Debug flag to monitor all updates made on bridged properties
|
|
19
19
|
let DEBUG_VIEW = DEBUG && false
|
|
20
20
|
|
|
21
21
|
// Debug flag to monitor correct SourceDescription buildup
|
|
@@ -235,26 +235,12 @@ class THEOplayerRCTMediaTrackEventHandler {
|
|
|
235
235
|
let player = self.player,
|
|
236
236
|
let track = self.activeTrack(tracks: player.videoTracks) {
|
|
237
237
|
if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received ACTIVE_QUALITY_CHANGED event for videoTrack") }
|
|
238
|
-
|
|
239
|
-
let label = self.labelFromBandWidth(track.activeQualityBandwidth)
|
|
238
|
+
|
|
240
239
|
forwardedMediaTrackEvent([
|
|
241
240
|
"trackUid" : track.uid,
|
|
242
241
|
"type" : MediaTrackEventType.ACTIVE_QUALITY_CHANGED.rawValue,
|
|
243
242
|
"trackType": MediaTrackType.VIDEO.rawValue,
|
|
244
|
-
"qualities":
|
|
245
|
-
"bandwidth": track.activeQualityBandwidth,
|
|
246
|
-
"codecs": "",
|
|
247
|
-
"id": identifier,
|
|
248
|
-
"uid": identifier,
|
|
249
|
-
"name": label,
|
|
250
|
-
"label": label,
|
|
251
|
-
"available": true,
|
|
252
|
-
"width": player.videoWidth,
|
|
253
|
-
"height": player.videoHeight,
|
|
254
|
-
//"frameRate": 0, // not available on iOS SDK
|
|
255
|
-
//"firstFrame": 0 // not available on iOS SDK
|
|
256
|
-
|
|
257
|
-
]
|
|
243
|
+
"qualities": THEOplayerRCTTrackMetadataAggregator.aggregatedQualityInfo(quality: event.quality)
|
|
258
244
|
])
|
|
259
245
|
}
|
|
260
246
|
}
|
|
@@ -264,23 +250,12 @@ class THEOplayerRCTMediaTrackEventHandler {
|
|
|
264
250
|
let player = self.player,
|
|
265
251
|
let track = self.activeTrack(tracks: player.audioTracks) {
|
|
266
252
|
if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received ACTIVE_QUALITY_CHANGED event for audioTrack") }
|
|
267
|
-
let identifier = String(track.activeQualityBandwidth)
|
|
268
|
-
let label = self.labelFromBandWidth(track.activeQualityBandwidth)
|
|
269
253
|
|
|
270
254
|
forwardedMediaTrackEvent([
|
|
271
255
|
"trackUid" : track.uid,
|
|
272
256
|
"type" : MediaTrackEventType.ACTIVE_QUALITY_CHANGED.rawValue,
|
|
273
257
|
"trackType": MediaTrackType.AUDIO.rawValue,
|
|
274
|
-
"qualities":
|
|
275
|
-
"bandwidth": track.activeQualityBandwidth,
|
|
276
|
-
"codecs": "",
|
|
277
|
-
"id": identifier,
|
|
278
|
-
"uid": identifier,
|
|
279
|
-
"name": label,
|
|
280
|
-
"label": label,
|
|
281
|
-
"available": true,
|
|
282
|
-
//"audioSamplingRate": 0 // not available on iOS SDK
|
|
283
|
-
]
|
|
258
|
+
"qualities": THEOplayerRCTTrackMetadataAggregator.aggregatedQualityInfo(quality: event.quality)
|
|
284
259
|
])
|
|
285
260
|
}
|
|
286
261
|
}
|
|
@@ -299,14 +274,4 @@ class THEOplayerRCTMediaTrackEventHandler {
|
|
|
299
274
|
}
|
|
300
275
|
return nil;
|
|
301
276
|
}
|
|
302
|
-
|
|
303
|
-
private func labelFromBandWidth(_ bandWidth: Int) -> String {
|
|
304
|
-
if bandWidth > 1000000 {
|
|
305
|
-
return "\(Double(bandWidth / 1000) / 1000) Mbps"
|
|
306
|
-
} else if bandWidth > 1000 {
|
|
307
|
-
return "\(bandWidth / 1000) kbps"
|
|
308
|
-
} else {
|
|
309
|
-
return "No Label"
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
277
|
}
|
|
@@ -268,6 +268,7 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
|
|
|
268
268
|
private func parseBackgroundAudioConfig(configDict: NSDictionary) -> BackgroundAudioConfig {
|
|
269
269
|
var backgroundAudio = BackgroundAudioConfig()
|
|
270
270
|
backgroundAudio.enabled = configDict["enabled"] as? Bool ?? false
|
|
271
|
+
backgroundAudio.stopOnBackground = configDict["stopOnBackground"] as? Bool ?? false
|
|
271
272
|
backgroundAudio.shouldResumeAfterInterruption = configDict["shouldResumeAfterInterruption"] as? Bool ?? false
|
|
272
273
|
if let audioSessionModeString = configDict["audioSessionMode"] as? String {
|
|
273
274
|
backgroundAudio.audioSessionMode = THEOplayerRCTTypeUtils.audioSessionModeFromString(audioSessionModeString)
|
|
@@ -63,6 +63,7 @@ let SD_PROP_RETRIEVE_POD_ID_URI: String = "retrievePodIdURI"
|
|
|
63
63
|
let SD_PROP_INITIALIZATION_DELAY: String = "initializationDelay"
|
|
64
64
|
let SD_PROP_HLS_DATE_RANGE: String = "hlsDateRange"
|
|
65
65
|
let SD_PROP_CMCD: String = "cmcd"
|
|
66
|
+
let SD_PROP_QUERY_PARAMETERS = "queryParameters"
|
|
66
67
|
|
|
67
68
|
let EXTENSION_HLS: String = ".m3u8"
|
|
68
69
|
let EXTENSION_MP4: String = ".mp4"
|
|
@@ -194,7 +195,7 @@ class THEOplayerRCTSourceDescriptionBuilder {
|
|
|
194
195
|
let type = typedSourceData[SD_PROP_TYPE] as? String
|
|
195
196
|
|
|
196
197
|
if integration == "theolive" || type == "theolive" {
|
|
197
|
-
return THEOplayerRCTSourceDescriptionBuilder.buildTHEOliveDescription(typedSourceData)
|
|
198
|
+
return THEOplayerRCTSourceDescriptionBuilder.buildTHEOliveDescription(typedSourceData, contentPotection: contentProtection)
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
if type == "millicast" {
|
|
@@ -230,8 +231,7 @@ class THEOplayerRCTSourceDescriptionBuilder {
|
|
|
230
231
|
guard let contentProtectionData = typedSourceData[SD_PROP_CONTENT_PROTECTION] as? [String:Any] else {
|
|
231
232
|
return nil
|
|
232
233
|
}
|
|
233
|
-
|
|
234
|
-
return THEOplayerRCTSourceDescriptionBuilder.buildContentProtection(sanitisedContentProtectionData)
|
|
234
|
+
return THEOplayerRCTSourceDescriptionBuilder.buildContentProtection(contentProtectionData)
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
/**
|
|
@@ -339,77 +339,65 @@ class THEOplayerRCTSourceDescriptionBuilder {
|
|
|
339
339
|
}
|
|
340
340
|
#endif
|
|
341
341
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
- returns: a THEOplayer
|
|
342
|
+
/**
|
|
343
|
+
Creates a THEOplayer DRMConfiguration. This requires a contentProtection property in the RN source description.
|
|
344
|
+
- returns: a THEOplayer DRMConfiguration
|
|
345
345
|
*/
|
|
346
|
-
static func
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
static func buildContentProtection(_ contentProtectionData: [String:Any]) -> MultiplatformDRMConfiguration? {
|
|
347
|
+
let customIntegrationId = contentProtectionData[SD_PROP_INTEGRATION] as? String ?? "internal"
|
|
348
|
+
|
|
349
|
+
// fairplay
|
|
350
|
+
var fairplayKeySystem: THEOplayerSDK.KeySystemConfiguration? = nil
|
|
349
351
|
if let fairplayData = contentProtectionData[SD_PROP_FAIRPLAY] as? [String:Any] {
|
|
350
|
-
var sanitisedFairplayData: [String:Any] = [:]
|
|
351
352
|
// certificateUrl
|
|
352
|
-
|
|
353
|
-
sanitisedFairplayData[SD_PROP_CERTIFICATE_URL] = certificateUrl
|
|
354
|
-
}
|
|
355
|
-
// convert certificate into certificateUrl with marker prefix (also supported by THEOplayer Web SDK)
|
|
353
|
+
var certificateUrl = fairplayData[SD_PROP_CERTIFICATE_URL] as? String
|
|
356
354
|
if let certificate = fairplayData[SD_PROP_CERTIFICATE] as? String {
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
// licenseAcquisitionURL
|
|
360
|
-
if let licenseAcquisitionURL = fairplayData[SD_PROP_LICENSE_URL] as? String {
|
|
361
|
-
sanitisedFairplayData[SD_PROP_LICENSE_URL] = licenseAcquisitionURL
|
|
355
|
+
certificateUrl = "\(CERTIFICATE_MARKER)\(certificate)"
|
|
362
356
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
357
|
+
let licenseAcquisitionURL = fairplayData[SD_PROP_LICENSE_URL] as? String
|
|
358
|
+
let headers = fairplayData[SD_PROP_HEADERS] as? [String:String]
|
|
359
|
+
let licenseType = fairplayData[SD_PROP_LICENSE_TYPE] as? String
|
|
360
|
+
let queryParameters = fairplayData[SD_PROP_QUERY_PARAMETERS] as? [String:String]
|
|
361
|
+
|
|
362
|
+
fairplayKeySystem = KeySystemConfiguration(
|
|
363
|
+
licenseAcquisitionURL: licenseAcquisitionURL,
|
|
364
|
+
certificateURL: certificateUrl,
|
|
365
|
+
licenseType: licenseType == "persistent" ? LicenseType.persistent : LicenseType.temporary,
|
|
366
|
+
headers: headers,
|
|
367
|
+
queryParameters: queryParameters
|
|
368
|
+
)
|
|
372
369
|
}
|
|
373
|
-
|
|
370
|
+
|
|
371
|
+
// widevine
|
|
372
|
+
var widevineKeySystem: THEOplayerSDK.KeySystemConfiguration? = nil
|
|
374
373
|
if let widevineData = contentProtectionData[SD_PROP_WIDEVINE] as? [String:Any] {
|
|
375
|
-
var sanitisedWidevineData: [String:Any] = [:]
|
|
376
374
|
// certificateUrl
|
|
377
|
-
|
|
378
|
-
sanitisedWidevineData[SD_PROP_CERTIFICATE_URL] = certificateUrl
|
|
379
|
-
}
|
|
380
|
-
// convert certificate into certificateUrl with marker prefix (also supported by THEOplayer Web SDK)
|
|
375
|
+
var certificateUrl = widevineData[SD_PROP_CERTIFICATE_URL] as? String
|
|
381
376
|
if let certificate = widevineData[SD_PROP_CERTIFICATE] as? String {
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
// licenseAcquisitionURL
|
|
385
|
-
if let licenseAcquisitionURL = widevineData[SD_PROP_LICENSE_URL] as? String {
|
|
386
|
-
sanitisedWidevineData[SD_PROP_LICENSE_URL] = licenseAcquisitionURL
|
|
387
|
-
}
|
|
388
|
-
// headers
|
|
389
|
-
if let headers = widevineData[SD_PROP_HEADERS] as? [String:String] {
|
|
390
|
-
sanitisedWidevineData[SD_PROP_HEADERS] = headers
|
|
377
|
+
certificateUrl = "\(CERTIFICATE_MARKER)\(certificate)"
|
|
391
378
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
*/
|
|
405
|
-
static func buildContentProtection(_ contentProtectionData: [String:Any]) -> MultiplatformDRMConfiguration? {
|
|
406
|
-
do {
|
|
407
|
-
let data = try JSONSerialization.data(withJSONObject: contentProtectionData)
|
|
408
|
-
return try JSONDecoder().decode(MultiplatformDRMConfiguration.self, from: data)
|
|
409
|
-
} catch {
|
|
410
|
-
PrintUtils.printLog(logText: "[NATIVE] unsupported contentProtection data format")
|
|
379
|
+
let licenseAcquisitionURL = widevineData[SD_PROP_LICENSE_URL] as? String
|
|
380
|
+
let headers = widevineData[SD_PROP_HEADERS] as? [String:String]
|
|
381
|
+
let licenseType = widevineData[SD_PROP_LICENSE_TYPE] as? String
|
|
382
|
+
let queryParameters = widevineData[SD_PROP_QUERY_PARAMETERS] as? [String:String]
|
|
383
|
+
|
|
384
|
+
widevineKeySystem = KeySystemConfiguration(
|
|
385
|
+
licenseAcquisitionURL: licenseAcquisitionURL,
|
|
386
|
+
certificateURL: certificateUrl,
|
|
387
|
+
licenseType: licenseType == "persistent" ? LicenseType.persistent : LicenseType.temporary,
|
|
388
|
+
headers: headers,
|
|
389
|
+
queryParameters: queryParameters
|
|
390
|
+
)
|
|
411
391
|
}
|
|
412
|
-
|
|
392
|
+
|
|
393
|
+
// global query parameters
|
|
394
|
+
let queryParameters = contentProtectionData[SD_PROP_QUERY_PARAMETERS] as? [String:String]
|
|
395
|
+
|
|
396
|
+
return MultiplatformDRMConfiguration(
|
|
397
|
+
customIntegrationId: customIntegrationId,
|
|
398
|
+
keySystemConfigurations: KeySystemConfigurationCollection(fairplay: fairplayKeySystem, widevine: widevineKeySystem),
|
|
399
|
+
queryParameters: queryParameters
|
|
400
|
+
)
|
|
413
401
|
}
|
|
414
402
|
|
|
415
403
|
|
|
@@ -179,9 +179,16 @@ class THEOplayerRCTTrackMetadataAggregator {
|
|
|
179
179
|
entry[PROP_LABEL] = audioTrack.label
|
|
180
180
|
entry[PROP_UNLOCALIZED_LABEL] = audioTrack.unlocalizedLabel
|
|
181
181
|
entry[PROP_ENABLED] = audioTrack.enabled
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
|
|
182
|
+
|
|
183
|
+
// add known qualities
|
|
184
|
+
entry[PROP_QUALITIES] = (0..<audioTrack.qualities.count).map { index in
|
|
185
|
+
return THEOplayerRCTTrackMetadataAggregator.aggregatedQualityInfo(quality: audioTrack.qualities.get(index))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// add active quality
|
|
189
|
+
if let activeQuality = audioTrack.activeQuality {
|
|
190
|
+
entry[PROP_ACTIVE_QUALITY] = THEOplayerRCTTrackMetadataAggregator.aggregatedQualityInfo(quality: activeQuality)
|
|
191
|
+
}
|
|
185
192
|
return entry
|
|
186
193
|
}
|
|
187
194
|
|
|
@@ -221,9 +228,17 @@ class THEOplayerRCTTrackMetadataAggregator {
|
|
|
221
228
|
entry[PROP_LABEL] = videoTrack.label
|
|
222
229
|
entry[PROP_UNLOCALIZED_LABEL] = videoTrack.unlocalizedLabel
|
|
223
230
|
entry[PROP_ENABLED] = videoTrack.enabled
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
|
|
231
|
+
|
|
232
|
+
// add known qualities
|
|
233
|
+
entry[PROP_QUALITIES] = (0..<videoTrack.qualities.count).map { index in
|
|
234
|
+
return THEOplayerRCTTrackMetadataAggregator.aggregatedQualityInfo(quality: videoTrack.qualities.get(index))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// add active quality
|
|
238
|
+
if let activeQuality = videoTrack.activeQuality {
|
|
239
|
+
entry[PROP_ACTIVE_QUALITY] = THEOplayerRCTTrackMetadataAggregator.aggregatedQualityInfo(quality: activeQuality)
|
|
240
|
+
}
|
|
241
|
+
|
|
227
242
|
return entry
|
|
228
243
|
}
|
|
229
244
|
|
|
@@ -240,6 +255,43 @@ class THEOplayerRCTTrackMetadataAggregator {
|
|
|
240
255
|
return 0
|
|
241
256
|
}
|
|
242
257
|
|
|
258
|
+
class func labelFromBandWidth(_ bandWidth: Int) -> String {
|
|
259
|
+
if bandWidth > 1000000 {
|
|
260
|
+
return "\(Double(bandWidth / 1000) / 1000) Mbps"
|
|
261
|
+
} else if bandWidth > 1000 {
|
|
262
|
+
return "\(bandWidth / 1000) kbps"
|
|
263
|
+
} else {
|
|
264
|
+
return "No Label"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
class func aggregatedQualityInfo(quality: Quality) -> [String:Any] {
|
|
269
|
+
let identifier = String(quality.bandwidth)
|
|
270
|
+
let label = THEOplayerRCTTrackMetadataAggregator.labelFromBandWidth(quality.bandwidth)
|
|
271
|
+
|
|
272
|
+
// common properties
|
|
273
|
+
var entry: [String:Any] = [
|
|
274
|
+
"bandwidth": quality.bandwidth,
|
|
275
|
+
"codecs": "",
|
|
276
|
+
"id": identifier,
|
|
277
|
+
"uid": identifier,
|
|
278
|
+
"name": label,
|
|
279
|
+
"label": label,
|
|
280
|
+
"available": true,
|
|
281
|
+
]
|
|
282
|
+
if let averageBandwidth = quality.averageBandwidth {
|
|
283
|
+
entry["averageBandwidth"] = averageBandwidth
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// videoQuality specific properties
|
|
287
|
+
if let videoQuality = quality as? VideoQuality {
|
|
288
|
+
entry["width"] = videoQuality.width
|
|
289
|
+
entry["height"] = videoQuality.height
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return entry
|
|
293
|
+
}
|
|
294
|
+
|
|
243
295
|
class func aggregatedMetadataAndChapterTrackInfo(trackDescriptions: [TextTrackDescription], completed: (([[String:Any]]) -> Void)? ) {
|
|
244
296
|
var trackIndex = 0
|
|
245
297
|
var tracksInfo: [[String:Any]] = []
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// THEOplayerRCTView+AppState.swift
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
extension THEOplayerRCTView {
|
|
6
|
+
func setupAppStateObservers() {
|
|
7
|
+
NotificationCenter.default.addObserver(
|
|
8
|
+
self,
|
|
9
|
+
selector: #selector(applicationDidEnterBackground),
|
|
10
|
+
name: UIApplication.didEnterBackgroundNotification,
|
|
11
|
+
object: nil
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
NotificationCenter.default.addObserver(
|
|
15
|
+
self,
|
|
16
|
+
selector: #selector(applicationWillEnterForeground),
|
|
17
|
+
name: UIApplication.willEnterForegroundNotification,
|
|
18
|
+
object: nil
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func clearAppStateObservers() {
|
|
23
|
+
NotificationCenter.default.removeObserver(self)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@objc private func applicationDidEnterBackground() {
|
|
27
|
+
self.isApplicationInBackground = true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@objc private func applicationWillEnterForeground() {
|
|
31
|
+
self.isApplicationInBackground = false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -21,6 +21,8 @@ import THEOplayerMillicastIntegration
|
|
|
21
21
|
import THEOplayerTHEOliveIntegration
|
|
22
22
|
#endif
|
|
23
23
|
|
|
24
|
+
let SAFE_AREA_INSET_OFFSET: CGFloat = 47.0
|
|
25
|
+
|
|
24
26
|
public class THEOplayerRCTView: UIView {
|
|
25
27
|
// MARK: Members
|
|
26
28
|
public private(set) var player: THEOplayer?
|
|
@@ -40,6 +42,7 @@ public class THEOplayerRCTView: UIView {
|
|
|
40
42
|
var remoteCommandsManager: THEOplayerRCTRemoteCommandsManager
|
|
41
43
|
var pipManager: THEOplayerRCTPipManager
|
|
42
44
|
var pipControlsManager: THEOplayerRCTPipControlsManager
|
|
45
|
+
var isApplicationInBackground: Bool = (UIApplication.shared.applicationState == .background)
|
|
43
46
|
|
|
44
47
|
var adsConfig = AdsConfig()
|
|
45
48
|
var castConfig = CastConfig()
|
|
@@ -108,8 +111,9 @@ public class THEOplayerRCTView: UIView {
|
|
|
108
111
|
self.remoteCommandsManager = THEOplayerRCTRemoteCommandsManager()
|
|
109
112
|
self.pipManager = THEOplayerRCTPipManager()
|
|
110
113
|
self.pipControlsManager = THEOplayerRCTPipControlsManager()
|
|
111
|
-
|
|
112
114
|
super.init(frame: .zero)
|
|
115
|
+
|
|
116
|
+
self.setupAppStateObservers()
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
required init?(coder aDecoder: NSCoder) {
|
|
@@ -126,6 +130,8 @@ public class THEOplayerRCTView: UIView {
|
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
deinit {
|
|
133
|
+
self.clearAppStateObservers()
|
|
134
|
+
|
|
129
135
|
self.mainEventHandler.destroy()
|
|
130
136
|
self.textTrackEventHandler.destroy()
|
|
131
137
|
self.mediaTrackEventHandler.destroy()
|
|
@@ -144,9 +150,12 @@ public class THEOplayerRCTView: UIView {
|
|
|
144
150
|
self.destroyBackgroundAudio()
|
|
145
151
|
self.player?.removeAllIntegrations()
|
|
146
152
|
self.player = nil
|
|
153
|
+
|
|
147
154
|
if DEBUG_THEOPLAYER_INTERACTION { PrintUtils.printLog(logText: "[NATIVE] THEOplayer instance destroyed.") }
|
|
148
155
|
}
|
|
149
156
|
|
|
157
|
+
// MARK: - View Layout
|
|
158
|
+
|
|
150
159
|
override public func layoutSubviews() {
|
|
151
160
|
super.layoutSubviews()
|
|
152
161
|
if let player = self.player {
|
|
@@ -157,6 +166,25 @@ public class THEOplayerRCTView: UIView {
|
|
|
157
166
|
}
|
|
158
167
|
}
|
|
159
168
|
|
|
169
|
+
override public var safeAreaInsets: UIEdgeInsets {
|
|
170
|
+
#if os(iOS)
|
|
171
|
+
// When in fullscreen mode, we need to provide some insets
|
|
172
|
+
// to avoid content being obscured by notches or home indicators.
|
|
173
|
+
if self.presentationModeManager.presentationMode == .fullscreen,
|
|
174
|
+
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
175
|
+
let orientation = windowScene.interfaceOrientation
|
|
176
|
+
let isPortrait = orientation.rawValue <= 2
|
|
177
|
+
let verticalInset = isPortrait ? SAFE_AREA_INSET_OFFSET: 0.0
|
|
178
|
+
let horizontalInset = isPortrait ? 0.0: SAFE_AREA_INSET_OFFSET
|
|
179
|
+
return UIEdgeInsets.init(top: verticalInset, left: horizontalInset, bottom: verticalInset, right: horizontalInset)
|
|
180
|
+
}
|
|
181
|
+
#endif
|
|
182
|
+
// When inline, the THEOplayerView itself should be possitioned correctly by the customer,
|
|
183
|
+
// taking into account their app's safe areas, so no explicit safe area insets needed.
|
|
184
|
+
// On tvOS no insets required.
|
|
185
|
+
return .zero
|
|
186
|
+
}
|
|
187
|
+
|
|
160
188
|
// MARK: - Create Player
|
|
161
189
|
|
|
162
190
|
func createPlayer() {
|