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
@@ -0,0 +1,178 @@
1
+ package com.bitmovin.player.reactnative
2
+
3
+ import com.bitmovin.player.api.source.Source
4
+ import com.bitmovin.player.reactnative.converter.JsonConverter
5
+ import com.facebook.react.bridge.*
6
+ import com.facebook.react.module.annotations.ReactModule
7
+ import com.facebook.react.uimanager.UIManagerModule
8
+
9
+ @ReactModule(name = SourceModule.name)
10
+ class SourceModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
11
+ /**
12
+ * In-memory mapping from `nativeId`s to `Source` instances.
13
+ */
14
+ private val sources: Registry<Source> = mutableMapOf()
15
+
16
+ /**
17
+ * JS exported module name.
18
+ */
19
+ companion object {
20
+ const val name = "SourceModule"
21
+ }
22
+ override fun getName() = SourceModule.name
23
+
24
+ /**
25
+ * Fetches the `Source` instance associated with `nativeId` from internal sources.
26
+ * @param nativeId `Source` instance ID.
27
+ * @return The associated `Source` instance or `null`.
28
+ */
29
+ fun getSource(nativeId: NativeId?): Source? {
30
+ if (nativeId == null) {
31
+ return null
32
+ }
33
+ return sources[nativeId]
34
+ }
35
+
36
+ /**
37
+ * Creates a new `Source` instance inside the internal sources using the provided `config` object.
38
+ * @param nativeId ID to be associated with the `Source` instance.
39
+ * @param config `SourceConfig` object received from JS.
40
+ */
41
+ @ReactMethod
42
+ fun initWithConfig(nativeId: NativeId, config: ReadableMap?) {
43
+ uiManager()?.addUIBlock {
44
+ if (!sources.containsKey(nativeId)) {
45
+ JsonConverter.toSourceConfig(config)?.let {
46
+ sources[nativeId] = Source.create(it)
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Creates a new `Source` instance inside the internal sources using the provided
54
+ * `config` object and an initialized DRM configuration ID.
55
+ * @param nativeId ID to be associated with the `Source` instance.
56
+ * @param drmNativeId ID of the DRM config to use.
57
+ * @param config `SourceConfig` object received from JS.
58
+ */
59
+ @ReactMethod
60
+ fun initWithDrmConfig(nativeId: NativeId, drmNativeId: NativeId, config: ReadableMap?) {
61
+ uiManager()?.addUIBlock {
62
+ val drmConfig = drmModule()?.getConfig(drmNativeId)
63
+ if (!sources.containsKey(nativeId) && drmConfig != null) {
64
+ JsonConverter.toSourceConfig(config)?.let {
65
+ it.drmConfig = drmConfig
66
+ sources[nativeId] = Source.create(it)
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Removes the `Source` instance associated with `nativeId` from the internal sources.
74
+ * @param nativeId `Source` to be disposed.
75
+ */
76
+ @ReactMethod
77
+ fun destroy(nativeId: NativeId) {
78
+ sources.remove(nativeId)
79
+ }
80
+
81
+ /**
82
+ * Whether `nativeId` source is currently attached to a player instance.
83
+ * @param nativeId Source `nativeId`.
84
+ * @param promise: JS promise object.
85
+ */
86
+ @ReactMethod
87
+ fun isAttachedToPlayer(nativeId: NativeId, promise: Promise) {
88
+ uiManager()?.addUIBlock {
89
+ promise.resolve(sources[nativeId]?.isAttachedToPlayer)
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Whether `nativeId` source is currently active in a `Player`.
95
+ * @param nativeId Source `nativeId`.
96
+ * @param promise: JS promise object.
97
+ */
98
+ @ReactMethod
99
+ fun isActive(nativeId: NativeId, promise: Promise) {
100
+ uiManager()?.addUIBlock {
101
+ promise.resolve(sources[nativeId]?.isActive)
102
+ }
103
+ }
104
+
105
+ /**
106
+ * The duration of `nativeId` source in seconds.
107
+ * @param nativeId Source `nativeId`.
108
+ * @param promise: JS promise object.
109
+ */
110
+ @ReactMethod
111
+ fun duration(nativeId: NativeId, promise: Promise) {
112
+ uiManager()?.addUIBlock {
113
+ promise.resolve(sources[nativeId]?.duration)
114
+ }
115
+ }
116
+
117
+ /**
118
+ * The current loading state of `nativeId` source.
119
+ * @param nativeId Source `nativeId`.
120
+ * @param promise: JS promise object.
121
+ */
122
+ @ReactMethod
123
+ fun loadingState(nativeId: NativeId, promise: Promise) {
124
+ uiManager()?.addUIBlock {
125
+ promise.resolve(sources[nativeId]?.loadingState?.ordinal)
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Metadata for the currently loaded `nativeId` source.
131
+ * @param nativeId Source `nativeId`.
132
+ * @param promise: JS promise object.
133
+ */
134
+ @ReactMethod
135
+ fun getMetadata(nativeId: NativeId, promise: Promise) {
136
+ uiManager()?.addUIBlock {
137
+ promise.resolve(sources[nativeId]?.config?.metadata)
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Set the metadata for a loaded `nativeId` source.
143
+ * @param nativeId Source `nativeId`.
144
+ * @param promise: JS promise object.
145
+ */
146
+ @ReactMethod
147
+ fun setMetadata(nativeId: NativeId, metadata: ReadableMap?) {
148
+ uiManager()?.addUIBlock {
149
+ sources[nativeId]?.config?.metadata = asStringMap(metadata)
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Helper method that converts a React `ReadableMap` into a kotlin String -> String map.
155
+ */
156
+ private fun asStringMap(readableMap: ReadableMap?): Map<String, String>? {
157
+ if (readableMap == null) {
158
+ return null
159
+ }
160
+ val map = mutableMapOf<String, String>()
161
+ for (entry in readableMap.entryIterator) {
162
+ map[entry.key] = entry.value.toString()
163
+ }
164
+ return map
165
+ }
166
+
167
+ /**
168
+ * Helper function that returns the initialized `UIManager` instance.
169
+ */
170
+ private fun uiManager(): UIManagerModule? =
171
+ context.getNativeModule(UIManagerModule::class.java)
172
+
173
+ /**
174
+ * Helper function that returns the initialized `DrmModule` instance.
175
+ */
176
+ private fun drmModule(): DrmModule? =
177
+ context.getNativeModule(DrmModule::class.java)
178
+ }
@@ -0,0 +1,20 @@
1
+ package com.bitmovin.player.reactnative
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
5
+ import com.facebook.react.bridge.ReactMethod
6
+ import java.util.*
7
+
8
+ class UuidModule(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
9
+ /**
10
+ * Exported JS module name.
11
+ */
12
+ override fun getName() = "UuidModule"
13
+
14
+ /**
15
+ * Synchronously generate a random UUIDv4.
16
+ * @return Random UUID RFC 4122 version 4.
17
+ */
18
+ @ReactMethod(isBlockingSynchronousMethod = true)
19
+ fun generate() = UUID.randomUUID().toString()
20
+ }
@@ -1,14 +1,17 @@
1
1
  package com.bitmovin.player.reactnative.converter
2
2
 
3
3
  import com.bitmovin.player.api.PlayerConfig
4
+ import com.bitmovin.player.api.drm.WidevineConfig
4
5
  import com.bitmovin.player.api.event.PlayerEvent
5
6
  import com.bitmovin.player.api.event.SourceEvent
6
7
  import com.bitmovin.player.api.event.data.SeekPosition
8
+ import com.bitmovin.player.api.media.subtitle.SubtitleTrack
7
9
  import com.bitmovin.player.api.source.Source
8
10
  import com.bitmovin.player.api.source.SourceConfig
9
11
  import com.bitmovin.player.api.source.SourceType
10
12
  import com.bitmovin.player.reactnative.extensions.getName
11
13
  import com.facebook.react.bridge.*
14
+ import java.util.UUID
12
15
 
13
16
  /**
14
17
  * Helper class to gather all conversion methods between JS -> Native objects.
@@ -21,7 +24,7 @@ class JsonConverter {
21
24
  * @return The generated `PlayerConfig` if successful, `null` otherwise.
22
25
  */
23
26
  @JvmStatic
24
- fun toPlayerConfig(json: ReadableMap?): PlayerConfig? {
27
+ fun toPlayerConfig(json: ReadableMap?): PlayerConfig {
25
28
  if (json != null && json.hasKey("licenseKey")) {
26
29
  return PlayerConfig(key = json.getString("licenseKey"))
27
30
  }
@@ -46,6 +49,14 @@ class JsonConverter {
46
49
  if (json.hasKey("isPosterPersistent")) {
47
50
  config.isPosterPersistent = json.getBoolean("isPosterPersistent")
48
51
  }
52
+ if (json.hasKey("subtitleTracks")) {
53
+ val subtitleTracks = json.getArray("subtitleTracks") as ReadableArray
54
+ for (i in 0 until subtitleTracks.size()) {
55
+ toSubtitleTrack(subtitleTracks.getMap(i))?.let {
56
+ config.addSubtitleTrack(it)
57
+ }
58
+ }
59
+ }
49
60
  return config
50
61
  }
51
62
 
@@ -119,6 +130,16 @@ class JsonConverter {
119
130
  json.putInt("code", event.code.value)
120
131
  json.putString("message", event.message)
121
132
  }
133
+ if (event is SourceEvent.SubtitleTrackAdded) {
134
+ json.putMap("subtitleTrack", fromSubtitleTrack(event.subtitleTrack))
135
+ }
136
+ if (event is SourceEvent.SubtitleTrackRemoved) {
137
+ json.putMap("subtitleTrack", fromSubtitleTrack(event.subtitleTrack))
138
+ }
139
+ if (event is SourceEvent.SubtitleTrackChanged) {
140
+ json.putMap("oldSubtitleTrack", fromSubtitleTrack(event.oldSubtitleTrack))
141
+ json.putMap("newSubtitleTrack", fromSubtitleTrack(event.newSubtitleTrack))
142
+ }
122
143
  return json
123
144
  }
124
145
 
@@ -158,5 +179,111 @@ class JsonConverter {
158
179
  }
159
180
  return json
160
181
  }
182
+
183
+ /**
184
+ * Converts an arbitrary `json` to `WidevineConfig`.
185
+ * @param json JS object representing the `WidevineConfig`.
186
+ * @return The generated `WidevineConfig` if successful, `null` otherwise.
187
+ */
188
+ @JvmStatic
189
+ fun toWidevineConfig(json: ReadableMap?): WidevineConfig? = json?.getMap("widevine")?.let {
190
+ val widevineConfig = WidevineConfig(it.getString("licenseUrl"))
191
+ if (it.hasKey("preferredSecurityLevel")) {
192
+ widevineConfig.preferredSecurityLevel = it.getString("preferredSecurityLevel")
193
+ }
194
+ widevineConfig
195
+ }
196
+
197
+ /**
198
+ * Converts an arbitrary `json` into a `SubtitleTrack`.
199
+ * @param json JS object representing the `SubtitleTrack`.
200
+ * @return The generated `SubtitleTrack` if successful, `null` otherwise.
201
+ */
202
+ @JvmStatic
203
+ fun toSubtitleTrack(json: ReadableMap?): SubtitleTrack? {
204
+ val url = json?.getString("url")
205
+ val label = json?.getString("label")
206
+ if (json == null || url == null || label == null) {
207
+ return null
208
+ }
209
+ val identifier = json.getString("identifier") ?: UUID.randomUUID().toString()
210
+ val isDefault = if (json.hasKey("isDefault")) {
211
+ json.getBoolean("isDefault")
212
+ } else {
213
+ false
214
+ }
215
+ val isForced = if (json.hasKey("isForced")) {
216
+ json.getBoolean("isForced")
217
+ } else {
218
+ false
219
+ }
220
+ val format = json.getString("format")
221
+ if (format != null && format.isNotBlank()) {
222
+ return SubtitleTrack(
223
+ url = url,
224
+ label = label,
225
+ id = identifier,
226
+ isDefault = isDefault,
227
+ language = json.getString("language"),
228
+ isForced = isForced,
229
+ mimeType = toSubtitleMimeType(format),
230
+ )
231
+ }
232
+ return SubtitleTrack(
233
+ url = url,
234
+ label = label,
235
+ id = identifier,
236
+ isDefault = isDefault,
237
+ language = json.getString("language"),
238
+ isForced = isForced,
239
+ )
240
+ }
241
+
242
+ /**
243
+ * Converts any subtitle format name in its mime type representation.
244
+ * @param format The file format string received from JS.
245
+ * @return The subtitle file mime type.
246
+ */
247
+ @JvmStatic
248
+ fun toSubtitleMimeType(format: String?): String? {
249
+ if (format == null) {
250
+ return null
251
+ }
252
+ return "text/${format}"
253
+ }
254
+
255
+ /**
256
+ * Converts any `SubtitleTrack` into its json representation.
257
+ * @param subtitleTrack `SubtitleTrack` object to be converted.
258
+ * @return The generated json map.
259
+ */
260
+ @JvmStatic
261
+ fun fromSubtitleTrack(subtitleTrack: SubtitleTrack?): WritableMap? {
262
+ if (subtitleTrack == null) {
263
+ return null
264
+ }
265
+ val json = Arguments.createMap()
266
+ json.putString("url", subtitleTrack.url)
267
+ json.putString("label", subtitleTrack.label)
268
+ json.putBoolean("isDefault", subtitleTrack.isDefault)
269
+ json.putString("identifier", subtitleTrack.id)
270
+ json.putString("language", subtitleTrack.language)
271
+ json.putBoolean("isForced", subtitleTrack.isForced)
272
+ json.putString("format", fromSubtitleMimeType(subtitleTrack.mimeType))
273
+ return json
274
+ }
275
+
276
+ /**
277
+ * Converts any subtitle track mime type into its json representation (file format value).
278
+ * @param mimeType `SubtitleTrack` file mime type.
279
+ * @return The extracted file format.
280
+ */
281
+ @JvmStatic
282
+ fun fromSubtitleMimeType(mimeType: String?): String? {
283
+ if (mimeType == null) {
284
+ return null
285
+ }
286
+ return mimeType.split("/").last()
287
+ }
161
288
  }
162
289
  }
@@ -10,6 +10,15 @@ fun PlayerEvent.getName(): String {
10
10
  return "on${this.javaClass.simpleName}"
11
11
  }
12
12
 
13
- fun SourceEvent.getName(): String {
14
- return "onSource${this.javaClass.simpleName}"
13
+ /**
14
+ * TODO: Until a certain point, all source events supported by this library followed the pattern
15
+ * `onSource{EventName}`. But it's necessary to improve the way how event names are dynamically
16
+ * computed based on the event type so they can match the same names provided by iOS and possibly
17
+ * other platforms.
18
+ */
19
+ fun SourceEvent.getName(): String = when (this) {
20
+ is SourceEvent.SubtitleTrackAdded -> "onSubtitleAdded"
21
+ is SourceEvent.SubtitleTrackChanged -> "onSubtitleChanged"
22
+ is SourceEvent.SubtitleTrackRemoved -> "onSubtitleRemoved"
23
+ else -> "onSource${this.javaClass.simpleName}"
15
24
  }
@@ -0,0 +1,16 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_REMAP_MODULE(DrmModule, DrmModule, NSObject)
4
+
5
+ RCT_EXTERN_METHOD(initWithConfig:(NSString *)nativeId config:(nullable id)config)
6
+ RCT_EXTERN_METHOD(destroy:(NSString *)nativeId)
7
+
8
+ RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(setPreparedCertificate:(NSString *)nativeId certificate:(NSString *)certificate)
9
+ RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(setPreparedMessage:(NSString *)nativeId message:(NSString *)message)
10
+ RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(setPreparedSyncMessage:(NSString *)nativeId syncMessage:(NSString *)syncMessage)
11
+ RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(setPreparedLicense:(NSString *)nativeId license:(NSString *)license)
12
+ RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(setPreparedLicenseServerUrl:(NSString *)nativeId url:(NSString *)url)
13
+ RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(setPreparedContentId:(NSString *)nativeId contentId:(NSString *)contentId)
14
+ RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(setProvidedLicenseData:(NSString *)nativeId licenseData:(nullable NSString *)licenseData)
15
+
16
+ @end