Error: The remote description was null react-native-webrtc

109 Views Asked by At

When i click start meeting, i see the following error in terminal. When i call first time i dont see error, but after ending call when i start meeting second time then i see this error:

 WARN Possible Unhandled Promise Rejection (id: 115):

Error: The remote description was null

after this error when other user joins call he get's thefollowing error: Your app just crashed. See the error below. java.lang.NullPointerException: Attempt to invoke virtual method 'org.webrtc.PeerConnection com.oney.WebRTCModule.PeerConnectionObserver.getPeerConnection()' on a null object reference com.oney.WebRTCModule.WebRTCModule.lambda$peerConnectionSetRemoteDescription$25$com-oney-WebRTCModule-WebRTCModule(WebRTCModule.java:1051) com.oney.WebRTCModule.WebRTCModule$$ExternalSyntheticLambda21.run(Unknown Source:8) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) java.lang.Thread.run(Thread.java:1012)

I do i get rid of these errors

Here're my Video Call Components:

JoinScreen.jsx :

import React, { useState, useEffect } from "react";
import { Text, StyleSheet, Button, View } from "react-native";

import {
 RTCPeerConnection,
 RTCView,
 mediaDevices,
 RTCIceCandidate,
 RTCSessionDescription,
 MediaStream,
} from "react-native-webrtc";
import { db } from "../firebase";
import {
 addDoc,
 collection,
 doc,
 setDoc,
 getDoc,
 updateDoc,
 onSnapshot,
 deleteField,
} from "firebase/firestore";
import CallActionBox from "./CallActionBox";

const configuration = {
 iceServers: [
  {
   urls: ["stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"],
  },
 ],
 iceCandidatePoolSize: 10,
};

export default function JoinScreen({ roomId, screens, setScreen }) {
 const [localStream, setLocalStream] = useState();
 const [remoteStream, setRemoteStream] = useState();
 const [cachedLocalPC, setCachedLocalPC] = useState();

 const [isMuted, setIsMuted] = useState(false);
 const [isOffCam, setIsOffCam] = useState(false);

 //Automatically start stream
 useEffect(() => {
  startLocalStream();
 }, []);
 useEffect(() => {
  if (localStream) {
   joinCall(roomId);
  }
 }, [localStream]);

 //End call button
 async function endCall() {
  if (cachedLocalPC) {
   const senders = cachedLocalPC.getSenders();
   senders.forEach((sender) => {
    cachedLocalPC.removeTrack(sender);
   });
   cachedLocalPC.close();
  }

  const roomRef = doc(db, "room", roomId);
  await updateDoc(roomRef, { answer: deleteField(), connected: false });

  setLocalStream();
  setRemoteStream(); // set remoteStream to null or empty when callee leaves the call
  setCachedLocalPC();
  // cleanup
  setScreen(screens.ROOM); //go back to room screen
 }

 //start local webcam on your device
 const startLocalStream = async () => {
  // isFront will determine if the initial camera should face user or environment
  const isFront = true;
  const devices = await mediaDevices.enumerateDevices();

  const facing = isFront ? "front" : "environment";
  const videoSourceId = devices.find(
   (device) => device.kind === "videoinput" && device.facing === facing
  );
  const facingMode = isFront ? "user" : "environment";
  const constraints = {
   audio: true,
   video: {
    mandatory: {
     minWidth: 500, // Provide your own width, height and frame rate here
     minHeight: 300,
     minFrameRate: 30,
    },
    facingMode,
    optional: videoSourceId ? [{ sourceId: videoSourceId }] : [],
   },
  };
  const newStream = await mediaDevices.getUserMedia(constraints);
  setLocalStream(newStream);
 };

 //join call function
 const joinCall = async (id) => {
  const roomRef = doc(db, "room", id);
  const roomSnapshot = await getDoc(roomRef);

  if (!roomSnapshot.exists) return;
  const localPC = new RTCPeerConnection(configuration);
  localStream.getTracks().forEach((track) => {
   localPC.addTrack(track, localStream);
  });

  const callerCandidatesCollection = collection(roomRef, "callerCandidates");
  const calleeCandidatesCollection = collection(roomRef, "calleeCandidates");

  localPC.addEventListener("icecandidate", (e) => {
   if (!e.candidate) {
    console.log("Got final candidate!");
    return;
   }
   addDoc(calleeCandidatesCollection, e.candidate.toJSON());
  });

  localPC.ontrack = (e) => {
   const newStream = new MediaStream();
   e.streams[0].getTracks().forEach((track) => {
    newStream.addTrack(track);
   });
   setRemoteStream(newStream);
  };

  const offer = roomSnapshot.data().offer;
  await localPC.setRemoteDescription(new RTCSessionDescription(offer));

  const answer = await localPC.createAnswer();
  await localPC.setLocalDescription(answer);

  await updateDoc(roomRef, { answer, connected: true }, { merge: true });

  onSnapshot(callerCandidatesCollection, (snapshot) => {
   snapshot.docChanges().forEach((change) => {
    if (change.type === "added") {
     let data = change.doc.data();
     localPC.addIceCandidate(new RTCIceCandidate(data));
    }
   });
  });

  onSnapshot(roomRef, (doc) => {
   const data = doc.data();
   if (!data.answer) {
    setScreen(screens.ROOM);
   }
  });

  setCachedLocalPC(localPC);
 };

 const switchCamera = () => {
  localStream.getVideoTracks().forEach((track) => track._switchCamera());
 };

 // Mutes the local's outgoing audio
 const toggleMute = () => {
  if (!remoteStream) {
   return;
  }
  localStream.getAudioTracks().forEach((track) => {
   track.enabled = !track.enabled;
   setIsMuted(!track.enabled);
  });
 };

 const toggleCamera = () => {
  localStream.getVideoTracks().forEach((track) => {
   track.enabled = !track.enabled;
   setIsOffCam(!isOffCam);
  });
 };

 return (
  <View className="flex-1">
   <RTCView
    className="flex-1"
    streamURL={remoteStream && remoteStream.toURL()}
    objectFit={"cover"}
   />

   {remoteStream && !isOffCam && (
    <RTCView
     className="w-32 h-48 absolute right-6 top-8"
     streamURL={localStream && localStream.toURL()}
    />
   )}
   <View className="absolute bottom-0 w-full">
    <CallActionBox
     switchCamera={switchCamera}
     toggleMute={toggleMute}
     toggleCamera={toggleCamera}
     endCall={endCall}
    />
   </View>
  </View>
 );
}

