bitmovin-player-react-native 0.40.0 → 0.42.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.
@@ -20,7 +20,7 @@ Pod::Spec.new do |s|
20
20
 
21
21
  s.swift_version = "5.10"
22
22
  s.dependency "React-Core"
23
- s.dependency "BitmovinPlayer", "3.85.2"
23
+ s.dependency "BitmovinPlayer", "3.90.0"
24
24
  s.ios.dependency "GoogleAds-IMA-iOS-SDK", "3.23.0"
25
25
  s.tvos.dependency "GoogleAds-IMA-tvOS-SDK", "4.13.0"
26
26
  end
@@ -103,8 +103,8 @@ dependencies {
103
103
  implementation "androidx.concurrent:concurrent-futures-ktx:1.1.0"
104
104
 
105
105
  // Bitmovin
106
- implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.33.0'
106
+ implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.35.1'
107
107
  implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
108
- implementation 'com.bitmovin.player:player:3.104.2+jason'
109
- implementation 'com.bitmovin.player:player-media-session:3.104.2+jason'
108
+ implementation 'com.bitmovin.player:player:3.112.0+jason'
109
+ implementation 'com.bitmovin.player:player-media-session:3.112.0+jason'
110
110
  }
@@ -1,8 +1,8 @@
1
1
  android.useAndroidX=true
2
2
  BitmovinPlayerReactNative_kotlinVersion=1.9.21
3
3
  BitmovinPlayerReactNative_minSdkVersion=21
4
- BitmovinPlayerReactNative_targetSdkVersion=34
5
- BitmovinPlayerReactNative_compileSdkVersion=34
4
+ BitmovinPlayerReactNative_targetSdkVersion=35
5
+ BitmovinPlayerReactNative_compileSdkVersion=35
6
6
  BitmovinPlayerReactNative_buildToolsVersion=34.0.0
7
7
  BitmovinPlayerReactNative_ndkversion=25.1.8937393
8
8
  BitmovinPlayerReactNative_androidToolsVersion=8.1.0
@@ -3,6 +3,7 @@ package com.bitmovin.player.reactnative
3
3
  import android.util.Log
4
4
  import com.bitmovin.player.api.Player
5
5
  import com.bitmovin.player.api.source.Source
6
+ import com.bitmovin.player.reactnative.extensions.decoderConfigModule
6
7
  import com.bitmovin.player.reactnative.extensions.drmModule
7
8
  import com.bitmovin.player.reactnative.extensions.networkModule
8
9
  import com.bitmovin.player.reactnative.extensions.offlineModule
