import React from 'react';
import { useEffect, useRef, useState } from 'react';
import { SafeAreaView, StatusBar, StyleSheet, View } from 'react-native';
import {
RTCPeerConnection,
RTCIceCandidate,
RTCSessionDescription,
mediaDevices,
MediaStream,
} from 'react-native-webrtc';
import GettingCall from './GettingCall';
import VideoButton from './VideoButton';
import Video from './Video';
import firestore from '@react-native-firebase/firestore';
const peerConstraints = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' },
{ urls: 'stun:stun3.l.google.com:19302' },
{ urls: 'stun:stun4.l.google.com:19302' },
{
urls: 'turn:turn.bistri.com:80',
credential: 'homeo',
username: 'homeo',
},
],
};
function VideoInitial() {
let localStream = useRef<MediaStream>();
let remoteStream = useRef<MediaStream>();
const [local, setLocal] = useState<string | null>(null);
const [remote, setRemote] = useState<string | null>(null);
const [gettingCall, setGettingCall] = useState(false);
const connecting = useRef(false);
let pc = useRef<RTCPeerConnection>();
// Global state
useEffect(() => {
const cRef = firestore().collection('meet').doc('chatId');
const subscribe = cRef.onSnapshot((snapshot) => {
const data = snapshot.data();
if (pc.current && !pc.current.remoteDescription && data && data.answer) {
pc.current.setRemoteDescription(new RTCSessionDescription(data.answer));
}
if (data && data.offer && !connecting.current) {
setGettingCall(true);
}
});
const subscribeDelete = cRef.collection('callee').onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === 'removed') {
hangup();
}
});
});
return () => {
subscribe();
subscribeDelete();
};
}, []);
async function setupWebrtc() {
pc.current = new RTCPeerConnection(peerConstraints);
localStream.current = await getStream();
if (localStream.current) {
setLocal(localStream.current.toURL());
localStream.current.getTracks().forEach((track) => {
localStream.current && pc.current?.addTrack(track, localStream.current);
});
}
remoteStream.current = new MediaStream(undefined);
pc.current.ontrack = (event: any) => {
if (remoteStream.current) {
remoteStream.current?.addTrack(event.track);
}
};
}
async function create() {
console.log('calling');
connecting.current = true;
await setupWebrtc();
const cRef = firestore().collection('meet').doc('chatId');
collectIceCandidates(cRef, 'caller', 'callee');
if (pc.current) {
console.log('create');
try {
let sessionConstraints = {
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true,
},
};
const offerDescription = await pc.current.createOffer(
sessionConstraints,
);
await pc.current.setLocalDescription(offerDescription);
const cWithOffer = {
offer: {
type: offerDescription.type,
sdp: offerDescription.sdp,
},
};
await cRef.set(cWithOffer);
} catch (error) {
console.log('error', error);
}
}
}
const join = async () => {
console.log('Joining the call');
connecting.current = true;
setGettingCall(false);
const cRef = firestore().collection('meet').doc('chatId');
const offer = (await cRef.get()).data()?.offer;
if (offer) {
await setupWebrtc();
collectIceCandidates(cRef, 'callee', 'caller');
if (pc.current) {
await pc.current.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await pc.current.createAnswer();
await pc.current.setLocalDescription(answer);
const cWithAnswer = {
answer: {
type: answer.type,
sdp: answer.sdp,
},
};
await cRef.update(cWithAnswer);
}
}
};
async function hangup() {
console.log('hangup');
setGettingCall(false);
connecting.current = false;
streamCleanUp();
firebaseCleanUp();
if (pc.current) {
pc.current.close();
}
}
async function getStream() {
let isVoiceOnly = false;
let mediaConstraints = {
audio: true,
video: {
frameRate: 30,
facingMode: 'user',
},
};
try {
const mediaStream = await mediaDevices.getUserMedia(mediaConstraints);
if (isVoiceOnly) {
let videoTrack = mediaStream.getVideoTracks()[0];
videoTrack.enabled = false;
}
return mediaStream;
} catch (err) {
console.log('err', err);
}
}
async function streamCleanUp() {
console.log('streamCleanUp');
if (localStream.current) {
localStream.current.getTracks().forEach((t) => t.stop());
localStream.current.release();
}
setLocal(null);
setRemote(null);
}
async function firebaseCleanUp() {
console.log('firebaseCleanUp');
const cRef = firestore().collection('meet').doc('chatId');
if (cRef) {
const callee = await cRef.collection('callee').get();
callee.forEach(async (candidate) => {
await candidate.ref.delete();
});
const caller = await cRef.collection('caller').get();
caller.forEach(async (candidate) => {
await candidate.ref.delete();
});
await cRef.delete();
}
}
function collectIceCandidates(cRef, localName, remoteName) {
console.log('localName', localName);
const candidateCollection = cRef.collection(localName);
if (pc.current) {
pc.current.onicecandidate = (event: any) => {
if (event.candidate && pc.current?.remoteDescription === null) {
candidateCollection.add(event.candidate.toJSON());
}
};
cRef.collection(remoteName).onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === 'added') {
const candidate = new RTCIceCandidate(change.doc.data());
pc.current?.addIceCandidate(candidate);
}
});
});
pc.current.oniceconnectionstatechange = () => {
if (
pc.current?.iceConnectionState === 'failed' ||
pc.current?.iceConnectionState === 'disconnected' ||
pc.current?.iceConnectionState === 'closed'
) {
console.log('failed');
} else if (pc.current?.iceConnectionState === 'checking') {
console.log('checking');
} else {
console.log('success');
remoteStream.current && setRemote(remoteStream.current?.toURL());
}
};
}
}
if (gettingCall) {
console.log('gettingCall');
return <GettingCall hangup={hangup} join={join} />;
}
if (local) {
console.log('localStream');
return <Video hangup={hangup} local={local} remote={remote} />;
}
return (
<SafeAreaView style={{ flex: 1 }}>
<StatusBar barStyle="dark-content" />
<View style={styles.container}>
<VideoButton iconName="video" backgroundColor="grey" onPress={create} />
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
export default VideoInitial;
I am working on simple webrtc and tried this code, but iceconnectionstate is not updated from checking to completed, so can’t show remote video stream.
Any help?