bitmovin-player-react-native 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/RNBitmovinPlayer.podspec +1 -1
  2. package/android/build.gradle +1 -2
  3. package/android/src/main/java/com/bitmovin/player/reactnative/AnalyticsModule.kt +43 -11
  4. package/android/src/main/java/com/bitmovin/player/reactnative/OfflineModule.kt +279 -0
  5. package/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +37 -1
  6. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt +1 -0
  7. package/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +123 -35
  8. package/android/src/main/java/com/bitmovin/player/reactnative/offline/OfflineContentManagerBridge.kt +216 -0
  9. package/android/src/main/java/com/bitmovin/player/reactnative/offline/OfflineDownloadRequest.kt +7 -0
  10. package/ios/DrmModule.swift +5 -5
  11. package/ios/OfflineContentManagerBridge.swift +141 -0
  12. package/ios/OfflineModule.m +19 -0
  13. package/ios/OfflineModule.swift +465 -0
  14. package/ios/PlayerModule.m +2 -0
  15. package/ios/PlayerModule.swift +40 -7
  16. package/ios/RCTConvert+BitmovinPlayer.swift +135 -1
  17. package/ios/RNBitmovinPlayer.h +1 -0
  18. package/lib/index.d.mts +382 -2
  19. package/lib/index.d.ts +382 -2
  20. package/lib/index.js +233 -0
  21. package/lib/index.mjs +231 -0
  22. package/package.json +1 -1
  23. package/src/adaptationConfig.ts +10 -0
  24. package/src/index.ts +2 -0
  25. package/src/offline/index.ts +7 -0
  26. package/src/offline/offlineContentConfig.ts +18 -0
  27. package/src/offline/offlineContentManager.ts +216 -0
  28. package/src/offline/offlineContentManagerListener.ts +187 -0
  29. package/src/offline/offlineContentOptions.ts +29 -0
  30. package/src/offline/offlineDownloadRequest.ts +20 -0
  31. package/src/offline/offlineSourceOptions.ts +11 -0
  32. package/src/offline/offlineState.ts +22 -0
  33. package/src/player.ts +30 -0
  34. package/src/source.ts +38 -0
