I want to implement conference video call in React-native and node js using without any paid library
I was Implement single Person Video call using react-native-webrtc and node js Socket using Peer-to-Peer
My React-Native files below
My Privider Files
import React, { useState } from "react";
import { Alert } from "react-native";
import {
mediaDevices,
MediaStream,
MediaStreamConstraints,
} from "react-native-webrtc";
import socketio from "socket.io-client";
import ReactNativeForegroundService from "@supersami/rn-foreground-service";
import { MainContext as MainContextType, User } from "../interfaces";
import {
SERVER_URL,
PEER_SERVER_HOST,
PEER_SERVER_PORT,
PEER_SERVER_PATH,
} from "../server";
// @ts-ignore
import Peer from "react-native-peerjs";
import { navigate } from "../helpers/RootNavigation";
import InCallManager from "react-native-incall-manager";
const initialValues: MainContextType = {
username: "",
peerId: "",
users: [],
localStream: null,
remoteStream: null,
remoteUser: null,
initialize: () => {},
setUsername: () => {},
call: () => {},
switchCamera: () => {},
toggleMute: () => {},
isMuted: false,
isShareScreen: false,
toggleSpeacker: () => {},
isSpeaker: true,
swipeWindow: false,
closeCall: () => {},
reset: () => {},
localStreamSet: () => {},
activeCall: null,
};
export const MainContext = React.createContext(initialValues);
interface Props {}
const MainContextProvider: React.FC<Props> = ({ children }) => {
const [username, setUsername] = useState(initialValues.username);
const [peerId, setPeerId] = useState(initialValues.peerId);
const [users, setUsers] = useState<User[]>(initialValues.users);
const [localStream, setLocalStream] = useState<MediaStream | null>(
initialValues.localStream
);
const [remoteStream, setRemoteStream] = useState<MediaStream | null>(
initialValues.remoteStream
);
const [remoteUser, setRemoteUser] = useState<User | null>(null);
const [socket, setSocket] = useState<SocketIOClient.Socket | null>(null);
const [peerServer, setPeerServer] = useState<any>(null);
const [isMuted, setIsMuted] = useState(initialValues.isMuted);
const [swipeWindow, setSwipeWindow] = useState(initialValues.swipeWindow);
const [isShareScreen, setIsShareScreen] = useState(
initialValues.isShareScreen
);
// const localStreamSet=(newstream)=>{
// setLocalStream(null)
// }
const [isSpeaker, setIsSpeaker] = useState(initialValues.isSpeaker);
const [activeCall, setActiveCall] = useState<any>(null);
const initialize = async () => {
const isFrontCamera = true;
const devices = await mediaDevices.enumerateDevices();
const facing = isFrontCamera ? "front" : "environment";
const videoSourceId = devices.find(
(device: any) => device.kind === "videoinput" && device.facing === facing
);
const facingMode = isFrontCamera ? "user" : "environment";
const constraints: MediaStreamConstraints = {
audio: true,
video: {
mandatory: {
minWidth: 1280,
minHeight: 720,
minFrameRate: 30,
},
facingMode,
optional: videoSourceId ? [{ sourceId: videoSourceId }] : [],
},
};
const newStream = await mediaDevices.getUserMedia(constraints);
setLocalStream(newStream as MediaStream);
const io = socketio.connect(SERVER_URL, {
reconnection: true,
autoConnect: true,
});
io.on("connect", () => {
setSocket(io);
io.emit("register", username);
});
io.on("users-change", (users: User[]) => {
setUsers(users);
});
io.on("accepted-call", (user: User) => {
InCallManager.start("video");
setRemoteUser(user);
});
io.on("rejected-call", (user: User) => {
InCallManager.stop();
InCallManager.start();
setRemoteUser(null);
setActiveCall(null);
Alert.alert("Your call request rejected by " + user?.username);
navigate("Users");
});
io.on("not-available", (username: string) => {
setRemoteUser(null);
setActiveCall(null);
Alert.alert(username + " is not available right now");
navigate("Users");
});
const peerServer = new Peer(undefined, {
host: PEER_SERVER_HOST,
path: PEER_SERVER_PATH,
secure: true,
port: PEER_SERVER_PORT,
config: {
iceServers: [
{
urls: [
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302",
],
},
],
},
});
peerServer.on("error", (err: Error) =>
);
peerServer.on("open", (peerId: string) => {
setPeerServer(peerServer);
setPeerId(peerId);
io.emit("set-peer-id", peerId);
});
io.on("call", (user: User) => {
peerServer.on("call", (call: any) => {
InCallManager.startRingtone("_BUNDLE_");
setRemoteUser(user);
Alert.alert(
"New Call",
"You have a new call from " + user?.username,
[
{
text: "Reject",
onPress: () => {
InCallManager.stopRingtone();
InCallManager.stop();
io.emit("reject-call", user?.username);
setRemoteUser(null);
setActiveCall(null);
},
style: "cancel",
},
{
text: "Accept",
onPress: () => {
InCallManager.stopRingtone();
InCallManager.start();
InCallManager.setSpeakerphoneOn(true);
io.emit("accept-call", user?.username);
call.answer(newStream);
setActiveCall(call);
navigate("Call");
},
},
],
{ cancelable: false }
);
call.on("stream", (stream: MediaStream) => {
setRemoteStream(stream);
});
call.on("close", () => {
closeCall();
});
call.on("error", () => {});
});
});
};
const call = (user: User) => {
if (!peerServer || !socket) {
Alert.alert("Peer server or socket connection not found");
return;
}
if (!user.peerId) {
Alert.alert("User not connected to peer server");
return;
}
socket.emit("call", user.username);
setRemoteUser(user);
try {
const call = peerServer.call(user.peerId, localStream);
call.on(
"stream",
(stream: MediaStream) => {
setActiveCall(call);
setRemoteStream(stream);
},
(err: Error) => {
console.error("Failed to get call stream", err);
}
);
} catch (error) {
}
};
const switchCamera = () => {
if (localStream) {
// @ts-ignore
localStream.getVideoTracks().forEach((track) => track._switchCamera());
}
};
const toggleMute = () => {
if (localStream)
localStream.getAudioTracks().forEach((track) => {
track.enabled = !track.enabled;
setIsMuted(!track.enabled);
});
};
const toggleswipeWindow = () => {
var templocalstream = localStream;
var tempremotestream = remoteStream;
if (swipeWindow == false) {
setLocalStream(tempremotestream);
setRemoteStream(templocalstream);
setSwipeWindow(!swipeWindow);
} else {
setLocalStream(tempremotestream);
setRemoteStream(templocalstream);
setSwipeWindow(!swipeWindow);
}
};
const toggleScreenShare = async () => {
if (localStream)
if (isShareScreen == false) {
mediaDevices
.getDisplayMedia({ video: true, audio: true })
.then(handleSuccess, handleError);
InCallManager.setKeepScreenOn(true);
setIsShareScreen(true);
} else {
// localStream.getVideoTracks().forEach((track) => {track.stop()}
ReactNativeForegroundService.stop();
InCallManager.setKeepScreenOn(false);
setIsShareScreen(false);
}
};
const toggleSpeacker = () => {
if (localStream) InCallManager.start();
InCallManager.setSpeakerphoneOn(!isSpeaker);
setIsSpeaker(!isSpeaker);
};
const closeCall = () => {
activeCall?.close();
setActiveCall(null);
setRemoteUser(null);
navigate("Users");
Alert.alert("Call is ended");
};
const reset = async () => {
peerServer?.destroy();
socket?.disconnect();
setActiveCall(null);
setRemoteUser(null);
setLocalStream(null);
setRemoteStream(null);
setUsername("");
setPeerId("");
};
const handleError = async (error) => {
};
const handleSuccess = async (stream) => {
localStream.getVideoTracks().forEach((track) => {
localStream.removeTrack(track);
});
localStream.addTrack(stream.getTracks()[0]);
if (swipeWindow == true) {
setRemoteStream(stream);
} else {
setLocalStream(stream);
}
stream.getVideoTracks()[0].addEventListener("ended", () => {
});
};
return (
<MainContext.Provider
value={{
username,
setUsername,
peerId,
setPeerId,
users,
setUsers,
localStream,
setLocalStream,
remoteStream,
setRemoteStream,
initialize,
call,
switchCamera,
toggleMute,
isMuted,
isSpeaker,
toggleSpeacker,
closeCall,
reset,
remoteUser,
activeCall,
toggleScreenShare,
isShareScreen,
toggleswipeWindow,
swipeWindow,
// localStreamSet
}}
>
{children}
</MainContext.Provider>
);
};
export default MainContextProvider;
Callscreen.js File
import React, {useContext} from 'react';
import {
ActivityIndicator,
Dimensions,
StyleSheet,
Text,
View,
TouchableWithoutFeedback,
TouchableHighlight
} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context';
import {RTCView} from 'react-native-webrtc';
import IconButton from '../components/IconButton';
import icons from '../constants/icons';
import {CallScreenNavigationProp} from '../interfaces/navigation';
import {MainContext} from '../store/MainProvider';
import InCallManager from 'react-native-incall-manager';
const {width, height} = Dimensions.get('window');
import RecordScreen from 'react-native-record-screen';
import Video from 'react-native-video';
interface Props {
navigation: CallScreenNavigationProp;
}
const Call = ({}: Props) => {
const {
localStream,
remoteStream,
activeCall,
remoteUser,
isMuted,
isSpeaker,
toggleSpeacker,
toggleScreenShare,
isShareScreen,
closeCall,
toggleMute,
switchCamera,
swipeWindow,
toggleswipeWindow
} = useContext(MainContext);
React.useEffect(() => {
if (InCallManager.recordPermission !== 'granted') {
InCallManager.requestRecordPermission()
.then((requestedRecordPermissionResult) => {
})
.catch((err) => {
});
}
});
const [recordscreen,setRecordScreen]=React.useState(false)
const btnStyle = React.useMemo(() => {
return recordscreen ? styles.btnActive : styles.btnDefault;
}, [recordscreen]);
const [uri, setUri] = React.useState('');
const _handleOnRecording = async () => {
if (recordscreen) {
setRecordScreen(false);
const res = await RecordScreen.stopRecording().catch((error) =>
console.warn(error)
);
if (res) {
setUri(res.result.outputURL);
}
} else {
setUri('');
setRecordScreen(true);
await RecordScreen.startRecording().catch((error) => {
console.warn(error);
setRecordScreen(false);
setUri('');
});
}
};
return (
<SafeAreaView style={styles.container}>
{uri ? (
<View style={styles.preview}>
<Video
source={{
uri,
}}
style={styles.video}
/>
</View>
) : null}
{remoteStream && (
<RTCView
key={2}
mirror={true}
style={styles.remoteStream}
streamURL={remoteStream.toURL()}
objectFit="cover"
/>
)}
{localStream && (
<View style={styles.myStreamWrapper}>
<TouchableWithoutFeedback onPress={toggleswipeWindow}>
<RTCView
style={styles.myStream}
objectFit="cover"
streamURL={localStream.toURL()}
zOrder={1}
/>
</TouchableWithoutFeedback>
</View>
)}
{!activeCall && (
<View style={styles.spinnerWrapper}>
<ActivityIndicator color="#341EFF" size={120} />
<Text style={styles.callingText}>Calling {remoteUser?.username}</Text>
</View>
)}
<View style={styles.iconsWrapper}>
<IconButton
icon={icons.CHANGE_CAMERA}
onPress={switchCamera}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
{isMuted ? (
<IconButton
icon={icons.UNMUTE}
onPress={toggleMute}
iconColor={'#fff'}
backgroundColor="red"
/>
) : (
<IconButton
icon={icons.MUTE}
onPress={toggleMute}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
)}
{isSpeaker ? (
<IconButton
icon={icons.SPEAKERON}
onPress={toggleSpeacker}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
) : (
<IconButton
icon={icons.SPEAKEROFF}
onPress={toggleSpeacker}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
)}
{recordscreen ? (
<IconButton
icon={icons.RECORDING}
onPress={_handleOnRecording}
iconColor={'#fff'}
backgroundColor="red"
/>
) : (
<IconButton
icon={icons.RECORDING}
onPress={_handleOnRecording}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
)}
{isShareScreen ? (
<IconButton
icon={icons.SCREENSHARE}
onPress={toggleScreenShare}
iconColor={'#fff'}
backgroundColor="red"
/>
) : (
<IconButton
icon={icons.SCREENSHARE}
onPress={toggleScreenShare}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
)}
<IconButton
icon={icons.END_CALL}
onPress={closeCall}
iconColor={'#fff'}
backgroundColor="red"
/>
</View>
</SafeAreaView>
);
};
export default Call;
const styles = StyleSheet.create({
preview: {
position: 'absolute',
right: 12,
bottom: 116,
width: Dimensions.get('window').width / 2,
height: Dimensions.get('window').height / 3,
zIndex: 1,
padding: 8,
backgroundColor: '#aaa',
},
video: {
flex: 1,
},
container: {
backgroundColor: '#0f0f0f',
flex: 1,
position: 'relative',
},
btnDefault: {
width: 48,
height: 48,
backgroundColor: '#fff',
borderRadius: 24,
borderWidth: 4,
borderStyle: 'solid',
borderColor: '#212121',
},
btnActive: {
width: 36,
height: 36,
backgroundColor: 'red',
borderRadius: 8,
},
myStream: {
height: width * 0.6,
width: width * 0.4,
},
myStreamWrapper: {
position: 'absolute',
bottom: 20,
right: 20,
height: width * 0.6 + 8,
width: width * 0.4 + 8,
backgroundColor: '#333',
borderRadius: 12,
overflow: 'hidden',
justifyContent: 'center',
alignItems: 'center',
},
remoteStreamWrapper: {},
remoteStream: {
width: '100%',
height: '100%',
},
spinnerWrapper: {
top: height * 0.3,
width: '100%',
justifyContent: 'center',
alignItems: 'center',
},
callingText: {
fontSize: 26,
color: '#fff',
},
iconsWrapper: {
position: 'absolute',
bottom: 20,
left: 20,
},
});
Socket Server.js
//socketio
const socketio = require("socket.io");
class SocketService {
io;
constructor() {
this.io = null;
}
listen = (server) => {
this.io = socketio(server);
this.io.users = {};
this.io.on("connection", (socket) => {
socket.on("register", (username) => this.onRegister(socket, username));
socket.on("set-peer-id", (peerId) => this.onSetPeerId(socket, peerId));
socket.on("call", (username) => this.onCall(socket, username));
socket.on("reject-call", (username) =>
this.onRejectCall(socket, username)
);
socket.on("accept-call", (username) =>
this.onAcceptCall(socket, username)
);
console.log(`${Date(Date.now()).toLocaleString()}: new user connected`);
socket.on("disconnect", () => this.onDisconnect(socket));
});
};
onAcceptCall = (socket, username) => {
if (this.io.users[username])
this.io
.to(this.io.users[username].socketId)
.emit("accepted-call", this.io.users[socket.username]);
};
onRejectCall = (socket, username) => {
if (this.io.users[username]) {
this.io
.to(this.io.users[username].socketId)
.emit("rejected-call", this.io.users[socket.username]);
}
};
onCall = (socket, username) => {
if (this.io.users[username]) {
this.io
.to(this.io.users[username].socketId)
.emit("call", this.io.users[socket.username]);
} else {
socket.emit("not-available", username);
}
};
onRegister = (socket, username) => {
console.log("Registered", username);
socket.username = username;
this.io.users[username] = {
username,
peerId: "",
socketId: socket.id,
};
this.onUsersChange(socket);
};
getUsers = () => {
const users = [];
Object.keys(this.io.users).forEach((key) => {
users.push(this.io.users[key]);
});
return users;
};
onUsersChange = (socket) => {
this.io.emit("users-change", this.getUsers());
};
onSetPeerId = (socket, peerId) => {
console.log("Set Peer Id user:", socket.username, " peerId: ", peerId);
this.io.users[socket.username] = {
peerId,
socketId: socket.id,
username: socket.username,
};
this.onUsersChange();
};
onDisconnect = (socket) => {
delete this.io.users[socket.username];
console.log(
`${Date(Date.now()).toLocaleString()} ID:${
socket.username
} user disconnected`
);
this.onUsersChange();
};
emit = (event, userId, data) => {
if (this.io.users[userId]) {
this.io.to(this.io.users[userId]).emit(event, data);
}
};
}
module.exports = new SocketService();
Peer Server.js (2nd API server in Node js)
require('dotenv').config()
const express = require("express");
const { ExpressPeerServer } = require("peer");
const app = express();
app.get("/", (req, res, next) => res.send("Hello world!"));
const http = require("http");
const server = http.createServer(app);
const peerServer = ExpressPeerServer(server, {
debug: true,
path: "/",
});
app.use("/peerjs", peerServer);
server.listen(process.env.PORT || 9000);