@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
package/README.md
CHANGED
|
@@ -85,6 +85,7 @@ See the [Expo Setup Guide](docs/guides/expo-setup.md) for detailed configuration
|
|
|
85
85
|
### Guides
|
|
86
86
|
|
|
87
87
|
- [Expo Setup Guide](docs/guides/expo-setup.md) - Expo configuration and prebuild
|
|
88
|
+
- [Fullscreen & Modal Player Guide](docs/guides/fullscreen.md) - Fullscreen, landscape, and modal-style presentation
|
|
88
89
|
- [Advertising Guide](docs/guides/advertising.md) - Ad integration and VAST/VPAID
|
|
89
90
|
- [Analytics Guide](docs/guides/analytics.md) - Analytics integration and custom statistics
|
|
90
91
|
- [Shorts Guide](docs/guides/shorts.md) - Vertical video player (TikTok-style experience)
|
|
@@ -201,7 +202,6 @@ export function EventListenerExample() {
|
|
|
201
202
|
const [playerState, setPlayerState] = useState<State>('IDLE');
|
|
202
203
|
const [playerPhase, setPlayerPhase] = useState<Phase>('INIT');
|
|
203
204
|
const [duration, setDuration] = useState(0);
|
|
204
|
-
const [currentTime, setCurrentTime] = useState(0);
|
|
205
205
|
|
|
206
206
|
return (
|
|
207
207
|
<View style={{ flex: 1 }}>
|
|
@@ -212,7 +212,6 @@ export function EventListenerExample() {
|
|
|
212
212
|
onDidTriggerStateChange={(state) => setPlayerState(state)}
|
|
213
213
|
onDidTriggerPhaseChange={(phase) => setPlayerPhase(phase)}
|
|
214
214
|
onDidTriggerDurationChange={(dur) => setDuration(dur)}
|
|
215
|
-
onDidTriggerTimeUpdate={(time) => setCurrentTime(time)}
|
|
216
215
|
onDidTriggerPlay={() => console.log('Playback started')}
|
|
217
216
|
onDidTriggerPause={() => console.log('Playback paused')}
|
|
218
217
|
onDidTriggerEnded={() => console.log('Playback ended')}
|
|
@@ -220,7 +219,7 @@ export function EventListenerExample() {
|
|
|
220
219
|
<View style={{ padding: 20, backgroundColor: '#f0f0f0' }}>
|
|
221
220
|
<Text>State: {playerState}</Text>
|
|
222
221
|
<Text>Phase: {playerPhase}</Text>
|
|
223
|
-
<Text>
|
|
222
|
+
<Text>Duration: {duration.toFixed(1)}s</Text>
|
|
224
223
|
</View>
|
|
225
224
|
</View>
|
|
226
225
|
);
|
|
@@ -267,6 +266,55 @@ export function DynamicLoading() {
|
|
|
267
266
|
}
|
|
268
267
|
```
|
|
269
268
|
|
|
269
|
+
### Loading with Playlist Context
|
|
270
|
+
|
|
271
|
+
When loading clips within a playlist, pass context to enable "next up" navigation and proper playlist handling:
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
import React, { useRef } from 'react';
|
|
275
|
+
import { View, Button } from 'react-native';
|
|
276
|
+
import { BBPlayerView, type BBPlayerViewMethods, type LoadContext } from '@bluebillywig/react-native-bb-player';
|
|
277
|
+
|
|
278
|
+
export function PlaylistPlayer() {
|
|
279
|
+
const playerRef = useRef<BBPlayerViewMethods>(null);
|
|
280
|
+
const playlistId = '12345';
|
|
281
|
+
|
|
282
|
+
// Load a clip from a playlist with context for "next up" navigation
|
|
283
|
+
const loadClipFromPlaylist = (clipId: string) => {
|
|
284
|
+
playerRef.current?.loadClip(clipId, {
|
|
285
|
+
autoPlay: true,
|
|
286
|
+
playout: 'default',
|
|
287
|
+
context: {
|
|
288
|
+
contextEntityType: 'MediaClipList',
|
|
289
|
+
contextEntityId: playlistId,
|
|
290
|
+
contextCollectionType: 'MediaClipList',
|
|
291
|
+
contextCollectionId: playlistId,
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<View style={{ flex: 1 }}>
|
|
298
|
+
<BBPlayerView
|
|
299
|
+
ref={playerRef}
|
|
300
|
+
jsonUrl="https://demo.bbvms.com/p/default/c/4701337.json"
|
|
301
|
+
style={{ flex: 1 }}
|
|
302
|
+
/>
|
|
303
|
+
<View style={{ flexDirection: 'row', padding: 10 }}>
|
|
304
|
+
<Button title="Load Video 1" onPress={() => loadClipFromPlaylist('clip1')} />
|
|
305
|
+
<Button title="Load Video 2" onPress={() => loadClipFromPlaylist('clip2')} />
|
|
306
|
+
<Button title="Load Video 3" onPress={() => loadClipFromPlaylist('clip3')} />
|
|
307
|
+
</View>
|
|
308
|
+
</View>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The context enables:
|
|
314
|
+
- **Next Up List**: Shows upcoming videos from the playlist
|
|
315
|
+
- **Playlist Navigation**: Previous/next buttons work correctly within the playlist
|
|
316
|
+
- **Proper Analytics**: Analytics correctly attribute views to the playlist context
|
|
317
|
+
|
|
270
318
|
### Fullscreen Player
|
|
271
319
|
|
|
272
320
|
Launch player in fullscreen mode:
|
|
@@ -278,30 +326,26 @@ import { BBPlayerView, type BBPlayerViewMethods } from '@bluebillywig/react-nati
|
|
|
278
326
|
export function FullscreenPlayer() {
|
|
279
327
|
const playerRef = useRef<BBPlayerViewMethods>(null);
|
|
280
328
|
|
|
281
|
-
useEffect(() => {
|
|
282
|
-
// Enter fullscreen after player is ready
|
|
283
|
-
const timer = setTimeout(() => {
|
|
284
|
-
playerRef.current?.enterFullscreen();
|
|
285
|
-
}, 1000);
|
|
286
|
-
|
|
287
|
-
return () => clearTimeout(timer);
|
|
288
|
-
}, []);
|
|
289
|
-
|
|
290
329
|
return (
|
|
291
330
|
<BBPlayerView
|
|
292
331
|
ref={playerRef}
|
|
293
332
|
jsonUrl="https://demo.bbvms.com/p/default/c/4701337.json"
|
|
294
|
-
options={{
|
|
295
|
-
autoPlay: true,
|
|
296
|
-
}}
|
|
333
|
+
options={{ autoPlay: true }}
|
|
297
334
|
style={{ flex: 1 }}
|
|
298
|
-
|
|
299
|
-
|
|
335
|
+
onDidTriggerApiReady={() => {
|
|
336
|
+
// Enter fullscreen landscape once the player is ready
|
|
337
|
+
playerRef.current?.enterFullscreenLandscape();
|
|
338
|
+
}}
|
|
339
|
+
onDidTriggerRetractFullscreen={() => {
|
|
340
|
+
console.log('User exited fullscreen');
|
|
341
|
+
}}
|
|
300
342
|
/>
|
|
301
343
|
);
|
|
302
344
|
}
|
|
303
345
|
```
|
|
304
346
|
|
|
347
|
+
See the [Fullscreen & Modal Player Guide](docs/guides/fullscreen.md) for advanced patterns including modal-style presentation and handling orientation.
|
|
348
|
+
|
|
305
349
|
### Chromecast Support
|
|
306
350
|
|
|
307
351
|
Open the Google Cast device picker to cast video to Chromecast devices:
|
|
@@ -344,7 +388,6 @@ export function ChromecastPlayer() {
|
|
|
344
388
|
| `jwt` | `string` | No | JWT token for authenticated playback |
|
|
345
389
|
| `options` | `Record<string, unknown>` | No | Player configuration options |
|
|
346
390
|
| `style` | `ViewStyle` | No | React Native style object |
|
|
347
|
-
| `enableTimeUpdates` | `boolean` | No | Enable time update events (default: false) |
|
|
348
391
|
| Event props | See below | No | Event callback handlers |
|
|
349
392
|
|
|
350
393
|
### Methods (via ref)
|
|
@@ -373,21 +416,20 @@ showCastPicker(): void
|
|
|
373
416
|
// Load Content (Primary API)
|
|
374
417
|
loadClip(clipId: string, options?: LoadClipOptions): void
|
|
375
418
|
|
|
376
|
-
// Load Content (Legacy)
|
|
377
|
-
loadWithClipId(clipId: string, initiator?: string, autoPlay?: boolean, seekTo?: number): void
|
|
378
|
-
loadWithClipListId(clipListId: string, initiator?: string, autoPlay?: boolean, seekTo?: number): void
|
|
379
|
-
loadWithProjectId(projectId: string, initiator?: string, autoPlay?: boolean, seekTo?: number): void
|
|
380
|
-
loadWithClipJson(clipJson: string, initiator?: string, autoPlay?: boolean, seekTo?: number): void
|
|
381
|
-
loadWithClipListJson(clipListJson: string, initiator?: string, autoPlay?: boolean, seekTo?: number): void
|
|
382
|
-
loadWithProjectJson(projectJson: string, initiator?: string, autoPlay?: boolean, seekTo?: number): void
|
|
383
|
-
loadWithJsonUrl(jsonUrl: string, autoPlay?: boolean): void
|
|
419
|
+
// Load Content (Legacy - all support optional context parameter)
|
|
420
|
+
loadWithClipId(clipId: string, initiator?: string, autoPlay?: boolean, seekTo?: number, context?: LoadContext): void
|
|
421
|
+
loadWithClipListId(clipListId: string, initiator?: string, autoPlay?: boolean, seekTo?: number, context?: LoadContext): void
|
|
422
|
+
loadWithProjectId(projectId: string, initiator?: string, autoPlay?: boolean, seekTo?: number, context?: LoadContext): void
|
|
423
|
+
loadWithClipJson(clipJson: string, initiator?: string, autoPlay?: boolean, seekTo?: number, context?: LoadContext): void
|
|
424
|
+
loadWithClipListJson(clipListJson: string, initiator?: string, autoPlay?: boolean, seekTo?: number, context?: LoadContext): void
|
|
425
|
+
loadWithProjectJson(projectJson: string, initiator?: string, autoPlay?: boolean, seekTo?: number, context?: LoadContext): void
|
|
426
|
+
loadWithJsonUrl(jsonUrl: string, autoPlay?: boolean, context?: LoadContext): void
|
|
384
427
|
|
|
385
428
|
// Async Getters (Primary API)
|
|
386
429
|
getPlayerState(): Promise<BBPlayerState | null> // Returns complete player state
|
|
387
430
|
|
|
388
431
|
// Async Getters (Individual)
|
|
389
432
|
getDuration(): Promise<number | null>
|
|
390
|
-
getCurrentTime(): Promise<number | null>
|
|
391
433
|
getMuted(): Promise<boolean | null>
|
|
392
434
|
getVolume(): Promise<number | null>
|
|
393
435
|
getPhase(): Promise<string | null>
|
|
@@ -419,7 +461,6 @@ onDidTriggerCanPlay?: () => void
|
|
|
419
461
|
onDidTriggerSeeking?: () => void
|
|
420
462
|
onDidTriggerSeeked?: (position: number) => void
|
|
421
463
|
onDidTriggerStall?: () => void
|
|
422
|
-
onDidTriggerTimeUpdate?: (currentTime: number, duration: number) => void
|
|
423
464
|
onDidTriggerDurationChange?: (duration: number) => void
|
|
424
465
|
onDidTriggerVolumeChange?: (volume: number) => void
|
|
425
466
|
|
|
@@ -481,6 +522,15 @@ type LoadClipOptions = {
|
|
|
481
522
|
autoPlay?: boolean; // Auto-play after loading
|
|
482
523
|
seekTo?: number; // Seek to position (seconds)
|
|
483
524
|
initiator?: string; // Analytics initiator
|
|
525
|
+
context?: LoadContext; // Playlist/collection context
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// Context for playlist/collection navigation
|
|
529
|
+
type LoadContext = {
|
|
530
|
+
contextEntityType?: 'MediaClipList'; // Type of the containing entity
|
|
531
|
+
contextEntityId?: string; // Playlist ID for "next up" list
|
|
532
|
+
contextCollectionType?: 'MediaClipList'; // Type of the collection
|
|
533
|
+
contextCollectionId?: string; // Collection ID if playing within a collection
|
|
484
534
|
};
|
|
485
535
|
|
|
486
536
|
// Complete player state object (returned by getPlayerState())
|
|
@@ -488,7 +538,6 @@ type BBPlayerState = {
|
|
|
488
538
|
state: State;
|
|
489
539
|
phase: Phase;
|
|
490
540
|
mode: string | null;
|
|
491
|
-
currentTime: number;
|
|
492
541
|
duration: number;
|
|
493
542
|
muted: boolean;
|
|
494
543
|
volume: number;
|
|
@@ -503,39 +552,11 @@ type BBPlayerEventPayloads = {
|
|
|
503
552
|
pause: void;
|
|
504
553
|
stateChange: { state: State };
|
|
505
554
|
phaseChange: { phase: Phase };
|
|
506
|
-
timeUpdate: { currentTime: number; duration: number };
|
|
507
555
|
// ... and more
|
|
508
556
|
};
|
|
509
557
|
```
|
|
510
558
|
|
|
511
|
-
##
|
|
512
|
-
|
|
513
|
-
### Time Updates (Opt-In)
|
|
514
|
-
|
|
515
|
-
By default, the player does **not** emit `onDidTriggerTimeUpdate` events to reduce CPU overhead. Enable only when needed:
|
|
516
|
-
|
|
517
|
-
```tsx
|
|
518
|
-
<BBPlayerView
|
|
519
|
-
enableTimeUpdates={true}
|
|
520
|
-
onDidTriggerTimeUpdate={(time, dur) => {
|
|
521
|
-
setCurrentTime(time);
|
|
522
|
-
setDuration(dur);
|
|
523
|
-
}}
|
|
524
|
-
/>
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
### Polling Current Time (Alternative)
|
|
528
|
-
|
|
529
|
-
If you only need the current time occasionally, use the async getter:
|
|
530
|
-
|
|
531
|
-
```tsx
|
|
532
|
-
const handleGetTime = async () => {
|
|
533
|
-
const time = await playerRef.current?.getCurrentTime();
|
|
534
|
-
console.log('Current time:', time);
|
|
535
|
-
};
|
|
536
|
-
```
|
|
537
|
-
|
|
538
|
-
### Getting Complete Player State
|
|
559
|
+
## Getting Complete Player State
|
|
539
560
|
|
|
540
561
|
Use `getPlayerState()` to fetch all player state at once:
|
|
541
562
|
|
|
@@ -544,7 +565,7 @@ const handleGetState = async () => {
|
|
|
544
565
|
const state = await playerRef.current?.getPlayerState();
|
|
545
566
|
if (state) {
|
|
546
567
|
console.log(`Playing: ${state.state === 'PLAYING'}`);
|
|
547
|
-
console.log(`
|
|
568
|
+
console.log(`Duration: ${state.duration}`);
|
|
548
569
|
console.log(`Clip: ${state.clip?.title}`);
|
|
549
570
|
console.log(`Volume: ${state.volume}, Muted: ${state.muted}`);
|
|
550
571
|
}
|
package/android/build.gradle
CHANGED
|
@@ -64,6 +64,7 @@ android {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
repositories {
|
|
67
|
+
mavenLocal()
|
|
67
68
|
mavenCentral()
|
|
68
69
|
google()
|
|
69
70
|
maven {
|
|
@@ -76,9 +77,9 @@ dependencies {
|
|
|
76
77
|
implementation 'com.facebook.react:react-android'
|
|
77
78
|
|
|
78
79
|
// Blue Billywig Native Player SDK
|
|
79
|
-
//
|
|
80
|
-
// Customers can
|
|
81
|
-
implementation 'com.bluebillywig.bbnativeplayersdk:bbnativeplayersdk
|
|
80
|
+
// Uses latest available version by default
|
|
81
|
+
// Customers can pin a specific version using resolutionStrategy in their app's build.gradle
|
|
82
|
+
implementation 'com.bluebillywig.bbnativeplayersdk:bbnativeplayersdk:+'
|
|
82
83
|
|
|
83
84
|
// AndroidX MediaRouter for Chromecast support
|
|
84
85
|
implementation 'androidx.mediarouter:mediarouter:1.7.0'
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
package com.bluebillywig.bbplayer
|
|
2
2
|
|
|
3
3
|
import android.util.Log
|
|
4
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
5
|
+
import com.bluebillywig.bbnativeplayersdk.BBNativePlayer
|
|
6
|
+
import com.bluebillywig.bbnativeplayersdk.BBNativePlayerView
|
|
7
|
+
import com.bluebillywig.bbnativeplayersdk.BBNativePlayerViewDelegate
|
|
8
|
+
import com.bluebillywig.bbnativeplayersdk.ModalPlayerViewHolder
|
|
4
9
|
import com.facebook.react.bridge.Arguments
|
|
5
10
|
import com.facebook.react.bridge.Promise
|
|
6
11
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
12
|
import com.facebook.react.bridge.ReactMethod
|
|
8
13
|
import com.facebook.react.bridge.UiThreadUtil
|
|
9
14
|
import com.facebook.react.module.annotations.ReactModule
|
|
15
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
10
16
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
11
17
|
import com.facebook.react.uimanager.common.UIManagerType
|
|
18
|
+
import org.json.JSONObject
|
|
12
19
|
|
|
13
20
|
/**
|
|
14
21
|
* Native Module for BBPlayer commands.
|
|
@@ -136,15 +143,17 @@ class BBPlayerModule(private val reactContext: ReactApplicationContext) :
|
|
|
136
143
|
clipId: String,
|
|
137
144
|
initiator: String?,
|
|
138
145
|
autoPlay: Boolean,
|
|
139
|
-
seekTo: Double
|
|
146
|
+
seekTo: Double,
|
|
147
|
+
contextJson: String?
|
|
140
148
|
) {
|
|
141
|
-
Log.d("BBPlayerModule", "loadWithClipId called - viewTag: $viewTag, clipId: $clipId, autoPlay: $autoPlay")
|
|
149
|
+
Log.d("BBPlayerModule", "loadWithClipId called - viewTag: $viewTag, clipId: $clipId, autoPlay: $autoPlay, context: $contextJson")
|
|
142
150
|
runOnUiThread(viewTag.toInt()) {
|
|
143
151
|
it.loadWithClipId(
|
|
144
152
|
clipId,
|
|
145
153
|
initiator,
|
|
146
154
|
if (autoPlay) true else null,
|
|
147
|
-
if (seekTo > 0) seekTo else null
|
|
155
|
+
if (seekTo > 0) seekTo else null,
|
|
156
|
+
contextJson
|
|
148
157
|
)
|
|
149
158
|
}
|
|
150
159
|
}
|
|
@@ -155,14 +164,16 @@ class BBPlayerModule(private val reactContext: ReactApplicationContext) :
|
|
|
155
164
|
clipListId: String,
|
|
156
165
|
initiator: String?,
|
|
157
166
|
autoPlay: Boolean,
|
|
158
|
-
seekTo: Double
|
|
167
|
+
seekTo: Double,
|
|
168
|
+
contextJson: String?
|
|
159
169
|
) {
|
|
160
170
|
runOnUiThread(viewTag.toInt()) {
|
|
161
171
|
it.loadWithClipListId(
|
|
162
172
|
clipListId,
|
|
163
173
|
initiator,
|
|
164
174
|
if (autoPlay) true else null,
|
|
165
|
-
if (seekTo > 0) seekTo else null
|
|
175
|
+
if (seekTo > 0) seekTo else null,
|
|
176
|
+
contextJson
|
|
166
177
|
)
|
|
167
178
|
}
|
|
168
179
|
}
|
|
@@ -173,14 +184,16 @@ class BBPlayerModule(private val reactContext: ReactApplicationContext) :
|
|
|
173
184
|
projectId: String,
|
|
174
185
|
initiator: String?,
|
|
175
186
|
autoPlay: Boolean,
|
|
176
|
-
seekTo: Double
|
|
187
|
+
seekTo: Double,
|
|
188
|
+
contextJson: String?
|
|
177
189
|
) {
|
|
178
190
|
runOnUiThread(viewTag.toInt()) {
|
|
179
191
|
it.loadWithProjectId(
|
|
180
192
|
projectId,
|
|
181
193
|
initiator,
|
|
182
194
|
if (autoPlay) true else null,
|
|
183
|
-
if (seekTo > 0) seekTo else null
|
|
195
|
+
if (seekTo > 0) seekTo else null,
|
|
196
|
+
contextJson
|
|
184
197
|
)
|
|
185
198
|
}
|
|
186
199
|
}
|
|
@@ -191,14 +204,16 @@ class BBPlayerModule(private val reactContext: ReactApplicationContext) :
|
|
|
191
204
|
clipJson: String,
|
|
192
205
|
initiator: String?,
|
|
193
206
|
autoPlay: Boolean,
|
|
194
|
-
seekTo: Double
|
|
207
|
+
seekTo: Double,
|
|
208
|
+
contextJson: String?
|
|
195
209
|
) {
|
|
196
210
|
runOnUiThread(viewTag.toInt()) {
|
|
197
211
|
it.loadWithClipJson(
|
|
198
212
|
clipJson,
|
|
199
213
|
initiator,
|
|
200
214
|
if (autoPlay) true else null,
|
|
201
|
-
if (seekTo > 0) seekTo else null
|
|
215
|
+
if (seekTo > 0) seekTo else null,
|
|
216
|
+
contextJson
|
|
202
217
|
)
|
|
203
218
|
}
|
|
204
219
|
}
|
|
@@ -209,14 +224,16 @@ class BBPlayerModule(private val reactContext: ReactApplicationContext) :
|
|
|
209
224
|
clipListJson: String,
|
|
210
225
|
initiator: String?,
|
|
211
226
|
autoPlay: Boolean,
|
|
212
|
-
seekTo: Double
|
|
227
|
+
seekTo: Double,
|
|
228
|
+
contextJson: String?
|
|
213
229
|
) {
|
|
214
230
|
runOnUiThread(viewTag.toInt()) {
|
|
215
231
|
it.loadWithClipListJson(
|
|
216
232
|
clipListJson,
|
|
217
233
|
initiator,
|
|
218
234
|
if (autoPlay) true else null,
|
|
219
|
-
if (seekTo > 0) seekTo else null
|
|
235
|
+
if (seekTo > 0) seekTo else null,
|
|
236
|
+
contextJson
|
|
220
237
|
)
|
|
221
238
|
}
|
|
222
239
|
}
|
|
@@ -227,14 +244,16 @@ class BBPlayerModule(private val reactContext: ReactApplicationContext) :
|
|
|
227
244
|
projectJson: String,
|
|
228
245
|
initiator: String?,
|
|
229
246
|
autoPlay: Boolean,
|
|
230
|
-
seekTo: Double
|
|
247
|
+
seekTo: Double,
|
|
248
|
+
contextJson: String?
|
|
231
249
|
) {
|
|
232
250
|
runOnUiThread(viewTag.toInt()) {
|
|
233
251
|
it.loadWithProjectJson(
|
|
234
252
|
projectJson,
|
|
235
253
|
initiator,
|
|
236
254
|
if (autoPlay) true else null,
|
|
237
|
-
if (seekTo > 0) seekTo else null
|
|
255
|
+
if (seekTo > 0) seekTo else null,
|
|
256
|
+
contextJson
|
|
238
257
|
)
|
|
239
258
|
}
|
|
240
259
|
}
|
|
@@ -243,37 +262,164 @@ class BBPlayerModule(private val reactContext: ReactApplicationContext) :
|
|
|
243
262
|
override fun loadWithJsonUrl(
|
|
244
263
|
viewTag: Double,
|
|
245
264
|
jsonUrl: String,
|
|
246
|
-
autoPlay: Boolean
|
|
265
|
+
autoPlay: Boolean,
|
|
266
|
+
contextJson: String?
|
|
247
267
|
) {
|
|
248
268
|
runOnUiThread(viewTag.toInt()) {
|
|
249
|
-
it.loadWithJsonUrl(jsonUrl, autoPlay)
|
|
269
|
+
it.loadWithJsonUrl(jsonUrl, autoPlay, contextJson)
|
|
250
270
|
}
|
|
251
271
|
}
|
|
252
272
|
|
|
253
|
-
|
|
254
|
-
|
|
273
|
+
@ReactMethod
|
|
274
|
+
override fun presentModal(viewTag: Double) {
|
|
275
|
+
runOnUiThread(viewTag.toInt()) { it.presentModal() }
|
|
276
|
+
}
|
|
255
277
|
|
|
256
|
-
// Getter methods with Promise support
|
|
257
278
|
@ReactMethod
|
|
258
|
-
override fun
|
|
279
|
+
override fun closeModal(viewTag: Double) {
|
|
280
|
+
runOnUiThread(viewTag.toInt()) { it.closeModal() }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// MARK: - Modal Player API (module-level, no React view needed)
|
|
284
|
+
|
|
285
|
+
private var modalPlayerView: BBNativePlayerView? = null
|
|
286
|
+
private var pendingClipListLoad: (() -> Unit)? = null
|
|
287
|
+
|
|
288
|
+
private fun emitEvent(eventName: String, params: com.facebook.react.bridge.WritableMap? = null) {
|
|
289
|
+
reactContext
|
|
290
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
291
|
+
.emit(eventName, params ?: Arguments.createMap())
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private val modalPlayerDelegate = object : BBNativePlayerViewDelegate {
|
|
295
|
+
override fun didTriggerPlay(playerView: BBNativePlayerView) {
|
|
296
|
+
emitEvent("modalPlayerPlay")
|
|
297
|
+
}
|
|
298
|
+
override fun didTriggerPause(playerView: BBNativePlayerView) {
|
|
299
|
+
emitEvent("modalPlayerPause")
|
|
300
|
+
}
|
|
301
|
+
override fun didTriggerEnded(playerView: BBNativePlayerView) {
|
|
302
|
+
emitEvent("modalPlayerEnded")
|
|
303
|
+
}
|
|
304
|
+
override fun didFailWithError(playerView: BBNativePlayerView, error: String?) {
|
|
305
|
+
emitEvent("modalPlayerError", Arguments.createMap().apply {
|
|
306
|
+
putString("error", error ?: "Unknown error")
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
override fun didTriggerApiReady(playerView: BBNativePlayerView) {
|
|
310
|
+
// Load cliplist for auto-advance once player API is ready
|
|
311
|
+
pendingClipListLoad?.invoke()
|
|
312
|
+
pendingClipListLoad = null
|
|
313
|
+
emitEvent("modalPlayerApiReady")
|
|
314
|
+
}
|
|
315
|
+
override fun didTriggerCanPlay(playerView: BBNativePlayerView) {
|
|
316
|
+
emitEvent("modalPlayerCanPlay")
|
|
317
|
+
}
|
|
318
|
+
override fun didCloseModalPlayer(playerView: BBNativePlayerView) {
|
|
319
|
+
emitEvent("modalPlayerDismissed")
|
|
320
|
+
modalPlayerView = null
|
|
321
|
+
pendingClipListLoad = null
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@ReactMethod
|
|
326
|
+
override fun presentModalPlayer(jsonUrl: String, optionsJson: String?, contextJson: String?) {
|
|
259
327
|
UiThreadUtil.runOnUiThread {
|
|
260
|
-
val
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
} else {
|
|
265
|
-
promise.resolve(null)
|
|
328
|
+
val activity = reactContext.currentActivity as? AppCompatActivity
|
|
329
|
+
if (activity == null) {
|
|
330
|
+
Log.w(NAME, "presentModalPlayer: no AppCompatActivity")
|
|
331
|
+
return@runOnUiThread
|
|
266
332
|
}
|
|
333
|
+
|
|
334
|
+
// Parse options from JSON string
|
|
335
|
+
val options = mutableMapOf<String, Any?>()
|
|
336
|
+
if (optionsJson != null) {
|
|
337
|
+
try {
|
|
338
|
+
val json = JSONObject(optionsJson)
|
|
339
|
+
json.keys().forEach { key ->
|
|
340
|
+
options[key] = when {
|
|
341
|
+
json.isNull(key) -> null
|
|
342
|
+
else -> {
|
|
343
|
+
val value = json.get(key)
|
|
344
|
+
when (value) {
|
|
345
|
+
is Boolean -> value
|
|
346
|
+
is Int -> value
|
|
347
|
+
is Double -> value
|
|
348
|
+
is String -> value
|
|
349
|
+
else -> value.toString()
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
} catch (e: Exception) {
|
|
355
|
+
Log.w(NAME, "presentModalPlayer: failed to parse options JSON", e)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
val playerView = BBNativePlayer.createModalPlayerView(activity, jsonUrl, options)
|
|
360
|
+
playerView.delegate = modalPlayerDelegate
|
|
361
|
+
modalPlayerView = playerView
|
|
362
|
+
|
|
363
|
+
// If context has a contextCollectionId (cliplist), load clip by ID with
|
|
364
|
+
// cliplist context. ProgramController will swap to loading the cliplist
|
|
365
|
+
// and find the clip by ID (matching web standardplayer pattern).
|
|
366
|
+
if (contextJson != null) {
|
|
367
|
+
try {
|
|
368
|
+
val ctxJson = JSONObject(contextJson)
|
|
369
|
+
val collectionId = ctxJson.optString("contextCollectionId", null)
|
|
370
|
+
val clipId = ctxJson.optString("contextEntityId", null)
|
|
371
|
+
|
|
372
|
+
if (collectionId != null && clipId != null) {
|
|
373
|
+
val context = mutableMapOf<String, Any?>(
|
|
374
|
+
"contextCollectionType" to (ctxJson.optString("contextCollectionType", null) ?: "MediaClipList"),
|
|
375
|
+
"contextCollectionId" to collectionId,
|
|
376
|
+
)
|
|
377
|
+
pendingClipListLoad = {
|
|
378
|
+
playerView.player?.loadWithClipId(clipId, "external", true, null, context)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} catch (e: Exception) {
|
|
382
|
+
Log.w(NAME, "presentModalPlayer: failed to parse context", e)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
Log.d(NAME, "Modal player presented with URL: $jsonUrl, context: $contextJson")
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@ReactMethod
|
|
391
|
+
override fun dismissModalPlayer() {
|
|
392
|
+
UiThreadUtil.runOnUiThread {
|
|
393
|
+
// ModalActivity is the top activity — finish it to close the modal
|
|
394
|
+
val activity = reactContext.currentActivity
|
|
395
|
+
if (activity is com.bluebillywig.bbnativeplayersdk.ModalActivity) {
|
|
396
|
+
activity.finish()
|
|
397
|
+
}
|
|
398
|
+
modalPlayerView = null
|
|
267
399
|
}
|
|
268
400
|
}
|
|
269
401
|
|
|
270
402
|
@ReactMethod
|
|
271
|
-
override fun
|
|
403
|
+
override fun addListener(eventName: String) {
|
|
404
|
+
// Required for NativeEventEmitter
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@ReactMethod
|
|
408
|
+
override fun removeListeners(count: Double) {
|
|
409
|
+
// Required for NativeEventEmitter
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Note: loadWithShortsId is NOT supported on BBPlayerView.
|
|
413
|
+
// For Shorts playback, use the BBShortsView component instead.
|
|
414
|
+
|
|
415
|
+
// Getter methods with Promise support
|
|
416
|
+
@ReactMethod
|
|
417
|
+
override fun getDuration(viewTag: Double, promise: Promise) {
|
|
272
418
|
UiThreadUtil.runOnUiThread {
|
|
273
419
|
val view = findPlayerView(viewTag.toInt())
|
|
274
420
|
if (view != null) {
|
|
275
|
-
val
|
|
276
|
-
promise.resolve(
|
|
421
|
+
val duration = view.getDuration()
|
|
422
|
+
promise.resolve(duration)
|
|
277
423
|
} else {
|
|
278
424
|
promise.resolve(null)
|
|
279
425
|
}
|