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 installWe 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-webrtcPermissions
The package ships the right permission strings in its AndroidManifest.xml and Info.plist,
but you still need to request them at runtime:
NSCameraUsageDescription;
NSMicrophoneUsageDescription;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.
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.
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-webrtcupstream — H.264 / VP9 / VP8 work today) - Screen-share on Android (iOS Replaykit-based screen-share works as of v0.2)