Migration Guide - v1.x.x → v2.0.0
This guide covers what you need to change when upgrading the React Native SDK from v1.0.1 to v2.0.0.
v2.0.0 changes the install and the native setup. MeetingProvider is now RoomProvider, useMeeting() is now useRoom(), and many fields, methods and callbacks are renamed. Some APIs are gone, and several callbacks, defaults and error codes behave differently.
Update the dependency first:
npm install @videosdk.live/react-native-sdk@2.0.0
v2.0.0 raises the platform requirements:
- Android: set
minSdkVersionto 24 (section 2). Run the app on an arm64 (arm64-v8a) device or emulator image. - Android permissions: declare
INTERNET,ACCESS_NETWORK_STATE,CAMERA,RECORD_AUDIO,MODIFY_AUDIO_SETTINGS,POST_NOTIFICATIONSand the Bluetooth permissions in your app'sAndroidManifest.xml. The SDK does not add them. - iOS: set your deployment target to 18.0.
- React Native: 0.74 or later, with React 18.0 or later.
Sections 1–4 change your project setup. The app does not build or start until they are done. Sections 5–10 mostly show up as TypeScript or native compile errors, although several of them fail silently in plain JavaScript. Sections 11–15 can compile without a single error and still change what your app does at runtime, so test each of those paths on a device.
1. Remove register() and the WebRTC packages
register() is gone. Delete every import and call of it. Most apps call it in index.js, or in the file that calls registerRootComponent() if they use Expo. Some also call it at the top of App.js. A call you leave behind throws a TypeError when the app starts.
Before
import { register } from "@videosdk.live/react-native-sdk";
import { AppRegistry } from "react-native";
import { name as appName } from "./app.json";
import App from "./src/App";
register();
AppRegistry.registerComponent(appName, () => App);
After
import { AppRegistry } from "react-native";
import { name as appName } from "./app.json";
import App from "./src/App";
AppRegistry.registerComponent(appName, () => App);
Key points:
- Uninstall
@videosdk.live/react-native-incallmanager(npm uninstall @videosdk.live/react-native-incallmanager). v2.0.0 does not use it. Do the same for@videosdk.live/react-native-webrtcif you added it to your ownpackage.json. - Uninstall
@videosdk.live/react-native-media-effectsas well. It needsreact-native-webrtc, so it breaks the native build (section 16). - v2.0.0 does not install
navigator.mediaDevicesor any other WebRTC globals. UsegetCameras(),getMics()and the permission methods ofuseMediaDevice(), andcreateCameraVideoTrack()/createMicrophoneAudioTrack()to open the camera and microphone. On Android, ask for camera and microphone access yourself before joining (section 14). - Importing v1.x.x also installed global
TextEncoder,TextDecoder,atobandbtoapolyfills, whether or not you calledregister(). v2.0.0 installs no globals, so add your own polyfill if your code relies on them.
2. Android: remove the react-native-webrtc wiring and raise minSdkVersion
Delete the react-native-webrtc lines that the v1.x.x setup added to your Android project. They break the Gradle build. Then raise minSdkVersion to 24.
Before
include ':rnwebrtc'
project(':rnwebrtc').projectDir = new File(rootProject.projectDir, '../node_modules/@videosdk.live/react-native-webrtc/android')
dependencies {
implementation project(':rnwebrtc')
}
import live.videosdk.rnwebrtc.WebRTCModulePackage
add(WebRTCModulePackage())
import live.videosdk.rnwebrtc.WebRTCModulePackage;
packages.add(new WebRTCModulePackage());
android.enableDexingArtifactTransform.desugaring=false
<service android:name="live.videosdk.rnwebrtc.MediaProjectionService" android:foregroundServiceType="mediaProjection" />
<service android:name="live.videosdk.rnwebrtc.MediaProjectionServiceTask" />
-keep class org.webrtc.** { *; }
After
buildscript {
ext {
minSdkVersion = 24
}
}
-keep class videosdk.org.** { *; }
-dontwarn videosdk.org.**
Key points:
- Set
minSdkVersionto 24 or later. - Delete everything in the Before block, including every
live.videosdk.rnwebrtcentry inAndroidManifest.xml. Those classes no longer exist. - If you minify your release build, replace the
org.webrtcrule with the twovideosdk.orgrules shown. The old rule no longer matches anything, so R8 removes classes the SDK needs. Keep both lines: the-keeprule alone fails the R8 step with a missing-class error. - The SDK now declares its own screen-share foreground service and the
FOREGROUND_SERVICEandFOREGROUND_SERVICE_MEDIA_PROJECTIONpermissions, so screen share needs no manifest entries from you. - The SDK's React Native package is now
live.videosdk.rncore.VideoSDKPackage, and autolinking adds it. If you register packages by hand, it replaceslive.videosdk.rnpubsub.NativePubsubPackage. - If you set
VideoSDKPubsub_kotlinVersioningradle.properties, rename it toVideoSDKReactNative_kotlinVersion.
3. iOS: add the VideoSDKRTC pod
Add the VideoSDKRTC pod from the podspec inside the package, remove the react-native-webrtc pod, and raise the platform to iOS 18.0.
Before
platform :ios, '12.0'
pod 'react-native-webrtc', :path => '../node_modules/@videosdk.live/react-native-webrtc'
After
platform :ios, '18.0'
target 'YourApp' do
# ...
pod 'VideoSDKRTC', :podspec => '../node_modules/@videosdk.live/react-native-sdk/ios/VideoSDKRTC.podspec'
end
Key points:
- Add the line inside your app's
targetblock, runpod install, and set the iOS Deployment Target of your app target to 18.0 in Xcode as well. - Without the
VideoSDKRTCline, CocoaPods picks an olderVideoSDKRTCfrom the public CocoaPods repository, which v2.0.0 does not work with. - The SDK's own pod is now
VideoSDKReactNativeand is autolinked. It replacesVideoSDKPubsub. - Upgrading from a 2.0.0 beta: run
pod update VideoSDKRTConce inios/.pod installkeeps theVideoSDKRTCversion locked inPodfile.lock.
4. Expo: upgrade the config plugin
Version 1 of @videosdk.live/expo-config-plugin adds react-native-webrtc to the native projects it generates, which breaks a v2.0.0 build. Version 2 of the plugin is built for v2.0.0, so upgrade it.
npx expo install @videosdk.live/expo-config-plugin@2.0.0
Your plugin entry in app.json stays the same.
Key points:
- The plugin adds the
VideoSDKRTCPodfile line and the camera and microphone usage strings for you. Runnpx expo prebuild --cleanso thereact-native-webrtclines that version 1 wrote are removed. - It does not add Android permissions. Keep them under
expo.android.permissions. - Uninstall
@videosdk.live/react-native-incallmanager. You still need a development build. - Remove the
event-target-shimworkaround frommetro.config.js. v2.0.0 does not need it.
5. Meeting names become Room names
The provider, the hook and most fields, methods and callbacks are renamed. No old name is kept as an alias. In TypeScript every old name is a compile error. In plain JavaScript an old import is undefined, an old config key or callback is ignored, and an old method is not a function, so search your code for each name in the tables below.
Before
import { MeetingProvider, useMeeting } from "@videosdk.live/react-native-sdk";
export default function App() {
return (
<MeetingProvider
config={{
meetingId: "meeting-id",
name: "Test User",
micEnabled: true,
webcamEnabled: true,
debugMode: false,
}}
token="your-token"
>
<MeetingView />
</MeetingProvider>
);
}
function MeetingView() {
const { join, leave, toggleMic, toggleWebcam, localMicOn, localWebcamOn } =
useMeeting({
onMeetingJoined: () => console.log("joined"),
onMeetingLeft: () => console.log("left"),
});
return (
<View>
<Button title="Join" onPress={() => join()} />
<Button title={localMicOn ? "Mute" : "Unmute"} onPress={() => toggleMic()} />
<Button
title={localWebcamOn ? "Camera off" : "Camera on"}
onPress={() => toggleWebcam()}
/>
<Button title="Leave" onPress={() => leave()} />
</View>
);
}
After
import { RoomProvider, useRoom } from "@videosdk.live/react-native-sdk";
export default function App() {
return (
<RoomProvider
config={{
roomId: "room-id",
participantName: "Test User",
micEnabled: true,
camEnabled: true,
}}
token="your-token"
>
<RoomView />
</RoomProvider>
);
}
function RoomView() {
const { join, leave, toggleMic, toggleCam, isMicEnabled, isCamEnabled } =
useRoom({
onRoomJoined: () => console.log("joined"),
onRoomLeft: () => console.log("left"),
});
return (
<View>
<Button title="Join" onPress={() => join()} />
<Button title={isMicEnabled ? "Mute" : "Unmute"} onPress={() => toggleMic()} />
<Button
title={isCamEnabled ? "Camera off" : "Camera on"}
onPress={() => toggleCam()}
/>
<Button title="Leave" onPress={() => leave()} />
</View>
);
}
Provider and hooks
| v1.0.1 | v2.0.0 |
|---|---|
MeetingProvider | RoomProvider |
MeetingConsumer | RoomConsumer |
useMeeting() | useRoom() |
RoomProvider takes config, token, joinWithoutUserInteraction and children. The deviceInfo and reinitialiseMeetingOnConfigChange props are gone.
RoomProvider config
| v1.0.1 | v2.0.0 |
|---|---|
meetingId | roomId |
name (required) | participantName, optional. The SDK picks a random name when you leave it out. |
webcamEnabled (required) | camEnabled, optional, default true |
micEnabled (required) | micEnabled, optional, default true |
customCameraVideoTrack | customCameraTrack |
customMicrophoneAudioTrack | customMicrophoneTrack |
debugMode (required) | Removed |
notification | Removed. The SDK never showed it. |
| — | enableBatching, default true: groups PubSub messages into batches |
signalingBaseUrl, preferredProtocol and codecSwitchEnabled keep their names. Only roomId is required. Leave out any other option to get the SDK default: a random 8-character participantId, a random 6-character participantName, multiStream true, mode "SEND_AND_RECV", maxResolution "hd", preferredProtocol "UDP_OVER_TCP", autoConsume true and dynaCast false.
useRoom() fields
v1.0.1 (useMeeting()) | v2.0.0 (useRoom()) |
|---|---|
meeting | room |
meetingId | roomId |
localMicOn | isMicEnabled |
localWebcamOn | isCamEnabled |
localScreenShareOn | isScreenSharing |
livestreamState | liveStreamState |
hlsUrls.downstreamUrl | Removed. hlsUrls.playbackHlsUrl and hlsUrls.livestreamUrl stay. |
mainParticipant | Removed |
characters, connections | Removed (section 16) |
| — | e2eeEnabled, transcriptionState, translationState, selectedCameraDevice, selectedMicrophoneDevice |
presenterId and activeSpeakerId keep their names. getActivePresenterId() and getActiveSpeakerId() return the same values.
useRoom() methods
v1.0.1 (useMeeting()) | v2.0.0 (useRoom()) |
|---|---|
enableWebcam(customTrack) | enableCam(customCameraTrack) |
disableWebcam() | disableCam() |
changeWebcam(deviceId) | changeCam(deviceIdOrTrack) |
unmuteMic(customAudioTrack) | unmuteMic(customMicrophoneTrack) |
toggleWebcam(customTrack) | toggleCam(customCameraTrack). toggleMic() and toggleScreenShare() keep their names. |
startHls(config), stopHls() | startHLS(config, transcription), stopHLS() |
switchTo({ meetingId, token }) | switchTo(roomId, token). token is optional. |
requestMediaRelay({ destinationMeetingId, token, kinds }) | requestMediaRelay({ destinationRoomId, token, kinds }). kinds takes "audio", "video" and "share"; leave it out to relay every kind. |
stopMediaRelay(destinationMeetingId) | stopMediaRelay(destinationRoomId) |
getMics(), getWebcams() | getMics() and getCameras() from useMediaDevice() (section 14) |
| — | send() (section 9), setWebcamQuality(), setVideoProcessor(), removeVideoProcessor(), setAudioDevice() |
changeCam() and changeMic() with no argument move to the next camera or microphone in the device list, and wrap around after the last one.
Room callbacks (useRoom({ … }) and RoomConsumer)
| v1.0.1 | v2.0.0 |
|---|---|
onMeetingJoined() | onRoomJoined() |
onMeetingLeft(reason) | onRoomLeft(reason) |
onMeetingStateChanged({ state }) | onRoomStateChanged({ roomState }) |
onRecordingStateChanged({ status }) | onRecordingStateChanged({ state }) |
onLivestreamStateChanged({ status }) | onLivestreamStateChanged({ state }) |
onLiveStreamStarted, onLiveStreamStopped | onLivestreamStarted, onLivestreamStopped |
onHlsStateChanged({ status, playbackHlsUrl, livestreamUrl, downstreamUrl }) | onHlsStateChanged({ state, hlsUrl: { playbackHlsUrl, livestreamUrl } }) |
onHlsStarted, onHlsStopped | Removed. Use onHlsStateChanged. |
onMainParticipantChanged | Removed |
onSwitchMeeting, onConnectionOpen, onConnetionClose, onAudioInputSilence | None of them is called on React Native. Remove them. |
onWebcamRequested, onMicRequested with { participantId, accept, reject } | Unchanged |
onPinStateChanged({ participantId, state, pinnedBy }) | Unchanged |
onMediaRelayStarted({ meetingId }) | onMediaRelayStarted({ roomId }) |
onMediaRelayStopped({ meetingId, reason }) | onMediaRelayStopped({ roomId, reason }) |
onMediaRelayError({ meetingId, error }) | onMediaRelayError({ roomId, error }) |
onMediaRelayRequestResponse({ decision, decidedBy, meetingId }) | onMediaRelayRequestResponse({ decision, decidedBy, roomId }), where decision is upper case: "ACCEPTED" or "REJECTED" (Constants.RelayDecision) |
onMediaRelayRequestReceived({ participantId, meetingId, displayName, accept, reject }) | onMediaRelayRequestReceived({ participantId, roomId, displayName, accept, reject }) |
onCodecChanged({ …, causedBy }), where causedBy is an id | onCodecChanged({ …, unsupportedRemoteCodec, causedBy }), where causedBy is the Participant |
| — | onExternalCallRinging, onExternalCallAnswered, onExternalCallRejected and onExternalCallHangup, each with { callType } |
onEntryRequested still gives you allow() and deny(), which now return a Promise. You can also answer with respondEntry(participantId, decision), where decision is "allowed" or "denied" (Constants.EntryDecision). onEntryResponded still receives two arguments, (participantId, decision).
Transcription, translation and the whiteboard stay on their own hooks: useTranscription(), useTranslation() and useWhiteboard().
useTranscription({ onTranscriptionStateChanged }) receives { state } instead of { status }.
Participants
These changes apply to useParticipant(), useAgentParticipant() and every participant object, such as room.participants, room.localParticipant and the participant that onParticipantJoined receives.
| v1.0.1 | v2.0.0 |
|---|---|
enableWebcam(), disableWebcam() | enableCam(), disableCam() |
isMainParticipant | Removed |
useRealtimeStore({ onValueChanged: ({ value, updatedBy }) => … }) | onValueChanged: (value, updatedBy) => …, where updatedBy is the Participant who wrote the value, or null once they have left |
| — | isAgent() on useParticipant(); isAgent(), getAgentId() and getAgentState() on useAgentParticipant(), next to agentId and agentState |
useParticipant().videoQuality stays undefined on React Native, and onVideoQualityChanged is not called.
Constants
| v1.0.1 | v2.0.0 |
|---|---|
Constants.errors | Constants.VideoSDKError |
Constants.modes | Constants.Mode. CONFERENCE and VIEWER are gone. |
Constants.AgentState values "idle", "listening", … | "IDLE", "LISTENING", …, the values getAgentState() and onAgentStateChanged report |
Constants.errors.*_MEETING_* names | *_ROOM_* names, with the same codes (section 15) |
| — | RoomState, FacingMode, MaxResolution, PinType, VideoQuality, MediaType, StreamKind, CaptureFlash, EncodingLayer, CustomCameraTrackConfig, CustomMicrophoneTrackConfig, VideoEffectsConfig, EntryDecision, RelayDecision, CallType, TranslationType, ConfigQuality and ConfigMode |
The new enums hold the same strings the SDK already accepted, so a string literal you pass today keeps working.
Key points:
RTCView'smirrorprop is nowsetMirror.
6. facingMode is "front" or "back", and some camera presets changed
This compiles, but behaves differently. facingMode now takes and reports "front" and "back". An old value is not rejected: "user" and "environment" both open the front camera, so a back-camera flow silently shows the wrong camera.
| v1.0.1 | v2.0.0 |
|---|---|
"user" | "front" |
"environment" | "back" |
Before
import { createCameraVideoTrack, useMeeting } from "@videosdk.live/react-native-sdk";
function BackCameraButton() {
const { getWebcams } = useMeeting();
const openBackCamera = async () => {
const webcams = await getWebcams();
const back = webcams.find((webcam) => webcam.facingMode === "environment");
return createCameraVideoTrack({
cameraId: back?.deviceId,
facingMode: "environment",
encoderConfig: "h144p_w192p",
});
};
return <Button title="Back camera" onPress={openBackCamera} />;
}
After
import {
createCameraVideoTrack,
useMediaDevice,
} from "@videosdk.live/react-native-sdk";
function BackCameraButton() {
const { getCameras } = useMediaDevice();
const openBackCamera = async () => {
const cameras = await getCameras();
const back = cameras.find((camera) => camera.facingMode === "back");
return createCameraVideoTrack({
cameraId: back?.deviceId,
facingMode: "back",
encoderConfig: "h180p_w240p",
});
};
return <Button title="Back camera" onPress={openBackCamera} />;
}
Key points:
getCameras()reports"front"or"back". v1.x.x reported"front"or"environment", so only a check for"environment"needs changing.videoConfig.facingModeinrunPreCallTest()takes"front"/"back"too.Constants.FacingModeholds both values.h144p_w192pis no longer a camera preset.h120p_w160pandh180p_w240pare the nearest 4:3 sizes.- New camera presets:
h180p_w320p,h540p_w960p,h1080p_w1920p,h120p_w160p,h180p_w240pandh360p_w480p.Constants.CustomCameraTrackConfiglists every preset.
7. Screen share takes a track
enableScreenShare() and toggleScreenShare() now take one optional argument: a track from createScreenShareVideoTrack(). The encoder preset and system audio are options of that track. The v1.x.x object argument is not an error in JavaScript, but v2.0.0 ignores it and shares with the default settings. On Android those settings include system audio, even for { enableAudio: false }.
Before
import { useMeeting } from "@videosdk.live/react-native-sdk";
function ShareButton() {
const { enableScreenShare } = useMeeting();
const startShare = async () => {
await enableScreenShare({ enableAudio: true });
};
return <Button title="Share screen" onPress={startShare} />;
}
After
import {
createScreenShareVideoTrack,
useRoom,
} from "@videosdk.live/react-native-sdk";
function ShareButton() {
const { enableScreenShare } = useRoom();
const startShare = async () => {
const track = await createScreenShareVideoTrack({
encoderConfig: "h720p_15fps",
withAudio: true, // Android only
});
await enableScreenShare(track);
};
return <Button title="Share screen" onPress={startShare} />;
}
Key points:
withAudiois a boolean that defaults tofalse. It works on Android only, where system audio is mixed into your microphone track, so muting the mic mutes both. On iOS it has no effect. If you passedwithAudio: "enable"or"disable"in v1.x.x, passtrueorfalseinstead.encoderConfigtakesh360p_30fps,h480p_15fps,h480p_30fps,h720p_5fps,h720p_15fps(the default),h720p_30fps,h1080p_15fpsorh1080p_30fps.multiStreamdefaults tofalse.- On Android, the screen-capture consent dialog appears when you create the track, not when you call
enableScreenShare(). - On Android,
enableScreenShare()with no track shares system audio, mixed into your microphone track. That includesonPress={enableScreenShare}. To share without system audio, pass a track fromcreateScreenShareVideoTrack(), wherewithAudiodefaults tofalse. - Sharing has started when the participant's
sharestream firesonStreamEnabled.
8. E2EE: useKeyProvider() is replaced by setKeyProvider()
useKeyProvider() and the keyProvider prop of the provider are gone. Create a BaseKeyProvider, set its key, and register it with setKeyProvider() before you join. The key applies to every room in your app.
Before
import { MeetingProvider, useKeyProvider } from "@videosdk.live/react-native-sdk";
export default function App() {
const keyProvider = useKeyProvider({ discardFrameWhenCryptorNotReady: true });
keyProvider?.setSharedKey("<YOUR-SECURE-KEY>");
return (
<MeetingProvider
config={{
meetingId: "meeting-id",
name: "Test User",
micEnabled: true,
webcamEnabled: true,
debugMode: true,
}}
token="your-token"
keyProvider={keyProvider || null}
>
<MeetingView />
</MeetingProvider>
);
}
After
import {
BaseKeyProvider,
RoomProvider,
setKeyProvider,
} from "@videosdk.live/react-native-sdk";
// Register the key before the room joins
async function enableEncryption() {
const provider = new BaseKeyProvider();
provider.setSharedKey("<YOUR-SECURE-KEY>");
await setKeyProvider(provider);
}
export default function App() {
return (
<RoomProvider
config={{
roomId: "room-id",
participantName: "Test User",
}}
token="your-token"
>
<RoomView />
</RoomProvider>
);
}
Key points:
- Call
setKeyProvider()beforejoin(), or before you renderRoomProviderif you usejoinWithoutUserInteraction. A provider set after the room joins does not encrypt that room. setSharedKey()stores the key and returns nothing, so do notawaitit. It throwsERROR_INVALID_PARAMETERfor an empty key.new BaseKeyProvider()takes no arguments.discardFrameWhenCryptorNotReadyis gone, so remove it.removeKeyProvider()turns E2EE off for the next room.setKeyProvider(null)rejects withERROR_INVALID_PARAMETER(3073), so callremoveKeyProvider()instead.getKeyProvider()returns the provider you registered, ornull.setParticipantKey(participantId, key)sets or replaces the key for one participant, also during a room. It is used only when no shared key is set. It rejects withERROR_INVALID_PARAMETERuntil you have calledsetKeyProvider(). To remove a key, passnullas the key, or callremoveKey(participantId)on the registered provider.- To check whether media is encrypted, read
e2eeEnabledfromuseRoom(). onE2EEStateChangedofuseParticipant()reportsEncryptionSuccesswhen encryption starts. Decryption failures are not reported.removeKey(participantId)stops decrypting that participant's media at once.
9. onChatMessage is replaced by onDataReceived
v1.x.x declared onChatMessage but never called it. v2.0.0 replaces it with onDataReceived, which fires for every message another participant sends with send(). send() is new.
Before
import { useMeeting } from "@videosdk.live/react-native-sdk";
function ChatListener() {
useMeeting({
onChatMessage: ({ senderId, message }) => {
console.log("message from", senderId, message);
},
});
return null;
}
After
import { useRoom } from "@videosdk.live/react-native-sdk";
function DataChannel() {
const { send } = useRoom({
onDataReceived: ({ senderId, text, data, isBinary, reliable, timestamp }) => {
console.log("message from", senderId, isBinary ? data : text);
},
});
const sendHello = async () => {
try {
await send("hello");
} catch (error) {
console.warn("Not sent:", error.message);
}
};
return <Button title="Send" onPress={sendHello} />;
}
Key points:
onDataReceivedreceives{ senderId, text, data, isBinary, reliable, timestamp }. A text message setstext. A binary message setsdata, aUint8Array.timestampis a number.send(message, reliable)accepts a string, anArrayBufferor a typed array, but not aBlob.reliableis a boolean that defaults totrue. Passfalseto send unreliably:send("hello", false).send()resolves once the message is sent, not when it is delivered. It rejects when the message did not go out.
10. Video processors are set on the room and register in a new place
Apply a processor with setVideoProcessor() and remove it with removeVideoProcessor(), both from useRoom(). They return a Promise and reject until a room exists, so call them after join(). On Android, native processors no longer register with react-native-webrtc's ProcessorProvider.
Before
import { VideoProcessor } from "@videosdk.live/react-native-webrtc";
function setEffect(enabled) {
if (enabled) {
VideoProcessor.applyVideoProcessor("ProcessorName");
} else {
VideoProcessor.removeVideoProcessor();
}
}
import live.videosdk.rnwebrtc.videoEffects.ProcessorProvider
// YourOwnBackgroundProcessor implements VideoFrameProcessorFactoryInterface
ProcessorProvider.addProcessor("ProcessorName", YourOwnBackgroundProcessor())
After
import { useRoom } from "@videosdk.live/react-native-sdk";
function EffectButton() {
const { setVideoProcessor, removeVideoProcessor } = useRoom();
// Call after join(); before that both reject
const setEffect = async (enabled) => {
try {
if (enabled) {
await setVideoProcessor("ProcessorName");
} else {
await removeVideoProcessor();
}
} catch (error) {
console.warn("Video processor:", error.code, error.message);
}
};
return <Button title="Effect on" onPress={() => setEffect(true)} />;
}
import live.videosdk.rncore.VideoProcessorRegistry
// YourOwnProcessor implements live.videosdk.rtc.android.listeners.VideoFrameProcessor
VideoProcessorRegistry.register("ProcessorName", YourOwnProcessor())
Key points:
- v2.0.0 does not install
@videosdk.live/react-native-webrtc, so an import ofVideoProcessorfrom it no longer resolves. - Register each Android processor when your app starts. It now implements
live.videosdk.rtc.android.listeners.VideoFrameProcessordirectly, with no factory. - Its one method is
onFrameCaptured(videoFrame: VideoFrame): VideoFrame, whereVideoFrameisvideosdk.org.webrtc.VideoFrameand arrives as I420. Change everyorg.webrtcimport in a v1.x.x processor tovideosdk.org.webrtc. There is noSurfaceTextureHelperargument, and the SDK releases both frames, so do not release them yourself. - On iOS, delete processor code that imports
react_native_webrtcor callsProcessorProvider. Both come fromreact-native-webrtc, so the iOS build fails until that code is gone. Write the processor as a class that conforms toVideoSDKVideoProcessorfromVideoSDKRTC, and register it withVideoProcessorRegistry.shared.register(name:processor:)fromVideoSDKReactNative. See Video Processor iOS Setup. - Applying a name that has no registered processor rejects with
ERROR_INVALID_PARAMETER(3073). - Background blur and virtual backgrounds from
@videosdk.live/react-native-media-effectsdo not work on v2.0.0. See section 16.
11. RoomProvider leaves the room when it unmounts
This compiles, but behaves differently. RoomProvider leaves the room when it unmounts. Keep it mounted for as long as the call lasts. If it sits inside a screen that unmounts when the user navigates away, the call ends there.
After
import { RoomProvider } from "@videosdk.live/react-native-sdk";
// Render RoomProvider above every screen of the call
export default function CallScreen({ roomId, token }) {
return (
<RoomProvider config={{ roomId, participantName: "Test User" }} token={token}>
<CallNavigator />
</RoomProvider>
);
}
Key points:
- To end the call from a button, call
leave()fromuseRoom().
12. Streams and tracks are no longer WebRTC objects
v1.x.x gave you react-native-webrtc's MediaStream and live MediaStreamTrack objects. In v2.0.0, a participant's stream (webcamStream, micStream, the values in participant.streams) is an RTCStream. You still render it by wrapping its track in the SDK's MediaStream, so that code does not change. A track you create with createCameraVideoTrack(), createMicrophoneAudioTrack() or createScreenShareVideoTrack() is a CustomStreamTrack, which renders with its own toURL().
Before
import {
createCameraVideoTrack,
MediaStream,
RTCView,
useParticipant,
} from "@videosdk.live/react-native-sdk";
function ParticipantView({ participantId }) {
const { webcamStream, webcamOn } = useParticipant(participantId);
return webcamOn && webcamStream ? (
<RTCView streamURL={new MediaStream([webcamStream.track]).toURL()} />
) : null;
}
const stream = await createCameraVideoTrack({ facingMode: "user" });
const preview = new MediaStream([]);
preview.addTrack(stream.getVideoTracks()[0]);
// objectFit defaulted to "contain"
<RTCView streamURL={preview.toURL()} />;
// Later, release the camera
stream.release();
After
import {
createCameraVideoTrack,
MediaStream,
RTCView,
useParticipant,
} from "@videosdk.live/react-native-sdk";
function ParticipantView({ participantId }) {
const { webcamStream, webcamOn } = useParticipant(participantId);
return webcamOn && webcamStream ? (
<RTCView
streamURL={new MediaStream([webcamStream.track]).toURL()}
objectFit="contain"
/>
) : null;
}
const track = await createCameraVideoTrack({ facingMode: "front" });
// objectFit now defaults to "cover"
<RTCView streamURL={track.toURL()} objectFit="contain" />;
// Later, free the camera if this track was never published
await track.stop();
Key points:
- An
RTCStreamhasid,kind,participantId,codec,track,paused,pause()andresume(). It has notoURL(): render it withnew MediaStream([stream.track]).toURL(). - A
CustomStreamTrackhastoURL(),getTracks(),getVideoTracks(),getAudioTracks(),stop(),isEndedandonEnded.stop()replacesrelease()and returns a Promise. It does nothing once a room has published the track. SetonEndedto a function to learn when the track ends. It is called once: with the error when the device fails, or withnullwhen the track was stopped, replaced or the room was left. Set after the track ended, it is called at once. createCameraVideoTrack()andcreateScreenShareVideoTrack()no longer takeoptimizationMode.bitrateModetakes"HIGH_QUALITY","BALANCED"or"BANDWIDTH_OPTIMIZED".RTCViewrenders only URLs built from SDK streams and tracks. ItsobjectFitnow defaults to"cover"instead of"contain", so passobjectFit="contain"to keep the v1.x.x look. Itsmirrorprop is nowsetMirror.- Pass only tracks created by the SDK's track functions to
RoomProvider,enableCam(),unmuteMic(),changeCam()andchangeMic(). - A stopped or released track is refused with
ERROR_INVALID_CUSTOM_VIDEO_TRACK(3029) for video orERROR_INVALID_CUSTOM_AUDIO_TRACK(3030) for audio, byenableCam(),changeCam(),unmuteMic(),changeMic()andenableScreenShare(). Create a new track instead.
13. iOS screen share: enableScreenShare() opens the broadcast picker
This compiles, but behaves differently. In v1.x.x you showed the iOS broadcast picker from your own native module (VideosdkRPK) and called enableScreenShare() once the broadcast started. In v2.0.0, enableScreenShare() shows the picker itself, and capture starts when the user taps Start Broadcast. Call it straight from your button.
Before
import { useMeeting } from "@videosdk.live/react-native-sdk";
import VideosdkRPK from "./VideosdkRPK";
function ShareButton() {
const { enableScreenShare, disableScreenShare } = useMeeting();
useEffect(() => {
VideosdkRPK.addListener("onScreenShare", (event) => {
if (event === "START_BROADCAST") enableScreenShare();
if (event === "STOP_BROADCAST") disableScreenShare();
});
return () => VideosdkRPK.removeSubscription("onScreenShare");
}, []);
return (
<Button title="Share screen" onPress={() => VideosdkRPK.startBroadcast()} />
);
}
After
import {
createScreenShareVideoTrack,
useRoom,
} from "@videosdk.live/react-native-sdk";
function ShareButton() {
const { enableScreenShare } = useRoom();
const startShare = async () => {
const track = await createScreenShareVideoTrack();
// Shows the iOS broadcast sheet; capture starts on "Start Broadcast"
await enableScreenShare(track);
};
return <Button title="Share screen" onPress={startShare} />;
}
Key points:
- You no longer need the
VideosdkRPKnative module or itsonScreenSharelistener. - Keep your Broadcast Upload Extension and the
RTCScreenSharingExtensionandRTCAppGroupIdentifierkeys in your app'sInfo.plist. v2.0.0 still needs both. - With Expo, keep the
@videosdk.live/expo-ios-screen-shareplugin inapp.json. It creates the extension and these keys. Only stop using itsVideosdkRPKmodule.
14. Ask for camera and microphone access on Android; device functions moved
This compiles, but behaves differently. On Android, v2.0.0 no longer asks the user for camera and microphone access. In v1.x.x the prompts appeared when the room first turned the camera or microphone on. Now the camera and microphone do not start until your app has access, so call requestPermission() before you join. On iOS, createCameraVideoTrack() and createMicrophoneAudioTrack() do not ask either: call requestPermission() before you create a track, or the factory rejects until access is granted.
useMediaDevice() keeps the device lists and permissions. getMics() replaces getAudioDeviceList(), and audio output is switched on the room. requestPermission() resolves with false values when the user refuses, instead of throwing.
Before
import { Constants, useMediaDevice } from "@videosdk.live/react-native-sdk";
function DeviceSetup() {
const [devices, setDevices] = useState([]);
const { requestPermission } = useMediaDevice({
onAudioDeviceChanged: ({ selectedAudioDevice, availableDevices }) => {
setDevices(availableDevices);
},
});
const askForAccess = async () => {
try {
await requestPermission(Constants.permission.AUDIO_AND_VIDEO);
} catch (error) {
openSettingsPrompt();
}
};
return null;
}
After
import { Constants, useMediaDevice, useRoom } from "@videosdk.live/react-native-sdk";
function DeviceSetup() {
const [devices, setDevices] = useState([]);
const { requestPermission, getMics } = useMediaDevice({
// devices is a Map of deviceId to device
onAudioDeviceChanged: (devices) => {
setDevices([...devices.values()]);
},
});
const { setAudioDevice } = useRoom();
// On Android, call this before you join
const askForAccess = async () => {
const granted = await requestPermission(Constants.permission.AUDIO_AND_VIDEO);
if (
!granted.get(Constants.permission.AUDIO) ||
!granted.get(Constants.permission.VIDEO)
) {
openSettingsPrompt();
}
};
const listAudioDevices = async () => setDevices(await getMics());
// Call after join(); pass one of the devices from getMics()
const chooseAudioDevice = (device) => setAudioDevice(device);
return null;
}
Device functions
| v1.0.1 | v2.0.0 |
|---|---|
useMediaDevice().getCameras(), useMeeting().getWebcams() | useMediaDevice().getCameras() |
useMediaDevice().getAudioDeviceList(), useMeeting().getMics(), top-level getAudioDeviceList() | useMediaDevice().getMics() |
useMediaDevice().getDevices() | Unchanged |
useMediaDevice({ onAudioDeviceChanged: ({ selectedAudioDevice, availableDevices }) => … }) | onAudioDeviceChanged: (devices) => …, where devices is a Map of device id to device |
switchAudioDevice(device) | useRoom().setAudioDevice(device) |
| — | getSelectedVideoDevice() and getSelectedAudioDevice() on useMediaDevice() |
Key points:
useMediaDevice()works outsideRoomProvider, so you can list devices and ask for access on your join screen. If you usejoinWithoutUserInteraction, ask before you renderRoomProvider.- In a live stream, ask before a host joins, and before
changeMode("SEND_AND_RECV")turns a viewer into a host. - Cameras report
facingMode:"front"or"back".getDevices()lists the microphones first, then the cameras.getMics()returns device objects instead of the route-name strings that the top-levelgetAudioDeviceList()returned. setAudioDevice()takes a device fromgetMics()or one of"SPEAKER_PHONE","EARPIECE","WIRED_HEADSET"and"BLUETOOTH". Call it afterjoin(). Before that it rejects.EARPIECEis no longer left out of the audio device list when a headset or Bluetooth device is connected.getSelectedVideoDevice()andgetSelectedAudioDevice()return{ deviceId }, ornullwhile the SDK picks the device.
15. Camera and microphone errors use the general error codes
This compiles, but behaves differently. Constants.errors is now Constants.VideoSDKError. No error code was renumbered, and no number changed meaning. Nine names that said MEETING now say ROOM (table below). What also changed is which codes reach you. Camera and microphone failures that v1.x.x reported with the React Native–specific ERROR_RN_* codes now arrive with the general codes. The ERROR_RN_* constants are still in Constants.VideoSDKError, so a check against them compiles and never matches.
Renamed, same code:
| Code | v1.0.1 | v2.0.0 |
|---|---|---|
3035 | ERROR_ACTION_PERFORMED_BEFORE_MEETING_JOINED | ERROR_ACTION_PERFORMED_BEFORE_ROOM_JOINED |
3044 | ERROR_MEETING_MEDIA_CONNECTION_FAILED | ERROR_ROOM_MEDIA_CONNECTION_FAILED |
3047 | ERROR_UNABLE_TO_JOIN_MEETING | ERROR_UNABLE_TO_JOIN_ROOM |
3057 | ERROR_END_MEETING_FAILED | ERROR_END_ROOM_FAILED |
3065 | ERROR_MEETING_RECONNECTING | ERROR_ROOM_RECONNECTING |
4003 | INVALID_MEETING_ID | INVALID_ROOM_ID |
4022 | UNAUTHORIZED_MEETING_ID | UNAUTHORIZED_ROOM_ID |
4052 | MEETING_ID_DISABLED | ROOM_ID_DISABLED |
4076 | SWITCH_MEETING_FAILED | SWITCH_ROOM_FAILED |
Match these on code, or update the names you compare against.
Before
import { Constants, useMeeting } from "@videosdk.live/react-native-sdk";
const { enableWebcam } = useMeeting();
try {
await enableWebcam();
} catch (error) {
if (error.code === Constants.errors.ERROR_RN_CAMERA_ACCESS_DENIED_OR_DISMISSED) {
showCameraPermissionHelp();
}
}
After
import { useRoom } from "@videosdk.live/react-native-sdk";
// 3017 ERROR_CAMERA_ACCESS_DENIED_OR_DISMISSED
// 3019 ERROR_CAMERA_PERMISSION_DENIED_BY_OS
const CAMERA_ACCESS_DENIED = [3017, 3019];
const { enableCam } = useRoom({
onError: (error) => {
if (CAMERA_ACCESS_DENIED.includes(error.code)) {
showCameraPermissionHelp();
}
},
});
Handle the general code instead:
| v1.0.1 | v2.0.0 |
|---|---|
3036 ERROR_RN_CAMERA_ACCESS_DENIED_OR_DISMISSED | 3017 ERROR_CAMERA_ACCESS_DENIED_OR_DISMISSED, or 3019 ERROR_CAMERA_PERMISSION_DENIED_BY_OS when access was already denied in iOS Settings |
3037 ERROR_RN_CAMERA_NOT_FOUND | 3021 ERROR_CAMERA_NOT_FOUND or 3051 ERROR_NO_WEBCAM_DEVICE_FOUND |
3038 ERROR_RN_MIC_ACCESS_DENIED_OR_DISMISSED | 3018 ERROR_MICROPHONE_ACCESS_DENIED_OR_DISMISSED, or 3020 ERROR_MICROPHONE_PERMISSION_DENIED_BY_OS when access was already denied in iOS Settings |
3039 ERROR_RN_MIC_NOT_FOUND | 3022 ERROR_MICROPHONE_NOT_FOUND or 3059 ERROR_NO_MICROPHONE_DEVICE_FOUND |
3040 ERROR_RN_CAMERA_ACCESS_UNAVAILABLE | 3033 ERROR_CAMERA_ACCESS_UNAVAILABLE |
3041 ERROR_RN_MIC_ACCESS_UNAVAILABLE | 3034 ERROR_MICROPHONE_ACCESS_UNAVAILABLE |
3042 ERROR_RN_CAMERA_TRACK_ENDED | 3027 ERROR_WEBCAM_TRACK_ENDED |
3043 ERROR_RN_MIC_TRACK_ENDED | 3028 ERROR_MICROPHONE_TRACK_ENDED |
Key points:
onErrorreceives{ code, name, message }. A failed method call still rejects with an error that has a numericcodeand, for every code inConstants.VideoSDKError, the matchingname.- A call made after
join()but beforeonRoomJoined, or while the room is reconnecting, behaves differently on each platform. On Android, most methods reportERROR_ACTION_PERFORMED_BEFORE_ROOM_JOINED(3035) orERROR_ROOM_RECONNECTING(3065) ononErrorand resolve without doing anything.changeCam(),changeMic(), theuseFile()and PubSub methods, and a stream'spause()/resume()report the code ononErrorand also reject. On iOS, most methods reject with the same codes and do not callonError.changeMode(), theuseFile()methods, a stream'spause()/resume()and participant actions such aspin(),unpin()andsetQuality()callonErrorand also reject. A remote participant'senableMic(),disableMic(),enableCam()anddisableCam()callonErrorand resolve. Handle both codes inonErrorand incatch. - On iOS, a camera or microphone that cannot be opened after
enableCam()orunmuteMic()is reported only ononError, and the call resolves. Android reports it ononErrorand also rejects the call. Check these codes inonError. - An unexpected failure rejects with the string code
ERROR_BRIDGE_UNEXPECTED.PreCallTestErroruses the same code. - A wrong-typed or missing argument to any SDK call rejects with
ERROR_INVALID_PARAMETER(3073). The same code also covers checks such as applying a video processor name that was never registered. captureImage()failures that had no code in v1.x.x now carry SDK error codes. For example, on Android, capturing while the camera is off rejects with3062ERROR_WEBCAM_NOT_ENABLED. CallcaptureImage()only for the local participant.- Learn more: React Native SDK Reference
16. Removed APIs
| Removed | Use instead |
|---|---|
register() | Nothing. Delete every import and call (section 1). |
mediaDevices, permissions | useMediaDevice() for permissions and device lists, and createCameraVideoTrack() / createMicrophoneAudioTrack() for capture. |
Live MediaStreamTrack objects, and the MediaStream methods other than toURL(), getTracks(), getVideoTracks() and getAudioTracks() | Wrap a stream's track in new MediaStream([track]) only to render it (section 12). |
useKeyProvider() and the keyProvider prop | BaseKeyProvider and setKeyProvider() (section 8) |
getAudioDeviceList() and switchAudioDevice() (top-level) | useMediaDevice().getMics() and useRoom().setAudioDevice() (section 14) |
mainParticipant, isMainParticipant, onMainParticipantChanged, onHlsStarted, onHlsStopped, hlsUrls.downstreamUrl | No replacement. Use onHlsStateChanged for HLS. |
debugMode, deviceInfo, reinitialiseMeetingOnConfigChange, notification | Nothing. Remove them. |
useCharacter(), the CharacterMode and CharacterState enums, useMeeting().characters, the onCharacterJoined and onCharacterLeft callbacks, Constants.characterState, Constants.characterMode and Constants.leaveReason.CHARACTER_REMOVED | No replacement. For AI agents that join as participants, use useAgentParticipant(). |
useConnection(), useMeeting().connections | No replacement. Remove them. |
External video: startVideo(), stopVideo(), pauseVideo(), resumeVideo(), seekVideo(), onVideoStateChanged, onVideoSeeked | No replacement. Remove them. |
useParticipant().switchTo() | useRoom().switchTo() |
Virtual background from @videosdk.live/react-native-media-effects (applyBlurBackground, applyImageBackground, applyColorBackground) | No replacement. The package needs react-native-webrtc, so it breaks a v2.0.0 build. Uninstall it. setVideoEffects() offers framing, low-light, style and colour filter effects, but no background blur or replacement. |
17. Other changes
| API | v1.0.1 | v2.0.0 |
|---|---|---|
setLogLevel(level) | Returned nothing. | Returns a Promise. The first call also prints the SDK's logs to the JS console. With no level it applies "INFO". An unknown level rejects with ERROR_INVALID_PARAMETER (3073). Read the level in force with getLogLevel(). |
runPreCallTest() | The result was typed with network, and videoConfig took "user" / "environment". | The result is typed with networkQuality, the key the SDK always returned. videoConfig.facingMode takes "front" / "back", and videoConfig.encoderConfig no longer accepts h216p_w384p, h540p_w720p, h1440p_w1920p, h1440p_w2560p or h2160p_w3840p. Stop a running test with stopPreCallTest(). |
PreCallTestError | Typed as a plain class. | Typed as extending Error (it already did at runtime). An unexpected failure has the code ERROR_BRIDGE_UNEXPECTED. |
RTCView props | Its own props only. | Also accepts standard View props, and style accepts an array. |
startRecording(), startHLS() | No transcription argument. | Take a post-transcription config as their last argument: { enabled, summary, modelId }. |
useStream(streamId, { onStreamStateChanged }) | — | Renamed useRTCStream(), with the same arguments and return values. |
useRealtimeStore().setValue() | Typed to take a string. | Typed to take a string or null. null deletes the key. |
Participant stats (getAudioStats(), getVideoStats(), getShareStats()) | — | With nothing to report they resolve an empty array on Android and null on iOS. Handle both. |
HLSConfig.recording | — | Accepted and ignored. |
18. New in v2.0.0
| Feature | API |
|---|---|
| Realtime translation | useTranslation(): startTranslation(), changeTranslationLanguage(), stopTranslation(), and the onTranslationStateChanged, onTranslationLanguageChanged and onTranslationText callbacks. Set the languages with config.speakingLanguage and config.translationLanguage. |
| Camera effects | setVideoEffects(), getAppliedVideoEffectsConfig() and publishFramingSubject(): auto-framing, low-light, style and colour filter effects. Effects render only with pipeline: "GRAPH". |
| DynaCast | config.dynaCast on RoomProvider. Needs multiStream: true. |
| Data channel | send() and onDataReceived on useRoom() (section 9) |
| Image capture with flash | captureImage({ flash: "ON" }) (Constants.CaptureFlash.ON), for the local participant only. On a front camera the SDK lights the screen instead of a torch. To set that screen flash up before you capture, call setScreenFlash(true). setScreenFlash(false) or setScreenFlash(null) clears it. |
| Pre-call stats | runPreCallTest(options, onStatsChange) also takes the stats callback as its second argument |
| Custom video processors on iOS | VideoProcessorRegistry from VideoSDKReactNative (section 10) |
| Screen-share track | createScreenShareVideoTrack({ encoderConfig, multiStream, withAudio }) (section 7) |
| Track end | CustomStreamTrack.isEnded, and onEnded, called once with the error or null when a track you created ends |
| Microphone processing | highPassFilter in the noiseConfig of createMicrophoneAudioTrack() |
| Selected devices | getSelectedVideoDevice() and getSelectedAudioDevice() (section 14) |
| Room state | room.roomState and Constants.RoomState |
| Encryption keys | setParticipantKey() sets or rotates one participant's key, also during a room. removeKeyProvider() turns encryption off, and BaseKeyProvider.removeKey() removes one participant's key (section 8). |
| Camera settings in use | room.getAppliedVideoConfig() |
| Video quality | setWebcamQuality() on useRoom() takes "low", "med" or "high" (Constants.VideoQuality), and setScreenShareQuality() on useParticipant(). |
| Choose whose media you receive | config.autoConsume = false, with consumeMicStreams() / consumeWebcamStreams() and the matching stopConsuming…() methods on useParticipant() |
| Phone calls | onExternalCallRinging, onExternalCallAnswered, onExternalCallRejected and onExternalCallHangup on useRoom() |
| E2EE stream state | onE2EEStateChanged on useParticipant() |
| Now in the TypeScript types | useRoom().transcriptionState and translationState, Constants.translationEvents, and ConfigLayout, the type of layout in recording, HLS and livestream configs |
Use the room.* methods on the room property of useRoom() (const { room } = useRoom();). room is null until you join.
API Reference
RoomProvider, useRoom, useParticipant, useMediaDevice, end-to-end encryption, custom tracks, RTCView, Constants and the error codes are described in the React Native SDK Reference.
See also: Release Notes
Got a Question? Ask us on discord

