react-native-theoplayer 11.2.0 → 11.3.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 (79) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/com/theoplayer/PlayerConfigAdapter.kt +30 -0
  4. package/android/src/main/java/com/theoplayer/abr/ABRConfigurationAdapter.kt +21 -0
  5. package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +38 -1
  6. package/android/src/main/java/com/theoplayer/track/TrackListAdapter.kt +2 -0
  7. package/ios/THEOplayerRCTPlayerAPI.swift +29 -13
  8. package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +31 -6
  9. package/ios/THEOplayerRCTTrackMetadataAggregator.swift +35 -16
  10. package/ios/THEOplayerRCTView.swift +3 -0
  11. package/ios/cmcd/THEOplayerRCTView+CmcdConfig.swift +36 -0
  12. package/lib/commonjs/api/barrel.js +36 -25
  13. package/lib/commonjs/api/barrel.js.map +1 -1
  14. package/lib/commonjs/api/cmcd/CmcdConfiguration.js +67 -0
  15. package/lib/commonjs/api/cmcd/CmcdConfiguration.js.map +1 -0
  16. package/lib/commonjs/api/cmcd/barrel.js.map +1 -0
  17. package/lib/commonjs/api/source/barrel.js +5 -16
  18. package/lib/commonjs/api/source/barrel.js.map +1 -1
  19. package/lib/commonjs/api/track/TextTrack.js.map +1 -1
  20. package/lib/commonjs/internal/adapter/THEOplayerWebAdapter.js +13 -4
  21. package/lib/commonjs/internal/adapter/THEOplayerWebAdapter.js.map +1 -1
  22. package/lib/commonjs/internal/adapter/web/TrackUtils.js +2 -0
  23. package/lib/commonjs/internal/adapter/web/TrackUtils.js.map +1 -1
  24. package/lib/commonjs/manifest.json +1 -1
  25. package/lib/module/api/barrel.js +1 -0
  26. package/lib/module/api/barrel.js.map +1 -1
  27. package/lib/module/api/cmcd/CmcdConfiguration.js +67 -0
  28. package/lib/module/api/cmcd/CmcdConfiguration.js.map +1 -0
  29. package/lib/module/api/cmcd/barrel.js.map +1 -0
  30. package/lib/module/api/source/barrel.js +0 -1
  31. package/lib/module/api/source/barrel.js.map +1 -1
  32. package/lib/module/api/track/TextTrack.js.map +1 -1
  33. package/lib/module/internal/adapter/THEOplayerWebAdapter.js +13 -4
  34. package/lib/module/internal/adapter/THEOplayerWebAdapter.js.map +1 -1
  35. package/lib/module/internal/adapter/web/TrackUtils.js +2 -0
  36. package/lib/module/internal/adapter/web/TrackUtils.js.map +1 -1
  37. package/lib/module/manifest.json +1 -1
  38. package/lib/typescript/api/abr/ABRConfiguration.d.ts +2 -2
  39. package/lib/typescript/api/barrel.d.ts +1 -0
  40. package/lib/typescript/api/barrel.d.ts.map +1 -1
  41. package/lib/typescript/api/cmcd/CmcdConfiguration.d.ts +166 -0
  42. package/lib/typescript/api/cmcd/CmcdConfiguration.d.ts.map +1 -0
  43. package/lib/typescript/api/cmcd/barrel.d.ts.map +1 -0
  44. package/lib/typescript/api/config/PlayerConfiguration.d.ts +10 -0
  45. package/lib/typescript/api/config/PlayerConfiguration.d.ts.map +1 -1
  46. package/lib/typescript/api/source/SourceDescription.d.ts +2 -2
  47. package/lib/typescript/api/source/SourceDescription.d.ts.map +1 -1
  48. package/lib/typescript/api/source/barrel.d.ts +0 -1
  49. package/lib/typescript/api/source/barrel.d.ts.map +1 -1
  50. package/lib/typescript/api/track/TextTrack.d.ts +4 -0
  51. package/lib/typescript/api/track/TextTrack.d.ts.map +1 -1
  52. package/lib/typescript/internal/adapter/THEOplayerWebAdapter.d.ts.map +1 -1
  53. package/lib/typescript/internal/adapter/web/TrackUtils.d.ts.map +1 -1
  54. package/package.json +1 -1
  55. package/react-native-theoplayer.podspec +1 -1
  56. package/src/api/abr/ABRConfiguration.ts +2 -2
  57. package/src/api/barrel.ts +1 -0
  58. package/src/api/cmcd/CmcdConfiguration.ts +175 -0
  59. package/src/api/config/PlayerConfiguration.ts +11 -0
  60. package/src/api/source/SourceDescription.ts +2 -2
  61. package/src/api/source/barrel.ts +0 -1
  62. package/src/api/track/TextTrack.ts +5 -0
  63. package/src/internal/adapter/THEOplayerWebAdapter.ts +9 -4
  64. package/src/internal/adapter/web/TrackUtils.ts +2 -1
  65. package/src/manifest.json +1 -1
  66. package/lib/commonjs/api/source/cmcd/CmcdConfiguration.js +0 -27
  67. package/lib/commonjs/api/source/cmcd/CmcdConfiguration.js.map +0 -1
  68. package/lib/commonjs/api/source/cmcd/barrel.js.map +0 -1
  69. package/lib/module/api/source/cmcd/CmcdConfiguration.js +0 -24
  70. package/lib/module/api/source/cmcd/CmcdConfiguration.js.map +0 -1
  71. package/lib/module/api/source/cmcd/barrel.js.map +0 -1
  72. package/lib/typescript/api/source/cmcd/CmcdConfiguration.d.ts +0 -83
  73. package/lib/typescript/api/source/cmcd/CmcdConfiguration.d.ts.map +0 -1
  74. package/lib/typescript/api/source/cmcd/barrel.d.ts.map +0 -1
  75. package/src/api/source/cmcd/CmcdConfiguration.ts +0 -88
  76. /package/lib/commonjs/api/{source/cmcd → cmcd}/barrel.js +0 -0
  77. /package/lib/module/api/{source/cmcd → cmcd}/barrel.js +0 -0
  78. /package/lib/typescript/api/{source/cmcd → cmcd}/barrel.d.ts +0 -0
  79. /package/src/api/{source/cmcd → cmcd}/barrel.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ 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
