@takeoffmedia/react-native-penthera 0.8.8 → 0.9.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.
@@ -7,6 +7,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
7
7
  import com.facebook.react.bridge.ReactMethod
8
8
  import com.facebook.react.bridge.Promise
9
9
  import com.facebook.react.bridge.ReadableArray
10
+ import com.facebook.react.bridge.UiThreadUtil
10
11
  import com.takeoffmediareactnativepenthera.virtuoso.OfflineVideoEngine
11
12
  import com.facebook.react.uimanager.UIManagerModule
12
13
 
@@ -75,11 +76,34 @@ class PentheraModule(val reactContext: ReactApplicationContext) : ReactContextBa
75
76
  }
76
77
 
77
78
  @ReactMethod
78
- fun loadBitmovinSourceManager(assetId: String, nativeId: String, startOffset: Double?, ancillaryFiles: String, promise: Promise) {
79
- uiManager()?.addUIBlock {
80
- val bitmovinSourceItem =
81
- offlineVideoEngine.loadBitmovinSourceManager(assetId, nativeId, startOffset, ancillaryFiles, promise)
82
- promise.resolve(bitmovinSourceItem)
79
+ fun loadBitmovinSourceManager(
80
+ assetId: String,
81
+ nativeId: String,
82
+ startOffset: Double?,
83
+ ancillaryFiles: String?,
84
+ promise: Promise
85
+ ) {
86
+ val manager = uiManager()
87
+ if (manager == null) {
88
+ UiThreadUtil.runOnUiThread {
89
+ offlineVideoEngine.loadBitmovinSourceManager(
90
+ assetId,
91
+ nativeId,
92
+ startOffset,
93
+ ancillaryFiles,
94
+ promise
95
+ )
96
+ }
97
+ return
98
+ }
99
+ manager.addUIBlock {
100
+ offlineVideoEngine.loadBitmovinSourceManager(
101
+ assetId,
102
+ nativeId,
103
+ startOffset,
104
+ ancillaryFiles,
105
+ promise
106
+ )
83
107
  }
84
108
  }
85
109
 
@@ -2,6 +2,7 @@ package com.takeoffmediareactnativepenthera.virtuoso
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
+ import android.os.SystemClock
5
6
  import android.util.Log
6
7
  import com.bitmovin.player.api.media.subtitle.SubtitleTrack
7
8
  import com.bitmovin.player.reactnative.PlayerRegistry
@@ -423,81 +424,144 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
423
424
  )
424
425
  }
425
426
 
