@bluebillywig/react-native-bb-player 8.44.0 → 8.45.4
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.
- package/README.md +80 -59
- package/android/build.gradle +4 -3
- package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerModule.kt +174 -28
- package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerView.kt +70 -167
- package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerViewManager.kt +9 -7
- package/android/src/paper/java/com/bluebillywig/bbplayer/NativeBBPlayerModuleSpec.java +19 -8
- package/ios/BBPlayerModule.mm +17 -8
- package/ios/BBPlayerModule.swift +192 -26
- package/ios/BBPlayerView.swift +55 -140
- package/ios/BBPlayerViewManager.m +2 -2
- package/ios/BBPlayerViewManager.swift +12 -0
- package/lib/commonjs/BBModalPlayer.js +21 -0
- package/lib/commonjs/BBModalPlayer.js.map +1 -0
- package/lib/commonjs/BBOutstreamView.js +2 -1
- package/lib/commonjs/BBOutstreamView.js.map +1 -1
- package/lib/commonjs/BBPlayerView.js +0 -1
- package/lib/commonjs/BBPlayerView.js.map +1 -1
- package/lib/commonjs/NativeCommands.js +32 -24
- package/lib/commonjs/NativeCommands.js.map +1 -1
- package/lib/commonjs/index.js +9 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/NativeBBPlayerModule.js.map +1 -1
- package/lib/module/BBModalPlayer.js +17 -0
- package/lib/module/BBModalPlayer.js.map +1 -0
- package/lib/module/BBOutstreamView.js +2 -1
- package/lib/module/BBOutstreamView.js.map +1 -1
- package/lib/module/BBPlayerView.js +0 -1
- package/lib/module/BBPlayerView.js.map +1 -1
- package/lib/module/NativeCommands.js +32 -24
- package/lib/module/NativeCommands.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/NativeBBPlayerModule.js.map +1 -1
- package/lib/typescript/src/BBModalPlayer.d.ts +13 -0
- package/lib/typescript/src/BBModalPlayer.d.ts.map +1 -0
- package/lib/typescript/src/BBOutstreamView.d.ts.map +1 -1
- package/lib/typescript/src/BBPlayer.types.d.ts +28 -20
- package/lib/typescript/src/BBPlayer.types.d.ts.map +1 -1
- package/lib/typescript/src/BBPlayerView.d.ts +0 -2
- package/lib/typescript/src/BBPlayerView.d.ts.map +1 -1
- package/lib/typescript/src/NativeCommands.d.ts +10 -9
- package/lib/typescript/src/NativeCommands.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeBBPlayerModule.d.ts +13 -8
- package/lib/typescript/src/specs/NativeBBPlayerModule.d.ts.map +1 -1
- package/package.json +9 -10
- package/src/BBModalPlayer.ts +33 -0
- package/src/BBOutstreamView.tsx +2 -1
- package/src/BBPlayer.types.ts +35 -17
- package/src/BBPlayerView.tsx +0 -12
- package/src/NativeCommands.ts +45 -26
- package/src/index.ts +2 -0
- package/src/specs/NativeBBPlayerModule.ts +23 -8
- package/android/proguard-rules.pro +0 -59
|
@@ -12,6 +12,7 @@ import android.widget.FrameLayout
|
|
|
12
12
|
import com.facebook.react.modules.core.ReactChoreographer
|
|
13
13
|
import androidx.collection.ArrayMap
|
|
14
14
|
import androidx.mediarouter.app.MediaRouteButton
|
|
15
|
+
import org.json.JSONObject
|
|
15
16
|
import com.bluebillywig.bbnativeplayersdk.BBNativePlayer
|
|
16
17
|
import com.bluebillywig.bbnativeplayersdk.BBNativePlayerView
|
|
17
18
|
import com.bluebillywig.bbnativeplayersdk.BBNativePlayerViewDelegate
|
|
@@ -176,14 +177,8 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
176
177
|
// END NATIVE LAYOUT INTEGRATION
|
|
177
178
|
// ==================================================================================
|
|
178
179
|
|
|
179
|
-
// Timer for periodic time updates (opt-in for performance)
|
|
180
|
-
private val timeUpdateHandler = Handler(Looper.getMainLooper())
|
|
181
|
-
private var timeUpdateRunnable: Runnable? = null
|
|
182
180
|
private var isPlaying = false
|
|
183
181
|
private var currentDuration: Double = 0.0
|
|
184
|
-
private var lastKnownTime: Double = 0.0
|
|
185
|
-
private var playbackStartTimestamp: Long = 0
|
|
186
|
-
private var enableTimeUpdates: Boolean = false
|
|
187
182
|
|
|
188
183
|
// Event emission helper using modern EventDispatcher
|
|
189
184
|
private fun sendEvent(eventName: String, params: WritableMap?) {
|
|
@@ -222,17 +217,6 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
222
217
|
this.options["autoPlay"] = autoPlay
|
|
223
218
|
}
|
|
224
219
|
|
|
225
|
-
fun setEnableTimeUpdates(enabled: Boolean) {
|
|
226
|
-
enableTimeUpdates = enabled
|
|
227
|
-
debugLog("BBPlayerView") { "Time updates ${if (enabled) "enabled" else "disabled"}" }
|
|
228
|
-
|
|
229
|
-
if (!enabled && timeUpdateRunnable != null) {
|
|
230
|
-
stopTimeUpdates()
|
|
231
|
-
} else if (enabled && isPlaying && timeUpdateRunnable == null) {
|
|
232
|
-
startTimeUpdates()
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
220
|
fun setOptions(optionsMap: ReadableMap?) {
|
|
237
221
|
optionsMap?.let { map ->
|
|
238
222
|
val iterator = map.keySetIterator()
|
|
@@ -275,91 +259,36 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
275
259
|
|
|
276
260
|
addView(playerView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
277
261
|
|
|
278
|
-
//
|
|
279
|
-
//
|
|
262
|
+
// RESIZE_MODE_FILL prevents SurfaceView bleed-through at container edges
|
|
263
|
+
// when embedding inline above a WebView. The container aspect ratio is
|
|
264
|
+
// adapted dynamically via onDidTriggerMediaClipLoaded to avoid stretching.
|
|
280
265
|
postDelayed({
|
|
281
|
-
setExoPlayerResizeMode(playerView
|
|
282
|
-
},
|
|
266
|
+
setExoPlayerResizeMode(playerView)
|
|
267
|
+
}, 500)
|
|
283
268
|
|
|
284
269
|
playerSetup = true
|
|
285
270
|
debugLog("BBPlayerView") { "Player setup complete with URL: $jsonUrl" }
|
|
286
271
|
}
|
|
287
272
|
|
|
288
|
-
companion object {
|
|
289
|
-
// AspectRatioFrameLayout resize mode constants
|
|
290
|
-
private const val RESIZE_MODE_FILL = 3
|
|
291
|
-
}
|
|
292
|
-
|
|
293
273
|
/**
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* This fixes top/bottom margin issues caused by AspectRatioFrameLayout letterboxing.
|
|
274
|
+
* Set ExoPlayer's resize mode to FILL via reflection.
|
|
275
|
+
* FILL prevents SurfaceView bleed-through artifacts at container edges.
|
|
297
276
|
*/
|
|
298
|
-
private fun setExoPlayerResizeMode(view: View
|
|
299
|
-
// Try Media3 AspectRatioFrameLayout via reflection
|
|
300
|
-
try {
|
|
301
|
-
val aspectRatioClass = Class.forName("androidx.media3.ui.AspectRatioFrameLayout")
|
|
302
|
-
if (aspectRatioClass.isInstance(view)) {
|
|
303
|
-
val method = aspectRatioClass.getMethod("setResizeMode", Int::class.javaPrimitiveType)
|
|
304
|
-
method.invoke(view, resizeMode)
|
|
305
|
-
return true
|
|
306
|
-
}
|
|
307
|
-
} catch (_: ClassNotFoundException) {
|
|
308
|
-
// Media3 not available
|
|
309
|
-
} catch (_: Exception) {
|
|
310
|
-
// Failed to set resize mode
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Try Media3 PlayerView via reflection
|
|
277
|
+
private fun setExoPlayerResizeMode(view: View): Boolean {
|
|
314
278
|
try {
|
|
315
279
|
val playerViewClass = Class.forName("androidx.media3.ui.PlayerView")
|
|
316
280
|
if (playerViewClass.isInstance(view)) {
|
|
317
281
|
val method = playerViewClass.getMethod("setResizeMode", Int::class.javaPrimitiveType)
|
|
318
|
-
method.invoke(view,
|
|
319
|
-
// Continue searching - there might be an AspectRatioFrameLayout inside
|
|
320
|
-
}
|
|
321
|
-
} catch (_: ClassNotFoundException) {
|
|
322
|
-
// Media3 PlayerView not available
|
|
323
|
-
} catch (_: Exception) {
|
|
324
|
-
// Failed to set resize mode
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Try legacy ExoPlayer2 AspectRatioFrameLayout via reflection
|
|
328
|
-
try {
|
|
329
|
-
val legacyAspectRatioClass = Class.forName("com.google.android.exoplayer2.ui.AspectRatioFrameLayout")
|
|
330
|
-
if (legacyAspectRatioClass.isInstance(view)) {
|
|
331
|
-
val method = legacyAspectRatioClass.getMethod("setResizeMode", Int::class.javaPrimitiveType)
|
|
332
|
-
method.invoke(view, resizeMode)
|
|
282
|
+
method.invoke(view, 3) // RESIZE_MODE_FILL = 3
|
|
333
283
|
return true
|
|
334
284
|
}
|
|
335
|
-
} catch (_:
|
|
336
|
-
// ExoPlayer2 not available
|
|
337
|
-
} catch (_: Exception) {
|
|
338
|
-
// Failed to set resize mode
|
|
339
|
-
}
|
|
285
|
+
} catch (_: Exception) {}
|
|
340
286
|
|
|
341
|
-
// Try legacy ExoPlayer2 StyledPlayerView via reflection
|
|
342
|
-
try {
|
|
343
|
-
val styledPlayerViewClass = Class.forName("com.google.android.exoplayer2.ui.StyledPlayerView")
|
|
344
|
-
if (styledPlayerViewClass.isInstance(view)) {
|
|
345
|
-
val method = styledPlayerViewClass.getMethod("setResizeMode", Int::class.javaPrimitiveType)
|
|
346
|
-
method.invoke(view, resizeMode)
|
|
347
|
-
}
|
|
348
|
-
} catch (_: ClassNotFoundException) {
|
|
349
|
-
// StyledPlayerView not available
|
|
350
|
-
} catch (_: Exception) {
|
|
351
|
-
// Failed to set resize mode
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Recursively search children
|
|
355
287
|
if (view is ViewGroup) {
|
|
356
288
|
for (i in 0 until view.childCount) {
|
|
357
|
-
if (setExoPlayerResizeMode(view.getChildAt(i)
|
|
358
|
-
return true
|
|
359
|
-
}
|
|
289
|
+
if (setExoPlayerResizeMode(view.getChildAt(i))) return true
|
|
360
290
|
}
|
|
361
291
|
}
|
|
362
|
-
|
|
363
292
|
return false
|
|
364
293
|
}
|
|
365
294
|
|
|
@@ -370,13 +299,13 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
370
299
|
*
|
|
371
300
|
* Note: Shorts URLs (/sh/{id}.json) are NOT supported here - use BBShortsView instead.
|
|
372
301
|
*/
|
|
373
|
-
fun loadWithJsonUrl(url: String, autoPlay: Boolean = true) {
|
|
302
|
+
fun loadWithJsonUrl(url: String, autoPlay: Boolean = true, contextJson: String? = null) {
|
|
374
303
|
if (!::playerView.isInitialized) {
|
|
375
304
|
Log.w("BBPlayerView", "Cannot load content - playerView not initialized")
|
|
376
305
|
return
|
|
377
306
|
}
|
|
378
307
|
|
|
379
|
-
Log.d("BBPlayerView", "loadWithJsonUrl called with URL: $url")
|
|
308
|
+
Log.d("BBPlayerView", "loadWithJsonUrl called with URL: $url, context: $contextJson")
|
|
380
309
|
|
|
381
310
|
// Extract ID from URL patterns like:
|
|
382
311
|
// /c/{id}.json or /mediaclip/{id}.json -> clip ID
|
|
@@ -388,6 +317,8 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
388
317
|
val projectIdRegex = Regex("/pj/([0-9]+)\\.json|/project/([0-9]+)")
|
|
389
318
|
val shortsIdRegex = Regex("/sh/([0-9]+)\\.json")
|
|
390
319
|
|
|
320
|
+
val context = parseContext(contextJson)
|
|
321
|
+
|
|
391
322
|
when {
|
|
392
323
|
shortsIdRegex.containsMatchIn(url) -> {
|
|
393
324
|
// Shorts require a separate BBShortsView component
|
|
@@ -401,7 +332,7 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
401
332
|
val clipListId = match?.groupValues?.drop(1)?.firstOrNull { it.isNotEmpty() }
|
|
402
333
|
if (clipListId != null) {
|
|
403
334
|
Log.d("BBPlayerView", "Loading ClipList by ID: $clipListId")
|
|
404
|
-
playerView.player?.loadWithClipListId(clipListId, "external", autoPlay, null)
|
|
335
|
+
playerView.player?.loadWithClipListId(clipListId, "external", autoPlay, null, context)
|
|
405
336
|
} else {
|
|
406
337
|
Log.e("BBPlayerView", "Failed to extract cliplist ID from URL: $url")
|
|
407
338
|
}
|
|
@@ -411,7 +342,7 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
411
342
|
val projectId = match?.groupValues?.drop(1)?.firstOrNull { it.isNotEmpty() }
|
|
412
343
|
if (projectId != null) {
|
|
413
344
|
Log.d("BBPlayerView", "Loading Project by ID: $projectId")
|
|
414
|
-
playerView.player?.loadWithProjectId(projectId, "external", autoPlay, null)
|
|
345
|
+
playerView.player?.loadWithProjectId(projectId, "external", autoPlay, null, context)
|
|
415
346
|
} else {
|
|
416
347
|
Log.e("BBPlayerView", "Failed to extract project ID from URL: $url")
|
|
417
348
|
}
|
|
@@ -421,7 +352,7 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
421
352
|
val clipId = match?.groupValues?.drop(1)?.firstOrNull { it.isNotEmpty() }
|
|
422
353
|
if (clipId != null) {
|
|
423
354
|
Log.d("BBPlayerView", "Loading Clip by ID: $clipId")
|
|
424
|
-
playerView.player?.loadWithClipId(clipId, "external", autoPlay, null)
|
|
355
|
+
playerView.player?.loadWithClipId(clipId, "external", autoPlay, null, context)
|
|
425
356
|
} else {
|
|
426
357
|
Log.e("BBPlayerView", "Failed to extract clip ID from URL: $url")
|
|
427
358
|
}
|
|
@@ -439,7 +370,6 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
439
370
|
debugLog("BBPlayerView") { "removePlayer called" }
|
|
440
371
|
playerSetup = false
|
|
441
372
|
isPlaying = false
|
|
442
|
-
stopTimeUpdates()
|
|
443
373
|
removeAllViews()
|
|
444
374
|
|
|
445
375
|
if (::playerView.isInitialized) {
|
|
@@ -468,11 +398,8 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
468
398
|
}
|
|
469
399
|
|
|
470
400
|
fun seekRelative(offsetInSeconds: Double) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
val newPosition = kotlin.math.max(0.0, kotlin.math.min(currentDuration, currentTime + offsetInSeconds))
|
|
474
|
-
playerView.player?.seek(newPosition)
|
|
475
|
-
}
|
|
401
|
+
// seekRelative not yet available in native SDK
|
|
402
|
+
android.util.Log.w("BBPlayerView", "seekRelative not supported by native SDK")
|
|
476
403
|
}
|
|
477
404
|
|
|
478
405
|
fun setVolume(volume: Double) {
|
|
@@ -505,6 +432,15 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
505
432
|
}
|
|
506
433
|
}
|
|
507
434
|
|
|
435
|
+
fun presentModal() {
|
|
436
|
+
// Use in-place fullscreen with landscape; modalPlayer option hides fullscreen button
|
|
437
|
+
enterFullscreenLandscape()
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
fun closeModal() {
|
|
441
|
+
exitFullscreen()
|
|
442
|
+
}
|
|
443
|
+
|
|
508
444
|
fun enterFullscreen() {
|
|
509
445
|
if (::playerView.isInitialized) {
|
|
510
446
|
shouldForceLandscape = false
|
|
@@ -610,22 +546,6 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
610
546
|
} else null
|
|
611
547
|
}
|
|
612
548
|
|
|
613
|
-
private fun calculateEstimatedCurrentTime(): Double {
|
|
614
|
-
return if (isPlaying && playbackStartTimestamp > 0) {
|
|
615
|
-
val elapsedSeconds = (System.currentTimeMillis() - playbackStartTimestamp) / 1000.0
|
|
616
|
-
val estimatedTime = lastKnownTime + elapsedSeconds
|
|
617
|
-
kotlin.math.min(estimatedTime, currentDuration)
|
|
618
|
-
} else {
|
|
619
|
-
lastKnownTime
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
fun getCurrentTime(): Double? {
|
|
624
|
-
return if (::playerView.isInitialized) {
|
|
625
|
-
calculateEstimatedCurrentTime()
|
|
626
|
-
} else null
|
|
627
|
-
}
|
|
628
|
-
|
|
629
549
|
fun getDuration(): Double? {
|
|
630
550
|
return if (::playerView.isInitialized) {
|
|
631
551
|
playerView.getApiProperty(com.bluebillywig.bbnativeshared.enums.ApiProperty.duration) as? Double
|
|
@@ -672,73 +592,63 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
672
592
|
return null
|
|
673
593
|
}
|
|
674
594
|
|
|
675
|
-
//
|
|
676
|
-
fun
|
|
677
|
-
if (
|
|
678
|
-
|
|
595
|
+
// Helper to parse context JSON into a Map for the native SDK
|
|
596
|
+
private fun parseContext(contextJson: String?): Map<String, Any?>? {
|
|
597
|
+
if (contextJson.isNullOrBlank()) return null
|
|
598
|
+
return try {
|
|
599
|
+
val json = JSONObject(contextJson)
|
|
600
|
+
mapOf(
|
|
601
|
+
"contextEntityType" to json.optString("contextEntityType", null),
|
|
602
|
+
"contextEntityId" to json.optString("contextEntityId", null),
|
|
603
|
+
"contextCollectionType" to json.optString("contextCollectionType", null),
|
|
604
|
+
"contextCollectionId" to json.optString("contextCollectionId", null)
|
|
605
|
+
).filterValues { it != null }
|
|
606
|
+
} catch (e: Exception) {
|
|
607
|
+
Log.w("BBPlayerView", "Failed to parse context JSON: $contextJson", e)
|
|
608
|
+
null
|
|
679
609
|
}
|
|
680
610
|
}
|
|
681
611
|
|
|
682
|
-
|
|
612
|
+
// Load methods
|
|
613
|
+
fun loadWithClipId(clipId: String, initiator: String? = "external", autoPlay: Boolean? = true, seekTo: Double? = null, contextJson: String? = null) {
|
|
683
614
|
if (::playerView.isInitialized) {
|
|
684
|
-
|
|
615
|
+
val context = parseContext(contextJson)
|
|
616
|
+
playerView.player?.loadWithClipId(clipId, initiator, autoPlay, seekTo, context)
|
|
685
617
|
}
|
|
686
618
|
}
|
|
687
619
|
|
|
688
|
-
fun
|
|
620
|
+
fun loadWithClipListId(clipListId: String, initiator: String? = "external", autoPlay: Boolean? = true, seekTo: Double? = null, contextJson: String? = null) {
|
|
689
621
|
if (::playerView.isInitialized) {
|
|
690
|
-
|
|
622
|
+
val context = parseContext(contextJson)
|
|
623
|
+
playerView.player?.loadWithClipListId(clipListId, initiator, autoPlay, seekTo, context)
|
|
691
624
|
}
|
|
692
625
|
}
|
|
693
626
|
|
|
694
|
-
fun
|
|
627
|
+
fun loadWithProjectId(projectId: String, initiator: String? = "external", autoPlay: Boolean? = true, seekTo: Double? = null, contextJson: String? = null) {
|
|
695
628
|
if (::playerView.isInitialized) {
|
|
696
|
-
|
|
629
|
+
val context = parseContext(contextJson)
|
|
630
|
+
playerView.player?.loadWithProjectId(projectId, initiator, autoPlay, seekTo, context)
|
|
697
631
|
}
|
|
698
632
|
}
|
|
699
633
|
|
|
700
|
-
fun
|
|
634
|
+
fun loadWithClipJson(clipJson: String, initiator: String? = "external", autoPlay: Boolean? = true, seekTo: Double? = null, contextJson: String? = null) {
|
|
701
635
|
if (::playerView.isInitialized) {
|
|
702
|
-
|
|
636
|
+
val context = parseContext(contextJson)
|
|
637
|
+
playerView.player?.loadWithClipJson(clipJson, initiator, autoPlay, seekTo, context)
|
|
703
638
|
}
|
|
704
639
|
}
|
|
705
640
|
|
|
706
|
-
fun
|
|
641
|
+
fun loadWithClipListJson(clipListJson: String, initiator: String? = "external", autoPlay: Boolean? = true, seekTo: Double? = null, contextJson: String? = null) {
|
|
707
642
|
if (::playerView.isInitialized) {
|
|
708
|
-
|
|
643
|
+
val context = parseContext(contextJson)
|
|
644
|
+
playerView.player?.loadWithClipListJson(clipListJson, initiator, autoPlay, seekTo, context)
|
|
709
645
|
}
|
|
710
646
|
}
|
|
711
647
|
|
|
712
|
-
|
|
713
|
-
if (
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
timeUpdateRunnable = object : Runnable {
|
|
718
|
-
override fun run() {
|
|
719
|
-
if (::playerView.isInitialized && isPlaying) {
|
|
720
|
-
val currentTime = calculateEstimatedCurrentTime()
|
|
721
|
-
|
|
722
|
-
if (currentDuration > 0) {
|
|
723
|
-
val params = Arguments.createMap().apply {
|
|
724
|
-
putDouble("currentTime", currentTime)
|
|
725
|
-
putDouble("duration", currentDuration)
|
|
726
|
-
}
|
|
727
|
-
sendEvent("onDidTriggerTimeUpdate", params)
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
timeUpdateHandler.postDelayed(this, 1000)
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
timeUpdateHandler.postDelayed(timeUpdateRunnable!!, 1000)
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
private fun stopTimeUpdates() {
|
|
739
|
-
timeUpdateRunnable?.let {
|
|
740
|
-
timeUpdateHandler.removeCallbacks(it)
|
|
741
|
-
timeUpdateRunnable = null
|
|
648
|
+
fun loadWithProjectJson(projectJson: String, initiator: String? = "external", autoPlay: Boolean? = true, seekTo: Double? = null, contextJson: String? = null) {
|
|
649
|
+
if (::playerView.isInitialized) {
|
|
650
|
+
val context = parseContext(contextJson)
|
|
651
|
+
playerView.player?.loadWithProjectJson(projectJson, initiator, autoPlay, seekTo, context)
|
|
742
652
|
}
|
|
743
653
|
}
|
|
744
654
|
|
|
@@ -756,10 +666,12 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
756
666
|
}
|
|
757
667
|
|
|
758
668
|
override fun didTriggerMediaClipLoaded(view: BBNativePlayerView, clipData: MediaClip?) {
|
|
759
|
-
debugLog("BBPlayerView") { "didTriggerMediaClipLoaded: ${clipData?.title}" }
|
|
669
|
+
debugLog("BBPlayerView") { "didTriggerMediaClipLoaded: ${clipData?.title} (${clipData?.width}x${clipData?.height})" }
|
|
760
670
|
val params = Arguments.createMap().apply {
|
|
761
671
|
putString("title", clipData?.title)
|
|
762
672
|
putString("id", clipData?.id)
|
|
673
|
+
clipData?.width?.let { putDouble("width", it.toDouble()) }
|
|
674
|
+
clipData?.height?.let { putDouble("height", it.toDouble()) }
|
|
763
675
|
}
|
|
764
676
|
sendEvent("onDidTriggerMediaClipLoaded", params)
|
|
765
677
|
}
|
|
@@ -839,24 +751,20 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
839
751
|
}
|
|
840
752
|
|
|
841
753
|
override fun didTriggerPlaying(view: BBNativePlayerView) {
|
|
842
|
-
debugLog("BBPlayerView") { "didTriggerPlaying
|
|
754
|
+
debugLog("BBPlayerView") { "didTriggerPlaying" }
|
|
843
755
|
isPlaying = true
|
|
844
|
-
playbackStartTimestamp = System.currentTimeMillis()
|
|
845
|
-
startTimeUpdates()
|
|
846
756
|
sendEvent("onDidTriggerPlaying")
|
|
847
757
|
}
|
|
848
758
|
|
|
849
759
|
override fun didTriggerPause(view: BBNativePlayerView) {
|
|
850
|
-
debugLog("BBPlayerView") { "didTriggerPause
|
|
760
|
+
debugLog("BBPlayerView") { "didTriggerPause" }
|
|
851
761
|
isPlaying = false
|
|
852
|
-
stopTimeUpdates()
|
|
853
762
|
sendEvent("onDidTriggerPause")
|
|
854
763
|
}
|
|
855
764
|
|
|
856
765
|
override fun didTriggerEnded(view: BBNativePlayerView) {
|
|
857
|
-
debugLog("BBPlayerView") { "didTriggerEnded
|
|
766
|
+
debugLog("BBPlayerView") { "didTriggerEnded" }
|
|
858
767
|
isPlaying = false
|
|
859
|
-
stopTimeUpdates()
|
|
860
768
|
sendEvent("onDidTriggerEnded")
|
|
861
769
|
}
|
|
862
770
|
|
|
@@ -867,9 +775,6 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
867
775
|
|
|
868
776
|
override fun didTriggerSeeked(view: BBNativePlayerView, seekOffset: Double?) {
|
|
869
777
|
debugLog("BBPlayerView") { "didTriggerSeeked: $seekOffset" }
|
|
870
|
-
lastKnownTime = seekOffset ?: 0.0
|
|
871
|
-
playbackStartTimestamp = System.currentTimeMillis()
|
|
872
|
-
|
|
873
778
|
val params = Arguments.createMap().apply {
|
|
874
779
|
putDouble("payload", seekOffset ?: 0.0)
|
|
875
780
|
}
|
|
@@ -1062,8 +967,6 @@ class BBPlayerView(private val reactContext: ThemedReactContext) : FrameLayout(r
|
|
|
1062
967
|
|
|
1063
968
|
override fun onDetachedFromWindow() {
|
|
1064
969
|
debugLog("BBPlayerView") { "onDetachedFromWindow - cleaning up player" }
|
|
1065
|
-
stopTimeUpdates()
|
|
1066
|
-
timeUpdateHandler.removeCallbacksAndMessages(null)
|
|
1067
970
|
removePlayer()
|
|
1068
971
|
super.onDetachedFromWindow()
|
|
1069
972
|
}
|
|
@@ -43,11 +43,6 @@ class BBPlayerViewManager : ViewGroupManager<BBPlayerView>() {
|
|
|
43
43
|
view.setAutoPlay(autoPlay)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
@ReactProp(name = "enableTimeUpdates")
|
|
47
|
-
fun setEnableTimeUpdates(view: BBPlayerView, enabled: Boolean) {
|
|
48
|
-
view.setEnableTimeUpdates(enabled)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
46
|
@ReactProp(name = "options")
|
|
52
47
|
fun setOptions(view: BBPlayerView, options: ReadableMap?) {
|
|
53
48
|
view.setOptions(options)
|
|
@@ -74,7 +69,9 @@ class BBPlayerViewManager : ViewGroupManager<BBPlayerView>() {
|
|
|
74
69
|
"loadWithProjectId" to COMMAND_LOAD_WITH_PROJECT_ID,
|
|
75
70
|
"loadWithClipJson" to COMMAND_LOAD_WITH_CLIP_JSON,
|
|
76
71
|
"loadWithClipListJson" to COMMAND_LOAD_WITH_CLIP_LIST_JSON,
|
|
77
|
-
"loadWithProjectJson" to COMMAND_LOAD_WITH_PROJECT_JSON
|
|
72
|
+
"loadWithProjectJson" to COMMAND_LOAD_WITH_PROJECT_JSON,
|
|
73
|
+
"presentModal" to COMMAND_PRESENT_MODAL,
|
|
74
|
+
"closeModal" to COMMAND_CLOSE_MODAL
|
|
78
75
|
)
|
|
79
76
|
|
|
80
77
|
// Override for Old Architecture (numeric command IDs)
|
|
@@ -102,6 +99,8 @@ class BBPlayerViewManager : ViewGroupManager<BBPlayerView>() {
|
|
|
102
99
|
COMMAND_LOAD_WITH_CLIP_JSON -> "loadWithClipJson"
|
|
103
100
|
COMMAND_LOAD_WITH_CLIP_LIST_JSON -> "loadWithClipListJson"
|
|
104
101
|
COMMAND_LOAD_WITH_PROJECT_JSON -> "loadWithProjectJson"
|
|
102
|
+
COMMAND_PRESENT_MODAL -> "presentModal"
|
|
103
|
+
COMMAND_CLOSE_MODAL -> "closeModal"
|
|
105
104
|
else -> return
|
|
106
105
|
}
|
|
107
106
|
receiveCommand(view, commandName, args)
|
|
@@ -124,6 +123,8 @@ class BBPlayerViewManager : ViewGroupManager<BBPlayerView>() {
|
|
|
124
123
|
"exitFullscreen" -> view.exitFullscreen()
|
|
125
124
|
"showCastPicker" -> view.showCastPicker()
|
|
126
125
|
"destroy" -> view.destroy()
|
|
126
|
+
"presentModal" -> view.presentModal()
|
|
127
|
+
"closeModal" -> view.closeModal()
|
|
127
128
|
"loadWithClipId" -> {
|
|
128
129
|
val clipId = args?.getString(0) ?: return
|
|
129
130
|
val initiator = args.getString(1)
|
|
@@ -186,7 +187,6 @@ class BBPlayerViewManager : ViewGroupManager<BBPlayerView>() {
|
|
|
186
187
|
"onDidTriggerEnded",
|
|
187
188
|
"onDidTriggerSeeking",
|
|
188
189
|
"onDidTriggerSeeked",
|
|
189
|
-
"onDidTriggerTimeUpdate",
|
|
190
190
|
"onDidTriggerDurationChange",
|
|
191
191
|
"onDidTriggerVolumeChange",
|
|
192
192
|
"onDidTriggerCanPlay",
|
|
@@ -249,5 +249,7 @@ class BBPlayerViewManager : ViewGroupManager<BBPlayerView>() {
|
|
|
249
249
|
private const val COMMAND_LOAD_WITH_CLIP_JSON = 18
|
|
250
250
|
private const val COMMAND_LOAD_WITH_CLIP_LIST_JSON = 19
|
|
251
251
|
private const val COMMAND_LOAD_WITH_PROJECT_JSON = 20
|
|
252
|
+
private const val COMMAND_PRESENT_MODAL = 21
|
|
253
|
+
private const val COMMAND_CLOSE_MODAL = 22
|
|
252
254
|
}
|
|
253
255
|
}
|
|
@@ -31,23 +31,34 @@ public abstract class NativeBBPlayerModuleSpec extends ReactContextBaseJavaModul
|
|
|
31
31
|
public abstract void collapse(int viewTag);
|
|
32
32
|
public abstract void expand(int viewTag);
|
|
33
33
|
|
|
34
|
+
// Void methods - modal control
|
|
35
|
+
public abstract void presentModal(int viewTag);
|
|
36
|
+
public abstract void closeModal(int viewTag);
|
|
37
|
+
|
|
34
38
|
// Void methods - other commands
|
|
35
39
|
public abstract void autoPlayNextCancel(int viewTag);
|
|
36
40
|
public abstract void destroy(int viewTag);
|
|
37
41
|
public abstract void showCastPicker(int viewTag);
|
|
38
42
|
|
|
43
|
+
// Modal player (module-level)
|
|
44
|
+
public abstract void presentModalPlayer(String jsonUrl, String optionsJson, String contextJson);
|
|
45
|
+
public abstract void dismissModalPlayer();
|
|
46
|
+
|
|
47
|
+
// Event emitter support
|
|
48
|
+
public abstract void addListener(String eventName);
|
|
49
|
+
public abstract void removeListeners(double count);
|
|
50
|
+
|
|
39
51
|
// Load methods
|
|
40
|
-
public abstract void loadWithClipId(int viewTag, String clipId, String initiator, boolean autoPlay, double seekTo);
|
|
41
|
-
public abstract void loadWithClipListId(int viewTag, String clipListId, String initiator, boolean autoPlay, double seekTo);
|
|
42
|
-
public abstract void loadWithProjectId(int viewTag, String projectId, String initiator, boolean autoPlay, double seekTo);
|
|
43
|
-
public abstract void loadWithClipJson(int viewTag, String clipJson, String initiator, boolean autoPlay, double seekTo);
|
|
44
|
-
public abstract void loadWithClipListJson(int viewTag, String clipListJson, String initiator, boolean autoPlay, double seekTo);
|
|
45
|
-
public abstract void loadWithProjectJson(int viewTag, String projectJson, String initiator, boolean autoPlay, double seekTo);
|
|
46
|
-
public abstract void loadWithJsonUrl(int viewTag, String jsonUrl, boolean autoPlay);
|
|
52
|
+
public abstract void loadWithClipId(int viewTag, String clipId, String initiator, boolean autoPlay, double seekTo, String contextJson);
|
|
53
|
+
public abstract void loadWithClipListId(int viewTag, String clipListId, String initiator, boolean autoPlay, double seekTo, String contextJson);
|
|
54
|
+
public abstract void loadWithProjectId(int viewTag, String projectId, String initiator, boolean autoPlay, double seekTo, String contextJson);
|
|
55
|
+
public abstract void loadWithClipJson(int viewTag, String clipJson, String initiator, boolean autoPlay, double seekTo, String contextJson);
|
|
56
|
+
public abstract void loadWithClipListJson(int viewTag, String clipListJson, String initiator, boolean autoPlay, double seekTo, String contextJson);
|
|
57
|
+
public abstract void loadWithProjectJson(int viewTag, String projectJson, String initiator, boolean autoPlay, double seekTo, String contextJson);
|
|
58
|
+
public abstract void loadWithJsonUrl(int viewTag, String jsonUrl, boolean autoPlay, String contextJson);
|
|
47
59
|
|
|
48
60
|
// Promise getters
|
|
49
61
|
public abstract void getDuration(int viewTag, Promise promise);
|
|
50
|
-
public abstract void getCurrentTime(int viewTag, Promise promise);
|
|
51
62
|
public abstract void getMuted(int viewTag, Promise promise);
|
|
52
63
|
public abstract void getVolume(int viewTag, Promise promise);
|
|
53
64
|
public abstract void getPhase(int viewTag, Promise promise);
|
package/ios/BBPlayerModule.mm
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
2
3
|
|
|
3
4
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
4
5
|
#import <BBPlayerModuleSpec/BBPlayerModuleSpec.h>
|
|
5
6
|
#endif
|
|
6
7
|
|
|
7
|
-
@interface RCT_EXTERN_MODULE(BBPlayerModule,
|
|
8
|
+
@interface RCT_EXTERN_MODULE(BBPlayerModule, RCTEventEmitter)
|
|
8
9
|
|
|
9
10
|
// Playback control
|
|
10
11
|
RCT_EXTERN_METHOD(play:(nonnull NSNumber *)viewTag)
|
|
@@ -29,13 +30,13 @@ RCT_EXTERN_METHOD(destroy:(nonnull NSNumber *)viewTag)
|
|
|
29
30
|
RCT_EXTERN_METHOD(showCastPicker:(nonnull NSNumber *)viewTag)
|
|
30
31
|
|
|
31
32
|
// Load methods
|
|
32
|
-
RCT_EXTERN_METHOD(loadWithClipId:(nonnull NSNumber *)viewTag clipId:(NSString *)clipId initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo)
|
|
33
|
-
RCT_EXTERN_METHOD(loadWithClipListId:(nonnull NSNumber *)viewTag clipListId:(NSString *)clipListId initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo)
|
|
34
|
-
RCT_EXTERN_METHOD(loadWithProjectId:(nonnull NSNumber *)viewTag projectId:(NSString *)projectId initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo)
|
|
35
|
-
RCT_EXTERN_METHOD(loadWithClipJson:(nonnull NSNumber *)viewTag clipJson:(NSString *)clipJson initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo)
|
|
36
|
-
RCT_EXTERN_METHOD(loadWithClipListJson:(nonnull NSNumber *)viewTag clipListJson:(NSString *)clipListJson initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo)
|
|
37
|
-
RCT_EXTERN_METHOD(loadWithProjectJson:(nonnull NSNumber *)viewTag projectJson:(NSString *)projectJson initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo)
|
|
38
|
-
RCT_EXTERN_METHOD(loadWithJsonUrl:(nonnull NSNumber *)viewTag jsonUrl:(NSString *)jsonUrl autoPlay:(BOOL)autoPlay)
|
|
33
|
+
RCT_EXTERN_METHOD(loadWithClipId:(nonnull NSNumber *)viewTag clipId:(NSString *)clipId initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo contextJson:(NSString *)contextJson)
|
|
34
|
+
RCT_EXTERN_METHOD(loadWithClipListId:(nonnull NSNumber *)viewTag clipListId:(NSString *)clipListId initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo contextJson:(NSString *)contextJson)
|
|
35
|
+
RCT_EXTERN_METHOD(loadWithProjectId:(nonnull NSNumber *)viewTag projectId:(NSString *)projectId initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo contextJson:(NSString *)contextJson)
|
|
36
|
+
RCT_EXTERN_METHOD(loadWithClipJson:(nonnull NSNumber *)viewTag clipJson:(NSString *)clipJson initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo contextJson:(NSString *)contextJson)
|
|
37
|
+
RCT_EXTERN_METHOD(loadWithClipListJson:(nonnull NSNumber *)viewTag clipListJson:(NSString *)clipListJson initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo contextJson:(NSString *)contextJson)
|
|
38
|
+
RCT_EXTERN_METHOD(loadWithProjectJson:(nonnull NSNumber *)viewTag projectJson:(NSString *)projectJson initiator:(NSString *)initiator autoPlay:(BOOL)autoPlay seekTo:(NSNumber *)seekTo contextJson:(NSString *)contextJson)
|
|
39
|
+
RCT_EXTERN_METHOD(loadWithJsonUrl:(nonnull NSNumber *)viewTag jsonUrl:(NSString *)jsonUrl autoPlay:(BOOL)autoPlay contextJson:(NSString *)contextJson)
|
|
39
40
|
|
|
40
41
|
// Getter methods with Promise
|
|
41
42
|
RCT_EXTERN_METHOD(getDuration:(nonnull NSNumber *)viewTag resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
@@ -49,6 +50,14 @@ RCT_EXTERN_METHOD(getClipData:(nonnull NSNumber *)viewTag resolver:(RCTPromiseRe
|
|
|
49
50
|
RCT_EXTERN_METHOD(getProjectData:(nonnull NSNumber *)viewTag resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
50
51
|
RCT_EXTERN_METHOD(getPlayoutData:(nonnull NSNumber *)viewTag resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
51
52
|
|
|
53
|
+
// Modal player (module-level)
|
|
54
|
+
RCT_EXTERN_METHOD(presentModalPlayer:(NSString *)jsonUrl optionsJson:(NSString *)optionsJson contextJson:(NSString *)contextJson)
|
|
55
|
+
RCT_EXTERN_METHOD(dismissModalPlayer)
|
|
56
|
+
|
|
57
|
+
// Event emitter support
|
|
58
|
+
RCT_EXTERN_METHOD(addListener:(NSString *)eventName)
|
|
59
|
+
RCT_EXTERN_METHOD(removeListeners:(double)count)
|
|
60
|
+
|
|
52
61
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
53
62
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
54
63
|
(const facebook::react::ObjCTurboModule::InitParams &)params
|