+ ## [Unreleased]
9
+
10
+ ## [11.3.0] - 26-06-18
11
+
12
+ ### Added
13
+
14
+ - Added Android support for `ABRConfiguration.preferredMaximumResolution`, mirroring the existing iOS behaviour. Set to `(0,0)` to remove the cap.
15
+ - Added basic support for CMCD event mode reporting of DRM and ad events.
16
+ - Added bridging of the `hlsDateRange` property from `PlayerConfiguration` to Android's `THEOplayerConfig`.
17
+
18
+ ### Fixed
19
+
20
+ - Fixed an issue on Android where `GoogleImaConfiguration.sessionId` was not properly passed.
21
+
22
+ ## [11.2.1] - 26-06-09
23
+
24
+ ### Fixed
25
+
26
+ - Fixed an issue on iOS where setting the targetVideoQuality uids was causing a crash due to an unsafe bounds-check.
27
+
8
28
  ## [11.2.0] - 26-06-04
9
29
 
10
30
  ### Added
@@ -126,8 +126,8 @@ repositories {
126
126
  mavenLocal()
127
127
  }
128
128
 
129
- // The minimum supported THEOplayer version is 11.4.0
130
- def theoVersion = safeExtGet('THEOplayer_sdk', '[11.4.0, 12.0.0)')
129
+ // The minimum supported THEOplayer version is 11.5.0
130
+ def theoVersion = safeExtGet('THEOplayer_sdk', '[11.5.0, 12.0.0)')
131
131
  def theoMediaSessionVersion = safeExtGet('THEOplayer_mediasession', '[11.0.0, 12.0.0)')
132
132
  def theoAdsWrapperVersion = "11.0.0"
133
133
  def coroutinesVersion = safeExtGet('coroutinesVersion', '1.10.2')
@@ -8,6 +8,8 @@ import com.theoplayer.android.api.THEOplayerConfig
8
8
  import com.theoplayer.android.api.THEOplayerGlobal
9
9
  import com.theoplayer.android.api.cast.CastStrategy
10
10
  import com.theoplayer.android.api.cast.CastConfiguration
11
+ import com.theoplayer.android.api.cmcd.CMCDConfiguration
12
+ import com.theoplayer.android.api.cmcd.CMCDEndpointConfiguration
11
13
  import com.theoplayer.android.api.pip.PipConfiguration
12
14
  import com.theoplayer.android.api.player.NetworkConfiguration
13
15
  import com.theoplayer.android.api.theolive.THEOLiveConfig
@@ -42,9 +44,15 @@ private const val PROP_THEOLIVE_CONFIG = "theoLive"
42
44
  private const val PROP_THEOLIVE_EXTERNAL_SESSION_ID = "externalSessionId"
43
45
  private const val PROP_THEOLIVE_ANALYTICS_DISABLED = "analyticsDisabled"
44
46
  private const val PROP_THEOLIVE_DISCOVERY_URL = "discoveryUrl"
47
+ private const val PROP_HLS_DATERANGE = "hlsDateRange"
45
48
  private const val PROP_MULTIMEDIA_TUNNELING_ENABLED = "tunnelingEnabled"
46
49
  private const val PROP_DEBUG_LOGS_ENABLED = "debugLogsEnabled"
47
50
  private const val PROP_SYSTEM_CAPTION_STYLE = "useSystemCaptionStyle"
51
+ private const val PROP_CMCD = "cmcd"
52
+ private const val PROP_CMCD_EXTERNAL_SESSION_ID = "externalSessionId"
53
+ private const val PROP_CMCD_USER_ID = "userId"
54
+ private const val PROP_CMCD_EVENT_ENDPOINTS = "eventEndpoints"
55
+ private const val PROP_CMCD_ENDPOINT_URL = "url"
48
56
 
49
57
 
