Example App
The SDK ships with a complete example app that demonstrates all components and APIs. The app is available as a pre-built APK (Android) and on TestFlight (iOS) from GitHub Releases.
App Structure
The example app is a single-screen React Native app with a home menu that links to demo screens:
| Screen | Component | Description |
|---|---|---|
| Simple Player | BBPlayerView | Minimal player — no event handlers |
| API Reference | BBPlayerView, BBCastButton, BBCastMiniControls | Full API test: play, pause, seek, volume, fullscreen, cast |
| Shorts | BBShortsView | Vertical video with swipe navigation |
| Outstream | BBOutstreamPlayerView | Standalone ad player with collapse/expand |
| Modal Player | BBModalPlayer | Native full-screen modal overlay |
| Deep Link | BBPlayerView | Player opened via URL scheme |
Source Code
App.tsx
The main entry point handles navigation between screens and deep link parsing.
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
View,
Text,
TouchableOpacity,
StatusBar,
Linking,
} from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { ApiScreen } from './src/screens/ApiScreen';
import { ShortsScreen } from './src/screens/ShortsScreen';
import { OutstreamScreen } from './src/screens/OutstreamScreen';
import { DeepLinkPlayerScreen } from './src/screens/DeepLinkPlayerScreen';
import { SimplePlayerScreen } from './src/screens/SimplePlayerScreen';
import { ModalPlayerScreen } from './src/screens/ModalPlayerScreen';
type Screen = 'home' | 'api' | 'shorts' | 'outstream' | 'deeplink' | 'simple' | 'modal';
function App(): React.JSX.Element {
const [currentScreen, setCurrentScreen] = useState<Screen>('home');
const [deepLinkClipId, setDeepLinkClipId] = useState<string | null>(null);
useEffect(() => {
const handleInitialUrl = async () => {
const initialUrl = await Linking.getInitialURL();
if (initialUrl) {
const match = initialUrl.match(/\/watch\/(\d+)/);
if (match) {
setDeepLinkClipId(match[1]);
setCurrentScreen('deeplink');
}
}
};
const handleUrlChange = (event: { url: string }) => {
const match = event.url.match(/\/watch\/(\d+)/);
if (match) {
setDeepLinkClipId(match[1]);
setCurrentScreen('deeplink');
}
};
handleInitialUrl();
const subscription = Linking.addEventListener('url', handleUrlChange);
return () => subscription.remove();
}, []);
// ... navigation and rendering
}
Simple Player
The most minimal integration — just a BBPlayerView with a JSON URL and no event handlers:
import React, { useRef, useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import {
BBPlayerView,
type BBPlayerViewMethods,
} from '@bluebillywig/react-native-bb-player';
const VIDEO_URL = 'https://demo.bbvms.com/p/native_sdk/c/4256593.json';
export function SimplePlayerScreen() {
const playerRef = useRef<BBPlayerViewMethods>(null);
// Clean up on unmount
useEffect(() => {
return () => {
playerRef.current?.destroy();
};
}, []);
return (
<View style={styles.playerContainer}>
<BBPlayerView
ref={playerRef}
jsonUrl={VIDEO_URL}
options={{ noChromeCast: true }}
style={styles.player}
/>
</View>
);
}
const styles = StyleSheet.create({
playerContainer: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#000',
},
player: {
width: '100%',
aspectRatio: 16 / 9,
},
});
API Reference Screen
Demonstrates all player API methods and event callbacks, including ChromeCast support:
import React, { useRef, useState, useCallback, useEffect } from 'react';
import { View, Text, TouchableOpacity, ScrollView, Alert } from 'react-native';
import {
BBPlayerView,
BBCastButton,
BBCastMiniControls,
type BBPlayerViewMethods,
} from '@bluebillywig/react-native-bb-player';
const INITIAL_JSON_URL = 'https://demo.bbvms.com/p/native_sdk/c/4256593.json';
export function ApiScreen() {
const playerRef = useRef<BBPlayerViewMethods>(null);
const [eventLog, setEventLog] = useState<string[]>([]);
useEffect(() => {
return () => { playerRef.current?.destroy(); };
}, []);
const handleAction = useCallback(async (action: string) => {
if (!playerRef.current) return;
switch (action) {
case 'Play':
playerRef.current.play();
break;
case 'Pause':
playerRef.current.pause();
break;
case 'Seek +10s':
playerRef.current.seekRelative(10);
break;
case 'Mute':
playerRef.current.setMuted(true);
break;
case 'Enter Fullscreen':
playerRef.current.enterFullscreen();
break;
case 'Get Duration':
const duration = await playerRef.current.getDuration();
console.log('Duration:', duration);
break;
case 'Load Clip':
playerRef.current.loadClip('4256575', { playout: 'native_sdk', autoPlay: true });
break;
case 'Show Cast Picker':
playerRef.current.showCastPicker();
break;
case 'Destroy':
playerRef.current.destroy();
break;
}
}, []);
return (
<View style={{ flex: 1 }}>
{/* Player */}
<BBPlayerView
ref={playerRef}
jsonUrl={INITIAL_JSON_URL}
style={{ width: '100%', aspectRatio: 16 / 9 }}
onDidTriggerApiReady={() => console.log('API Ready')}
onDidTriggerPlay={() => console.log('Play')}
onDidTriggerPause={() => console.log('Pause')}
onDidTriggerStateChange={(state) => console.log('State:', state)}
onDidTriggerPhaseChange={(phase) => console.log('Phase:', phase)}
onDidTriggerFullscreen={() => console.log('Fullscreen')}
onDidTriggerDurationChange={(d) => console.log('Duration:', d)}
onDidTriggerMediaClipLoaded={(clip) => console.log('Loaded:', clip.title)}
onDidFailWithError={(err) => console.error('Error:', err)}
/>
{/* ChromeCast controls */}
<View style={{ flexDirection: 'row', alignItems: 'center', padding: 8 }}>
<BBCastButton style={{ width: 40, height: 40 }} tintColor="#007AFF" />
<BBCastMiniControls style={{ flex: 1, height: 40 }} />
</View>
{/* Action buttons and event log */}
</View>
);
}
Shorts Screen
Demonstrates vertical video with BBShortsView, including publication selection and display format toggle (full/shelf):
import React, { useRef, useCallback, useState } from 'react';
import { View } from 'react-native';
import {
BBShortsView,
type BBShortsViewMethods,
} from '@bluebillywig/react-native-bb-player';
export function ShortsPlayer({ publication, shortsId }: {
publication: string;
shortsId: string;
}) {
const shortsRef = useRef<BBShortsViewMethods>(null);
const jsonUrl = `https://${publication}.bbvms.com/sh/${shortsId}.json`;
return (
<BBShortsView
ref={shortsRef}
jsonUrl={jsonUrl}
style={{ flex: 1 }}
onDidSetupWithJsonUrl={(url) => console.log('Shorts loaded:', url)}
onDidFailWithError={(error) => console.error('Error:', error)}
/>
);
}
// Shelf mode (horizontal scrolling list)
export function ShortsShelf({ publication, shortsId }: {
publication: string;
shortsId: string;
}) {
const shortsRef = useRef<BBShortsViewMethods>(null);
const jsonUrl = `https://${publication}.bbvms.com/sh/${shortsId}.json`;
return (
<BBShortsView
ref={shortsRef}
jsonUrl={jsonUrl}
options={{
displayFormat: 'list',
shelfStartSpacing: 16,
shelfEndSpacing: 16,
}}
style={{ height: 400 }}
onDidSetupWithJsonUrl={(url) => console.log('Shelf loaded:', url)}
onDidFailWithError={(error) => console.error('Error:', error)}
/>
);
}
Outstream Ads Screen
Demonstrates BBOutstreamPlayerView embedded in article content with collapse/expand animation:
import React, { useRef, useState, useEffect } from 'react';
import { View, Text, ScrollView } from 'react-native';
import {
BBOutstreamPlayerView,
type BBOutstreamViewMethods,
type OutstreamAnimationType,
} from '@bluebillywig/react-native-bb-player';
const OUTSTREAM_URL = 'https://demo.bbvms.com/a/native_sdk_outstream.json';
export function OutstreamScreen() {
const outstreamRef = useRef<BBOutstreamViewMethods>(null);
const [isCollapsed, setIsCollapsed] = useState(false);
useEffect(() => {
return () => { outstreamRef.current?.destroy(); };
}, []);
return (
<ScrollView>
<Text>Article content above the ad...</Text>
<View>
<Text style={{ fontSize: 10, color: '#999', textAlign: 'center' }}>
ADVERTISEMENT
</Text>
<BBOutstreamPlayerView
ref={outstreamRef}
jsonUrl={OUTSTREAM_URL}
expandedHeight={250}
animation={{
type: 'timing', // 'timing' | 'spring' | 'layout' | 'none'
duration: 300,
damping: 15,
stiffness: 100,
}}
options={{ autoPlay: false, autoMute: true }}
onCollapsed={() => setIsCollapsed(true)}
onExpanded={() => setIsCollapsed(false)}
onDidTriggerAdStarted={() => console.log('Ad started')}
onDidTriggerAdFinished={() => console.log('Ad finished')}
onDidTriggerAdError={(err) => console.log('Ad error:', err)}
onDidTriggerAdNotFound={() => console.log('No ad available')}
/>
</View>
<Text>Article content below the ad...</Text>
</ScrollView>
);
}
Manual controls — collapse and expand the outstream player programmatically:
outstreamRef.current?.animateCollapse();
outstreamRef.current?.animateExpand();
outstreamRef.current?.play();
outstreamRef.current?.pause();
Modal Player Screen
Demonstrates BBModalPlayer — a native full-screen modal overlay with swipe-to-close (iOS):
import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { BBModalPlayer, type ModalPlayerContext } from '@bluebillywig/react-native-bb-player';
const BASE_URL = 'https://demo.bbvms.com/p/native_sdk/c';
export function ModalPlayerScreen() {
useEffect(() => {
const sub1 = BBModalPlayer.addEventListener('onModalPlayerPresented', () => {
console.log('Modal presented');
});
const sub2 = BBModalPlayer.addEventListener('onModalPlayerDismissed', () => {
console.log('Modal dismissed');
});
return () => { sub1.remove(); sub2.remove(); };
}, []);
// Present a single clip
const handlePresent = (clipId: string) => {
BBModalPlayer.present(`${BASE_URL}/${clipId}.json`, { autoPlay: true });
};
// Present a clip within a playlist context (enables auto-advance)
const handlePresentWithContext = (clipId: string) => {
const context: ModalPlayerContext = {
contextEntityId: clipId,
contextCollectionType: 'MediaClipList',
contextCollectionId: '681',
};
BBModalPlayer.present(`${BASE_URL}/${clipId}.json`, { autoPlay: true }, context);
};
// Dismiss the modal programmatically
const handleDismiss = () => {
BBModalPlayer.dismiss();
};
return (
<View>
<Text>Module available: {BBModalPlayer.isAvailable ? 'Yes' : 'No'}</Text>
<TouchableOpacity onPress={() => handlePresent('4256593')}>
<Text>Open Modal</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => handlePresentWithContext('4256593')}>
<Text>Open with Playlist</Text>
</TouchableOpacity>
<TouchableOpacity onPress={handleDismiss}>
<Text>Dismiss</Text>
</TouchableOpacity>
</View>
);
}
Deep Link Player Screen
Player opened via URL scheme (bbplayer://watch/<clipId> or https://www.bluebillywig.tv/watch/<clipId>):
import React, { useRef, useEffect } from 'react';
import { View, Text } from 'react-native';
import {
BBPlayerView,
type BBPlayerViewMethods,
} from '@bluebillywig/react-native-bb-player';
interface DeepLinkPlayerScreenProps {
clipId: string;
}
export function DeepLinkPlayerScreen({ clipId }: DeepLinkPlayerScreenProps) {
const playerRef = useRef<BBPlayerViewMethods>(null);
const jsonUrl = `https://demo.bbvms.com/p/default/c/${clipId}.json`;
useEffect(() => {
return () => { playerRef.current?.destroy(); };
}, []);
return (
<View style={{ flex: 1, backgroundColor: '#000' }}>
<BBPlayerView
ref={playerRef}
jsonUrl={jsonUrl}
style={{ flex: 1 }}
options={{ autoPlay: true }}
onDidSetupWithJsonUrl={(url) => console.log('Ready:', url)}
onDidFailWithError={(err) => console.error('Error:', err)}
onDidTriggerMediaClipLoaded={(clip) => console.log('Loaded:', clip.title)}
/>
<Text style={{ color: '#fff', padding: 16, fontFamily: 'monospace', fontSize: 11 }}>
{jsonUrl}
</Text>
</View>
);
}
Building the Example App
Android
npm ci
npm run build
cd example && npm ci
cd android && ./gradlew assembleRelease
# APK at: app/build/outputs/apk/release/app-release.apk
iOS
npm ci
npm run build
cd example && npm ci
cd ios && LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 pod install
xcodebuild -workspace BBPlayerExample.xcworkspace \
-scheme BBPlayerExample \
-configuration Release \
-destination 'generic/platform=iOS Simulator' \
build
Pre-built Binaries
- Android APK: Available from GitHub Releases
- iOS TestFlight: Uploaded automatically as part of each release