react-native-webrtc-call
v1.2.0
Published
A complete WebRTC calling solution for React Native with UI components, Context API integration, and signaling support
Downloads
2,370
Maintainers
Readme
react-native-webrtc-call
A complete WebRTC calling solution for React Native with UI components, Context API integration, and signaling support. This package provides everything you need to implement audio and video calling in your React Native application.
Features
- ✅ P2P & Group Calling - Support for both one-on-one and group calls (default: P2P)
- ✅ Audio & Video - Full support for both audio and video calls
- ✅ Complete WebRTC Implementation - Full peer-to-peer audio/video calling
- ✅ Context API Integration - Built-in React Context for state management
- ✅ Signaling Support - Socket.io-based signaling server integration
- ✅ ICE Server Management - Automatic TURN/STUN server configuration
- ✅ Call Controls - Mute, video toggle, speaker control
- ✅ Call UI Components - Ready-to-use UI components for calls
- ✅ TypeScript Support - Full TypeScript definitions
- ✅ Cross-platform - Works on both iOS and Android
Installation
npm install react-native-webrtc-call
# or
yarn add react-native-webrtc-callPeer Dependencies
Make sure you have these peer dependencies installed:
npm install react-native-webrtc react-native-incall-manager socket.io-client react-native-flash-message @react-native-async-storage/async-storage react-native-vector-iconsNote: react-native-vector-icons is used for UI icons. If you're using Expo, you can use @expo/vector-icons instead (which is included by default in Expo projects).
Repository Layout (TypeScript + Compiled JS)
This package ships both the original TypeScript sources under src/ and the compiled JavaScript output under lib/. Consumers rely on the lib/ bundle when the package is published, while contributors typically edit the files in src/ and then run the build to regenerate lib/. Seeing duplicate filenames in both folders is expected—avoid editing only one side or your changes may be lost the next time the build runs.
iOS Setup
For iOS, you may need to add permissions to Info.plist:
<key>NSCameraUsageDescription</key>
<string>We need access to your camera for video calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need access to your microphone for audio calls</string>Android Setup
For Android, add permissions to AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />Quick Start
1. Setup Signaling Server
Before initializing the WebRTC service, you need to set up a signaling server. Here's a complete Express/Socket.io signaling server implementation:
Server Setup
Create a new file server.js in your project:
const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const cors = require("cors");
require("dotenv").config();
const twilio = require("twilio");
const app = express();
const server = http.createServer(app);
app.use(cors());
app.use(express.json());
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"],
},
});
// Store connected users
const users = new Map();
const activeRooms = new Map();
// ICE configuration cache
let cachedIceConfig = null;
let cachedIceExpiresAt = 0;
function isTwilioConfigured() {
return (
Boolean(process.env.TWILIO_ACCOUNT_SID && process.env.TWILIO_AUTH_TOKEN) ||
Boolean(process.env.TWILIO_API_KEY_SID && process.env.TWILIO_API_KEY_SECRET)
);
}
function createTwilioClient() {
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const apiKeySid = process.env.TWILIO_API_KEY_SID;
const apiKeySecret = process.env.TWILIO_API_KEY_SECRET;
if (apiKeySid && apiKeySecret && accountSid) {
return twilio(apiKeySid, apiKeySecret, { accountSid });
}
if (apiKeySid && apiKeySecret) {
return twilio(apiKeySid, apiKeySecret);
}
if (accountSid && authToken) {
return twilio(accountSid, authToken);
}
return null;
}
async function fetchTwilioIceServers() {
const ttlSeconds = Number(process.env.ICE_TTL_SECONDS || 3600);
const client = createTwilioClient();
if (!client) return null;
const token = await client.tokens.create({ ttl: ttlSeconds });
return {
iceServers: token.iceServers || [],
ttl: ttlSeconds,
provider: "twilio",
};
}
async function getIceConfig() {
const now = Date.now();
// Refresh 30 seconds before expiry
const refreshBufferMs = 30 * 1000;
if (cachedIceConfig && now < cachedIceExpiresAt - refreshBufferMs) {
return cachedIceConfig;
}
if (isTwilioConfigured()) {
try {
const twilioConfig = await fetchTwilioIceServers();
if (
twilioConfig &&
twilioConfig.iceServers &&
twilioConfig.iceServers.length
) {
cachedIceConfig = twilioConfig;
cachedIceExpiresAt = now + (twilioConfig.ttl || 3600) * 1000;
return cachedIceConfig;
}
} catch (err) {
console.error("❌ Failed to fetch Twilio ICE servers:", err.message);
}
}
// Fallback to public Twilio STUN only (no TURN)
cachedIceConfig = {
iceServers: [{ urls: ["stun:global.stun.twilio.com:3478"] }],
ttl: 3600,
provider: "static-stun",
note: "Using public STUN only. Configure TWILIO_* env vars to enable TURN for NAT traversal.",
};
cachedIceExpiresAt = now + cachedIceConfig.ttl * 1000;
return cachedIceConfig;
}
// Health check endpoint
app.get("/health", (req, res) => {
res.json({
status: "ok",
connectedUsers: users.size,
activeRooms: activeRooms.size,
timestamp: new Date().toISOString(),
iceProvider: cachedIceConfig
? cachedIceConfig.provider
: isTwilioConfigured()
? "twilio"
: "static-stun",
});
});
// Endpoint to retrieve ICE configuration for clients
app.get("/ice", async (req, res) => {
try {
const config = await getIceConfig();
res.json({
...config,
fetchedAt: new Date().toISOString(),
expiresAt: new Date(cachedIceExpiresAt).toISOString(),
});
} catch (error) {
console.error("❌ /ice error:", error);
res.status(500).json({ error: "Failed to load ICE configuration" });
}
});
// Get connected users (for debugging)
app.get("/users", (req, res) => {
const userList = Array.from(users.entries()).map(([userId, userData]) => ({
userId,
userType: userData.userType,
connected: true,
}));
res.json(userList);
});
io.on("connection", (socket) => {
console.log("🔌 User connected:", socket.id);
// Register user with their profile information
socket.on("register", (userData) => {
try {
users.set(userData.userId, {
socketId: socket.id,
userType: userData.userType,
connectedAt: new Date().toISOString(),
...userData,
});
console.log("👤 User registered:", {
userId: userData.userId,
userType: userData.userType,
socketId: socket.id,
});
// Confirm registration with ICE configuration
getIceConfig()
.then((iceConfig) => {
socket.emit("registered", {
success: true,
userId: userData.userId,
connectedUsers: users.size,
iceServers: iceConfig.iceServers,
iceProvider: iceConfig.provider,
});
})
.catch(() => {
socket.emit("registered", {
success: true,
userId: userData.userId,
connectedUsers: users.size,
iceServers: [{ urls: ["stun:global.stun.twilio.com:3478"] }],
iceProvider: "static-stun",
});
});
} catch (error) {
console.error("❌ Registration error:", error);
socket.emit("registered", { success: false, error: error.message });
}
});
// Handle all signaling messages
socket.on("signaling-message", (message) => {
try {
const senderId = getUserIdBySocketId(socket.id);
const recipient = users.get(message.to);
console.log("📡 Signaling message:", {
type: message.type,
from: senderId,
to: message.to,
callId: message.callId,
recipientFound: !!recipient,
});
if (recipient) {
// Forward the message to the recipient
io.to(recipient.socketId).emit("signaling-message", {
...message,
from: senderId,
timestamp: new Date().toISOString(),
});
// Handle room management for calls
if (message.type === "call-request") {
activeRooms.set(message.callId, {
caller: senderId,
callee: message.to,
startTime: new Date().toISOString(),
status: "ringing",
});
} else if (message.type === "call-accept") {
const room = activeRooms.get(message.callId);
if (room) {
room.status = "active";
room.acceptTime = new Date().toISOString();
}
} else if (
message.type === "call-end" ||
message.type === "call-reject"
) {
const room = activeRooms.get(message.callId);
if (room) {
room.status = message.type === "call-end" ? "ended" : "rejected";
room.endTime = new Date().toISOString();
// Clean up room after 5 minutes
setTimeout(() => {
activeRooms.delete(message.callId);
}, 5 * 60 * 1000);
}
}
console.log("✅ Message forwarded successfully");
} else {
console.log("❌ Recipient not found:", message.to);
// Notify sender that recipient is not available
socket.emit("signaling-error", {
type: "recipient-not-found",
message: "The person you are trying to call is not available",
originalMessage: message,
});
}
} catch (error) {
console.error("❌ Signaling error:", error);
socket.emit("signaling-error", {
type: "server-error",
message: "An error occurred while processing your request",
error: error.message,
});
}
});
// Handle user status updates
socket.on("update-status", (status) => {
const userId = getUserIdBySocketId(socket.id);
if (userId && users.has(userId)) {
const userData = users.get(userId);
userData.status = status;
userData.lastStatusUpdate = new Date().toISOString();
users.set(userId, userData);
console.log("📊 Status updated:", { userId, status });
}
});
// Handle ping/pong for connection health
socket.on("ping", () => {
socket.emit("pong", { timestamp: new Date().toISOString() });
});
// Handle disconnect
socket.on("disconnect", (reason) => {
const userId = getUserIdBySocketId(socket.id);
if (userId) {
// Clean up any active calls
for (const [callId, room] of activeRooms.entries()) {
if (room.caller === userId || room.callee === userId) {
room.status = "disconnected";
room.endTime = new Date().toISOString();
// Notify the other party
const otherUserId =
room.caller === userId ? room.callee : room.caller;
const otherUser = users.get(otherUserId);
if (otherUser) {
io.to(otherUser.socketId).emit("signaling-message", {
type: "call-end",
callId: callId,
from: userId,
reason: "peer-disconnected",
});
}
}
}
users.delete(userId);
console.log("👋 User disconnected:", {
userId,
socketId: socket.id,
reason,
remainingUsers: users.size,
});
} else {
console.log("👋 Unknown user disconnected:", socket.id);
}
});
// Handle errors
socket.on("error", (error) => {
console.error("🔥 Socket error:", error);
});
});
// Helper function to get user ID by socket ID
function getUserIdBySocketId(socketId) {
for (const [userId, userData] of users.entries()) {
if (userData.socketId === socketId) {
return userId;
}
}
return null;
}
// Cleanup inactive rooms periodically
setInterval(() => {
const now = new Date();
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
for (const [callId, room] of activeRooms.entries()) {
const roomTime = new Date(room.endTime || room.startTime);
if (roomTime < oneHourAgo) {
activeRooms.delete(callId);
}
}
}, 10 * 60 * 1000); // Run every 10 minutes
// Graceful shutdown
process.on("SIGTERM", () => {
console.log("🛑 SIGTERM received, shutting down gracefully");
server.close(() => {
console.log("✅ Server closed");
process.exit(0);
});
});
process.on("SIGINT", () => {
console.log("🛑 SIGINT received, shutting down gracefully");
server.close(() => {
console.log("✅ Server closed");
process.exit(0);
});
});
const PORT = process.env.PORT || 3001;
server.listen(PORT, () => {
console.log(`🚀 PlugEx Signaling Server running on port ${PORT}`);
console.log(`📊 Health check available at http://localhost:${PORT}/health`);
console.log(`👥 Users endpoint available at http://localhost:${PORT}/users`);
});Server Dependencies
Install the required dependencies:
npm install express socket.io cors dotenv twilio
# or
yarn add express socket.io cors dotenv twilioEnvironment Variables
Create a .env file in your server directory:
PORT=3001
ICE_TTL_SECONDS=3600
# Optional: Twilio Configuration (for TURN servers)
# Option 1: Using Account SID and Auth Token
TWILIO_ACCOUNT_SID=your_account_sid
TWILIO_AUTH_TOKEN=your_auth_token
# Option 2: Using API Key (recommended)
TWILIO_API_KEY_SID=your_api_key_sid
TWILIO_API_KEY_SECRET=your_api_key_secretRunning the Server
node server.jsThe server will start on port 3001 (or the port specified in your .env file).
2. Wrap Your App with WebRTCCallManagerWrapper
Wrap your app (or the part that needs calling functionality) with WebRTCCallManagerWrapper and pass the configuration as props:
import React from "react";
import { WebRTCCallManagerWrapper } from "react-native-webrtc-call";
import AsyncStorage from "@react-native-async-storage/async-storage";
const App = () => {
return (
<WebRTCCallManagerWrapper
config={{
// Use http:// or https:// (not ws:// or wss://)
// The service will automatically convert to WebSocket protocol
signalingServerUrl: "http://localhost:3001", // For development
// signalingServerUrl: 'https://your-signaling-server.com', // For production
// Optional: If your server provides ICE servers via /ice endpoint
turnEndpoint: "http://localhost:3001/ice", // Optional
icePolicy: "all", // or 'relay'
defaultCallType: "p2p", // Default to P2P, can be 'group'
// Optional: Provide custom token retrieval
getUserToken: async () => {
return await AsyncStorage.getItem("userToken");
},
// Optional: Provide custom user profile
getUserProfile: () => {
return {
_id: "user123",
firstName: "John",
lastName: "Doe",
userType: "user",
profilePicture: "https://...",
};
},
}}
userId="user123" // Required for user registration
userName="John Doe" // Optional - for display purposes
userAvatar="https://..." // Optional - for display purposes
onEvents={(event) => {
// Optional: Handle background events
console.log("WebRTC Event:", event);
}}
>
{/* Your app components */}
</WebRTCCallManagerWrapper>
);
};
export default App;Important Notes:
- Use
http://orhttps://forsignalingServerUrl(notws://orwss://) - The service automatically converts the protocol to WebSocket internally
- For local development, use
http://localhost:3001 - For production, use your deployed server URL with
https:// userIdis required for user registration with the signaling server- If
getUserProfileis not provided, it will be built fromuserId,userName, anduserAvatarprops
3. Use in Your Components
When using WebRTCCallManagerWrapper, the service is created internally. For components that need to initiate calls, you have a few options:
Option A: Create a Service Instance for Direct Access
If you need direct access to the service methods, create a separate service instance with the same configuration:
// services/webRTCService.ts
import { WebRTCService, WebRTCConfig } from "react-native-webrtc-call";
import AsyncStorage from "@react-native-async-storage/async-storage";
const config: WebRTCConfig = {
signalingServerUrl: "http://localhost:3001",
turnEndpoint: "http://localhost:3001/ice",
icePolicy: "all",
defaultCallType: "p2p",
getUserToken: async () => {
return await AsyncStorage.getItem("userToken");
},
getUserProfile: () => {
return {
_id: "user123",
firstName: "John",
lastName: "Doe",
userType: "user",
};
},
};
export const webRTCService = new WebRTCService(config);Then use it in your components:
import { useWebRTC } from "react-native-webrtc-call";
import { webRTCService } from "./services/webRTCService";
const MyComponent = () => {
const { initiateCall } = useWebRTC();
const handleCall = async () => {
try {
// P2P call (default)
const callId = await webRTCService.initiateCall(
"recipient-id",
false // video: false for audio, true for video
);
initiateCall(
{
id: "recipient-id",
name: "Recipient Name",
avatar: "https://...",
userType: "user",
},
callId
);
} catch (error) {
console.error("Call failed:", error);
}
};
return <Button onPress={handleCall} title="Call" />;
};Option B: Using CallButton Component
import { CallButton } from "react-native-webrtc-call";
import { webRTCService } from "./services/webRTCService";
const MyComponent = () => {
return (
<CallButton
recipientId="recipient-id"
recipientName="Recipient Name"
webRTCService={webRTCService}
onInitiateCall={(callId) => {
// Optional: Handle call initiation
console.log("Call initiated:", callId);
}}
/>
);
};Initiate a Group Call
import { useWebRTC } from "react-native-webrtc-call";
import { webRTCService } from "./services/webRTCService";
const MyComponent = () => {
const { initiateCall, setCallType } = useWebRTC();
const handleGroupCall = async () => {
try {
const participantIds = ["user1", "user2", "user3"];
// Group call
const callId = await webRTCService.initiateGroupCall(
participantIds,
true // video: true for video, false for audio
);
setCallType("group");
initiateCall(
{
id: "group",
name: "Group Call",
},
callId
);
} catch (error) {
console.error("Group call failed:", error);
}
};
return <Button onPress={handleGroupCall} title="Start Group Call" />;
};Accept/Reject Incoming Calls
The WebRTCCallManagerWrapper automatically displays the IncomingCallScreen when there's an incoming call. You can also use the IncomingCallScreen component directly:
import { IncomingCallScreen } from "react-native-webrtc-call";
import { webRTCService } from "./services/webRTCService";
const MyApp = () => {
return <IncomingCallScreen webRTCService={webRTCService} />;
};Or handle it manually:
import { useWebRTC } from "react-native-webrtc-call";
import { webRTCService } from "./services/webRTCService";
const IncomingCallHandler = () => {
const { state, acceptCall, rejectCall } = useWebRTC();
const { isIncomingCall, caller } = state;
const handleAccept = async () => {
acceptCall();
await webRTCService.acceptCall(false); // false for audio, true for video
};
const handleReject = () => {
rejectCall();
webRTCService.rejectCall();
};
if (!isIncomingCall) return null;
return (
<View>
<Text>Incoming call from {caller?.name}</Text>
<Button onPress={handleAccept} title="Accept" />
<Button onPress={handleReject} title="Reject" />
</View>
);
};Call Controls
import { webRTCService } from "./services/webRTCService";
const CallControls = () => {
const handleMute = () => {
webRTCService.toggleMute();
};
const handleVideo = () => {
webRTCService.toggleVideo();
};
const handleSpeaker = () => {
webRTCService.toggleSpeaker();
};
const handleEndCall = () => {
webRTCService.endCall();
};
return (
<View>
<Button onPress={handleMute} title="Mute" />
<Button onPress={handleVideo} title="Video" />
<Button onPress={handleSpeaker} title="Speaker" />
<Button onPress={handleEndCall} title="End Call" />
</View>
);
};API Reference
WebRTCService
Methods
initiateCall(calleeId: string, video?: boolean, callIdOverride?: string): Promise<string>- Initiates a P2P call to the specified user (default)
- Returns the call ID
initiateP2PCall(calleeId: string, video?: boolean, callIdOverride?: string): Promise<string>- Explicitly initiates a P2P call
- Returns the call ID
initiateGroupCall(participantIds: string[], video?: boolean, callIdOverride?: string): Promise<string>- Initiates a group call with multiple participants
- Returns the call ID
acceptCall(video?: boolean): Promise<void>- Accepts an incoming call
rejectCall(): void- Rejects an incoming call
endCall(reason?: string): void- Ends the current call
toggleMute(): void- Toggles microphone mute state
toggleVideo(): Promise<void>- Toggles video on/off
toggleSpeaker(): void- Toggles speakerphone on/off
getFormattedCallDuration(seconds: number): string- Formats call duration as MM:SS
cleanup(): void- Cleans up all resources
getState(): Partial<CallState>- Returns current call state
Context API
The package provides a useWebRTC hook that gives you access to:
- State: All call state (isCallActive, isIncomingCall, caller, callee, etc.)
- Actions: All state management functions
import { useWebRTC } from "react-native-webrtc-call";
const MyComponent = () => {
const {
state,
initiateCall,
acceptCall,
rejectCall,
endCall,
toggleMute,
toggleVideo,
toggleSpeaker,
} = useWebRTC();
// Access state
const { isCallActive, caller, callee } = state;
// Use actions
const handleCall = () => {
initiateCall(callee, callId);
};
};Call State
interface CallState {
isCallActive: boolean;
isIncomingCall: boolean;
isOutgoingCall: boolean;
callId: string | null;
callType: "p2p" | "group"; // Call type
localStream: any;
remoteStream: any; // For P2P calls
remoteParticipants: RemoteParticipant[]; // For group calls
isMuted: boolean;
isVideoEnabled: boolean;
isSpeakerEnabled: boolean;
callStartTime: number | null;
callDuration: number;
caller: CallParticipant | null;
callee: CallParticipant | null;
participants: CallParticipant[]; // All participants (for group calls)
connectionState: "connecting" | "connected" | "disconnected" | "failed";
isCallMinimized: boolean;
incomingWantsVideo: boolean | null;
}Signaling Server Requirements
Your signaling server should handle the following Socket.io events:
Client → Server
register- Register user with signaling server{ userId: string; userType: string; token?: string; }signaling-message- Send signaling messages{ type: "offer" | "answer" | "ice-candidate" | "call-request" | "call-accept" | "call-reject" | "call-end"; data?: any; callId: string; to: string; }
Server → Client
registered- Registration confirmation with ICE servers{ success: boolean; iceServers: RTCIceServer[]; iceProvider?: string; }signaling-message- Receive signaling messages
ICE Server Configuration
The package supports both STUN and TURN servers. TURN servers are required for calls to work across different networks (NAT traversal).
You can provide ICE servers in two ways:
- Via signaling server registration response - The server returns ICE servers when the client registers
- Via TURN endpoint - Configure a
turnEndpointin the config that returns ICE servers
Example TURN endpoint response:
{
"iceServers": [
{
"urls": "turn:your-turn-server.com:3478",
"username": "username",
"credential": "password"
}
]
}Testing
Testing Signaling Server Connection
You can test your signaling server setup using the following methods:
Automated Test Script
Run the included test script to verify all endpoints:
npm run test:server
# or with custom URL
node test/signaling-server-test.js http://localhost:3001This will test:
- Health check endpoint (
/health) - ICE configuration endpoint (
/ice) - Connected users endpoint (
/users)
1. Health Check
Test if your server is running:
curl http://localhost:3001/healthExpected response:
{
"status": "ok",
"connectedUsers": 0,
"activeRooms": 0,
"timestamp": "2024-01-01T00:00:00.000Z",
"iceProvider": "static-stun"
}2. ICE Configuration Endpoint
Test ICE server configuration:
curl http://localhost:3001/iceExpected response:
{
"iceServers": [
{
"urls": ["stun:global.stun.twilio.com:3478"]
}
],
"ttl": 3600,
"provider": "static-stun",
"fetchedAt": "2024-01-01T00:00:00.000Z",
"expiresAt": "2024-01-01T01:00:00.000Z"
}3. Connected Users
Check currently connected users:
curl http://localhost:3001/users4. Testing signalingServerUrl in Your App
Add this test function to verify your signaling server connection:
import { WebRTCService } from "react-native-webrtc-call";
async function testSignalingServer() {
const testConfig = {
signalingServerUrl: "http://localhost:3001", // Your server URL
getUserProfile: () => ({
_id: "test-user",
firstName: "Test",
lastName: "User",
userType: "user",
}),
onError: (error) => {
console.error("Test Error:", error);
},
onCallStateChange: (state) => {
console.log("Test State:", state);
},
};
const service = new WebRTCService(testConfig);
try {
// Register user (this will connect to signaling server)
await service.registerUser();
console.log("✅ Successfully connected to signaling server");
// Check connection state
const state = service.getState();
console.log("Connection state:", state);
// Cleanup
service.cleanup();
} catch (error) {
console.error("❌ Failed to connect to signaling server:", error);
}
}
// Call the test function
testSignalingServer();5. Network Testing
For testing on physical devices or emulators:
- iOS Simulator: Use
http://localhost:3001if testing on the same machine - Android Emulator: Use
http://10.0.2.2:3001(Android emulator's localhost) - Physical Devices: Use your computer's local IP address (e.g.,
http://192.168.1.100:3001)
Find your local IP:
# macOS/Linux
ifconfig | grep "inet " | grep -v 127.0.0.1
# Windows
ipconfigTroubleshooting
Calls not connecting
- Check that your signaling server is running and accessible
- Test with:
curl http://localhost:3001/health - Verify the server logs show connections
- Test with:
- Verify
signalingServerUrlis correctly configured- Use
http://orhttps://(notws://orwss://) - For Android emulator, use
http://10.0.2.2:3001 - For physical devices, use your computer's local IP
- Use
- Verify ICE servers are properly configured (TURN servers required for cross-network calls)
- Check
/iceendpoint returns valid ICE servers - Review server logs for Twilio configuration errors
- Check
- Check device permissions (camera, microphone)
- Review connection state using the
useWebRTChook
Signaling Server Connection Issues
Connection refused errors
- Ensure the server is running:
node server.js - Check the port matches your configuration
- Verify firewall settings allow connections
- Ensure the server is running:
WebSocket connection fails
- Ensure you're using
http://orhttps://insignalingServerUrl - Check CORS settings on the server
- Verify network connectivity between client and server
- Ensure you're using
Registration fails
- Check server logs for registration errors
- Verify
getUserProfile()returns valid user data - Ensure user ID is unique and not empty
Audio/Video not working
- Ensure permissions are granted
- Check that media tracks are being added to peer connection
- Verify local and remote streams using the
useWebRTChook
Connection drops
- Check network connectivity
- Verify TURN servers are working
- Review ICE restart attempts in logs
- Check server logs for disconnection reasons
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