50
58
  class PlayerConfigAdapter(private val configProps: ReadableMap?) {
@@ -82,12 +90,18 @@ class PlayerConfigAdapter(private val configProps: ReadableMap?) {
82
90
  pip(PipConfiguration.Builder().build())
83
91
  // Opt-out for auto-integrations for now
84
92
  autoIntegrations(false)
93
+ if (hasKey(PROP_HLS_DATERANGE)) {
94
+ hlsDateRange(getBoolean(PROP_HLS_DATERANGE))
95
+ }
85
96
  if (hasKey(PROP_MULTIMEDIA_TUNNELING_ENABLED)) {
86
97
  tunnelingEnabled(getBoolean(PROP_MULTIMEDIA_TUNNELING_ENABLED))
87
98
  }
88
99
  if (hasKey(PROP_SYSTEM_CAPTION_STYLE)) {
89
100
  useSystemCaptionStyle(getBoolean(PROP_SYSTEM_CAPTION_STYLE))
90
101
  }
102
+ if (hasKey(PROP_CMCD)) {
103
+ cmcd(cmcdConfig())
104
+ }
91
105
  }
92
106
  }.build()
93
107
  }
@@ -241,6 +255,22 @@ class PlayerConfigAdapter(private val configProps: ReadableMap?) {
241
255
  return MediaSessionConfigAdapter.fromProps(configProps?.getMap(PROP_MEDIA_CONTROL))
242
256
  }
243
257
 