@@ -0,0 +1,465 @@
1
+
2
+ import Foundation
3
+ import BitmovinPlayer
4
+
5
+ @objc(OfflineModule)
6
+ class OfflineModule: RCTEventEmitter {
7
+ /// JS module name.
8
+ override static func moduleName() -> String! {
9
+ "BitmovinOfflineModule"
10
+ }
11
+
12
+ /// Module requires main thread initialization.
13
+ override static func requiresMainQueueSetup() -> Bool {
14
+ true
15
+ }
16
+
17
+ override func supportedEvents() -> [String]! {
18
+ return ["BitmovinOfflineEvent"]
19
+ }
20
+
21
+ /// Since most `OfflineModule` operations are UI related and need to be executed on the main thread, they are scheduled with `UIManager.addBlock`.
22
+ override var methodQueue: DispatchQueue! {
23
+ bridge.uiManager.methodQueue
24
+ }
25
+
26
+ #if os(iOS)
27
+ private var offlineContentManagerBridges: Registry<OfflineContentManagerBridge> = [:]
28
+
29
+ /**
30
+ Retrieves the `OfflineContentManager` instance associated with `nativeId` from the internal offline managers.
31
+ - Parameter nativeId `OfflineContentManager` instance ID.
32
+ - Returns: The associated `OfflineContentManager` instance or `nil`.
33
+ */
34
+ func retrieve(_ nativeId: NativeId) -> OfflineContentManagerBridge? {
35
+ offlineContentManagerBridges[nativeId]
36
+ }
37
+ #endif
38
+
39
+ /**
40
+ Creates a new `OfflineContentManager` instance inside the internal offline managers using the provided config object.
41
+ - @param config Config object received from JS. Should contain `sourceConfig` and `identifier`.
42
+ */
43
+ @objc(initWithConfig:config:drmNativeId:resolver:rejecter:)
44
+ func initWithConfig(
45
+ _ nativeId: NativeId,
46
+ config: Any?,
47
+ drmNativeId: NativeId?,
48
+ resolver resolve: @escaping RCTPromiseResolveBlock,
49
+ rejecter reject: @escaping RCTPromiseRejectBlock
50
+ ) {
51
+ #if os(iOS)
52
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
53
+ guard
54
+ let self = self,
55
+ self.offlineContentManagerBridges[nativeId] == nil,
56
+ let config = config as? [String: Any?],
57
+ let identifier = config["identifier"] as? String
58
+ else {
59
+ reject("BitmovinOfflineModule", "Could not create an offline content manager", nil)
60
+ return
61
+ }
62
+
63
+ let fairplayConfig = drmNativeId.flatMap { self.bridge[DrmModule.self]?.retrieve($0) }
64
+ guard let sourceConfig = RCTConvert.sourceConfig(config["sourceConfig"], drmConfig: fairplayConfig) else {
65
+ reject("BitmovinOfflineModule", "Invalid source config", nil)
66
+ return
67
+ }
68
+
69
+ do {
70
+ let offlineContentManager = try OfflineManager.sharedInstance()
71
+ .offlineContentManager(for: sourceConfig, id: identifier)
72
+ let offlineContentManagerBridge = OfflineContentManagerBridge(
73
+ forManager: offlineContentManager,
74
+ eventEmitter: self,
75
+ nativeId: nativeId,
76
+ identifier: identifier
77
+ )
78
+
79
+ self.offlineContentManagerBridges[nativeId] = offlineContentManagerBridge
80
+ resolve(nil)
81
+ } catch let error as NSError {
82
+ reject("BitmovinOfflineModule", "Could not create an offline content manager", error)
83
+ }
84
+ }
85
+ #endif
86
+ }
87
+
88
+ @objc(getState:resolver:rejecter:)
89
+ func getState(
90
+ _ nativeId: NativeId,
91
+ resolver resolve: @escaping RCTPromiseResolveBlock,
92
+ rejecter reject: @escaping RCTPromiseRejectBlock
93
+ ) {
94
+ #if os(iOS)
95
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
96
+ guard
97
+ let self = self,
98
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
99
+ else {
100
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
101
+ return
102
+ }
103
+
104
+ resolve(RCTConvert.toJson(offlineState: offlineContentManagerBridge.offlineContentManager.offlineState))
105
+ }
106
+ #endif
107
+ }
108
+
109
+ /**
110
+ Starts the `OfflineContentManager`'s asynchronous process of fetching the `OfflineContentOptions`.
111
+ When the options are loaded a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onOptionsAvailable`.
112
+ - Parameter nativeId: Target offline module Id.
113
+ - Parameter resolver: JS promise resolver.
114
+ - Parameter rejecter: JS promise rejecter.
115
+ */
116
+ @objc(getOptions:resolver:rejecter:)
117
+ func getOptions(
118
+ _ nativeId: NativeId,
119
+ resolver resolve: @escaping RCTPromiseResolveBlock,
120
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
121
+ #if os(iOS)
122
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
123
+ guard
124
+ let self = self,
125
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
126
+ else {
127
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
128
+ return
129
+ }
130
+
131
+ offlineContentManagerBridge.fetchAvailableTracks()
132
+ resolve(nil)
133
+ }
134
+ #endif
135
+ }
136
+
137
+ /**
138
+ Enqueues downloads according to the `OfflineDownloadRequest`.
139
+ * The promise will reject in the event of null or invalid request parameters.
140
+ - Parameter nativeId: Target offline module Id
141
+ - Parameter request: The download request js object containing the requested bitrate and track option ids to download.
142
+ - Parameter resolver: JS promise resolver.
143
+ - Parameter rejecter: JS promise rejecter.
144
+ */
145
+ @objc(download:request:resolver:rejecter:)
146
+ func download(
147
+ _ nativeId: NativeId,
148
+ request: Any?,
149
+ resolver resolve: @escaping RCTPromiseResolveBlock,
150
+ rejecter reject: @escaping RCTPromiseRejectBlock
151
+ ) {
152
+ #if os(iOS)
153
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
154
+ guard
155
+ let self = self,
156
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
157
+ else {
158
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
159
+ return
160
+ }
161
+
162
+ switch offlineContentManagerBridge.offlineContentManager.offlineState {
163
+ case .downloaded:
164
+ reject("BitmovinOfflineModule", "Download already completed", nil)
165
+ return
166
+ case .downloading:
167
+ reject("BitmovinOfflineModule", "Download already in progress", nil)
168
+ return
169
+ case .suspended:
170
+ reject("BitmovinOfflineModule", "Download is suspended", nil)
171
+ return
172
+ @unknown default:
173
+ break
174
+ }
175
+
176
+ guard let request = request as? [String: Any?] else {
177
+ reject("BitmovinOfflineModule", "Invalid download request", nil)
178
+ return
179
+ }
180
+
181
+ guard
182
+ let currentTrackSelection = offlineContentManagerBridge.currentTrackSelection
183
+ else {
184
+ reject("BitmovinOfflineModule", "Invalid download options", nil)
185
+ return
186
+ }
187
+
188
+ if let audioOptionIds = request["audioOptionIds"] as? [String],
189
+ !audioOptionIds.isEmpty {
190
+ currentTrackSelection.audioTracks.forEach {
191
+ if audioOptionIds.contains($0.label) {
192
+ $0.action = .download
193
+ } else {
194
+ $0.action = .none
195
+ }
196
+ }
197
+ }
198
+
199
+ if let textOptionIds = request["textOptionIds"] as? [String],
200
+ !textOptionIds.isEmpty {
201
+ currentTrackSelection.textTracks.forEach {
202
+ if textOptionIds.contains($0.label) {
203
+ $0.action = .download
204
+ } else {
205
+ $0.action = .none
206
+ }
207
+ }
208
+ }
209
+
210
+ let config = DownloadConfig()
211
+
212
+ if let minimumBitrate = request["minimumBitrate"] as? NSNumber {
213
+ config.minimumBitrate = minimumBitrate
214
+ }
215
+
216
+ offlineContentManagerBridge.offlineContentManager.download(tracks: currentTrackSelection, downloadConfig: config)
217
+ resolve(nil)
218
+ }
219
+ #endif
220
+ }
221
+
222
+ /**
223
+ Resumes all suspended actions.
224
+ - Parameter nativeId: Target offline module Id
225
+ - Parameter resolver: JS promise resolver.
226
+ - Parameter rejecter: JS promise rejecter.
227
+ */
228
+ @objc(resume:resolver:rejecter:)
229
+ func resume(
230
+ _ nativeId: NativeId,
231
+ resolver resolve: @escaping RCTPromiseResolveBlock,
232
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
233
+ #if os(iOS)
234
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
235
+ guard
236
+ let self = self,
237
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
238
+ else {
239
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
240
+ return
241
+ }
242
+
243
+ offlineContentManagerBridge.offlineContentManager.resumeDownload()
244
+ resolve(nil)
245
+ }
246
+ #endif
247
+ }
248
+
249
+ /**
250
+ Suspends all active actions.
251
+ - Parameter nativeId: Target offline module Id
252
+ - Parameter resolver: JS promise resolver.
253
+ - Parameter rejecter: JS promise rejecter.
254
+ */
255
+ @objc(suspend:resolver:rejecter:)
256
+ func suspend(
257
+ _ nativeId: NativeId,
258
+ resolver resolve: @escaping RCTPromiseResolveBlock,
259
+ rejecter reject: @escaping RCTPromiseRejectBlock
260
+ ) {
261
+ #if os(iOS)
262
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
263
+ guard
264
+ let self = self,
265
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
266
+ else {
267
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
268
+ return
269
+ }
270
+
271
+ offlineContentManagerBridge.offlineContentManager.suspendDownload()
272
+ resolve(nil)
273
+ }
274
+ #endif
275
+ }
276
+
277
+ /**
278
+ Cancels all active downloads and removes the data.
279
+ - Parameter nativeId: Target offline module Id
280
+ - Parameter resolver: JS promise resolver.
281
+ - Parameter rejecter: JS promise rejecter.
282
+ */
283
+ @objc(cancelDownload:resolver:rejecter:)
284
+ func cancelDownload(
285
+ _ nativeId: NativeId,
286
+ resolver resolve: @escaping RCTPromiseResolveBlock,
287
+ rejecter reject: @escaping RCTPromiseRejectBlock
288
+ ) {
289
+ #if os(iOS)
290
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
291
+ guard
292
+ let self = self,
293
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
294
+ else {
295
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
296
+ return
297
+ }
298
+
299
+ offlineContentManagerBridge.offlineContentManager.cancelDownload()
300
+ resolve(nil)
301
+ }
302
+ #endif
303
+ }
304
+
305
+ /**
306
+ Resolve `nativeId`'s current `usedStorage`.
307
+ - Parameter nativeId: Target offline module Id
308
+ - Parameter resolver: JS promise resolver.
309
+ - Parameter rejecter: JS promise rejecter.
310
+ */
311
+ @objc(usedStorage:resolver:rejecter:)
312
+ func usedStorage(
313
+ _ nativeId: NativeId,
314
+ resolver resolve: @escaping RCTPromiseResolveBlock,
315
+ rejecter reject: @escaping RCTPromiseRejectBlock
316
+ ) {
317
+ #if os(iOS)
318
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
319
+ guard
320
+ let self = self,
321
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
322
+ else {
323
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
324
+ return
325
+ }
326
+
327
+ resolve(offlineContentManagerBridge.offlineContentManager.usedStorage)
328
+ }
329
+ #endif
330
+ }
331
+
332
+ /**
333
+ Deletes everything related to the related content ID.
334
+ - Parameter nativeId: Target offline module Id
335
+ - Parameter resolver: JS promise resolver.
336
+ - Parameter rejecter: JS promise rejecter.
337
+ */
338
+ @objc(deleteAll:resolver:rejecter:)
339
+ func deleteAll(
340
+ _ nativeId: NativeId,
341
+ resolver resolve: @escaping RCTPromiseResolveBlock,
342
+ rejecter reject: @escaping RCTPromiseRejectBlock
343
+ ) {
344
+ #if os(iOS)
345
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
346
+ guard
347
+ let self = self,
348
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
349
+ else {
350
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
351
+ return
352
+ }
353
+
354
+ offlineContentManagerBridge.offlineContentManager.deleteOfflineData()
355
+ resolve(nil)
356
+ }
357
+ #endif
358
+ }
359
+
360
+ /**
361
+ Downloads the offline license.
362
+ When finished successfully a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onDrmLicenseUpdated`.
363
+ Errors are transmitted by a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onError`.
364
+ - Parameter nativeId: Target offline module Id
365
+ - Parameter resolver: JS promise resolver.
366
+ - Parameter rejecter: JS promise rejecter.
367
+ */
368
+ @objc(donwloadLicense:resolver:rejecter:)
369
+ func downloadLicense(
370
+ _ nativeId: NativeId,
371
+ resolver resolve: @escaping RCTPromiseResolveBlock,
372
+ rejecter reject: @escaping RCTPromiseRejectBlock
373
+ ) {
374
+ #if os(iOS)
375
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
376
+ guard
377
+ let self = self,
378
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
379
+ else {
380
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
381
+ return
382
+ }
383
+
384
+ offlineContentManagerBridge.offlineContentManager.syncOfflineDrmLicenseInformation()
385
+ resolve(nil)
386
+ }
387
+ #endif
388
+ }
389
+
390
+ /**
391
+ Renews the already downloaded DRM license.
392
+ When finished successfully a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onDrmLicenseUpdated`.
393
+ Errors are transmitted by a device event will be fired where the event type is `BitmovinOfflineEvent` and the data has an event type of `onError`.
394
+ - Parameter nativeId: Target offline module Id
395
+ - Parameter resolver: JS promise resolver.
396
+ - Parameter rejecter: JS promise rejecter.
397
+ */
398
+ @objc(renewOfflineLicense:resolver:rejecter:)
399
+ func renewOfflineLicense(
400
+ _ nativeId: NativeId,
401
+ resolver resolve: @escaping RCTPromiseResolveBlock,
402
+ rejecter reject: @escaping RCTPromiseRejectBlock
403
+ ) {
404
+ #if os(iOS)
405
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
406
+ guard
407
+ let self = self,
408
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
409
+ else {
410
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
411
+ return
412
+ }
413
+
414
+ offlineContentManagerBridge.offlineContentManager.renewOfflineLicense()
415
+ resolve(nil)
416
+ }
417
+ #endif
418
+ }
419
+
420
+ /**
421
+ Removes the `OfflineContentManagerListener` for the `nativeId`'s offline content manager.
422
+ IMPORTANT: Call this when the component, in which it was created, is destroyed.
423
+ The `OfflineManager` should not be used after calling this method.
424
+ - Parameter nativeId: Target offline module Id
425
+ - Parameter resolver: JS promise resolver.
426
+ - Parameter rejecter: JS promise rejecter.
427
+ */
428
+ @objc(release:resolver:rejecter:)
429
+ func release(
430
+ _ nativeId: NativeId,
431
+ resolver resolve: @escaping RCTPromiseResolveBlock,
432
+ rejecter reject: @escaping RCTPromiseRejectBlock
433
+ ) {
434
+ #if os(iOS)
435
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
436
+ guard
437
+ let self = self,
438
+ let offlineContentManagerBridge = self.offlineContentManagerBridges[nativeId]
439
+ else {
440
+ reject("BitmovinOfflineModule", "Could not find the offline module instance", nil)
441
+ return
442
+ }
443
+
444
+ offlineContentManagerBridge.release()
445
+ self.offlineContentManagerBridges[nativeId] = nil
446
+ resolve(nil)
447
+ }
448
+ #endif
449
+ }
450
+
451
+ /**
452
+ This method is no-op on iOS.
453
+ - Parameter nativeId: Target offline module Id
454
+ - Parameter resolver: JS promise resolver.
455
+ - Parameter rejecter: JS promise rejecter.
456
+ */
457
+ @objc(releaseLicense:resolver:rejecter:)
458
+ func releaseLicense(
459
+ _ nativeId: NativeId,
460
+ resolver resolve: @escaping RCTPromiseResolveBlock,
461
+ rejecter reject: @escaping RCTPromiseRejectBlock
462
+ ) {
463
+ resolve(nil)
464
+ }
465
+ }
@@ -4,6 +4,7 @@
4
4
 
