bitmovin-player-react-native 0.1.0 → 0.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 (44) hide show
  1. package/README.md +163 -1
  2. package/RNBitmovinPlayer.podspec +3 -3
  3. package/android/src/main/java/com/bitmovin/player/reactnative/DrmModule.kt +191 -0
  4. package/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +116 -101
  5. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +69 -38
  6. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +10 -26
  7. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt +3 -0
  8. package/android/src/main/java/com/bitmovin/player/reactnative/Registry.kt +11 -0
  9. package/android/src/main/java/com/bitmovin/player/reactnative/SourceModule.kt +178 -0
  10. package/android/src/main/java/com/bitmovin/player/reactnative/UuidModule.kt +20 -0
  11. package/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +128 -1
  12. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/Events.kt +11 -2
  13. package/ios/DrmModule.m +16 -0
  14. package/ios/DrmModule.swift +442 -0
  15. package/ios/Event+JSON.swift +31 -0
  16. package/ios/PlayerModule.m +21 -25
  17. package/ios/PlayerModule.swift +122 -120
  18. package/ios/RCTConvert+BitmovinPlayer.swift +131 -12
  19. package/ios/RNPlayerView+PlayerListener.swift +12 -0
  20. package/ios/RNPlayerView.swift +5 -3
  21. package/ios/RNPlayerViewManager.m +3 -1
  22. package/ios/RNPlayerViewManager.swift +4 -21
  23. package/ios/Registry.swift +5 -0
  24. package/ios/SourceModule.m +30 -0
  25. package/ios/SourceModule.swift +187 -0
  26. package/ios/UuidModule.m +9 -0
  27. package/ios/UuidModule.swift +23 -0
  28. package/lib/index.d.ts +670 -235
  29. package/lib/index.js +257 -106
  30. package/lib/index.mjs +260 -112
  31. package/package.json +5 -4
  32. package/src/components/PlayerView/events.ts +24 -18
  33. package/src/components/PlayerView/index.tsx +29 -26
  34. package/src/drm/fairplayConfig.ts +90 -0
  35. package/src/drm/index.ts +178 -0
  36. package/src/drm/widevineConfig.ts +37 -0
  37. package/src/events.ts +67 -6
  38. package/src/hooks/useProxy.ts +19 -15
  39. package/src/index.ts +5 -3
  40. package/src/nativeInstance.ts +64 -0
  41. package/src/player.ts +51 -42
  42. package/src/source.ts +88 -8
  43. package/src/subtitleTrack.ts +60 -0
  44. package/src/utils.ts +15 -0
