npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bluebillywig/react-native-channel

v1.1.0

Published

Blue Billywig Channel SDK for React Native - Embed video channels in your React Native apps

Readme

Blue Billywig React Native Channel SDK

Embed Blue Billywig video channels in your React Native app with full navigation, authentication, and player control support.

Installation

npm install @bluebillywig/react-native-channel react-native-webview

iOS Setup

cd ios && pod install

Android Setup

No additional setup required.

Quick Start

import { BBChannel } from '@bluebillywig/react-native-channel';

function App() {
  return (
    <BBChannel
      channelUrl="https://demo.bbvms.com/ch/123.json"
      onReady={() => console.log('Channel loaded')}
      onMediaPlay={(media) => console.log('Playing:', media.title)}
    />
  );
}

BBChannel Component

The main component for embedding a video channel.

Props

Required

| Prop | Type | Description | |------|------|-------------| | channelUrl | string | URL to the channel JSON configuration |

Configuration Options

| Prop | Type | Default | Description | |------|------|---------|-------------| | options.autoPlay | boolean | true | Enable/disable autoplay | | options.searchBar | boolean | true | Show/hide search bar | | options.noStats | boolean | false | Disable analytics | | options.jwt | string | - | JWT token for authenticated content | | options.rpcToken | string | - | RPC token for authenticated content | | options.playout | string | - | Custom playout configuration name | | options.contentId | string | - | Initial content ID to display | | options.contentType | 'mediaclip' \| 'mediacliplist' | - | Type of initial content | | options.bundleUrl | string | CDN URL | Custom bundle URL (must be *.bluebillywig.com) | | options.fullscreenMode | 'fullscreen' \| 'landscape' | 'landscape' | Fullscreen behavior |

Lifecycle Events

| Prop | Type | Description | |------|------|-------------| | onReady | () => void | Called when channel is fully loaded | | onError | (error: BBErrorEvent) => void | Called on errors |

Navigation Events

| Prop | Type | Description | |------|------|-------------| | onNavigate | (event: BBNavigationEvent) => void | Called on navigation changes | | onNavigationStateChange | (state: BBNavigationState) => void | Called when navigation state changes | | onCanGoBackChange | (canGoBack: boolean) => void | Called when back navigation availability changes | | initialNavigationState | BBNavigationState | Initial state for deep linking |

Search Events

| Prop | Type | Description | |------|------|-------------| | onSearch | (event: BBSearchEvent) => void | Called when a search is performed |

Media Events

| Prop | Type | Description | |------|------|-------------| | onMediaPlay | (media: BBMediaInfo) => void | Called when media starts playing | | onMediaPause | (media: BBMediaInfo) => void | Called when media is paused | | onMediaEnd | (media: BBMediaInfo) => void | Called when media ends | | onFirstPlayerMedia | (media: BBMediaInfo) => void | Called when first player media is identified |

Player Events

| Prop | Type | Description | |------|------|-------------| | onPlayerStateChange | (state: BBPlayerState) => void | Called on player state changes | | onPlayerEvent | (event: BBPlayerEvent) => void | Called on any player event | | onTimeUpdate | (data: BBTimeUpdateEvent) => void | Called frequently during playback | | onClipLoaded | (data: BBClipLoadedEvent) => void | Called when a clip is loaded | | onAdStart | (data: BBAdEvent) => void | Called when an ad starts | | onAdEnd | (data: BBAdEvent) => void | Called when an ad ends |

Ref Methods

Access methods via ref:

const channelRef = useRef<BBChannelRef>(null);

// Later...
channelRef.current?.play();
channelRef.current?.setJwt('new-token');

Authentication Methods

| Method | Description | |--------|-------------| | setJwt(jwt: string \| null) | Update JWT token for authenticated content | | setRpcToken(token: string \| null) | Update RPC token |

Navigation Methods