5
5
  RCT_EXTERN_METHOD(initWithConfig:(NSString *)nativeId config:(nullable id)config)
6
6
  RCT_EXTERN_METHOD(loadSource:(NSString *)nativeId sourceNativeId:(NSString *)sourceNativeId)
7
+ RCT_EXTERN_METHOD(loadOfflineContent:(NSString *)nativeId offlineContentManagerBridgeId:(NSString *)offlineContentManagerBridgeId options:(nullable id)options)
7
8
  RCT_EXTERN_METHOD(unload:(NSString *)nativeId)
8
9
  RCT_EXTERN_METHOD(play:(NSString *)nativeId)
9
10
  RCT_EXTERN_METHOD(pause:(NSString *)nativeId)
@@ -82,5 +83,6 @@ RCT_EXTERN_METHOD(
82
83
  getMaxTimeShift:(NSString *)nativeId
83
84
  resolver:(RCTPromiseResolveBlock)resolve
84
85
  rejecter:(RCTPromiseRejectBlock)reject)
86
+ RCT_EXTERN_METHOD(setMaxSelectableBitrate:(NSString *)nativeId maxSelectableBitrate:(nonnull NSNumber *)maxSelectableBitrate)
85
87
 
86
88
  @end
@@ -59,7 +59,7 @@ class PlayerModule: NSObject, RCTBridgeModule {
59
59
  bridge.uiManager.addUIBlock { [weak self] _, _ in
60
60
  guard
61
61
  let player = self?.players[nativeId],
62
- let source = self?.getSourceModule()?.retrieve(sourceNativeId)
62
+ let source = self?.bridge[SourceModule.self]?.retrieve(sourceNativeId)
63
63
  else {
64
64
  return
65
65
  }
@@ -67,9 +67,29 @@ class PlayerModule: NSObject, RCTBridgeModule {
67
67
  }
68
68
  }
69
69
 
70
- /// Fetches the initialized `SourceModule` instance on RN's bridge object.
71
- private func getSourceModule() -> SourceModule? {
72
- bridge.module(for: SourceModule.self) as? SourceModule
70
+ /**
71
+ Loads the given offline source configuration into `nativeId`'s `Player` object.
72
+ - Parameter nativeId: Target player.
73
+ - Parameter offlineContentManagerBridgeId: The `nativeId` of the `OfflineModule` object.
74
+ */
75
+ @objc(loadOfflineContent:offlineContentManagerBridgeId:options:)
76
+ func loadOfflineContent(_ nativeId: NativeId, offlineContentManagerBridgeId: NativeId, options: Any?) {
77
+ #if os(iOS)
78
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
79
+ guard
80
+ let player = self?.players[nativeId],
81
+ let offlineContentManagerBridge = self?.bridge[OfflineModule.self]?.retrieve(offlineContentManagerBridgeId)
82
+ else {
83
+ return
84
+ }
85
+ let optionsDictionary = options as? [String: Any?] ?? [:]
86
+ let restrictedToAssetCache = optionsDictionary["restrictedToAssetCache"] as? Bool ?? true
87
+ let offlineSourceConfig = offlineContentManagerBridge.offlineContentManager.createOfflineSourceConfig(restrictedToAssetCache: restrictedToAssetCache)
88
+
89
+ guard let offlineSourceConfig = offlineSourceConfig else { return }
90
+ player.load(sourceConfig: offlineSourceConfig)
91
+ }
92
+ #endif
73
93
  }
74
94
 
75
95
  /**
@@ -116,7 +136,7 @@ class PlayerModule: NSObject, RCTBridgeModule {
116
136
  self?.players[nativeId]?.seek(time: time.doubleValue)
117
137
  }
118
138
  }
119
-
139
+
120
140
  /**
121
141
  Sets `timeShift` on `nativeId`'s player.
122
142
  - Parameter nativeId: Target player Id.
@@ -462,7 +482,7 @@ class PlayerModule: NSObject, RCTBridgeModule {
462
482
  resolve(self?.players[nativeId]?.isAd)
463
483
  }
464
484
  }
465
-
485
+
466
486
  /**
467
487
  The current time shift of the live stream in seconds. This value is always 0 if the active `source` is not a
468
488
  live stream or there are no sources loaded.
@@ -480,7 +500,7 @@ class PlayerModule: NSObject, RCTBridgeModule {
480
500
  resolve(self?.players[nativeId]?.timeShift)
481
501
  }
482
502
  }
483
-
503
+
484
504
  /**
485
505
  Returns the limit in seconds for time shift. Is either negative or 0. Is applicable for live streams only.
486
506
  - Parameter nativeId: Target player id.
@@ -497,4 +517,17 @@ class PlayerModule: NSObject, RCTBridgeModule {
497
517
  resolve(self?.players[nativeId]?.maxTimeShift)
498
518
  }
499
519
  }
520
+
521
+ /**
522
+ Sets the max selectable bitrate for the player.
523
+ - Parameter nativeId: Target player id.
524
+ - Parameter maxBitrate: The desired max bitrate limit.
525
+ */
526
+ @objc(setMaxSelectableBitrate:maxSelectableBitrate:)
527
+ func setMaxSelectableBitrate(_ nativeId: NativeId, maxSelectableBitrate: NSNumber) {
528
+ let maxSelectableBitrateValue = maxSelectableBitrate.uintValue
529
+ bridge.uiManager.addUIBlock { [weak self] _, _ in
530
+ self?.players[nativeId]?.maxSelectableBitrate = maxSelectableBitrateValue != -1 ? maxSelectableBitrateValue : 0
531
+ }
532
+ }
500
533
  }