package/README.md CHANGED
@@ -7,7 +7,7 @@ Official React Native bindings for Bitmovin's mobile Player SDKs.
7
7
  [![MIT License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
8
8
  [![Bitmovin Community](https://img.shields.io/discourse/users?label=community&server=https%3A%2F%2Fcommunity.bitmovin.com)](https://community.bitmovin.com/?utm_source=github&utm_medium=bitmovin-player-react-native&utm_campaign=dev-community)
9
9
 
10
- > :warning: **Beta Version**: The library is under active development. The current Beta release supports basic playback of unprotected video assets.
10
+ > :warning: **Beta Version**: The library is under active development.
11
11
 
12
12
  - [Installation](#installation)
13
13
  - [Add package dependency](#add-package-dependency)
@@ -17,6 +17,8 @@ Official React Native bindings for Bitmovin's mobile Player SDKs.
17
17
  - [Setting up a license key](#setting-up-a-license-key)
18
18
  - [Accessing native `Player` instances](#accessing-native-player-instances)
19
19
  - [Listening to events](#listening-to-events)
20
+ - [Enabling DRM protection](#enabling-drm-protection)
21
+ - [Adding external subtitle tracks](#adding-subtitle-tracks)
20
22
  - [Contributing](#contributing)
21
23
 
22
24
  ## Installation
@@ -269,6 +271,166 @@ return (
269
271
  );
270
272
  ```
271
273
 
274
+ ### Enabling DRM protection
275
+
276
+ > ⚠️ **Beta Version**: For now, only `FairPlay` is supported on iOS and
277
+ > only `Widevine` is supported on Android. More DRM systems will be added in the future.
278
+
279
+ Simple streaming of protected assets can be enabled with just a little configuration on `SourceConfig.drmConfig`:
280
+
281
+ ```typescript
282
+ import { Platform } from 'react-native';
283
+ import { SourceConfig, SourceType } from 'bitmovin-player-react-native';
284
+
285
+ // Source configuration for protected assets.
286
+ const drmSource: SourceConfig = {
287
+ // Protected stream URL.
288
+ url:
289
+ Platform.OS === 'ios'
290
+ ? 'https://fps.ezdrm.com/demo/video/ezdrm.m3u8' // iOS stream url
291
+ : 'https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/mpds/11331.mpd', // Android stream url
292
+ // Stream type.
293
+ type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH,
294
+ // DRM setup.
295
+ // Each key in this object maps to a different DRM system config (`widevine` or `fairplay`).
296
+ drmConfig: {
297
+ // Widevine is the default and only DRM system supported on Android for now.
298
+ widevine: {
299
+ licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
300
+ },
301
+ // FairPlay is the default and only DRM system supported on iOS for now.
302
+ fairplay: {
303
+ licenseUrl:
304
+ 'https://fps.ezdrm.com/api/licenses/09cc0377-6dd4-40cb-b09d-b582236e70fe',
305
+ certificateUrl: 'https://fps.ezdrm.com/demo/video/eleisure.cer',
306
+ },
307
+ },
308
+ };
309
+ ```
310
+
311
+ #### Prepare hooks
312
+
313
+ In the native SDKs, some DRM properties like `message` and `license` can have their value transformed before use in order
314
+ to enable some more complex use cases: such as extracting the `license` from a `JSON`, for example.
315
+
316
+ In order to handle such transformations, it's possible to hook methods onto `SourceConfig.drmConfig` to proxy DRM values
317
+ and potentially alter them:
318
+
319
+ ```typescript
320
+ import { Platform } from 'react-native';
321
+ import { SourceConfig, SourceType } from 'bitmovin-player-react-native';
322
+
323
+ // Source configuration for protected assets.
324
+ const drmSource: SourceConfig = {
325
+ // Protected stream URL.
326
+ url:
327
+ Platform.OS === 'ios'
328
+ ? 'https://fps.ezdrm.com/demo/video/ezdrm.m3u8' // iOS stream url
329
+ : 'https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/mpds/11331.mpd', // Android stream url
330
+ // Stream type.
331
+ type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH,
332
+ // DRM setup.
333
+ drmConfig: {
334
+ // Widevine is the default and only DRM system supported on Android for now.
335
+ widevine: {
336
+ licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
337
+ // Data is passed as a base64 string and expects to return a base64 string.
338
+ prepareLicense: (license: string) => {
339
+ // Do something with the `license` value...
340
+ // And return processed data as base64 string.
341
+ return license; // base64 string
342
+ },
343
+ },
344
+ // FairPlay is the default and only DRM system supported on iOS for now.
345
+ fairplay: {
346
+ licenseUrl:
347
+ 'https://fps.ezdrm.com/api/licenses/09cc0377-6dd4-40cb-b09d-b582236e70fe',
348
+ certificateUrl: 'https://fps.ezdrm.com/demo/video/eleisure.cer',
349
+ // Data is passed as a base64 string and expects to return a base64 string.
350
+ prepareLicense: (license: string) => {
351
+ // Do something with the `license` value...
352
+ // And return processed data as base64 string.
353
+ return license; // base64 string
354
+ },
355
+ // Data is passed as a base64 string and expects to return a base64 string.
356
+ prepareMessage: (message: string, assetId: string) => {
357
+ // Do something with the `assetId` and `message` values...
358
+ // And return processed data as base64 string.
359
+ return message; // base64 string
360
+ },
361
+ },
362
+ },
363
+ };
364
+ ```
365
+
366
+ The [`FairplayConfig`](https://github.com/bitmovin/bitmovin-player-react-native/blob/development/src/drm.ts#L10) interface provides a bunch of hooks that can be used to fetch and transform different DRM related data. Check out the [docs](https://github.com/bitmovin/bitmovin-player-react-native/blob/development/src/drm.ts#L10) for a complete list and detailed information on them.
367
+
368
+ Also, don't forget to check out the [example](https://github.com/bitmovin/bitmovin-player-react-native/tree/development/example) app for a complete iOS/Android [DRM example](https://github.com/bitmovin/bitmovin-player-react-native/blob/development/example/src/screens/BasicDRMPlayback.tsx).
369
+
370
+ ### Adding external subtitle tracks
371
+
372
+ Usually, subtitle tracks are provided in the manifest of your content (see [Enconding Manifests API](https://bitmovin.com/docs/encoding/api-reference/sections/manifests) for more information). And if they are provided this way, the player already recognizes them and show them in the subtitles selection menu without any further configuration.
373
+
374
+ Otherwise, it's also possible to add external tracks via the subtitle API:
375
+
376
+ ```typescript
377
+ import { Platform } from 'react-native';
378
+ import {
379
+ SourceConfig,
380
+ SourceType,
381
+ SubtitleFormat,
382
+ } from 'bitmovin-player-react-native';
383
+
384
+ // Source config with an external subtitle track.
385
+ const config: SourceConfig = {
386
+ url:
387
+ Platform.OS === 'ios'
388
+ ? 'https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8'
389
+ : 'https://bitmovin-a.akamaihd.net/content/sintel/sintel.mpd',
390
+ type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH,
391
+ poster: 'https://bitmovin-a.akamaihd.net/content/sintel/poster.png',
392
+ // External subtitle tracks list to be added to this source.
393
+ subtitleTracks: [
394
+ // You can select 'Custom English' in the subtitles menu.
395
+ {
396
+ // The URL of the subtitle file. Required.
397
+ url: 'https://bitdash-a.akamaihd.net/content/sintel/subtitles/subtitles_en.vtt',
398
+ // External file format.
399
+ // Supports `.vtt`, `.ttml` and `.cea` extensions.
400
+ //
401
+ // This option can be left empty since the player automatically recognizes the format
402
+ // from the provided url most of the time.
403
+ format: SubtitleFormat.VTT,
404
+ // Label for this track shown under the selection menu. Required.
405
+ label: 'Custom English',
406
+ // The IETF BCP 47 language tag associated with this track. Required.
407
+ language: 'en',
408
+ // The unique identifier used for this track.
409
+ // The default value for this options is a randomly generated UUID.
410
+ identifier: 'sub1',
411
+ // This track is considered the default if set to `true`.
412
+ // The default value for this option is `false`.
413
+ isDefault: false,
414
+ // If set to `true` it means that the player should automatically select and switch this
415
+ // subtitle according to the selected audio language. Forced subtitles do not appear in
416
+ // `Player.getAvailableSubtitles`.
417
+ //
418
+ // The default value for this option is `false`.
419
+ isForced: false,
420
+ },
421
+ // You may add even more tracks to the list...
422
+ ],
423
+ };
424
+ ```
425
+
426
+ The supported `PlayerView` events for subtitles are:
427
+
428
+ - `onSubtitleAdded`
429
+ - `onSubtitleRemoved`
430
+ - `onSubtitleChanged`
431
+
432
+ You might check out a complete subtitle example in the [`example/`](https://github.com/bitmovin/bitmovin-player-react-native/tree/development/example) app.
433
+
272
434
  ## Contributing
273
435
 
274
436
  See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
@@ -10,10 +10,10 @@ Pod::Spec.new do |s|
10
10
  s.license = package["license"]
11
11
  s.authors = package["author"]
12
12
 
13
- s.platforms = { :ios => "12.0" }
13
+ s.platforms = { :ios => "12.4" }
14
14
  s.source = {
15
- :git => "https://github.com/bitmovin/player-react-native-bridge.git",
16
- :tag => "#{s.version}"
15
+ :git => "https://github.com/bitmovin/bitmovin-player-react-native.git",
16
+ :tag => "v#{s.version}"
17
17
  }
18
18
 
19
19
  s.source_files = "ios/**/*.{h,m,mm,swift}"
@@ -0,0 +1,191 @@
1
+ package com.bitmovin.player.reactnative
2
+
3
+ import android.util.Base64
4
+ import com.bitmovin.player.api.drm.PrepareLicenseCallback
5
+ import com.bitmovin.player.api.drm.PrepareMessageCallback
6
+ import com.bitmovin.player.api.drm.WidevineConfig
7
+ import com.bitmovin.player.reactnative.converter.JsonConverter
8
+ import com.facebook.react.bridge.*
9
+ import com.facebook.react.module.annotations.ReactModule
10
+ import com.facebook.react.uimanager.UIManagerModule
11
+ import java.util.concurrent.locks.Condition
12
+ import java.util.concurrent.locks.ReentrantLock
13
+ import kotlin.concurrent.withLock
14
+
15
+ /**
16
+ * Represents some operation that transforms data as bytes.
17
+ */
18
+ typealias PrepareCallback = (ByteArray) -> ByteArray
19
+
20
+ @ReactModule(name = DrmModule.name)
21
+ class DrmModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
22
+ /**
23
+ * In-memory mapping from `nativeId`s to `WidevineConfig` instances.
24
+ */
25
+ private val drmConfigs: Registry<WidevineConfig> = mutableMapOf()
26
+
27
+ /**
28
+ * Module's local lock object used to sync calls between Kotlin and JS.
29
+ */
30
+ private val lock = ReentrantLock()
31
+
32
+ /**
33
+ * Mapping between an object's `nativeId` and the value that'll be returned by its `prepareMessage` callback.
34
+ */
35
+ private val preparedMessages: Registry<String> = mutableMapOf()
36
+
37
+ /**
38
+ * Lock condition used to sync read/write operations on `preparedMessages`.
39
+ */
40
+ private val preparedMessagesCondition = lock.newCondition()
41
+
42
+ /**
43
+ * Mapping between an object's `nativeId` and the value that'll be returned by its `prepareLicense` callback.
44
+ */
45
+ private val preparedLicenses: Registry<String> = mutableMapOf()
46
+
47
+ /**
48
+ * Lock condition used to sync read/write operations on `preparedMessages`.
49
+ */
50
+ private val preparedLicensesCondition = lock.newCondition()
51
+
52
+ /**
53
+ * JS exported module name.
54
+ */
55
+ companion object {
56
+ const val name = "DrmModule"
57
+ }
58
+ override fun getName() = DrmModule.name
59
+
60
+ /**
61
+ * Fetches the `WidevineConfig` instance associated with `nativeId` from internal drmConfigs.
62
+ * @param nativeId `WidevineConfig` instance ID.
63
+ * @return The associated `WidevineConfig` instance or `null`.
64
+ */
65
+ fun getConfig(nativeId: NativeId?): WidevineConfig? {
66
+ if (nativeId == null) {
67
+ return null
68
+ }
69
+ return drmConfigs[nativeId]
70
+ }
71
+
72
+ /**
73
+ * Creates a new `WidevineConfig` instance inside the internal drmConfigs using the provided `config` object.
74
+ * @param nativeId ID to associate with the `WidevineConfig` instance.
75
+ * @param config `DrmConfig` object received from JS.
76
+ */
77
+ @ReactMethod
78
+ fun initWithConfig(nativeId: NativeId, config: ReadableMap?) {
79
+ uiManager()?.addUIBlock {
80
+ if (!drmConfigs.containsKey(nativeId) && config != null) {
81
+ JsonConverter.toWidevineConfig(config)?.let {
82
+ drmConfigs[nativeId] = it
83
+ initPrepareMessage(nativeId, config)
84
+ initPrepareLicense(nativeId, config)
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Removes the `WidevineConfig` instance associated with `nativeId` from the internal drmConfigs.
92
+ * @param nativeId `WidevineConfig` to be disposed.
93
+ */
94
+ @ReactMethod
95
+ fun destroy(nativeId: NativeId) {
96
+ drmConfigs.remove(nativeId)
97
+ }
98
+
99
+ /**
100
+ * Function called from JS to store the computed `prepareMessage` return value for `nativeId`.
101
+ */
102
+ @ReactMethod(isBlockingSynchronousMethod = true)
103
+ fun setPreparedMessage(nativeId: NativeId, message: String) {
104
+ lock.withLock {
105
+ preparedMessages[nativeId] = message
106
+ preparedMessagesCondition.signal()
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Function called from JS to store the computed `prepareLicense` return value for `nativeId`.
112
+ */
113
+ @ReactMethod(isBlockingSynchronousMethod = true)
114
+ fun setPreparedLicense(nativeId: NativeId, license: String) {
115
+ lock.withLock {
116
+ preparedLicenses[nativeId] = license
117
+ preparedLicensesCondition.signal()
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Initialize the `prepareMessage` block in the `WidevineConfig` associated with `nativeId`.
123
+ * @param nativeId Instance ID.
124
+ * @param config `DrmConfig` config object sent from JS.
125
+ */
126
+ private fun initPrepareMessage(nativeId: NativeId, config: ReadableMap) {
127
+ val widevineConfig = drmConfigs[nativeId]
128
+ val widevineJson = config.getMap("widevine")
129
+ if (widevineConfig != null && widevineJson != null && widevineJson.hasKey("prepareMessage")) {
130
+ val prepareMessage = createPrepareCallback(
131
+ nativeId,
132
+ "onPrepareMessage",
133
+ preparedMessages,
134
+ preparedMessagesCondition
135
+ )
136
+ widevineConfig.prepareMessageCallback = PrepareMessageCallback {
137
+ prepareMessage(it)
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Initialize the `prepareLicense` block in the `WidevineConfig` associated with `nativeId`.
144
+ * @param nativeId Instance ID.
145
+ * @param config `DrmConfig` config object sent from JS.
146
+ */
147
+ private fun initPrepareLicense(nativeId: NativeId, config: ReadableMap) {
148
+ val widevineConfig = drmConfigs[nativeId]
149
+ val widevineJson = config.getMap("widevine")
150
+ if (widevineConfig != null && widevineJson != null && widevineJson.hasKey("prepareLicense")) {
151
+ val prepareLicense = createPrepareCallback(
152
+ nativeId,
153
+ "onPrepareLicense",
154
+ preparedLicenses,
155
+ preparedLicensesCondition
156
+ )
157
+ widevineConfig.prepareLicenseCallback = PrepareLicenseCallback {
158
+ prepareLicense(it)
159
+ }
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Creates the body of a preparation callback e.g. `prepareMessage`, `prepareLicense`, etc.
165
+ * @param nativeId Instance ID.
166
+ * @param method JS prepare callback name.
167
+ * @param registry Registry where JS preparation result will be stored.
168
+ * @return The preparation callback function.
169
+ */
170
+ private fun createPrepareCallback(
171
+ nativeId: NativeId,
172
+ method: String,
173
+ registry: Registry<String>,
174
+ registryCondition: Condition
175
+ ): PrepareCallback = {
176
+ val args = Arguments.createArray()
177
+ args.pushString(Base64.encodeToString(it, Base64.NO_WRAP))
178
+ context.catalystInstance.callFunction("DRM-${nativeId}", method, args as NativeArray)
179
+ lock.withLock {
180
+ registryCondition.await()
181
+ val result = registry[nativeId]
182
+ Base64.decode(result, Base64.NO_WRAP)
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Helper function that returns the initialized `UIManager` instance.
188
+ */
189
+ private fun uiManager(): UIManagerModule? =
190
+ context.getNativeModule(UIManagerModule::class.java)
191
+ }