@takeoffmedia/react-native-penthera 0.8.7 → 0.8.9

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,9 +2,10 @@ 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
- import com.bitmovin.player.reactnative.PlayerModule
8
+ import com.bitmovin.player.reactnative.PlayerRegistry
8
9
  import com.facebook.react.bridge.Arguments
9
10
  import com.facebook.react.bridge.Promise
10
11
  import com.facebook.react.bridge.ReactApplicationContext
@@ -135,16 +136,23 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
135
136
  for (i in 0 until subtitles.length()) {
136
137
  val subtitle = subtitles.getJSONObject(i)
137
138
  val language = subtitle["language"] as String
138
- val href = subtitle["href"]
139
- fileList.add(
140
- AncillaryFile(
141
- URL(href.toString()),
142
- language,
143
- arrayOf(language),
144
- language,
145
- URL(href.toString())
146
- )
147
- )
139
+ val href = subtitle.optString("href", "").trim()
140
+ if (href.isNotBlank() && !href.equals("null", ignoreCase = true)) {
141
+ try {
142
+ val subtitleUrl = URL(href)
143
+ fileList.add(
144
+ AncillaryFile(
145
+ subtitleUrl,
146
+ language,
147
+ arrayOf(language),
148
+ language,
149
+ subtitleUrl
150
+ )
151
+ )
152
+ } catch (e: MalformedURLException) {
153
+ Log.w("Penthera", "Skipping invalid subtitle URL: $href", e)
154
+ }
155
+ }
148
156
  }
149
157
 
150
158
  val params = MPDAssetBuilder().apply {
@@ -416,82 +424,144 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
416
424
  )
417
425
  }
418
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
+
419
477
  fun loadBitmovinSourceManager(
420
478
  assetId: String,
421
479
  nativeId: String,
422
480
  startOffset: Double?,
423
- ancillaries: String,
481
+ ancillaries: String?,
424
482
  promise: Promise
425
483
  ) {
426
- 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
+
427
491
  val sourceManager = BitmovinSourceManager(context, asset as ISegmentedAsset)
428
492
  val sourceItem = sourceManager.bitmovinSourceItem
429
- val playerModule = context.getNativeModule(PlayerModule::class.java)
430
- if (playerModule != null && sourceItem != null) {
431
- var player = playerModule.getPlayerOrNull(nativeId)
432
- while (player == null) {
433
- Thread.sleep(100)
434
- player = playerModule.getPlayerOrNull(nativeId) as Nothing?
435
- }
436
- val metadata = JSONObject(asset.metadata)
437
- val subtitles = JSONArray(metadata["subtitles"] as String)
438
- val ancillaryFiles = JSONArray(ancillaries)
439
- for (i in 0 until subtitles.length()) {
440
- val subtitleMap = Arguments.createMap()
441
- val subtitle = subtitles.getJSONObject(i)
442
-
443
- for (j in 0 until ancillaryFiles.length()) {
444
- val ancillary = ancillaryFiles.getJSONObject(j)
445
- if (ancillary.getString("description") == subtitle.getString("language")) {
446
- subtitleMap.putString("url", ancillary.getString("playbackUrl").toString())
447
- }
448
- }
449
- subtitleMap.putString("language", subtitle.getString("language"))
450
- subtitleMap.putString("label", subtitle.getString("label"))
451
- subtitleMap.putString("format", "vtt")
493
+ if (sourceItem == null) {
494
+ promise.reject("Error", "Source item is null")
495
+ return
496
+ }
452
497
 
453
- toSubtitleTrack(subtitleMap)?.let {
454
- sourceItem.addSubtitleTrack(it)
455
- }
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
456
512
  }
457
- if (startOffset != null) {
458
- 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)
459
517
  }
460
- val metaDataMap = mutableMapOf<String, String>()
461
-
462
- val data = metadata.getJSONObject("data")
463
- val infoVideoPlayer = data.getJSONObject("infoVideoPlayer")
518
+ subtitleMap.putString("language", language)
519
+ subtitleMap.putString("label", label)
520
+ subtitleMap.putString("format", "vtt")
464
521
 
465
- infoVideoPlayer.optString("title").let { title ->
466
- if (title.isNotBlank()) {
467
- sourceItem.title = title
468
- }
522
+ toSubtitleTrack(subtitleMap)?.let {
523
+ sourceItem.addSubtitleTrack(it)
469
524
  }
525
+ }
470
526
 
471
- infoVideoPlayer.optString("description").let { description ->
472
- if (description.isNotBlank()) {
473
- metaDataMap["description"] = description
474
- }
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
475
538
  }
539
+ }
476
540
 
477
- infoVideoPlayer.optString("advisoryClassification").let { advisoryClassification ->
478
- if (advisoryClassification.isNotBlank()) {
479
- metaDataMap["advisoryClassification"] = advisoryClassification
480
- }
541
+ infoVideoPlayer?.optString("description")?.let { description ->
542
+ if (description.isNotBlank()) {
543
+ metaDataMap["description"] = description
481
544
  }
545
+ }
482
546
 
483
- infoVideoPlayer.optString("advisoryDescription").let { advisoryDescription ->
484
- if (advisoryDescription.isNotBlank()) {
485
- metaDataMap["advisoryDescription"] = advisoryDescription
486
- }
547
+ infoVideoPlayer?.optString("advisoryClassification")?.let { advisoryClassification ->
548
+ if (advisoryClassification.isNotBlank()) {
549
+ metaDataMap["advisoryClassification"] = advisoryClassification
487
550
  }
551
+ }
488
552
 
489
- sourceItem.metadata = metaDataMap
490
- player.load(sourceItem)
491
- promise.resolve(true)
553
+ infoVideoPlayer?.optString("advisoryDescription")?.let { advisoryDescription ->
554
+ if (advisoryDescription.isNotBlank()) {
555
+ metaDataMap["advisoryDescription"] = advisoryDescription
556
+ }
492
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")
493
564
  }
494
- promise.reject("Error", "Error loading asset")
495
565
  }
496
566
 
497
567
  fun getAuthenticationStatus(promise: Promise) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takeoffmedia/react-native-penthera",
3
- "version": "0.8.7",
3
+ "version": "0.8.9",
4
4
  "description": "test",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",