LevelChatLevelChatDocs
React Native

SDKs

React Native SDK

react-native-webrtc wrapper.

The React Native SDK wraps react-native-webrtc with an API that mirrors @levelchat/web-react, so you can share screens and component-level code with a web app via react-native-web.

Install

~
npm install @levelchat/react-native react-native-webrtc
# pnpm add @levelchat/react-native react-native-webrtc
# yarn add @levelchat/react-native react-native-webrtc
cd ios && pod install

We follow semver; pin major. Latest version: 0.2.x.

You also need to install Expo's config plugin if you're on a managed Expo workflow:

~
npx expo install @config-plugins/react-native-webrtc

Permissions

The package ships the right permission strings in its AndroidManifest.xml and Info.plist, but you still need to request them at runtime:

TypeScriptios — Info.plist (added by the package)
NSCameraUsageDescription;
NSMicrophoneUsageDescription;
TypeScriptandroid
import { PermissionsAndroid } from 'react-native';

await PermissionsAndroid.requestMultiple([
  PermissionsAndroid.PERMISSIONS.CAMERA,
  PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
]);

UI Kit

The package exports brand-pinned RN components matching the React web kit's component set.

TypeScript (TSX)
import {
  VideoTile,
  ControlBar,
  PreJoin,
  SpeakerStage,
  Chat,
  PollPanel,
  QAPanel,
  Whiteboard,
} from '@levelchat/react-native';

<VideoTile
  name="Mert"
  quality="good"
  micOn
  isSpeaking={speaker === myId}
  style={{ width: 240, height: 180 }}
/>;

The RN kit ships VideoTile, ControlBar, PreJoin, SpeakerStage, Chat, PollPanel, QAPanel, and Whiteboard — every component the React web kit exports has an RN counterpart with the same name and brand tokens.

Connect

The same concepts as the Web SDK — LevelChat, Room, tracks, events — adapted to React Native via react-native-webrtc for media and AppState for lifecycle. Naming follows RN idiom: events are camelCase ('participantJoined', not Web's kebab-case 'participant-joined') and you publish with room.publishMicrophone() (Web spells it publishMic). See the SDK parity matrix for the full per-platform naming table.

The RN SDK ships both hooks AND components. Hooks (useRoomState, useLocalParticipant, useRemoteParticipants, useParticipantViews, useParticipantTracks) live in @levelchat/react-native. The UI kit (VideoTile, ControlBar, PreJoin, SpeakerStage, Chat, PollPanel, QAPanel, Whiteboard) ships from the same package — see the UI Kit section above.

The lifetime is explicit: you call client.joinRoom(...) (or client.joinLive(...)) and pass the returned Room to the hooks. This avoids the auto-reconnect-on-render footgun.

TypeScript (TSX)App.tsx
import { useEffect, useState } from 'react';
import { Button, View } from 'react-native';
import {
  LevelChat,
  type Room,
  useLocalParticipant,
  useRemoteParticipants,
  useRoomState,
} from '@levelchat/react-native';

export default function App() {
  const [room, setRoom] = useState<Room | null>(null);

  useEffect(() => {
    let active = true;
    (async () => {
      const roomId = 'r_demo';
      // Mint a token on YOUR server — see /quickstart.
      const tokRes = await fetch(`https://your-app.com/api/lc-token?room=${roomId}`, {
        method: 'POST',
      });
      if (!tokRes.ok) throw new Error(`token mint failed: HTTP ${tokRes.status}`);
      const { token } = (await tokRes.json()) as { token: string };
      const client = new LevelChat({ logLevel: 'info' });
      // `joinRoom` takes the room id first, then the options. Or use
      // `client.joinLive({ token, role })` to decode the room id from the JWT.
      const r = await client.joinRoom(roomId, { token });
      if (!active) {
        await r.close();
        return;
      }
      setRoom(r);
    })();
    return () => {
      active = false;
      room?.close();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <View>
      <Tiles room={room} />
      <Controls room={room} />
    </View>
  );
}

function Tiles({ room }: { room: Room | null }) {
  const { connected } = useRoomState(room);
  const me = useLocalParticipant(room);
  const remotes = useRemoteParticipants(room);
  if (!connected) return null;
  return (
    <View>
      {/* Render `me?.tracks` + each remote's `tracks` with your own tile component. */}
      {/* `nativeTrack` on a TrackView is the underlying `react-native-webrtc` MediaStreamTrack. */}
    </View>
  );
}

function Controls({ room }: { room: Room | null }) {
  // Publishing is a `Room` method — `publishCamera` / `publishMicrophone` /
  // `publishScreen`, and `unpublish('camera' | 'microphone' | 'screen')`.
  return (
    <>
      <Button title="Share camera" onPress={() => room?.publishCamera()} disabled={!room} />
      <Button title="Share mic" onPress={() => room?.publishMicrophone()} disabled={!room} />
    </>
  );
}

Cross-platform code sharing

The web React UI kit (@levelchat/web-react) and the RN UI kit ship the same brand-pinned primitives by the same component names — drop the same <VideoTile /> and <Chat /> JSX into either tree and they look identical. RN hooks take an explicit Room argument (no auto-reconnect-on-render footgun); the web UI kit reads from <LevelChatProvider> context.

Background mode (calls when the app is backgrounded)

iOS background-VoIP is opt-in. Add voip to UIBackgroundModes in Info.plist if you want calls to keep flowing when the user backgrounds your app. CallKit integration ships in @levelchat/react-native/callkit (separate entry).

What's not yet shipped

  • Hardware AV1 encode (waiting on react-native-webrtc upstream — H.264 / VP9 / VP8 work today)
  • Screen-share on Android (iOS Replaykit-based screen-share works as of v0.2)
React Native SDK — LevelChat Docs