| Method | Description | |--------|-------------| | navigateTo(page: 'main' \| 'search') | Navigate to a page | | goBack() | Navigate back | | search(query: string) | Perform a search | | reload() | Reload the channel | | playVideo(videoId: string, type?) | Play a specific video | | navigateToEntity(entityId, entityType, options?) | Navigate to entity detail page | | navigateToSubChannel(subChannelId: string) | Navigate to sub-channel | | resetNavigation() | Clear history and go to main | | setNavigationState(state: BBNavigationState) | Restore navigation state | | getNavigationState(): Promise<BBNavigationState> | Get current navigation state | | canGoBack(): Promise<boolean> | Check if back navigation is possible |

Player Control Methods

| Method | Description | |--------|-------------| | play() | Start or resume playback | | pause() | Pause playback | | seek(seconds: number) | Seek to position | | setVolume(volume: number) | Set volume (0.0 - 1.0) | | setMuted(muted: boolean) | Mute or unmute | | enterFullscreen() | Enter fullscreen mode | | exitFullscreen() | Exit fullscreen mode | | getPlayerState(): Promise<BBPlayerState \| null> | Get current player state |

Types

BBMediaInfo

Information about a media clip.

interface BBMediaInfo {
  jsonUrl: string;        // URL for loading in native player
  playoutId: string;      // Playout configuration ID
  autoplay: boolean;      // Whether to autoplay
  thumbnailUrl?: string;  // Thumbnail URL
  title?: string;         // Media title
  mediaType?: 'video' | 'audio';  // Content type
}

BBNavigationState

Full navigation state for deep linking and state persistence.

interface BBNavigationState {
  pageType: 'main' | 'detailPage' | 'overviewPage' | 'searchPage';
  contentId?: string;           // Entity ID
  contentType?: 'mediaclip' | 'mediacliplist';
  searchQuery?: string;
  clickedSearchResult?: boolean;
  blockId?: string;
  contextId?: string;
  playout?: string;
  collectionId?: string;
  subChannelId?: string;
}

BBPlayerState

Current player state.

interface BBPlayerState {
  isPlaying: boolean;
  currentTime?: number;   // In seconds
  duration?: number;      // In seconds
  volume?: number;        // 0.0 - 1.0
  isMuted?: boolean;
  isFullscreen?: boolean;
}

BBPlayerEvent

Union type for all player events. Use with onPlayerEvent for fine-grained control.

// Event types:
// 'play', 'pause', 'ended', 'canplay', 'waiting',
// 'seeking', 'seeked', 'timeupdate', 'durationchange', 'progress',
// 'volumechange', 'fullscreenchange', 'loadstart', 'loadedmetadata', 'loadeddata',
// 'cliploaded', 'clipfailed', 'cliplistloaded',
// 'adstart', 'adend', 'adskip', 'aderror', 'error'

BBTimeUpdateEvent

Time update data sent frequently during playback.

interface BBTimeUpdateEvent {
  timestamp: number;
  clipId?: string;
  title?: string;
  currentTime: number;  // In seconds
  duration: number;     // In seconds
  progress: number;     // 0-100 percentage
}

BBClipLoadedEvent

Clip loaded event data.

interface BBClipLoadedEvent {
  timestamp: number;
  clipId: string;
  title: string;
  duration: number;     // In seconds
  thumbnailUrl?: string;
  isLive?: boolean;
}

BBAdEvent

Advertisement event data.

interface BBAdEvent {
  timestamp: number;
  position?: 'pre' | 'mid' | 'post';
  duration?: number;      // In seconds
  remainingTime?: number; // In seconds
  skippable?: boolean;
  skipOffset?: number;    // Seconds until skip available
}

BBErrorEvent

Error event data.

interface BBErrorEvent {
  code: string;
  message: string;
  details?: unknown;
}

BBSearchEvent

Search event data.

interface BBSearchEvent {
  query: string;
  resultCount: number;
}

Examples

Authentication

function AuthenticatedChannel() {
  const channelRef = useRef<BBChannelRef>(null);
  const [token, setToken] = useState<string | null>(null);

  // Update token when it changes
  useEffect(() => {
    channelRef.current?.setJwt(token);
  }, [token]);

  return (
    <BBChannel
      ref={channelRef}
      channelUrl="https://demo.bbvms.com/ch/123.json"
      options={{ jwt: token ?? undefined }}
    />
  );
}

Player Progress Tracking

