Deep Linking
Navigate users directly to specific content — a video, playlist, search results, or sub-channel.
Navigation State
All channel navigation is represented by BBNavigationState:
interface BBNavigationState {
pageType: 'main' | 'detailPage' | 'overviewPage' | 'searchPage';
contentId?: string; // Clip or playlist ID
contentType?: string; // 'mediaclip' or 'mediacliplist'
searchQuery?: string; // Active search term
subChannelId?: string; // Active sub-channel in multi-channel setup
}
Initial Navigation State
Open the channel at a specific location by passing initialNavigationState:
// Open directly on a video detail page
<BBChannel
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
initialNavigationState={{
pageType: 'detailPage',
contentId: '12345',
contentType: 'mediaclip',
}}
/>
// Open on a playlist overview
<BBChannel
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
initialNavigationState={{
pageType: 'overviewPage',
contentId: '678',
contentType: 'mediacliplist',
}}
/>
// Open with search results
<BBChannel
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
initialNavigationState={{
pageType: 'searchPage',
searchQuery: 'highlights',
}}
/>
// Open on a specific sub-channel
<BBChannel
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
initialNavigationState={{
pageType: 'main',
subChannelId: 'sports',
}}
/>
State Persistence
Save the navigation state so users return where they left off:
import AsyncStorage from '@react-native-async-storage/async-storage';
const NAV_STATE_KEY = '@channel_navigation';
function ChannelScreen() {
const [initialState, setInitialState] = useState<BBNavigationState | undefined>();
const [isReady, setIsReady] = useState(false);
useEffect(() => {
AsyncStorage.getItem(NAV_STATE_KEY)
.then((json) => {
if (json) setInitialState(JSON.parse(json));
})
.finally(() => setIsReady(true));
}, []);
if (!isReady) return null;
return (
<BBChannel
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
initialNavigationState={initialState}
onNavigationStateChange={(state) => {
AsyncStorage.setItem(NAV_STATE_KEY, JSON.stringify(state));
}}
/>
);
}
Programmatic Navigation
After the channel is loaded, use ref methods to navigate:
const channelRef = useRef<BBChannelRef>(null);
// Navigate to a specific entity
channelRef.current?.navigateToEntity('mediaclip', '12345');
channelRef.current?.navigateToEntity('mediacliplist', '678');
// Set full navigation state
channelRef.current?.setNavigationState({
pageType: 'detailPage',
contentId: '12345',
contentType: 'mediaclip',
});
// Search
channelRef.current?.search('highlights');
// Navigate to a sub-channel
channelRef.current?.navigateToSubChannel('sports');
// Go back to main page
channelRef.current?.navigateTo('main');
Programmatic navigation only works after the channel is ready. Wait for the onReady callback before calling navigation methods.
Web Hash URL Mapping
The web channel stores navigation state in the URL hash as #bbch=<base64>. You can convert between hash URLs and BBNavigationState:
| Web URL Hash | Navigation State |
|---|---|
#bbch=eyJwIjoibWFpbiJ9 | { pageType: 'main' } |
#bbch=eyJwIjoiZCIsImNpIjoiMTIzNDUiLCJjdCI6Im1jIn0= | { pageType: 'detailPage', contentId: '12345', contentType: 'mediaclip' } |
#bbch=eyJwIjoicyIsInNxIjoiaGlnaGxpZ2h0cyJ9 | { pageType: 'searchPage', searchQuery: 'highlights' } |
The base64-encoded JSON uses shortened keys:
| Short Key | Full Property |
|---|---|
p | pageType (main, d = detailPage, o = overviewPage, s = searchPage) |
ci | contentId |
ct | contentType (mc = mediaclip, mcl = mediacliplist) |
sq | searchQuery |
sc | subChannelId |
Handling Incoming Deep Links
Use React Native's Linking API to handle deep links from outside the app:
import { Linking } from 'react-native';
function ChannelScreen() {
const channelRef = useRef<BBChannelRef>(null);
const [initialState, setInitialState] = useState<BBNavigationState | undefined>();
const [isReady, setIsReady] = useState(false);
const channelReady = useRef(false);
// Parse deep link URL into navigation state
function parseDeepLink(url: string): BBNavigationState | undefined {
const parsed = new URL(url);
const path = parsed.pathname; // e.g., /video/12345 or /search/query
if (path.startsWith('/video/')) {
return {
pageType: 'detailPage',
contentId: path.split('/')[2],
contentType: 'mediaclip',
};
}
if (path.startsWith('/playlist/')) {
return {
pageType: 'overviewPage',
contentId: path.split('/')[2],
contentType: 'mediacliplist',
};
}
if (path.startsWith('/search/')) {
return {
pageType: 'searchPage',
searchQuery: decodeURIComponent(path.split('/')[2]),
};
}
return undefined;
}
// Handle initial deep link (cold start)
useEffect(() => {
Linking.getInitialURL().then((url) => {
if (url) setInitialState(parseDeepLink(url));
setIsReady(true);
});
}, []);
// Handle deep links while app is running (warm start)
useEffect(() => {
const subscription = Linking.addEventListener('url', ({ url }) => {
const state = parseDeepLink(url);
if (state && channelReady.current) {
channelRef.current?.setNavigationState(state);
}
});
return () => subscription.remove();
}, []);
if (!isReady) return null;
return (
<BBChannel
ref={channelRef}
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
initialNavigationState={initialState}
onReady={() => {
channelReady.current = true;
}}
/>
);
}
URL Scheme Examples
Define your deep link URL scheme and map routes to navigation states:
| URL | Navigation State |
|---|---|
myapp://channel | { pageType: 'main' } |
myapp://channel/video/12345 | { pageType: 'detailPage', contentId: '12345', contentType: 'mediaclip' } |
myapp://channel/playlist/678 | { pageType: 'overviewPage', contentId: '678', contentType: 'mediacliplist' } |
myapp://channel/search/highlights | { pageType: 'searchPage', searchQuery: 'highlights' } |
myapp://channel/tab/sports | { pageType: 'main', subChannelId: 'sports' } |
https://example.com/videos/12345 | { pageType: 'detailPage', contentId: '12345', contentType: 'mediaclip' } |
Configure these in your app's AndroidManifest.xml (Android) and Info.plist (iOS) as intent filters and universal links respectively.