@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(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
package/android/src/main/java/com/takeoffmediareactnativepenthera/virtuoso/OfflineVideoEngine.kt
CHANGED
|
@@ -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.
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
URL(href
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
454
|
-
|
|
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
|
-
|
|
458
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
val infoVideoPlayer = data.getJSONObject("infoVideoPlayer")
|
|
518
|
+
subtitleMap.putString("language", language)
|
|
519
|
+
subtitleMap.putString("label", label)
|
|
520
|
+
subtitleMap.putString("format", "vtt")
|
|
464
521
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
sourceItem.title = title
|
|
468
|
-
}
|
|
522
|
+
toSubtitleTrack(subtitleMap)?.let {
|
|
523
|
+
sourceItem.addSubtitleTrack(it)
|
|
469
524
|
}
|
|
525
|
+
}
|
|
470
526
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
541
|
+
infoVideoPlayer?.optString("description")?.let { description ->
|
|
542
|
+
if (description.isNotBlank()) {
|
|
543
|
+
metaDataMap["description"] = description
|
|
481
544
|
}
|
|
545
|
+
}
|
|
482
546
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
547
|
+
infoVideoPlayer?.optString("advisoryClassification")?.let { advisoryClassification ->
|
|
548
|
+
if (advisoryClassification.isNotBlank()) {
|
|
549
|
+
metaDataMap["advisoryClassification"] = advisoryClassification
|
|
487
550
|
}
|
|
551
|
+
}
|
|
488
552
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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) {
|