react-native-theoplayer 9.1.2 → 9.2.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 (70) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -1
  3. package/android/build.gradle +12 -0
  4. package/android/proguard-rules.pro +1 -0
  5. package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +14 -0
  6. package/android/src/main/java/com/theoplayer/source/MillicastSourceAdapter.kt +104 -0
  7. package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +7 -5
  8. package/android/src/main/java/com/theoplayer/track/TextTrackStyleAdapter.kt +4 -0
  9. package/ios/THEOplayerRCTBridge.m +2 -0
  10. package/ios/THEOplayerRCTPlayerAPI.swift +13 -0
  11. package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +5 -1
  12. package/ios/THEOplayerRCTView.swift +8 -1
  13. package/ios/millicast/THEOplayerRCTSourceDescriptionBuilder+Millicast.swift +189 -0
  14. package/ios/millicast/THEOplayerRCTView+Millicast.swift +21 -0
  15. package/ios/sideloadedMetadata/THEOplayerRCTSideloadedMetadataProcessor.swift +3 -1
  16. package/lib/commonjs/api/barrel.js +31 -20
  17. package/lib/commonjs/api/barrel.js.map +1 -1
  18. package/lib/commonjs/api/millicast/MillicastConnectOptions.js +2 -0
  19. package/lib/commonjs/api/millicast/MillicastConnectOptions.js.map +1 -0
  20. package/lib/commonjs/api/millicast/MillicastSource.js +6 -0
  21. package/lib/commonjs/api/millicast/MillicastSource.js.map +1 -0
  22. package/lib/commonjs/api/millicast/barrel.js +28 -0
  23. package/lib/commonjs/api/millicast/barrel.js.map +1 -0
  24. package/lib/commonjs/api/track/TextTrackStyle.js.map +1 -1
  25. package/lib/commonjs/internal/adapter/track/TextTrackStyleAdapter.js +10 -0
  26. package/lib/commonjs/internal/adapter/track/TextTrackStyleAdapter.js.map +1 -1
  27. package/lib/commonjs/internal/utils/Dimensions.js +4 -6
  28. package/lib/commonjs/internal/utils/Dimensions.js.map +1 -1
  29. package/lib/commonjs/manifest.json +1 -1
  30. package/lib/module/api/barrel.js +1 -0
  31. package/lib/module/api/barrel.js.map +1 -1
  32. package/lib/module/api/millicast/MillicastConnectOptions.js +2 -0
  33. package/lib/module/api/millicast/MillicastConnectOptions.js.map +1 -0
  34. package/lib/module/api/millicast/MillicastSource.js +4 -0
  35. package/lib/module/api/millicast/MillicastSource.js.map +1 -0
  36. package/lib/module/api/millicast/barrel.js +5 -0
  37. package/lib/module/api/millicast/barrel.js.map +1 -0
  38. package/lib/module/api/track/TextTrackStyle.js.map +1 -1
  39. package/lib/module/internal/adapter/track/TextTrackStyleAdapter.js +10 -0
  40. package/lib/module/internal/adapter/track/TextTrackStyleAdapter.js.map +1 -1
  41. package/lib/module/internal/utils/Dimensions.js +5 -7
  42. package/lib/module/internal/utils/Dimensions.js.map +1 -1
  43. package/lib/module/manifest.json +1 -1
  44. package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts +2 -2
  45. package/lib/typescript/api/barrel.d.ts +1 -0
  46. package/lib/typescript/api/barrel.d.ts.map +1 -1
  47. package/lib/typescript/api/millicast/MillicastConnectOptions.d.ts +204 -0
  48. package/lib/typescript/api/millicast/MillicastConnectOptions.d.ts.map +1 -0
  49. package/lib/typescript/api/millicast/MillicastSource.d.ts +51 -0
  50. package/lib/typescript/api/millicast/MillicastSource.d.ts.map +1 -0
  51. package/lib/typescript/api/millicast/barrel.d.ts +3 -0
  52. package/lib/typescript/api/millicast/barrel.d.ts.map +1 -0
  53. package/lib/typescript/api/track/TextTrackStyle.d.ts +7 -0
  54. package/lib/typescript/api/track/TextTrackStyle.d.ts.map +1 -1
  55. package/lib/typescript/internal/adapter/track/TextTrackStyleAdapter.d.ts +3 -0
  56. package/lib/typescript/internal/adapter/track/TextTrackStyleAdapter.d.ts.map +1 -1
  57. package/lib/typescript/internal/utils/Dimensions.d.ts.map +1 -1
  58. package/package.json +3 -2
  59. package/react-native-theoplayer.json +1 -1
  60. package/react-native-theoplayer.podspec +15 -9
  61. package/src/api/backgroundAudio/BackgroundAudioConfiguration.ts +2 -2
  62. package/src/api/barrel.ts +1 -0
  63. package/src/api/millicast/MillicastConnectOptions.ts +229 -0
  64. package/src/api/millicast/MillicastSource.ts +56 -0
  65. package/src/api/millicast/barrel.ts +2 -0
  66. package/src/api/track/TextTrackStyle.ts +8 -0
  67. package/src/internal/adapter/THEOplayerWebAdapter.ts +1 -1
  68. package/src/internal/adapter/track/TextTrackStyleAdapter.ts +12 -0
  69. package/src/internal/utils/Dimensions.ts +5 -7
  70. package/src/manifest.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ 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