258
+ private fun cmcdConfig(): CMCDConfiguration {
259
+ val config = configProps?.getMap(PROP_CMCD)
260
+ val endpoints = config?.getArray(PROP_CMCD_EVENT_ENDPOINTS)?.let { arr ->
261
+ (0 until arr.size()).mapNotNull { i ->
262
+ arr.getMap(i)?.getString(PROP_CMCD_ENDPOINT_URL)?.let { url ->
263
+ CMCDEndpointConfiguration(url = url)
264
+ }
265
+ }
266
+ }
267
+ return CMCDConfiguration(
268
+ externalSessionId = config?.getString(PROP_CMCD_EXTERNAL_SESSION_ID),
269
+ userId = config?.getString(PROP_CMCD_USER_ID),
270
+ eventEndpoints = endpoints
271
+ )
272
+ }
273
+
244
274
  private fun theoLiveConfig (): THEOLiveConfig {
245
275
  val config = configProps?.getMap(PROP_THEOLIVE_CONFIG)
246
276
  return THEOLiveConfig.Builder(
@@ -1,5 +1,6 @@
1
1
  package com.theoplayer.abr
2
2
 
3
+ import android.util.Size
3
4
  import com.facebook.react.bridge.ReadableMap
4
5
  import com.theoplayer.android.api.abr.AbrStrategyConfiguration
5
6
  import com.theoplayer.android.api.abr.AbrStrategyMetadata
@@ -12,6 +13,9 @@ object ABRConfigurationAdapter {
12
13
  private const val PROP_METADATA = "metadata"
13
14
  private const val PROP_TYPE = "type"
14
15
  private const val PROP_BITRATE = "bitrate"
16
+ private const val PROP_PREFERRED_MAXIMUM_RESOLUTION = "preferredMaximumResolution"
17
+ private const val PROP_WIDTH = "width"
18
+ private const val PROP_HEIGHT = "height"
15
19
 
16
20
  fun applyABRConfigurationFromProps(player: Player?, abrProps: ReadableMap?) {
17
21
  if (abrProps == null || player == null) {
@@ -20,6 +24,11 @@ object ABRConfigurationAdapter {
20
24
  if (abrProps.hasKey(PROP_TARGET_BUFFER)) {
21
25
  player.abr.targetBuffer = abrProps.getInt(PROP_TARGET_BUFFER)
22
26
  }
27
+ // (0,0) is the documented sentinel for "no cap" and maps to a null Size on the native SDK.
28
+ val preferredMaximumResolutionProps = abrProps.getMap(PROP_PREFERRED_MAXIMUM_RESOLUTION)
29
+ if (preferredMaximumResolutionProps != null) {
30
+ player.abr.preferredMaximumResolution = preferredMaximumResolutionFromProps(preferredMaximumResolutionProps)
31
+ }
23
32
  // Strategy can be either a string or an object
24
33
  try {
25
34
  val abrStrategyPropsString = abrProps.getString(PROP_STRATEGY)
@@ -50,6 +59,18 @@ object ABRConfigurationAdapter {
50
59
  }
51
60
  }
52
61
 
62
+ private fun preferredMaximumResolutionFromProps(props: ReadableMap?): Size? {
63
+ if (props == null || !props.hasKey(PROP_WIDTH) || !props.hasKey(PROP_HEIGHT)) {
64
+ return null
65
+ }
66
+ val width = props.getDouble(PROP_WIDTH).toInt()
67
+ val height = props.getDouble(PROP_HEIGHT).toInt()
68
+ if (width <= 0 || height <= 0) {
69
+ return null
70
+ }
71
+ return Size(width, height)
72
+ }
73
+
53
74
  private fun abrMetadataFromProps(props: ReadableMap?): AbrStrategyMetadata? {
54
75
  if (props == null) {
55
76
  return null
@@ -20,6 +20,8 @@ import com.theoplayer.android.api.player.track.texttrack.TextTrackKind
20
20
  import com.theoplayer.android.api.source.metadata.ChromecastMetadataImage
21
21
  import com.theoplayer.BuildConfig
22
22
  import com.theoplayer.android.api.ads.theoads.TheoAdsLayoutOverride
23
+ import com.theoplayer.android.api.cmcd.CMCDEndpointConfiguration
24
+ import com.theoplayer.android.api.cmcd.CMCDSourceConfiguration
23
25
  import com.theoplayer.android.api.cmcd.CMCDTransmissionMode
24
26
  import com.theoplayer.android.api.error.ErrorCode
25
27
  import com.theoplayer.android.api.source.AdIntegration
@@ -94,6 +96,11 @@ private const val TYPE_MILLICAST = "millicast"
94
96
 
95
97
  private const val PROP_CMCD = "cmcd"
96
98
  private const val CMCD_TRANSMISSION_MODE = "transmissionMode"
99
+ private const val CMCD_SESSION_ID = "sessionID"
100
+ private const val CMCD_EXTERNAL_SESSION_ID = "externalSessionId"
101
+ private const val CMCD_USER_ID = "userId"
102
+ private const val CMCD_EVENT_ENDPOINTS = "eventEndpoints"
103
+ private const val CMCD_ENDPOINT_URL = "url"
97
104
 
98
105
  class SourceAdapter {
99
106
  private val gson = Gson()
@@ -175,6 +182,11 @@ class SourceAdapter {
175
182
  if (metadataDescription != null) {
176
183
  builder.metadata(metadataDescription)
177
184
  }
185
+ if (jsonSourceObject.has(PROP_CMCD)) {
186
+ parseCmcdSourceConfiguration(jsonSourceObject.getJSONObject(PROP_CMCD))?.let {
187
+ builder.cmcd(it)
188
+ }
189
+ }
178
190
  return builder.build()
179
191
  } catch (e: JSONException) {
180
192
  e.printStackTrace()
@@ -464,7 +476,10 @@ class SourceAdapter {
464
476
  return BridgeUtils.fromJSONObjectToBridge(JSONObject(gson.toJson(typedSource)))
465
477
  }
466
478
 
467
- private fun parseCmcdTransmissionMode(cmcdConfiguration : JSONObject) : CMCDTransmissionMode {
479
+ private fun parseCmcdTransmissionMode(cmcdConfiguration : JSONObject) : CMCDTransmissionMode? {
480
+ if (!cmcdConfiguration.has(CMCD_TRANSMISSION_MODE)) {
481
+ return null
482
+ }
468
483
  try {
469
484
  val transmissionMode = cmcdConfiguration.optInt(CMCD_TRANSMISSION_MODE)
470
485
  if (transmissionMode == CmcdTransmissionMode.QUERY_ARGUMENT.ordinal) {
@@ -476,4 +491,26 @@ class SourceAdapter {
476
491
  return CMCDTransmissionMode.HTTP_HEADER
477
492
  }
478
493
  }
494
+
495
+ private fun parseCmcdSourceConfiguration(cmcdJson: JSONObject): CMCDSourceConfiguration? {
496
+ val sessionId = cmcdJson.optString(CMCD_SESSION_ID, null)
497
+ val externalSessionId = cmcdJson.optString(CMCD_EXTERNAL_SESSION_ID, null)
498
+ val userId = cmcdJson.optString(CMCD_USER_ID, null)
499
+ val endpoints = cmcdJson.optJSONArray(CMCD_EVENT_ENDPOINTS)?.let { arr ->
500
+ (0 until arr.length()).mapNotNull { i ->
501
+ arr.optJSONObject(i)?.optString(CMCD_ENDPOINT_URL)?.let { url ->
502
+ CMCDEndpointConfiguration(url = url)
503
+ }
504
+ }
505
+ }
506
+ if (sessionId == null && externalSessionId == null && userId == null && endpoints == null) {
507
+ return null
508
+ }
509
+ return CMCDSourceConfiguration(
510
+ sessionId = sessionId,
511
+ externalSessionId = externalSessionId,
512
+ userId = userId,
513
+ eventEndpoints = endpoints
514
+ )
515
+ }
479
516
  }
@@ -26,6 +26,7 @@ private const val PROP_NAME = "name"
26
26
  private const val PROP_ENABLED = "enabled"
27
27
  private const val PROP_SRC = "src"
28
28
  private const val PROP_FORCED = "forced"
29
+ private const val PROP_IN_BAND_METADATA_TRACK_DISPATCH_TYPE = "inBandMetadataTrackDispatchType"
29
30
  private const val PROP_CAPTION_CHANNEL = "captionChannel"
30
31
  private const val PROP_AUDIO_SAMPLING_RATE = "audioSamplingRate"
31
32
  private const val PROP_BANDWIDTH = "bandwidth"
@@ -71,6 +72,7 @@ object TrackListAdapter {
71
72
  textTrackPayload.putBoolean(PROP_FORCED, textTrack.isForced)
72
73
 
73
74
  // THEOplayer v10.13+
75
+ textTrackPayload.putString(PROP_IN_BAND_METADATA_TRACK_DISPATCH_TYPE, textTrack.inBandMetadataTrackDispatchType ?: "")
74
76
  textTrack.captionChannel?.let { textTrackPayload.putInt(PROP_CAPTION_CHANNEL, it) }
75
77
 
76
78
  // Optionally pass cue list.
@@ -281,11 +281,13 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
281
281
  let player = theView.player {
282
282
  let uidValue = uid.intValue
283
283
  let textTracks: TextTrackList = player.textTracks
284
- guard textTracks.count > 0 else {
284
+ let textTrackCount = textTracks.count
285
+ guard textTrackCount > 0 else {
285
286
  return
286
287
  }
287
288
  if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Showing textTrack \(uidValue) on TheoPlayer") }
288
- for i in 0...textTracks.count-1 {
289
+ for i in 0..<textTrackCount {
290
+ guard i < textTracks.count else { break }
289
291
  let textTrack: TextTrack = textTracks.get(i)
290
292
  if textTrack.uid == uidValue {
291
293
  textTrack.mode = TextTrackMode.showing
@@ -304,11 +306,13 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
304
306
  let player = theView.player {
305
307
  let uidValue = uid.intValue
306
308
  let audioTracks: AudioTrackList = player.audioTracks
307
- guard audioTracks.count > 0 else {
309
+ let audioTrackCount = audioTracks.count
310
+ guard audioTrackCount > 0 else {
308
311
  return
309
312
  }
310
313
  if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Enabling audioTrack \(uidValue) on TheoPlayer") }
311
- for i in 0...audioTracks.count-1 {
314
+ for i in 0..<audioTrackCount {
315
+ guard i < audioTracks.count else { break }
312
316
  let audioTrack: MediaTrack = audioTracks.get(i)
313
317
  audioTrack.enabled = (audioTrack.uid == uidValue)
314
318
  }
@@ -323,11 +327,13 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
323
327
  let player = theView.player {
324
328
  let uidValue = uid.intValue
325
329
  let videoTracks: VideoTrackList = player.videoTracks
326
- guard videoTracks.count > 0 else {
330
+ let videoTrackCount = videoTracks.count
331
+ guard videoTrackCount > 0 else {
327
332
  return
328
333
  }
329
334
  if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Enabling videoTrack \(uidValue) on TheoPlayer") }
330
- for i in 0...videoTracks.count-1 {
335
+ for i in 0..<videoTrackCount {
336
+ guard i < videoTracks.count else { break }
331
337
  let videoTrack: MediaTrack = videoTracks.get(i)
332
338
  videoTrack.enabled = (videoTrack.uid == uidValue)
333
339
  }
@@ -341,27 +347,37 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
341
347
  if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView,
342
348
  let player = theView.player {
343
349
  let videoTracks: VideoTrackList = player.videoTracks
344
- guard videoTracks.count > 0 else {
350
+ let videoTrackCount = videoTracks.count
351
+ guard videoTrackCount > 0 else {
345
352
  return
346
353
  }
347
354
  var activeVideoTrack: VideoTrack?
348
- for i in 0...videoTracks.count-1 {
355
+ for i in 0..<videoTrackCount {
356
+ guard i < videoTracks.count else { break }
349
357
  let videoTrack: MediaTrack = videoTracks.get(i)
350
358
  if videoTrack.enabled {
351
359
  activeVideoTrack = videoTrack as? VideoTrack
352
360
  }
353
361
  }
354
362
  if let foundTrack = activeVideoTrack {
355
- let matchingQualities = (0..<foundTrack.qualities.count).compactMap { index in
363
+ let requestedBandwidths = Set(uids.map { $0.intValue })
364
+ let currentBandwidths = Set(foundTrack.targetQualities?.map { $0.bandwidth } ?? [])
365
+ guard requestedBandwidths != currentBandwidths else {
366
+ if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] targetQualities: \(uids) already set on active videotrack. Skipping update.") }
367
+ return
368
+ }
369
+ let qualityCount = foundTrack.qualities.count
370
+ let matchingQualities = (0..<qualityCount).compactMap { index -> Quality? in
371
+ guard index < foundTrack.qualities.count else { return nil }
356
372
  let quality = foundTrack.qualities.get(index)
357
- return uids.contains { $0.intValue == quality.bandwidth } ? quality : nil
373
+ return uids.contains(where: { $0.intValue == quality.bandwidth }) ? quality : nil
358
374
  }
359
- foundTrack.targetQualities = matchingQualities.count > 0 ? matchingQualities : nil
375
+ foundTrack.targetQualities = matchingQualities.isEmpty ? nil : matchingQualities
360
376
  if DEBUG_PLAYER_API {
361
377
  if matchingQualities.count > 0 {
362
- if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] targetQualities: \(uids) set on active videotrack. (matching: \(matchingQualities.map(\.bandwidth)))") }
378
+ PrintUtils.printLog(logText: "[NATIVE] targetQualities: \(uids) set on active videotrack. (matching: \(matchingQualities.map(\.bandwidth)))")
363
379
  } else {
364
- if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] targetQualities: \(uids) set on active videotrack. (no match or empty) => no quality restriction.") }
380
+ PrintUtils.printLog(logText: "[NATIVE] targetQualities: \(uids) set on active videotrack. (no match or empty) => no quality restriction.")
365
381
  }
366
382
  }
367
383
  }
@@ -64,6 +64,7 @@ let SD_PROP_INITIALIZATION_DELAY: String = "initializationDelay"
64
64
  let SD_PROP_HLS_DATE_RANGE: String = "hlsDateRange"
65
65
  let SD_PROP_BREAK_MANIFEST_URL: String = "breakManifestUrl"
66
66
  let SD_PROP_CMCD: String = "cmcd"
67
+ let SD_PROP_TRANSMISSION_MODE: String = "transmissionMode"
67
68
  let SD_PROP_QUERY_PARAMETERS: String = "queryParameters"
68
69
 
69
70
  let EXTENSION_HLS: String = ".m3u8"
@@ -167,11 +168,15 @@ class THEOplayerRCTSourceDescriptionBuilder {
167
168
  }
168
169
 
169
170
  // 6. configure CMCD
170
- let cmcd = sourceData[SD_PROP_CMCD] as? [String:Any]
171
- if cmcd != nil {
172
- typedSources.forEach { typedSource in
173
- typedSource.cmcd = true;
171
+ var cmcdSourceConfig: CMCDSourceConfiguration? = nil
172
+ if let cmcd = sourceData[SD_PROP_CMCD] as? [String:Any] {
173
+ let requestModeEnabled = cmcd[SD_PROP_TRANSMISSION_MODE] != nil
174
+ if requestModeEnabled {
175
+ typedSources.forEach { typedSource in
176
+ typedSource.cmcd = true
177
+ }
174
178
  }
179
+ cmcdSourceConfig = THEOplayerRCTSourceDescriptionBuilder.buildCmcdSourceConfiguration(cmcd)
175
180
  }
176
181
 
177
182
  // 7. construct the SourceDescription
@@ -179,13 +184,33 @@ class THEOplayerRCTSourceDescriptionBuilder {
179
184
  textTracks: textTrackDescriptions,
180
185
  ads: adsDescriptions,
181
186
  poster: poster,
182
- metadata: metadataDescription)
183
-
187
+ metadata: metadataDescription,
188
+ cmcdConfiguration: cmcdSourceConfig)
189
+
184
190
  return (sourceDescription, metadataAndChapterTrackDescriptions)
185
191
  }
186
192
 
187
193
  // MARK: Private build methods
188
194
 
195
+ private static func buildCmcdSourceConfiguration(_ cmcdData: [String:Any]) -> CMCDSourceConfiguration? {
196
+ let sessionId = cmcdData["sessionID"] as? String
197
+ let externalSessionId = cmcdData["externalSessionId"] as? String
198
+ let userId = cmcdData["userId"] as? String
199
+ let endpoints: [CMCDEndpointConfiguration]? = (cmcdData["eventEndpoints"] as? [[String: Any]])?.compactMap { dict in
200
+ guard let url = dict["url"] as? String else { return nil }
201
+ return CMCDEndpointConfiguration(url: url)
202
+ }
203
+ if sessionId == nil && externalSessionId == nil && userId == nil && endpoints == nil {
204
+ return nil
205
+ }
206
+ return CMCDSourceConfiguration(
207
+ sessionId: sessionId,
208
+ externalSessionId: externalSessionId,
209
+ userId: userId,
210
+ eventEndpoints: endpoints
211
+ )
212
+ }
213
+
189
214
  /**
190
215
  Creates a THEOplayer TypedSource. This requires a source property for non SSAI strreams (either as a string or as an object contiaining a src property). For SSAI streams the TypeSource can be created from the ssai property.
191
216
  - returns: a THEOplayer TypedSource. In case of SSAI we support GoogleDAITypedSource with GoogleDAIVodConfiguration or GoogleDAILiveConfiguration
@@ -29,6 +29,7 @@ let PROP_CUE_CONTENT: String = "content"
29
29
  let PROP_CUE_CUSTOM_ATTRIBUTES: String = "customAttributes"
30
30
  let PROP_SRC: String = "src"
31
31
  let PROP_FORCED: String = "forced"
32
+ let PROP_IN_BAND_METADATA_TRACK_DISPATCH_TYPE: String = "inBandMetadataTrackDispatchType"
32
33
  let PROP_START_DATE: String = "startDate"
33
34
  let PROP_END_DATE: String = "endDate"
34
35
  let PROP_ATTRIBUTE_CLASS: String = "class"
@@ -56,10 +57,12 @@ class THEOplayerRCTTrackMetadataAggregator {
56
57
  // MARK: TEXTTRACKS
57
58
  class func aggregatedTextTrackListInfo(textTracks: TextTrackList, metadataTracks: [[String:Any]], player: THEOplayer) -> [[String:Any]] {
58
59
  var trackEntries:[[String:Any]] = metadataTracks
59
- guard textTracks.count > 0 else {
60
+ let textTrackCount = textTracks.count
61
+ guard textTrackCount > 0 else {
60
62
  return trackEntries
61
63
  }
62
- for i in 0...textTracks.count-1 {
64
+ for i in 0..<textTrackCount {
65
+ guard i < textTracks.count else { break }
63
66
  let textTrack: TextTrack = textTracks.get(i)
64
67
  trackEntries.append(THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack, player: player))
65
68
  }
@@ -78,6 +81,7 @@ class THEOplayerRCTTrackMetadataAggregator {
78
81
  entry[PROP_TYPE] = textTrack.type
79
82
  entry[PROP_SRC] = textTrack.src
80
83
  entry[PROP_FORCED] = textTrack.forced
84
+ entry[PROP_IN_BAND_METADATA_TRACK_DISPATCH_TYPE] = textTrack.inBandMetadataTrackDispatchType
81
85
  // process cues when texttrack contains them
82
86
  if !textTrack.cues.isEmpty {
83
87
  var cueList: [[String:Any]] = []
@@ -90,10 +94,12 @@ class THEOplayerRCTTrackMetadataAggregator {
90
94
  }
91
95
 
92
96
  private class func selectedTextTrack(textTracks: TextTrackList) -> Int {
93
- guard textTracks.count > 0 else {
97
+ let textTrackCount = textTracks.count
98
+ guard textTrackCount > 0 else {
94
99
  return 0
95
100
  }
96
- for i in 0...textTracks.count-1 {
101
+ for i in 0..<textTrackCount {
102
+ guard i < textTracks.count else { break }
97
103
  let textTrack: TextTrack = textTracks.get(i)
98
104
  if textTrack.mode == TextTrackMode.showing {
99
105
  return textTrack.uid
@@ -160,10 +166,12 @@ class THEOplayerRCTTrackMetadataAggregator {
160
166
  // MARK: AUDIOTRACKS
161
167
  private class func aggregatedAudioTrackListInfo(audioTracks: AudioTrackList) -> [[String:Any]] {
162
168
  var audioTrackEntries:[[String:Any]] = []
163
- guard audioTracks.count > 0 else {
169
+ let audioTrackCount = audioTracks.count
170
+ guard audioTrackCount > 0 else {
164
171
  return audioTrackEntries
165
172
  }
166
- for i in 0...audioTracks.count-1 {
173
+ for i in 0..<audioTrackCount {
174
+ guard i < audioTracks.count else { break }
167
175
  let audioTrack: MediaTrack = audioTracks.get(i) // should be casted to AudioTrack
168
176
  audioTrackEntries.append(THEOplayerRCTTrackMetadataAggregator.aggregatedAudioTrackInfo(audioTrack: audioTrack))
169
177
  }
@@ -181,7 +189,9 @@ class THEOplayerRCTTrackMetadataAggregator {
181
189
  entry[PROP_ENABLED] = audioTrack.enabled
182
190
 
183
191
  // add known qualities
184
- entry[PROP_QUALITIES] = (0..<audioTrack.qualities.count).map { index in
192
+ let audioQualityCount = audioTrack.qualities.count
193
+ entry[PROP_QUALITIES] = (0..<audioQualityCount).compactMap { index -> [String:Any]? in
194
+ guard index < audioTrack.qualities.count else { return nil }
185
195
  return THEOplayerRCTTrackMetadataAggregator.aggregatedQualityInfo(quality: audioTrack.qualities.get(index))
186
196
  }
187
197
 
@@ -193,10 +203,12 @@ class THEOplayerRCTTrackMetadataAggregator {
193
203
  }
194
204
 
195
205
  private class func selectedAudioTrack(audioTracks: AudioTrackList) -> Int {
196
- guard audioTracks.count > 0 else {
206
+ let audioTrackCount = audioTracks.count
207
+ guard audioTrackCount > 0 else {
197
208
  return 0
198
209
  }
199
- for i in 0...audioTracks.count-1 {
210
+ for i in 0..<audioTrackCount {
211
+ guard i < audioTracks.count else { break }
200
212
  let audioTrack: MediaTrack = audioTracks.get(i)
201
213
  if audioTrack.enabled {
202
214
  return audioTrack.uid
@@ -208,10 +220,12 @@ class THEOplayerRCTTrackMetadataAggregator {
208
220
  // MARK: VIDEOTRACKS
209
221
  private class func aggregatedVideoTrackListInfo(videoTracks: VideoTrackList) -> [[String:Any]] {
210
222
  var videoTrackEntries:[[String:Any]] = []
211
- guard videoTracks.count > 0 else {
223
+ let videoTrackCount = videoTracks.count
224
+ guard videoTrackCount > 0 else {
212
225
  return videoTrackEntries
213
226
  }
214
- for i in 0...videoTracks.count-1 {
227
+ for i in 0..<videoTrackCount {
228
+ guard i < videoTracks.count else { break }
215
229
  if let videoTrack: VideoTrack = videoTracks.get(i) as? VideoTrack {
216
230
  videoTrackEntries.append(THEOplayerRCTTrackMetadataAggregator.aggregatedVideoTrackInfo(videoTrack: videoTrack))
217
231
  }
@@ -230,7 +244,9 @@ class THEOplayerRCTTrackMetadataAggregator {
230
244
  entry[PROP_ENABLED] = videoTrack.enabled
231
245
 
232
246
  // add known qualities
233
- entry[PROP_QUALITIES] = (0..<videoTrack.qualities.count).map { index in
247
+ let videoQualityCount = videoTrack.qualities.count
248
+ entry[PROP_QUALITIES] = (0..<videoQualityCount).compactMap { index -> [String:Any]? in
249
+ guard index < videoTrack.qualities.count else { return nil }
234
250
  return THEOplayerRCTTrackMetadataAggregator.aggregatedQualityInfo(quality: videoTrack.qualities.get(index))
235
251
  }
236
252
 
@@ -243,12 +259,14 @@ class THEOplayerRCTTrackMetadataAggregator {
243
259
  }
244
260
 
245
261
  private class func selectedVideoTrack(videoTracks: VideoTrackList) -> Int {
246
- guard videoTracks.count > 0 else {
262
+ let videoTrackCount = videoTracks.count
263
+ guard videoTrackCount > 0 else {
247
264
  return 0
248
265
  }
249
- for i in 0...videoTracks.count-1 {
266
+ for i in 0..<videoTrackCount {
267
+ guard i < videoTracks.count else { break }
250
268
  let videoTrack: MediaTrack = videoTracks.get(i)
251
- if videoTracks.get(i).enabled {
269
+ if videoTrack.enabled {
252
270
  return videoTrack.uid
253
271
  }
254
272
  }
@@ -274,7 +292,7 @@ class THEOplayerRCTTrackMetadataAggregator {
274
292
  "bandwidth": quality.bandwidth,
275
293
  "codecs": "",
276
294
  "id": identifier,
277
- "uid": identifier,
295
+ "uid": quality.bandwidth,
278
296
  "name": label,
279
297
  "label": label,
280
298
  "available": true,
@@ -311,6 +329,7 @@ class THEOplayerRCTTrackMetadataAggregator {
311
329
  track[PROP_LABEL] = trackDescription.label ?? "no label"
312
330
  track[PROP_TYPE] = "webvtt"
313
331
  track[PROP_SRC] = trackDescription.src.absoluteString
332
+ track[PROP_IN_BAND_METADATA_TRACK_DISPATCH_TYPE] = ""
314
333
  var cueList: [[String:Any]] = []
315
334
  var cueIndex = 0
316
335
  for c in cues {
@@ -48,6 +48,7 @@ public class THEOplayerRCTView: UIView {
48
48
  var castConfig = CastConfig()
49
49
  var uiConfig = UIConfig()
50
50
  var theoliveConfig = THEOliveConfig()
51
+ var cmcdConfig = CmcdConfig()
51
52
 
52
53
  public var bypassDropInstanceOnReactLifecycle = false // controls superView detaching behaviour
53
54
 
@@ -232,6 +233,7 @@ public class THEOplayerRCTView: UIView {
232
233
  config.hlsDateRange = self.hlsDateRange
233
234
  config.license = self.license
234
235
  config.licenseUrl = self.licenseUrl
236
+ config.cmcd = self.playerCmcdConfiguration()
235
237
  self.player = THEOplayer(configuration: config.build())
236
238
 
237
239
  self.initAdsIntegration()
@@ -322,6 +324,7 @@ public class THEOplayerRCTView: UIView {
322
324
  self.parseUIConfig(configDict: configDict)
323
325
  self.parseMediaControlConfig(configDict: configDict)
324
326
  self.parseTHEOliveConfig(configDict: configDict)
327
+ self.parseCmcdConfig(configDict: configDict)
325
328
  if DEBUG_VIEW { PrintUtils.printLog(logText: "[NATIVE] config prop updated.") }
326
329
 
327
330
  // Given the bridged config, create the initial THEOplayer instance
@@ -0,0 +1,36 @@
1
+ // THEOplayerRCTView+CmcdConfig.swift
2
+
3
+ import Foundation
4
+ import THEOplayerSDK
5
+
6
+ struct CmcdConfig {
7
+ var externalSessionId: String?
8
+ var userId: String?
9
+ var eventEndpoints: [CMCDEndpointConfiguration]?
10
+ }
11
+
12
+ extension THEOplayerRCTView {
13
+
14
+ func parseCmcdConfig(configDict: NSDictionary) {
15
+ if let cmcdDict = configDict["cmcd"] as? NSDictionary {
16
+ self.cmcdConfig.externalSessionId = cmcdDict["externalSessionId"] as? String
17
+ self.cmcdConfig.userId = cmcdDict["userId"] as? String
18
+ self.cmcdConfig.eventEndpoints = (cmcdDict["eventEndpoints"] as? [[String: Any]])?.compactMap { dict -> CMCDEndpointConfiguration? in
19
+ guard let url = dict["url"] as? String else { return nil }
20
+ return CMCDEndpointConfiguration(url: url)
21
+ }
22
+ }
23
+ }
24
+
25
+ func playerCmcdConfiguration() -> CMCDConfiguration? {
26
+ let config = self.cmcdConfig
27
+ if config.externalSessionId == nil && config.userId == nil && config.eventEndpoints == nil {
28
+ return nil
29
+ }
30
+ return CMCDConfiguration(
31
+ externalSessionId: config.externalSessionId,
32
+ userId: config.userId,
33
+ eventEndpoints: config.eventEndpoints
34
+ )
35
+ }
36
+ }