427
+ private fun waitForPlayer(nativeId: String, timeoutMs: Long = 3000L) =
428
+ run {
429
+ val deadline = SystemClock.elapsedRealtime() + timeoutMs
430
+ var player = PlayerRegistry.getPlayer(nativeId)
431
+ while (player == null && SystemClock.elapsedRealtime() < deadline) {
432
+ SystemClock.sleep(50)
433
+ player = PlayerRegistry.getPlayer(nativeId)
434
+ }
435
+ player
436
+ }
437
+
438
+ private fun parseSubtitles(metadata: JSONObject): JSONArray {
439
+ if (!metadata.has("subtitles")) {
440
+ return JSONArray()
441
+ }
442
+ return try {
443
+ when (val raw = metadata.get("subtitles")) {
444
+ is JSONArray -> raw
445
+ is String -> if (raw.isNotBlank()) JSONArray(raw) else JSONArray()
446
+ else -> JSONArray()
447
+ }
448
+ } catch (_: Exception) {
449
+ JSONArray()
450
+ }
451
+ }
452
+
453
+ private fun parseAncillaryFiles(ancillaries: String?): JSONArray {
454
+ if (ancillaries.isNullOrBlank()) {
455
+ return JSONArray()
456
+ }
457
+ return try {
458
+ JSONArray(ancillaries)
459
+ } catch (_: Exception) {
460
+ JSONArray()
461
+ }
462
+ }
463
+
464
+ private fun findAncillaryPlaybackUrl(ancillaryFiles: JSONArray, language: String): String? {
465
+ for (j in 0 until ancillaryFiles.length()) {
466
+ val ancillary = ancillaryFiles.optJSONObject(j) ?: continue
467
+ if (ancillary.optString("description") == language) {
468
+ val playbackUrl = ancillary.optString("playbackUrl")
469
+ if (playbackUrl.isNotBlank()) {
470
+ return playbackUrl
471
+ }
472
+ }
473
+ }
474
+ return null
475
+ }
476
+
426
477
  fun loadBitmovinSourceManager(
427
478
  assetId: String,
428
479
  nativeId: String,
429
480
  startOffset: Double?,
430
- ancillaries: String,
481
+ ancillaries: String?,
431
482
  promise: Promise
432
483
  ) {
433
- virtuoso.assetManager.getByAssetId(assetId)?.firstOrNull()?.let { asset ->
484
+ try {
485
+ val asset = virtuoso.assetManager.getByAssetId(assetId)?.firstOrNull()
486
+ if (asset == null) {
487
+ promise.reject("Error", "Asset not found")
488
+ return
489
+ }
490
+
434
491
  val sourceManager = BitmovinSourceManager(context, asset as ISegmentedAsset)
435
492
  val sourceItem = sourceManager.bitmovinSourceItem
436
- if (sourceItem != null) {
437
- var player = PlayerRegistry.getPlayer(nativeId)
438
- while (player == null) {
439
- Thread.sleep(100)
440
- player = PlayerRegistry.getPlayer(nativeId)
441
- }
442
- val metadata = JSONObject(asset.metadata)
443
- val subtitles = JSONArray(metadata["subtitles"] as String)
444
- val ancillaryFiles = JSONArray(ancillaries)
445
- for (i in 0 until subtitles.length()) {
446
- val subtitleMap = Arguments.createMap()
447
- val subtitle = subtitles.getJSONObject(i)
448
-
449
- for (j in 0 until ancillaryFiles.length()) {
450
- val ancillary = ancillaryFiles.getJSONObject(j)
451
- if (ancillary.getString("description") == subtitle.getString("language")) {
452
- subtitleMap.putString("url", ancillary.getString("playbackUrl").toString())
453
- }
454
- }
455
- subtitleMap.putString("language", subtitle.getString("language"))
456
- subtitleMap.putString("label", subtitle.getString("label"))
457
- subtitleMap.putString("format", "vtt")
493
+ if (sourceItem == null) {
494
+ promise.reject("Error", "Source item is null")
495
+ return
496
+ }
458
497
 
459
- toSubtitleTrack(subtitleMap)?.let {
460
- sourceItem.addSubtitleTrack(it)
461
- }
498
+ val player = waitForPlayer(nativeId)
499
+ if (player == null) {
500
+ promise.reject("Error", "Player not ready")
501
+ return
502
+ }
503
+
504
+ val metadata = JSONObject(asset.metadata)
505
+ val subtitles = parseSubtitles(metadata)
506
+ val ancillaryFiles = parseAncillaryFiles(ancillaries)
507
+ for (i in 0 until subtitles.length()) {
508
+ val subtitle = subtitles.optJSONObject(i) ?: continue
509
+ val language = subtitle.optString("language")
510
+ if (language.isBlank()) {
511
+ continue
462
512
  }
463
- if (startOffset != null) {
464
- sourceItem.options.startOffset = startOffset
513
+ val label = subtitle.optString("label", language)
514
+ val subtitleMap = Arguments.createMap()
515
+ findAncillaryPlaybackUrl(ancillaryFiles, language)?.let { url ->
516
+ subtitleMap.putString("url", url)
465
517
  }
466
- val metaDataMap = mutableMapOf<String, String>()
518
+ subtitleMap.putString("language", language)
519
+ subtitleMap.putString("label", label)
520
+ subtitleMap.putString("format", "vtt")
467
521
 
468
- val data = metadata.getJSONObject("data")
469
- val infoVideoPlayer = data.getJSONObject("infoVideoPlayer")
470
-
471
- infoVideoPlayer.optString("title").let { title ->
472
- if (title.isNotBlank()) {
473
- sourceItem.title = title
474
- }
522
+ toSubtitleTrack(subtitleMap)?.let {
523
+ sourceItem.addSubtitleTrack(it)
475
524
  }
525
+ }
476
526
 
477
- infoVideoPlayer.optString("description").let { description ->
478
- if (description.isNotBlank()) {
479
- metaDataMap["description"] = description
480
- }
527
+ if (startOffset != null) {
528
+ sourceItem.options.startOffset = startOffset
529
+ }
530
+
531
+ val metaDataMap = mutableMapOf<String, String>()
532
+ val data = metadata.optJSONObject("data")
533
+ val infoVideoPlayer = data?.optJSONObject("infoVideoPlayer")
534
+
535
+ infoVideoPlayer?.optString("title")?.let { title ->
536
+ if (title.isNotBlank()) {
537
+ sourceItem.title = title
481
538
  }
539
+ }
482
540
 
483
- infoVideoPlayer.optString("advisoryClassification").let { advisoryClassification ->
484
- if (advisoryClassification.isNotBlank()) {
485
- metaDataMap["advisoryClassification"] = advisoryClassification
486
- }
541
+ infoVideoPlayer?.optString("description")?.let { description ->
542
+ if (description.isNotBlank()) {
543
+ metaDataMap["description"] = description
487
544
  }
545
+ }
488
546
 
489
- infoVideoPlayer.optString("advisoryDescription").let { advisoryDescription ->
490
- if (advisoryDescription.isNotBlank()) {
491
- metaDataMap["advisoryDescription"] = advisoryDescription
492
- }
547
+ infoVideoPlayer?.optString("advisoryClassification")?.let { advisoryClassification ->
548
+ if (advisoryClassification.isNotBlank()) {
549
+ metaDataMap["advisoryClassification"] = advisoryClassification
493
550
  }
551
+ }
494
552
 
495
- sourceItem.metadata = metaDataMap
496
- player?.load(sourceItem)
497
- promise.resolve(true)
553
+ infoVideoPlayer?.optString("advisoryDescription")?.let { advisoryDescription ->
554
+ if (advisoryDescription.isNotBlank()) {
555
+ metaDataMap["advisoryDescription"] = advisoryDescription
556
+ }
498
557
  }
558
+
559
+ sourceItem.metadata = metaDataMap
560
+ player.load(sourceItem)
561
+ promise.resolve(true)
562
+ } catch (e: Exception) {
563
+ promise.reject("Error", e.message ?: "Error loading asset")
499
564
  }
500
- promise.reject("Error", "Error loading asset")
501
565
  }
502
566
 
503
567
  fun getAuthenticationStatus(promise: Promise) {
@@ -412,6 +412,30 @@ class Penthera: RCTEventEmitter, VirtuosoDownloadEngineNotificationsDelegate {
412
412
  }
413
413
  }
414
414
 
415
+ @objc(delete:withResolver:withRejecter:)
416
+ func deleteAsset(assetID: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
417
+ let downloadComplete = VirtuosoAsset.completedAssets(withAvailabilityFilter: false)
418
+ let downloadPendings = VirtuosoAsset.pendingAssets(withAvailabilityFilter: false)
419
+ let allAssets = downloadComplete + downloadPendings
420
+
421
+ guard let assetToDelete = allAssets.first(where: {
422
+ guard let va = $0 as? VirtuosoAsset else {
423
+ return false
424
+ }
425
+ return va.assetID == assetID
426
+ }) as? VirtuosoAsset else {
427
+ let errorMessage = "delete: asset not found"
428
+ let userInfo = [NSLocalizedDescriptionKey: errorMessage]
429
+ let error = NSError(domain: "takeoffmedia-react-native-penthera", code: 0, userInfo: userInfo)
430
+ reject("ASSET_DELETE_ERROR", errorMessage, error)
431
+ return
432
+ }
433
+
434
+ VirtuosoAsset.delete([assetToDelete]) {
435
+ resolve(assetID)
436
+ }
437
+ }
438
+
415
439
  @objc(deleteMany:withResolver:withRejecter:)
416
440
  func deleteMany(assetIDS: [String], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
417
441
  // initialize assetResponse {}. Ex {"123":false, "543":false}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takeoffmedia/react-native-penthera",
3
- "version": "0.8.8",
3
+ "version": "0.9.0",
4
4
  "description": "test",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",