+ ## [9.2.0] - 25-05-20
9
+
10
+ ### Added
11
+
12
+ - Added support for Millicast on iOS, Android and Web.
13
+ - Added support for `TextTrackStyle.edgeColor` on Android.
14
+ - Added support for THEOads on tvOS.
15
+
16
+ ### Fixed
17
+
18
+ - Fixed an issue where the last cue of a VTT was not parsed if there is no newline after it.
19
+ - Fixed an issue on Android where a conversion build error would occur when targeting SDK version 9.4.
20
+
21
+ ### Changed
22
+
23
+ - Changed the fullscreen dimensions calculation on iOS to make use of the native screen width and height, while taking into account the device orientation.
24
+
8
25
  ## [9.1.2] - 25-05-14
9
26
 
10
27
  ### Fixed
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # React Native THEOplayer
2
2
 
3
- ![](./doc/logo-react-native.png) ![](./doc/logo-theo.png)
3
+ <img src="./doc/logo-optiview-dark.png#gh-dark-mode-only" height="120" alt="OptiView logo"><img src="./doc/logo-optiview-light.png#gh-light-mode-only" height="120" alt="OptiView logo"><img src="./doc/logo-react-native.png" height="120" alt="React Native logo">
4
4
 
5
5
  ## License
6
6
 
@@ -183,6 +183,7 @@ This section gives an overview of features, limitations and known issues:
183
183
  - [Fullscreen presentation](./doc/fullscreen.md)
184
184
  - [Media Caching](./doc/media-caching.md)
185
185
  - [Migrating to THEOplayer 9.x🔥](./doc/migrating-to-react-native-theoplayer-9.md)
186
+ - [Millicast](./doc/millicast.md)
186
187
  - [Picture-in-Picture (PiP)](./doc/pip.md)
187
188
  - [Subtitles, Closed Captions and Metadata tracks](./doc/texttracks.md)
188
189
  - [Limitations and known issues](./doc/limitations.md)
@@ -40,6 +40,7 @@ def enabledTHEOads = safeExtGet("THEOplayer_extensionTHEOads", 'false').toBoolea
40
40
  def enabledAds = enabledGoogleIMA || enabledGoogleDAI || enabledTHEOads
41
41
  def enabledCast = safeExtGet("THEOplayer_extensionCast", 'false').toBoolean()
42
42
  def enabledMediaSession = safeExtGet("THEOplayer_extensionMediaSession", 'true').toBoolean()
43
+ def enabledMillicast = safeExtGet("THEOplayer_extensionMillicast", 'false').toBoolean()
43
44
 