function ProgressTrackingChannel() {
  const [progress, setProgress] = useState(0);

  return (
    <>
      <ProgressBar value={progress} />
      <BBChannel
        channelUrl="https://demo.bbvms.com/ch/123.json"
        onTimeUpdate={(data) => setProgress(data.progress)}
        onPlayerEvent={(event) => {
          if (event.type === 'ended') {
            console.log('Video finished!');
          }
        }}
      />
    </>
  );
}

Deep Linking

function DeepLinkChannel() {
  const channelRef = useRef<BBChannelRef>(null);

  // Handle deep link
  useEffect(() => {
    const url = Linking.getInitialURL();
    if (url?.includes('video=')) {
      const videoId = extractVideoId(url);
      channelRef.current?.navigateToEntity(videoId, 'mediaclip', { autoPlay: true });
    }
  }, []);

  // Save state for restoration
  const handleNavigationChange = (state: BBNavigationState) => {
    AsyncStorage.setItem('channelState', JSON.stringify(state));
  };

  return (
    <BBChannel
      ref={channelRef}
      channelUrl="https://demo.bbvms.com/ch/123.json"
      onNavigationStateChange={handleNavigationChange}
    />
  );
}

Android Back Button

function BackButtonChannel() {
  const channelRef = useRef<BBChannelRef>(null);
  const [canGoBack, setCanGoBack] = useState(false);

  useEffect(() => {
    const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
      if (canGoBack) {
        channelRef.current?.goBack();
        return true;
      }
      return false;
    });
    return () => backHandler.remove();
  }, [canGoBack]);

  return (
    <BBChannel
      ref={channelRef}
      channelUrl="https://demo.bbvms.com/ch/123.json"
      onCanGoBackChange={setCanGoBack}
    />
  );
}

Player Controls

function PlayerControlsChannel() {
  const channelRef = useRef<BBChannelRef>(null);

  const handlePlayPause = async () => {
    const state = await channelRef.current?.getPlayerState();
    if (state?.isPlaying) {
      channelRef.current?.pause();
    } else {
      channelRef.current?.play();
    }
  };

  return (
    <>
      <TouchableOpacity onPress={handlePlayPause}>
        <Text>Play/Pause</Text>
      </TouchableOpacity>
      <BBChannel
        ref={channelRef}
        channelUrl="https://demo.bbvms.com/ch/123.json"
      />
    </>
  );
}

BBChannelWithPlayer Component

A channel component with integrated native video player. Combines BBChannel (WebView) with BBPlayerView (native player) for optimal video playback. Requires @bluebillywig/react-native-bb-player as a peer dependency.

import { BBChannelWithPlayer } from '@bluebillywig/react-native-channel';

function App() {
  return (
    <BBChannelWithPlayer
      channelUrl="https://demo.bbvms.com/ch/123.json"
      playerMode="inline"
      onNativePlayerStateChange={(state) => console.log('Player:', state)}
    />
  );
}

Props (in addition to BBChannel props)

| Prop | Type | Default | Description | |------|------|---------|-------------| | playerMode | 'inline' \| 'fullscreen' | 'inline' | Player display mode | | playerHeight | number | - | Fixed height for inline player (uses aspectRatio if not set) | | playerAspectRatio | number | 16/9 | Aspect ratio for inline player | | showBackArrow | boolean | true | Show close button over inline player | | jwt | string | - | JWT token for both channel and player | | onNativePlayerStateChange | (state) => void | - | Called on player state changes | | onNativePlayerError | (error) => void | - | Called on player errors |

Troubleshooting

WebView not rendering

Make sure react-native-webview is properly installed and linked:

npm install react-native-webview
cd ios && pod install

Video not playing on iOS

Ensure your app has the proper permissions:

<!-- ios/YourApp/Info.plist -->
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

Channel not loading

  1. Verify the channel URL is accessible
  2. Check network connectivity
  3. Ensure CORS is properly configured on your publication

Authentication errors

  • Verify JWT/RPC tokens are valid and not expired
  • Check that tokens have the correct permissions

Support

License

Proprietary software © Blue Billywig. Use is permitted only under a current written agreement with Blue Billywig covering the use of Blue Billywig channels. See LICENSE.md for details.