RoomScreen.jsx :

import React, { useEffect, useState } from "react";
import { Text, View, TextInput, TouchableOpacity, Alert } from "react-native";

import { db } from "../firebase";
import {
 addDoc,
 collection,
 doc,
 setDoc,
 getDoc,
 updateDoc,
 onSnapshot,
 deleteField,
} from "firebase/firestore";
import { useDataContext } from "./DataContext";
import AsyncStorage from "@react-native-async-storage/async-storage";

export default function RoomScreen({ setScreen, screens, setRoomId, roomId }) {
 const { userData, currentUserData, videoCallEmail, setVideoCallEmail, serverAddress, fetchCurrentUserByEmail } = useDataContext()
 const onCallOrJoin = async (screen) => {
  if (roomId.length > 0) {
   setScreen(screen);
  }
  try {
   const response = await fetch(`${serverAddress}/api/update-receiving-call/${videoCallEmail}`, {
    method: 'PUT',
    headers: {
     'Content-Type': 'application/json',
    },
   });
   if (response.ok) {
    console.log('Updated Receiving Call of another user');
   } else {
    console.error('Error updating receiving call status');
   }
  } catch (error) {
   console.error('Error updating receiving call status:', error);
  }
 };

 //checks if room is existing
 const checkMeeting = async () => {
  if (roomId) {
   const roomRef = doc(db, "room", roomId);
   const roomSnapshot = await getDoc(roomRef);

   // console.log(roomSnapshot.data());

   if (!roomSnapshot.exists() || roomId === "") {
    // console.log(`Room ${roomId} does not exist.`);
    Alert.alert("Wait for your instructor to start the meeting.");
    return;
   } else {
    onCallOrJoin(screens.JOIN);
   }
  } else {
   Alert.alert("Provide a valid Room ID.");
  }
 };

 return (
  <View>
   {/* <Text className="text-2xl font-bold text-center">Enter Room ID:</Text>
   <TextInput
    className="bg-white border-sky-600 border-2 mx-5 my-3 p-2 rounded-md"
    value={roomId}
    onChangeText={setRoomId}
   /> */}
   <View className="gap-y-3 mx-5 mt-2">
    <TouchableOpacity
     className="bg-sky-300 p-2 rounded-md"
     onPress={() => onCallOrJoin(screens.CALL)}
    >
     <Text className="color-black text-center text-xl font-bold ">
      Start meeting
     </Text>
    </TouchableOpacity>
    {
     (currentUserData.email == videoCallEmail) && 
     <TouchableOpacity
      className="bg-sky-300 p-2 rounded-md"
      onPress={() => checkMeeting()}
     >
      <Text className="color-black text-center text-xl font-bold ">
       Join meeting
      </Text>
     </TouchableOpacity>
    }
   </View>
  </View>
 );
}

VideoCall.jsx :

import React, { useState } from "react";
import { Text, SafeAreaView } from "react-native";
import RoomScreen from "./RoomScreen";
import CallScreen from "./CallScreen";
import JoinScreen from "./JoinScreen";

// Just to handle navigation
export default function VideoCall() {
 const screens = {
  ROOM: "JOIN_ROOM",
  CALL: "CALL",
  JOIN: "JOIN",
 };

 const [screen, setScreen] = useState(screens.ROOM);
 const [roomId, setRoomId] = useState("myroom");

 let content;

 switch (screen) {
  case screens.ROOM:
   content = (
    <RoomScreen
     roomId={roomId}
     setRoomId={setRoomId}
     screens={screens}
     setScreen={setScreen}
    />
   );
   break;

  case screens.CALL:
   content = (
    <CallScreen roomId={roomId} screens={screens} setScreen={setScreen} />
   );
   break;

  case screens.JOIN:
   content = (
    <JoinScreen roomId={roomId} screens={screens} setScreen={setScreen} />
   );
   break;

  default:
   content = <Text>Wrong Screen</Text>;
 }

 return (
  <SafeAreaView className="flex-1 justify-center ">{content}</SafeAreaView>
 );
}

I'm using expo 49 with firebase

0

There are 0 best solutions below