44
45
  android {
45
46
  compileSdk safeExtGet('THEOplayer_compileSdkVersion', 34)
@@ -76,6 +77,7 @@ android {
76
77
  buildConfigField "boolean", "EXTENSION_ADS", "${enabledAds}"
77
78
  buildConfigField "boolean", "EXTENSION_CAST", "${enabledCast}"
78
79
  buildConfigField "boolean", "EXTENSION_MEDIASESSION", "${enabledMediaSession}"
80
+ buildConfigField "boolean", "EXTENSION_MILLICAST", "${enabledMillicast}"
79
81
 
80
82
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
81
83
 
@@ -173,6 +175,16 @@ dependencies {
173
175
  println('Disable THEOplayer cast extension.')
174
176
  compileOnly "com.theoplayer.theoplayer-sdk-android:integration-cast:$theoplayer_sdk_version"
175
177
  }
178
+
179
+ if (enabledMillicast) {
180
+ println('Enable THEOplayer millicast extension.')
181
+ implementation "com.theoplayer.theoplayer-sdk-android:integration-millicast:${theoplayer_sdk_version}"
182
+ implementation "com.millicast:millicast-sdk-android:2.0.0"
183
+ } else {
184
+ println('Disable THEOplayer millicast extension.')
185
+ compileOnly "com.theoplayer.theoplayer-sdk-android:integration-millicast:${theoplayer_sdk_version}"
186
+ compileOnly "com.millicast:millicast-sdk-android:2.0.0"
187
+ }
176
188
  }
177
189
 
178
190
  // Make sure to align all ads extension versions
@@ -2,6 +2,7 @@
2
2
  # is disabled: it is expected.
3
3
  -dontwarn com.theoplayer.android.api.**
4
4
  -dontwarn com.google.android.gms.cast.**
5
+ -dontwarn com.millicast.**
5
6
 
6
7
  # We rely on gson to instantiate some source classes from json, so make sure they are not obfuscated.
7
8
  -keep,includedescriptorclasses class com.theoplayer.android.api.source.** { *; }
@@ -26,6 +26,8 @@ import com.theoplayer.android.api.cast.CastIntegration
26
26
  import com.theoplayer.android.api.cast.CastIntegrationFactory
27
27
  import com.theoplayer.android.api.event.EventListener
28
28
  import com.theoplayer.android.api.event.player.*
29
+ import com.theoplayer.android.api.millicast.MillicastIntegration
30
+ import com.theoplayer.android.api.millicast.MillicastIntegrationFactory
29
31
  import com.theoplayer.android.api.player.Player
30
32
  import com.theoplayer.android.api.player.RenderingTarget
31
33
  import com.theoplayer.android.connector.mediasession.MediaSessionConnector
@@ -90,6 +92,7 @@ class ReactTHEOplayerContext private constructor(
90
92
  @Suppress("UnstableApiUsage")
91
93
  var wasPlayingOnHostPause: Boolean = false
92
94
  private var isHostPaused: Boolean = false
95
+ private var millicastIntegration: MillicastIntegration? = null
93
96
 
94
97
  private val isBackgroundAudioEnabled: Boolean
95
98
  get() = backgroundAudioConfig.enabled
@@ -332,6 +335,17 @@ class ReactTHEOplayerContext private constructor(
332
335
  } catch (e: Exception) {
333
336
  Log.w(TAG, "Failed to configure Cast integration ${e.message}")
334
337
  }
338
+ try {
339
+ if (BuildConfig.EXTENSION_MILLICAST) {
340
+ millicastIntegration = MillicastIntegrationFactory.createMillicastIntegration()
341
+ .also {
342
+ playerView.player.addIntegration(it)
343
+ }
344
+ }
345
+ } catch (e: Exception) {
346
+ Log.w(TAG, "Failed to configure Millicast integration ${e.message}")
347
+ }
348
+
335
349
  // Add other future integrations here.
336
350
  }
337
351
 
@@ -0,0 +1,104 @@
1
+ package com.theoplayer.source
2
+
3
+ import com.millicast.subscribers.ForcePlayoutDelay
4
+ import com.millicast.subscribers.Option
5
+ import com.theoplayer.BuildConfig
6
+ import com.theoplayer.android.api.error.ErrorCode
7
+ import com.theoplayer.android.api.error.THEOplayerException
8
+ import com.theoplayer.android.api.millicast.MillicastSource
9
+ import org.json.JSONObject
10
+
11
+ private const val PROP_SRC = "src"
12
+ private const val PROP_STREAM_ACCOUNT_ID = "streamAccountId"
13
+ private const val PROP_API_URL = "apiUrl"
14
+ private const val PROP_SUBSCRIBER_TOKEN = "subscriberToken"
15
+ private const val PROP_CONNECT_OPTIONS = "connectOptions"
16
+
17
+ private const val BWE_MONITOR_DURATION_US = "bweMonitorDurationUs"
18
+ private const val BWE_RATE_CHANGE_PERCENTAGE = "bweRateChangePercentage"
19
+ private const val DISABLE_AUDIO = "disableAudio"
20
+ private const val DTX = "dtx"
21
+ private const val EXCLUDED_SOURCE_ID = "excludedSourceIds"
22
+ private const val FORCE_PLAYOUT_DELAY = "forcePlayoutDelay"
23
+ private const val MINIMUM_DELAY = "min"
24
+ private const val MAXIMUM_DELAY = "max"
25
+ private const val FORCE_SMOOTH = "forceSmooth"
26
+ private const val JITTER_MINIMUM_DELAY_MS = "jitterMinimumDelayMs"
27
+ private const val MAXIMUM_BITRATE = "maximumBitrate"
28
+ private const val MULTIPLEXED_AUDIO_TRACK = "multiplexedAudioTracks"
29
+ private const val PINNED_SOURCE_ID = "pinnedSourceId"
30
+ private const val RTC_EVENT_LOG_OUTPUT_PATH = "rtcEventLogOutputPath"
31
+ private const val STATS_DELAY_MS = "statsDelayMs"
32
+ private const val STEREO = "stereo"
33
+ private const val UPWARDS_LAYER_WAIT_TIME_MS = "upwardsLayerWaitTimeMs"
34
+
35
+ private const val ERROR_MILLICAST_NOT_ENABLED = "Millicast support not enabled."
36
+
37
+ fun parseMillicastSource(jsonTypedSource: JSONObject): MillicastSource {
38
+ // Check whether the integration was enabled
39
+ if (!BuildConfig.EXTENSION_MILLICAST) {
40
+ throw THEOplayerException(ErrorCode.SOURCE_INVALID, ERROR_MILLICAST_NOT_ENABLED)
41
+ }
42
+ return MillicastSource(
43
+ src = jsonTypedSource.optString(PROP_SRC),
44
+ streamAccountId = jsonTypedSource.optString(PROP_STREAM_ACCOUNT_ID),
45
+ apiUrl = jsonTypedSource.optString(PROP_API_URL),
46
+ subscriberToken = when (val token = jsonTypedSource.optString(PROP_SUBSCRIBER_TOKEN)) {
47
+ "" -> null
48
+ else -> token
49
+ },
50
+ connectOptions = jsonTypedSource.optJSONObject(PROP_CONNECT_OPTIONS)
51
+ ?.let { buildMillicastConnectOptions(it) }
52
+ )
53
+ }
54
+
55
+ private fun buildMillicastConnectOptions(jsonObject: JSONObject): Option {
56
+ return Option(
57
+ bweMonitorDurationUs = when (val bweMonitorDurationUs = jsonObject.optInt(BWE_MONITOR_DURATION_US, -1)) {
58
+ -1 -> null
59
+ else -> bweMonitorDurationUs
60
+ },
61
+ bweRateChangePercentage = when (val bweRateChangePercentage = jsonObject.optDouble(BWE_RATE_CHANGE_PERCENTAGE, -1.0)) {
62
+ -1.0 -> null
63
+ else -> bweRateChangePercentage
64
+ },
65
+ disableAudio = jsonObject.optBoolean(DISABLE_AUDIO),
66
+ dtx = jsonObject.optBoolean(DTX),
67
+ excludedSourceId = when (val excludedSourceId = jsonObject.optJSONArray(EXCLUDED_SOURCE_ID)) {
68
+ null -> arrayOf()
69
+ else -> Array(excludedSourceId.length()) {
70
+ excludedSourceId.getString(it)
71
+ }
72
+ },
73
+ forcePlayoutDelay = jsonObject.optJSONObject(FORCE_PLAYOUT_DELAY)?.let {
74
+ ForcePlayoutDelay(
75
+ minimumDelay = it.getInt(MINIMUM_DELAY),
76
+ maximumDelay = it.getInt(MAXIMUM_DELAY)
77
+ )
78
+ },
79
+ forceSmooth = jsonObject.optBoolean(FORCE_SMOOTH),
80
+ jitterMinimumDelayMs = jsonObject.optInt(JITTER_MINIMUM_DELAY_MS),
81
+ maximumBitrate = when (val maximumBitrate = jsonObject.optInt(MAXIMUM_BITRATE, -1)) {
82
+ -1 -> null
83
+ else -> maximumBitrate
84
+ },
85
+ multiplexedAudioTrack = when (val multiplexedAudioTrack = jsonObject.optString(MULTIPLEXED_AUDIO_TRACK)) {
86
+ "" -> UByte.MIN_VALUE
87
+ else -> multiplexedAudioTrack.toUByte()
88
+ },
89
+ pinnedSourceId = when (val pinnedSourceId = jsonObject.optString(PINNED_SOURCE_ID)) {
90
+ "" -> null
91
+ else -> pinnedSourceId
92
+ },
93
+ rtcEventLogOutputPath = jsonObject.optString(RTC_EVENT_LOG_OUTPUT_PATH),
94
+ statsDelayMs = when (val statsDelayMs = jsonObject.optInt(STATS_DELAY_MS, -1)) {
95
+ -1 -> 1000
96
+ else -> statsDelayMs
97
+ },
98
+ stereo = jsonObject.optBoolean(STEREO, true),
99
+ upwardsLayerWaitTimeMs = when (val upwardsLayerWaitTimeMs = jsonObject.optInt(UPWARDS_LAYER_WAIT_TIME_MS, -1)) {
100
+ -1 -> null
101
+ else -> upwardsLayerWaitTimeMs
102
+ }
103
+ )
104
+ }
@@ -79,6 +79,7 @@ private const val ERROR_MISSING_CSAI_INTEGRATION = "Missing CSAI integration"
79
79
  private const val PROP_SSAI_INTEGRATION_GOOGLE_DAI = "google-dai"
80
80
 
81
81
  private const val INTEGRATION_THEOLIVE = "theolive"
82
+ private const val TYPE_MILLICAST = "millicast"
82
83
  private const val PLAYBACK_PIPELINE_LEGACY = "legacy"
83
84
 
84
85
  private const val PROP_CMCD = "cmcd"
@@ -107,7 +108,7 @@ class SourceAdapter {
107
108
  // CMCD
108
109
  var cmcdTransmissionMode: CMCDTransmissionMode? = null
109
110
  if (jsonSourceObject.has(PROP_CMCD)) {
110
- cmcdTransmissionMode = parseCmcdTransmissionMode(jsonSourceObject.getJSONObject(PROP_CMCD));
111
+ cmcdTransmissionMode = parseCmcdTransmissionMode(jsonSourceObject.getJSONObject(PROP_CMCD))
111
112
  }
112
113
 
113
114
  // typed sources
@@ -184,8 +185,9 @@ class SourceAdapter {
184
185
  @Throws(THEOplayerException::class)
185
186
  private fun parseTypedSource(jsonTypedSource: JSONObject, cmcdTransmissionMode: CMCDTransmissionMode? = null): TypedSource {
186
187
  // Some integrations do not support the Builder pattern
187
- return when (jsonTypedSource.optString(PROP_INTEGRATION)) {
188
- INTEGRATION_THEOLIVE -> parseTheoLiveSource(jsonTypedSource)
188
+ return when {
189
+ (jsonTypedSource.optString(PROP_INTEGRATION)) == INTEGRATION_THEOLIVE -> parseTheoLiveSource(jsonTypedSource)
190
+ (jsonTypedSource.optString(PROP_TYPE)) == TYPE_MILLICAST -> parseMillicastSource(jsonTypedSource)
189
191
  else -> parseTypedSourceFromBuilder(jsonTypedSource, cmcdTransmissionMode)
190
192
  }
191
193
  }
@@ -406,7 +408,7 @@ class SourceAdapter {
406
408
  }
407
409
 
408
410
  private fun parseMetadataDescription(metadataDescription: JSONObject): MetadataDescription {
409
- val metadata = HashMap<String, Any>()
411
+ val metadata = HashMap<String, Any?>()
410
412
  val keys = metadataDescription.keys()
411
413
  while (keys.hasNext()) {
412
414
  val key = keys.next()
@@ -420,7 +422,7 @@ class SourceAdapter {
420
422
  Log.w(TAG, "Failed to parse metadata key $key")
421
423
  }
422
424
  }
423
- return MetadataDescription(metadata)
425
+ return MetadataDescription(metadata as MutableMap<String, Any?>)
424
426
  }
425
427
 
426
428
  private fun parseDashConfig(dashConfig: JSONObject): DashPlaybackConfiguration {
@@ -6,6 +6,7 @@ import com.theoplayer.android.api.player.track.texttrack.TextTrackStyle
6
6
 
7
7
  private const val PROP_BACKGROUND_COLOR = "backgroundColor"
8
8
  private const val PROP_EDGE_STYLE = "edgeStyle"
9
+ private const val PROP_EDGE_COLOR = "edgeColor"
9
10
  private const val PROP_FONT_COLOR = "fontColor"
10
11
  private const val PROP_FONT_FAMILY = "fontFamily"
11
12
  private const val PROP_FONT_SIZE = "fontSize"
@@ -30,6 +31,9 @@ object TextTrackStyleAdapter {
30
31
  if (props.hasKey(PROP_EDGE_STYLE)) {
31
32
  style.edgeType = edgeStyleFromProps(props.getString(PROP_EDGE_STYLE))
32
33
  }
34
+ if (props.hasKey(PROP_EDGE_COLOR)) {
35
+ style.edgeColor = colorFromBridgeColor(props.getMap(PROP_EDGE_COLOR)) ?: Color.WHITE
36
+ }
33
37
  if (props.hasKey(PROP_FONT_COLOR)) {
34
38
  style.fontColor = colorFromBridgeColor(props.getMap(PROP_FONT_COLOR)) ?: Color.WHITE
35
39
  }
@@ -118,6 +118,8 @@ RCT_EXTERN_METHOD(setTextTrackStyle:(nonnull NSNumber *)node
118
118
  RCT_EXTERN_METHOD(setKeepScreenOn:(nonnull NSNumber *)node
119
119
  keepScreenOn:(BOOL)keepScreenOn)
120
120
 
121
+ RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(getUsableScreenDimensions)
122
+
121
123
  @end
122
124
 
123
125
  // ----------------------------------------------------------------------------
@@ -383,4 +383,17 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
383
383
  }
384
384
  }
385
385
  }
386
+
387
+ @objc(getUsableScreenDimensions)
388
+ func getUsableScreenDimensions() -> NSDictionary {
389
+ let boundsSize: CGSize = UIScreen.main.bounds.size;
390
+ let smallest = boundsSize.width < boundsSize.height ? boundsSize.width : boundsSize.height
391
+ let biggest = boundsSize.width < boundsSize.height ? boundsSize.height : boundsSize.width
392
+ #if os(iOS)
393
+ if UIDevice.current.orientation.isPortrait {
394
+ return ["width": smallest, "height": biggest] // ios portrait
395
+ }
396
+ #endif
397
+ return ["width": biggest, "height": smallest] // ios landscape or tvos
398
+ }
386
399
  }
@@ -189,7 +189,11 @@ class THEOplayerRCTSourceDescriptionBuilder {
189
189
  if let integration = typedSourceData[SD_PROP_INTEGRATION] as? String, integration == "theolive" {
190
190
  return THEOplayerRCTSourceDescriptionBuilder.buildTHEOliveDescription(typedSourceData)
191
191
  }
192
-
192
+
193
+ if let type = typedSourceData[SD_PROP_TYPE] as? String, type == "millicast" {
194
+ return THEOplayerRCTSourceDescriptionBuilder.buildMillicastDescription(typedSourceData)
195
+ }
196
+
193
197
  if let src = typedSourceData[SD_PROP_SRC] as? String {
194
198
  // extract the type
195
199
  let type = typedSourceData[SD_PROP_TYPE] as? String ?? THEOplayerRCTSourceDescriptionBuilder.extractMimeType(src)
@@ -14,6 +14,9 @@ import GoogleInteractiveMediaAds
14
14
  #if canImport(THEOplayerGoogleCastIntegration)
15
15
  import THEOplayerGoogleCastIntegration
16
16
  #endif
17
+ #if canImport(THEOplayerMillicastIntegration)
18
+ import THEOplayerMillicastIntegration
19
+ #endif
17
20
 
18
21
  public class THEOplayerRCTView: UIView {
19
22
  // MARK: Members
@@ -46,7 +49,10 @@ public class THEOplayerRCTView: UIView {
46
49
  #if canImport(THEOplayerGoogleCastIntegration)
47
50
  var castIntegration: THEOplayerGoogleCastIntegration.CastIntegration?
48
51
  #endif
49
-
52
+ #if canImport(THEOplayerMillicastIntegration)
53
+ var millicastIntegration: THEOplayerMillicastIntegration.MillicastIntegration?
54
+ #endif
55
+
50
56
  var mediaControlConfig = MediaControlConfig() {
51
57
  didSet {
52
58
  self.remoteCommandsManager.setMediaControlConfig(mediaControlConfig)
@@ -173,6 +179,7 @@ public class THEOplayerRCTView: UIView {
173
179
  self.initAdsIntegration()
174
180
  self.initCastIntegration()
175
181
  self.initTheoAdsIntegration()
182
+ self.initMillicastIntegration()
176
183
  self.initBackgroundAudio()
177
184
  self.initPip()
178
185
  return self.player
@@ -0,0 +1,189 @@
1
+ // THEOplayerRCTSourceDescriptionBuilder+Millicast.swift
2
+
3
+ import Foundation
4
+ import THEOplayerSDK
5
+ import UIKit
6
+
7
+ #if canImport(THEOplayerMillicastIntegration)
8
+ import MillicastSDK
9
+ import THEOplayerMillicastIntegration
10
+ #endif
11
+
12
+ let SD_PROP_ACCOUNTID: String = "streamAccountId"
13
+ let SD_PROP_SUBSCRIBERTOKEN: String = "subscriberToken"
14
+ let SD_PROP_CONNECTOPTIONS: String = "connectOptions"
15
+
16
+ extension THEOplayerRCTSourceDescriptionBuilder {
17
+
18
+ /**
19
+ Builds a THEOplayer SourceDescription that can be passed as a source for the THEOplayer.
20
+ - returns: a Millicast TypedSource.
21
+ */
22
+ static func buildMillicastDescription(_ millicastData: [String:Any]) -> TypedSource? {
23
+ #if canImport(THEOplayerMillicastIntegration)
24
+ if let src = millicastData[SD_PROP_SRC] as? String,
25
+ let accountID = millicastData[SD_PROP_ACCOUNTID] as? String {
26
+ let subscriberToken = millicastData[SD_PROP_SUBSCRIBERTOKEN] as? String;
27
+
28
+ if millicastData[SD_PROP_CONNECTOPTIONS] == nil {
29
+ return MillicastSource(src: src, streamAccountId: accountID, subscriberToken: subscriberToken)
30
+ }
31
+
32
+ let dict = millicastData[SD_PROP_CONNECTOPTIONS] as? NSDictionary;
33
+ let connectOptions = buildConnectOptions(dict)
34
+ return MillicastSource(src: src, streamAccountId: accountID, subscriberToken: nil, connectOptions: connectOptions)
35
+ }
36
+ #endif
37
+ return nil
38
+ }
39
+
40
+ #if canImport(THEOplayerMillicastIntegration)
41
+ static func buildConnectOptions(_ connectOptions: NSDictionary?) -> MCClientOptions {
42
+ let result: MCClientOptions = .init()
43
+ if let bweMonitorDurationUs = connectOptions?["bweMonitorDurationUs"] as? NSNumber {
44
+ result.bweMonitorDurationUs = bweMonitorDurationUs
45
+ }
46
+ if let bweRateChangePercentage = connectOptions?["bweRateChangePercentage"] as? NSNumber {
47
+ result.bweRateChangePercentage = bweRateChangePercentage
48
+ }
49
+ if let degradationPreferences = connectOptions?["degradationPreferences"] as? String {
50
+ switch degradationPreferences {
51
+ case "balanced":
52
+ result.degradationPreferences = MCDegradationPreferences.balanced
53
+ case "disabled":
54
+ result.degradationPreferences = MCDegradationPreferences.disabled
55
+ case "maintainFrameRate":
56
+ result.degradationPreferences = MCDegradationPreferences.maintainFrameRate
57
+ case "maintainResolution":
58
+ result.degradationPreferences = MCDegradationPreferences.maintainResolution
59
+ default:
60
+ result.degradationPreferences = MCDegradationPreferences.default
61
+ }
62
+ }
63
+ if let disableAudio = connectOptions?["disableAudio"] as? Bool {
64
+ result.disableAudio = disableAudio
65
+ }
66
+ if let dtx = connectOptions?["dtx"] as? Bool {
67
+ result.dtx = dtx
68
+ }
69
+ if let excludedSourceId = connectOptions?["excludedSourceIds"] as? [Any] {
70
+ result.excludedSourceId = excludedSourceId
71
+ }
72
+ if let forcePlayoutDelayDict = connectOptions?["forcePlayoutDelay"] as? NSDictionary,
73
+ let maximum = forcePlayoutDelayDict["max"] as? Int32,
74
+ let minimum = forcePlayoutDelayDict["min"] as? Int32 {
75
+ let forcePlayoutDelay: MCForcePlayoutDelay = .init(min: minimum, max: maximum)
76
+ result.forcePlayoutDelay = forcePlayoutDelay
77
+ }
78
+ if let forceSmooth = connectOptions?["forceSmooth"] as? Bool {
79
+ result.forceSmooth = forceSmooth
80
+ }
81
+ if let jitterMinimumDelayMs = connectOptions?["jitterMinimumDelayMs"] as? Int32 {
82
+ result.jitterMinimumDelayMs = jitterMinimumDelayMs
83
+ }
84
+ if let maximumBitrate = connectOptions?["maximumBitrate"] as? NSNumber {
85
+ result.maximumBitrate = maximumBitrate
86
+ }
87
+ if let multiplexedAudioTrack = connectOptions?["multiplexedAudioTracks"] as? Int32 {
88
+ result.multiplexedAudioTrack = multiplexedAudioTrack
89
+ }
90
+ if let pinnedSourceId = connectOptions?["pinnedSourceId"] as? String {
91
+ result.pinnedSourceId = pinnedSourceId
92
+ }
93
+ if let priority = connectOptions?["priority"] as? NSNumber {
94
+ result.priority = priority
95
+ }
96
+ if let recordStream = connectOptions?["recordStream"] as? Bool {
97
+ result.recordStream = recordStream
98
+ }
99
+ if let rtcEventLogOutputPath = connectOptions?["rtcEventLogOutputPath"] as? String {
100
+ result.rtcEventLogOutputPath = rtcEventLogOutputPath
101
+ }
102
+ if let simulcast = connectOptions?["simulcast"] as? Bool {
103
+ result.simulcast = simulcast
104
+ }
105
+ if let sourceId = connectOptions?["sourceId"] as? String {
106
+ result.sourceId = sourceId
107
+ }
108
+ if let statsDelayMs = connectOptions?["statsDelayMs"] as? Int32 {
109
+ result.statsDelayMs = statsDelayMs
110
+ }
111
+ if let stereo = connectOptions?["stereo"] as? Bool {
112
+ result.stereo = stereo
113
+ }
114
+ if let svcMode = connectOptions?["svcMode"] as? String {
115
+ switch svcMode {
116
+ case "l1t2":
117
+ result.svcMode = MCScalabilityMode.l1t2
118
+ case "l1t2h":
119
+ result.svcMode = MCScalabilityMode.l1t2h
120
+ case "l1t3":
121
+ result.svcMode = MCScalabilityMode.l1t3
122
+ case "l1t3h":
123
+ result.svcMode = MCScalabilityMode.l1t3h
124
+ case "l2t1":
125
+ result.svcMode = MCScalabilityMode.l2t1
126
+ case "l2t1Key":
127
+ result.svcMode = MCScalabilityMode.l2t1Key
128
+ case "l2t1h":
129
+ result.svcMode = MCScalabilityMode.l2t1h
130
+ case "l2t2":
131
+ result.svcMode = MCScalabilityMode.l2t2
132
+ case "l2t2Key":
133
+ result.svcMode = MCScalabilityMode.l2t2Key
134
+ case "l2t2KeyShift":
135
+ result.svcMode = MCScalabilityMode.l2t2KeyShift
136
+ case "l2t2h":
137
+ result.svcMode = MCScalabilityMode.l2t2h
138
+ case "l2t3":
139
+ result.svcMode = MCScalabilityMode.l2t3
140
+ case "l2t3h":
141
+ result.svcMode = MCScalabilityMode.l2t3h
142
+ case "l3t1":
143
+ result.svcMode = MCScalabilityMode.l3t1
144
+ case "l3t2":
145
+ result.svcMode = MCScalabilityMode.l3t2
146
+ case "l3t3":
147
+ result.svcMode = MCScalabilityMode.l3t3
148
+ case "l3t3Key":
149
+ result.svcMode = MCScalabilityMode.l3t3Key
150
+ case "none":
151
+ result.svcMode = MCScalabilityMode.none
152
+ case "s2t1":
153
+ result.svcMode = MCScalabilityMode.s2t1
154
+ case "s2t1h":
155
+ result.svcMode = MCScalabilityMode.s2t1h
156
+ case "s2t2":
157
+ result.svcMode = MCScalabilityMode.s2t2
158
+ case "s2t2h":
159
+ result.svcMode = MCScalabilityMode.s2t2h
160
+ case "s2t3":
161
+ result.svcMode = MCScalabilityMode.s2t3
162
+ case "s2t3h":
163
+ result.svcMode = MCScalabilityMode.s2t3h
164
+ case "s3t1":
165
+ result.svcMode = MCScalabilityMode.s3t1
166
+ case "s3t1h":
167
+ result.svcMode = MCScalabilityMode.s3t1h
168
+ case "s3t2":
169
+ result.svcMode = MCScalabilityMode.s3t2
170
+ case "s3t2h":
171
+ result.svcMode = MCScalabilityMode.s3t2h
172
+ case "s3t3":
173
+ result.svcMode = MCScalabilityMode.s3t3
174
+ case "s3t3h":
175
+ result.svcMode = MCScalabilityMode.s3t3h
176
+ default:
177
+ break
178
+ }
179
+ }
180
+ if let upwardsLayerWaitTimeMs = connectOptions?["upwardsLayerWaitTimeMs"] as? NSNumber {
181
+ result.upwardsLayerWaitTimeMs = upwardsLayerWaitTimeMs
182
+ }
183
+ if let videoCodec = connectOptions?["videoCodec"] as? String {
184
+ result.videoCodec = videoCodec
185
+ }
186
+ return result
187
+ }
188
+ #endif
189
+ }
@@ -0,0 +1,21 @@
1
+ // THEOplayerRCTView+Millicast.swift
2
+
3
+ import Foundation
4
+ import THEOplayerSDK
5
+
6
+ #if canImport(THEOplayerMillicastIntegration)
7
+ import THEOplayerMillicastIntegration
8
+ #endif
9
+
10
+ extension THEOplayerRCTView {
11
+ func initMillicastIntegration() {
12
+ guard let player = self.player else {
13
+ return
14
+ }
15
+
16
+ #if canImport(THEOplayerMillicastIntegration)
17
+ self.millicastIntegration = MillicastIntegrationFactory.createIntegration()
18
+ player.addIntegration(self.millicastIntegration!)
19
+ #endif
20
+ }
21
+ }
@@ -72,7 +72,9 @@ class THEOplayerRCTSideloadedWebVTTProcessor {
72
72
  }
73
73
  }
74
74
  }
75
-
75
+ if let cue = currentCue {
76
+ cues.append(cue) // append last cue if no newline at end of vtt
77
+ }
76
78
  return cues
77
79
  }
78
80