@@ -58,6 +59,9 @@ abstract class BitmovinBaseModule(
58
59
  protected val RejectPromiseOnExceptionBlock.networkModule: NetworkModule get() = context.networkModule
59
60
  ?: throw IllegalStateException("NetworkModule not found")
60
61
 
62
+ protected val RejectPromiseOnExceptionBlock.decoderConfigModule: DecoderConfigModule
63
+ get() = context.decoderConfigModule ?: throw IllegalStateException("DecoderConfigModule not found")
64
+
61
65
  fun RejectPromiseOnExceptionBlock.getPlayer(
62
66
  nativeId: NativeId,
63
67
  playerModule: PlayerModule = this.playerModule,
@@ -0,0 +1,81 @@
1
+ package com.bitmovin.player.reactnative
2
+
3
+ import androidx.concurrent.futures.CallbackToFutureAdapter
4
+ import com.bitmovin.player.api.decoder.DecoderConfig
5
+ import com.bitmovin.player.api.decoder.DecoderPriorityProvider
6
+ import com.bitmovin.player.api.decoder.MediaCodecInfo
7
+ import com.bitmovin.player.reactnative.converter.toJson
8
+ import com.bitmovin.player.reactnative.converter.toMediaCodecInfoList
9
+ import com.facebook.react.bridge.*
10
+ import com.facebook.react.module.annotations.ReactModule
11
+ import java.util.concurrent.ConcurrentHashMap
12
+
13
+ private const val MODULE_NAME = "DecoderConfigModule"
14
+
15
+ @ReactModule(name = MODULE_NAME)
16
+ class DecoderConfigModule(context: ReactApplicationContext) : BitmovinBaseModule(context) {
17
+ override fun getName() = MODULE_NAME
18
+
19
+ /**
20
+ * In-memory mapping from `nativeId`s to `DecoderConfig` instances.
21
+ */
22
+ private val decoderConfigs: Registry<DecoderConfig> = mutableMapOf()
23
+ private val overrideDecoderPriorityProviderCompleters =
24
+ ConcurrentHashMap<NativeId, CallbackToFutureAdapter.Completer<List<MediaCodecInfo>>>()
25
+
26
+ fun getConfig(nativeId: NativeId?): DecoderConfig? = nativeId?.let { decoderConfigs[it] }
27
+
28
+ @ReactMethod
29
+ fun initWithConfig(nativeId: NativeId, config: ReadableMap, promise: Promise) {
30
+ if (decoderConfigs.containsKey(nativeId)) {
31
+ return
32
+ }
33
+ if (config.getMap("playbackConfig")?.hasKey("decoderConfig") == false) {
34
+ return
35
+ }
36
+
37
+ val decoderConfig = DecoderConfig(
38
+ decoderPriorityProvider = object : DecoderPriorityProvider {
39
+ override fun overrideDecodersPriority(
40
+ context: DecoderPriorityProvider.DecoderContext,
41
+ preferredDecoders: List<MediaCodecInfo>,
42
+ ): List<MediaCodecInfo> {
43
+ return overrideDecoderPriorityProvider(nativeId, context, preferredDecoders)
44
+ }
45
+ },
46
+ )
47
+ decoderConfigs[nativeId] = decoderConfig
48
+ }
49
+
50
+ @ReactMethod
51
+ fun destroy(nativeId: NativeId) {
52
+ decoderConfigs.remove(nativeId)
53
+ overrideDecoderPriorityProviderCompleters.keys.filter { it.startsWith(nativeId) }.forEach {
54
+ overrideDecoderPriorityProviderCompleters.remove(it)
55
+ }
56
+ }
57
+
58
+ private fun overrideDecoderPriorityProvider(
59
+ nativeId: NativeId,
60
+ context: DecoderPriorityProvider.DecoderContext,
61
+ preferredDecoders: List<MediaCodecInfo>,
62
+ ): List<MediaCodecInfo> {
63
+ return CallbackToFutureAdapter.getFuture { completer ->
64
+ overrideDecoderPriorityProviderCompleters[nativeId] = completer
65
+ val args = Arguments.createArray()
66
+ args.pushMap(context.toJson())
67
+ args.pushArray(preferredDecoders.toJson())
68
+ this@DecoderConfigModule.context.catalystInstance.callFunction(
69
+ "DecoderConfigBridge-$nativeId",
70
+ "overrideDecodersPriority",
71
+ args as NativeArray,
72
+ )
73
+ }.get()
74
+ }
75
+
76
+ @ReactMethod
77
+ fun overrideDecoderPriorityProviderComplete(nativeId: NativeId, response: ReadableArray) {
78
+ overrideDecoderPriorityProviderCompleters[nativeId]?.set(response.toMediaCodecInfoList())
79
+ overrideDecoderPriorityProviderCompleters.remove(nativeId)
80
+ }
81
+ }
@@ -43,8 +43,21 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex
43
43
  * @param config `PlayerConfig` object received from JS.
44
44
  */
45
45
  @ReactMethod
46
- fun initWithConfig(nativeId: NativeId, config: ReadableMap?, networkNativeId: NativeId?, promise: Promise) {
47
- init(nativeId, config, networkNativeId = networkNativeId, analyticsConfigJson = null, promise)
46
+ fun initWithConfig(
47
+ nativeId: NativeId,
48
+ config: ReadableMap?,
49
+ networkNativeId: NativeId?,
50
+ decoderNativeId: NativeId?,
51
+ promise: Promise,
52
+ ) {
53
+ init(
54
+ nativeId,
55
+ config,
56
+ networkNativeId = networkNativeId,
57
+ decoderNativeId = decoderNativeId,
58
+ analyticsConfigJson = null,
59
+ promise,
60
+ )
48
61
  }
49
62
 
50
63
  /**
@@ -57,14 +70,23 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex
57
70
  nativeId: NativeId,
58
71
  playerConfigJson: ReadableMap?,
59
72
  networkNativeId: NativeId?,
73
+ decoderNativeId: NativeId?,
60
74
  analyticsConfigJson: ReadableMap,
61
75
  promise: Promise,
62
- ) = init(nativeId, playerConfigJson, networkNativeId, analyticsConfigJson, promise)
76
+ ) = init(
77
+ nativeId,
78
+ playerConfigJson,
79
+ networkNativeId,
80
+ decoderNativeId,
81
+ analyticsConfigJson,
82
+ promise,
83
+ )
63
84
 
64
85
  private fun init(
65
86
  nativeId: NativeId,
66
87
  playerConfigJson: ReadableMap?,
67
88
  networkNativeId: NativeId?,
89
+ decoderNativeId: NativeId?,
68
90
  analyticsConfigJson: ReadableMap?,
69
91
  promise: Promise,
70
92
  ) = promise.unit.resolveOnUiThread {
@@ -85,6 +107,11 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex
85
107
  playerConfig.networkConfig = networkConfig
86
108
  }
87
109
 
110
+ val decoderConfig = decoderNativeId?.let { decoderConfigModule.getConfig(it) }
111
+ if (decoderConfig != null) {
112
+ playerConfig.playbackConfig = playerConfig.playbackConfig.copy(decoderConfig = decoderConfig)
113
+ }
114
+
88
115
  players[nativeId] = if (analyticsConfig == null) {
89
116
  Player.create(context, playerConfig)
90
117
  } else {
@@ -31,6 +31,7 @@ class RNPlayerViewPackage : ReactPackage {
31
31
  BufferModule(reactContext),
32
32
  NetworkModule(reactContext),
33
33
  DebugModule(reactContext),
34
+ DecoderConfigModule(reactContext),
34
35
  )
35
36
  }
36
37
 
@@ -25,6 +25,8 @@ import com.bitmovin.player.api.buffer.BufferLevel
25
25
  import com.bitmovin.player.api.buffer.BufferMediaTypeConfig
26
26
  import com.bitmovin.player.api.buffer.BufferType
27
27
  import com.bitmovin.player.api.casting.RemoteControlConfig
28
+ import com.bitmovin.player.api.decoder.DecoderPriorityProvider.DecoderContext
29
+ import com.bitmovin.player.api.decoder.MediaCodecInfo
28
30
  import com.bitmovin.player.api.drm.WidevineConfig
29
31
  import com.bitmovin.player.api.event.PlayerEvent
30
32
  import com.bitmovin.player.api.event.SourceEvent
@@ -32,6 +34,7 @@ import com.bitmovin.player.api.event.data.CastPayload
32
34
  import com.bitmovin.player.api.event.data.SeekPosition
33
35
  import com.bitmovin.player.api.live.LiveConfig
34
36
  import com.bitmovin.player.api.media.AdaptationConfig
37
+ import com.bitmovin.player.api.media.MediaTrackRole
35
38
  import com.bitmovin.player.api.media.MediaType
36
39
  import com.bitmovin.player.api.media.audio.AudioTrack
37
40
  import com.bitmovin.player.api.media.subtitle.SubtitleTrack
@@ -206,7 +209,6 @@ fun ReadableMap.toTweaksConfig(): TweaksConfig = TweaksConfig().apply {
206
209
  withBoolean("useDrmSessionForClearPeriods") { useDrmSessionForClearPeriods = it }
207
210
  withBoolean("useDrmSessionForClearSources") { useDrmSessionForClearSources = it }
208
211
  withBoolean("useFiletypeExtractorFallbackForHls") { useFiletypeExtractorFallbackForHls = it }
209
- withBoolean("preferSoftwareDecodingForAds") { preferSoftwareDecodingForAds = it }
210
212
  withStringArray("forceReuseVideoCodecReasons") {
211
213
  forceReuseVideoCodecReasons = it
212
214
  .filterNotNull()
@@ -558,6 +560,7 @@ fun AudioTrack.toJson(): WritableMap = Arguments.createMap().apply {
558
560
  putBoolean("isDefault", isDefault)
559
561
  putString("identifier", id)
560
562
  putString("language", language)
563
+ putArray("roles", roles.mapToReactArray { it.toJson() })
561
564
  }
562
565
 
563
566
  /**
@@ -595,6 +598,7 @@ fun SubtitleTrack.toJson(): WritableMap = Arguments.createMap().apply {
595
598
  putString("language", language)
596
599
  putBoolean("isForced", isForced)
597
600
  putString("format", mimeType?.textMimeTypeToJson())
601
+ putArray("roles", roles.mapToReactArray { it.toJson() })
598
602
  }
599
603
 
600
604
  /**
@@ -949,3 +953,43 @@ private fun CastPayload.toJson(): WritableMap = Arguments.createMap().apply {
949
953
  }
950
954
 
951
955
  private fun WritableMap.putStringIfNotNull(name: String, value: String?) = value?.let { putString(name, value) }
956
+
957
+ fun DecoderContext.toJson(): ReadableMap = Arguments.createMap().apply {
958
+ putString("mediaType", mediaType.name)
959
+ putBoolean("isAd", isAd)
960
+ }
961
+
962
+ fun List<MediaCodecInfo>.toJson(): ReadableArray = Arguments.createArray().apply {
963
+ forEach {
964
+ pushMap(it.toJson())
965
+ }
966
+ }
967
+
968
+ fun MediaCodecInfo.toJson(): ReadableMap = Arguments.createMap().apply {
969
+ putString("name", name)
970
+ putBoolean("isSoftware", isSoftware)
971
+ }
972
+
973
+ fun ReadableArray.toMediaCodecInfoList(): List<MediaCodecInfo> {
974
+ if (size() <= 0) {
975
+ return emptyList()
976
+ }
977
+ val mediaCodecInfoList = mutableListOf<MediaCodecInfo>()
978
+ (0 until size()).forEach {
979
+ val info = getMap(it).toMediaCodecInfo() ?: return@forEach
980
+ mediaCodecInfoList.add(info)
981
+ }
982
+ return mediaCodecInfoList
983
+ }
984
+
985
+ fun ReadableMap.toMediaCodecInfo(): MediaCodecInfo? {
986
+ val name = getString("name") ?: return null
987
+ val isSoftware = getBooleanOrNull("isSoftware") ?: return null
988
+ return MediaCodecInfo(name, isSoftware)
989
+ }
990
+
991
+ fun MediaTrackRole.toJson(): WritableMap = Arguments.createMap().apply {
992
+ putString("id", id)
993
+ putString("schemeIdUri", schemeIdUri)
994
+ putString("value", value)
995
+ }
@@ -1,5 +1,6 @@
1
1
  package com.bitmovin.player.reactnative.extensions
2
2
 
3
+ import com.bitmovin.player.reactnative.DecoderConfigModule
3
4
  import com.bitmovin.player.reactnative.DrmModule
4
5
  import com.bitmovin.player.reactnative.NetworkModule
5
6
  import com.bitmovin.player.reactnative.OfflineModule
@@ -20,3 +21,4 @@ val ReactApplicationContext.uiManagerModule get() = getModule<UIManagerModule>()
20
21
  val ReactApplicationContext.drmModule get() = getModule<DrmModule>()
21
22
  val ReactApplicationContext.customMessageHandlerModule get() = getModule<CustomMessageHandlerModule>()
22
23
  val ReactApplicationContext.networkModule get() = getModule<NetworkModule>()
24
+ val ReactApplicationContext.decoderConfigModule get() = getModule<DecoderConfigModule>()
@@ -150,12 +150,12 @@ class OfflineContentManagerBridge(
150
150
  /**
151
151
  * Called when a process call has completed.
152
152
  */
153
- override fun onCompleted(source: SourceConfig?, options: OfflineContentOptions?) {
153
+ override fun onCompleted(source: SourceConfig, options: OfflineContentOptions) {
154
154
  this.contentOptions = options
155
155
  sendEvent(
156
156
  OfflineEventType.ON_COMPLETED,
157
157
  Arguments.createMap().apply {
158
- putMap("options", options?.toJson())
158
+ putMap("options", options.toJson())
159
159
  },
160
160
  )
161
161
  }
@@ -163,12 +163,12 @@ class OfflineContentManagerBridge(
163
163
  /**
164
164
  * Called when an error occurs.
165
165
  */
166
- override fun onError(source: SourceConfig?, event: ErrorEvent?) {
166
+ override fun onError(source: SourceConfig, event: ErrorEvent) {
167
167
  sendEvent(
168
168
  OfflineEventType.ON_ERROR,
169
169
  Arguments.createMap().apply {
170
- event?.code?.value?.let { putInt("code", it) }
171
- putString("message", event?.message)
170
+ putInt("code", event.code.value)
171
+ putString("message", event.message)
172
172
  },
173
173
  )
174
174
  }
@@ -176,7 +176,7 @@ class OfflineContentManagerBridge(
176
176
  /**
177
177
  * Called when the progress for a process call changes.
178
178
  */
179
- override fun onProgress(source: SourceConfig?, progress: Float) {
179
+ override fun onProgress(source: SourceConfig, progress: Float) {
180
180
  sendEvent(
181
181
  OfflineEventType.ON_PROGRESS,
182
182
  Arguments.createMap().apply {
@@ -188,12 +188,12 @@ class OfflineContentManagerBridge(
188
188
  /**
189
189
  * Called after a getOptions or when am OfflineOptionEntry has been updated during a process call.
190
190
  */
191
- override fun onOptionsAvailable(source: SourceConfig?, options: OfflineContentOptions?) {
191
+ override fun onOptionsAvailable(source: SourceConfig, options: OfflineContentOptions) {
192
192
  this.contentOptions = options
193
193
  sendEvent(
194
194
  OfflineEventType.ON_OPTIONS_AVAILABLE,
195
195
  Arguments.createMap().apply {
196
- putMap("options", options?.toJson())
196
+ putMap("options", options.toJson())
197
197
  },
198
198
  )
199
199
  }
@@ -201,21 +201,21 @@ class OfflineContentManagerBridge(
201
201
  /**
202
202
  * Called when the DRM license was updated.
203
203
  */
204
- override fun onDrmLicenseUpdated(source: SourceConfig?) {
204
+ override fun onDrmLicenseUpdated(source: SourceConfig) {
205
205
  sendEvent(OfflineEventType.ON_DRM_LICENSE_UPDATED)
206
206
  }
207
207
 
208
208
  /**
209
209
  * Called when all actions have been suspended.
210
210
  */
211
- override fun onSuspended(source: SourceConfig?) {
211
+ override fun onSuspended(source: SourceConfig) {
212
212
  sendEvent(OfflineEventType.ON_SUSPENDED)
213
213
  }
214
214
 
215
215
  /**
216
216
  * Called when all actions have been resumed.
217
217
  */
218
- override fun onResumed(source: SourceConfig?) {
218
+ override fun onResumed(source: SourceConfig) {
219
219
  sendEvent(OfflineEventType.ON_RESUMED)
220
220
  }
221
221
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  @interface RCT_EXTERN_REMAP_MODULE(PlayerModule, PlayerModule, NSObject)
4
4
 
5
- RCT_EXTERN_METHOD(initWithConfig:(NSString *)nativeId config:(nullable id)config networkNativeId:(nullable NSString *)networkNativeId)
6
- RCT_EXTERN_METHOD(initWithAnalyticsConfig:(NSString *)nativeId config:(nullable id)config networkNativeId:(nullable NSString *)networkNativeId analyticsConfig:(nullable id)analyticsConfig)
5
+ RCT_EXTERN_METHOD(initWithConfig:(NSString *)nativeId config:(nullable id)config networkNativeId:(nullable NSString *)networkNativeId decoderNativeId:(nullable NSString *)decoderNativeId)
6
+ RCT_EXTERN_METHOD(initWithAnalyticsConfig:(NSString *)nativeId config:(nullable id)config networkNativeId:(nullable NSString *)networkNativeId analyticsConfig:(nullable id)analyticsConfig decoderNativeId:(nullable NSString *)decoderNativeId)
7
7
  RCT_EXTERN_METHOD(loadSource:(NSString *)nativeId sourceNativeId:(NSString *)sourceNativeId)
8
8
  RCT_EXTERN_METHOD(loadOfflineContent:(NSString *)nativeId offlineContentManagerBridgeId:(NSString *)offlineContentManagerBridgeId options:(nullable id)options)
9
9
  RCT_EXTERN_METHOD(unload:(NSString *)nativeId)
@@ -46,11 +46,12 @@ public class PlayerModule: NSObject, RCTBridgeModule { // swiftlint:disable:this
46
46
  Creates a new `Player` instance inside the internal players using the provided `config` object.
47
47
  - Parameter config: `PlayerConfig` object received from JS.
48
48
  */
49
- @objc(initWithConfig:config:networkNativeId:)
49
+ @objc(initWithConfig:config:networkNativeId:decoderNativeId:)
50
50
  func initWithConfig(
51
51
  _ nativeId: NativeId,
52
52
  config: Any?,
53
- networkNativeId: NativeId?
53
+ networkNativeId: NativeId?,
54
+ decoderNativeId: NativeId?
54
55
  ) {
55
56
  bridge.uiManager.addUIBlock { [weak self] _, _ in
56
57
  guard
@@ -76,12 +77,13 @@ public class PlayerModule: NSObject, RCTBridgeModule { // swiftlint:disable:this
76
77
  - Parameter config: `PlayerConfig` object received from JS.
77
78
  - Parameter analyticsConfig: `AnalyticsConfig` object received from JS.
78
79
  */
79
- @objc(initWithAnalyticsConfig:config:networkNativeId:analyticsConfig:)
80
+ @objc(initWithAnalyticsConfig:config:networkNativeId:analyticsConfig:decoderNativeId:)
80
81
  func initWithAnalyticsConfig(
81
82
  _ nativeId: NativeId,
82
83
  config: Any?,
83
84
  networkNativeId: NativeId?,
84
- analyticsConfig: Any?
85
+ analyticsConfig: Any?,
86
+ decoderNativeId: NativeId?
85
87
  ) {
86
88
  bridge.uiManager.addUIBlock { [weak self] _, _ in
87
89
  let analyticsConfigJson = analyticsConfig
@@ -561,13 +561,20 @@ extension RCTConvert {
561
561
  guard let audioTrack else {
562
562
  return nil
563
563
  }
564
- return [
564
+ var audioTrackDict: [String: Any?] = [
565
565
  "url": audioTrack.url?.absoluteString,
566
566
  "label": audioTrack.label,
567
567
  "isDefault": audioTrack.isDefaultTrack,
568
568
  "identifier": audioTrack.identifier,
569
569
  "language": audioTrack.language
570
570
  ]
571
+ audioTrackDict["roles"] = audioTrack.characteristics.map { characteristic in
572
+ [
573
+ "schemeIdUri": "urn:hls:characteristic",
574
+ "value": characteristic
575
+ ]
576
+ }
577
+ return audioTrackDict
571
578
  }
572
579
 
573
580
  /**
@@ -641,26 +648,34 @@ extension RCTConvert {
641
648
  guard let subtitleTrack else {
642
649
  return nil
643
650
  }
644
- return [
651
+ var subtitleTrackDict: [String: Any?] = [
645
652
  "url": subtitleTrack.url?.absoluteString,
646
653
  "label": subtitleTrack.label,
647
654
  "isDefault": subtitleTrack.isDefaultTrack,
648
655
  "identifier": subtitleTrack.identifier,
649
656
  "language": subtitleTrack.language,
650
657
  "isForced": subtitleTrack.isForced,
651
- "format": {
652
- switch subtitleTrack.format {
653
- case .cea:
654
- return "cea"
655
- case .webVtt:
656
- return "vtt"
657
- case .ttml:
658
- return "ttml"
659
- case .srt:
660
- return "srt"
661
- }
662
- }(),
663
658
  ]
659
+ switch subtitleTrack.format {
660
+ case .cea:
661
+ subtitleTrackDict["format"] = "cea"
662
+ case .webVtt:
663
+ subtitleTrackDict["format"] = "vtt"
664
+ case .ttml:
665
+ subtitleTrackDict["format"] = "ttml"
666
+ case .srt:
667
+ subtitleTrackDict["format"] = "srt"
668
+ default:
669
+ break
670
+ }
671
+
672
+ subtitleTrackDict["roles"] = subtitleTrack.characteristics.map { characteristic in
673
+ [
674
+ "schemeIdUri": "urn:hls:characteristic",
675
+ "value": characteristic
676
+ ]
677
+ }
678
+ return subtitleTrackDict
664
679
  }
665
680
 
666
681
  /**