react-nativesocket.ioexpovideocallreact-native-webrtc

React-native WebRTC with socket.io on Expo development build does not work as expected


i tyry to develop videocall on react-native expo on development build refer from old code and i just update react-native WebRTC version from 1.9.4 to 111.0.3 and then my remoteStream does not take value but my localStream still take value and show local camera only i dunno what they change code between version and because i use expo SDK 49 and i need to upgrade react-native WebRTC based on their document here my code

import React, { useEffect, useState, useRef } from "react";
import {
  Platform,
  KeyboardAvoidingView,
  TouchableWithoutFeedback,
  Keyboard,
  View,
  Text,
  TouchableOpacity,
} from "react-native";
import TextInputContainer from "./components/TextInputContainer";
import SocketIOClient from "socket.io-client";
import {
  mediaDevices,
  RTCPeerConnection,
  RTCView,
  RTCIceCandidate,
  RTCSessionDescription,
} from "react-native-webrtc";
import CallEnd from "./assets/CallEnd";
import CallAnswer from "./assets/CallAnswer";
import MicOn from "./assets/MicOn";
import MicOff from "./assets/MicOff";
import VideoOn from "./assets/VideoOn";
import VideoOff from "./assets/VideoOff";
import CameraSwitch from "./assets/CameraSwitch";
import IconContainer from "./components/IconContainer";
import InCallManager from "react-native-incall-manager";

export default function App({}) {
  const [localStream, setlocalStream] = useState(null);

  const [remoteStream, setRemoteStream] = useState(null);

  const [type, setType] = useState("JOIN");

  const [callerId] = useState(
    Math.floor(100000 + Math.random() * 900000).toString()
  );
  const otherUserId = useRef(null);

  const socket = SocketIOClient("http://192.168.68.79:3500", {
    transports: ["websocket"],
    query: {
      callerId,
    },
  });

  const [localMicOn, setlocalMicOn] = useState(true);

  const [localWebcamOn, setlocalWebcamOn] = useState(true);

  const peerConnection = useRef(
    new RTCPeerConnection({
      iceServers: [
        {
          urls: "stun:stun.l.google.com:19302",
        },
        {
          urls: "stun:stun1.l.google.com:19302",
        },
        {
          urls: "stun:stun2.l.google.com:19302",
        },
      ],
    })
  );

  let remoteRTCMessage = useRef(null);

  useEffect(() => {
    socket.on("newCall", (data) => {
      remoteRTCMessage.current = data.rtcMessage;
      otherUserId.current = data.callerId;
      setType("INCOMING_CALL");
    });

    socket.on("callAnswered", (data) => {
      remoteRTCMessage.current = data.rtcMessage;
      peerConnection.current.setRemoteDescription(
        new RTCSessionDescription(remoteRTCMessage.current)
      );
      setType("WEBRTC_ROOM");
    });

    socket.on("ICEcandidate", (data) => {
      let message = data.rtcMessage;

      if (peerConnection.current) {
        peerConnection?.current
          .addIceCandidate(
            new RTCIceCandidate({
              candidate: message.candidate,
              sdpMid: message.id,
              sdpMLineIndex: message.label,
            })
          )
          .then((data) => {
            console.log("SUCCESS");
          })
          .catch((err) => {
            console.log("Error", err);
          });
      }
    });

    let isFront = false;

    mediaDevices.enumerateDevices().then((sourceInfos) => {
      let videoSourceId;
      for (let i = 0; i < sourceInfos.length; i++) {
        const sourceInfo = sourceInfos[i];
        if (
          sourceInfo.kind == "videoinput" &&
          sourceInfo.facing == (isFront ? "user" : "environment")
        ) {
          videoSourceId = sourceInfo.deviceId;
        }
      }

      mediaDevices
      .getUserMedia({
        audio: true,
        video: {
          mandatory: {
            minWidth: 500, // Provide your own width, height and frame rate here
            minHeight: 300,
            minFrameRate: 30,
          },
          facingMode: isFront ? 'user' : 'environment',
          optional: videoSourceId ? [{sourceId: videoSourceId}] : [],
        },
      })
      .then(stream => {
        // Got stream!

        setlocalStream(stream);

        // setup stream listening
        peerConnection.current.addStream(stream);
      })
      .catch(error => {
        // Log error
      });
  });

  peerConnection.current.onaddtrack = event => {
    setRemoteStream(event.stream);
  };

    // Setup ice handling
    peerConnection.current.onicecandidate = event => {
      if (event.candidate) {
        sendICEcandidate({
          calleeId: otherUserId.current,
          rtcMessage: {
            label: event.candidate.sdpMLineIndex,
            id: event.candidate.sdpMid,
            candidate: event.candidate.candidate,
          },
        });
      } else {
        console.log('End of candidates.');
      }
    };


    return () => {
      socket.off("newCall");
      socket.off("callAnswered");
      socket.off("ICEcandidate");
    };
  }, []);

  useEffect(() => {
    InCallManager.start();
    InCallManager.setKeepScreenOn(true);
    InCallManager.setForceSpeakerphoneOn(true);

    return () => {
      InCallManager.stop();
    };
  }, []);

  function sendICEcandidate(data) {
    socket.emit("ICEcandidate", data);
  }

  async function processCall() {
    const sessionDescription = await peerConnection.current.createOffer();
    await peerConnection.current.setLocalDescription(sessionDescription);
    sendCall({
      calleeId: otherUserId.current,
      rtcMessage: sessionDescription,
    });
  }

  async function processAccept() {
    peerConnection.current.setRemoteDescription(
      new RTCSessionDescription(remoteRTCMessage.current)
    );
    const sessionDescription = await peerConnection.current.createAnswer();
    await peerConnection.current.setLocalDescription(sessionDescription);
    answerCall({
      callerId: otherUserId.current,
      rtcMessage: sessionDescription,
    });
  }

  function answerCall(data) {
    socket.emit("answerCall", data);
  }

  function sendCall(data) {
    socket.emit("call", data);
  }

  const JoinScreen = () => {
    return (
      <KeyboardAvoidingView
        behavior={Platform.OS === "ios" ? "padding" : "height"}
        style={{
          flex: 1,
          backgroundColor: "#050A0E",
          justifyContent: "center",
          paddingHorizontal: 42,
        }}
      >
        <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
          <>
            <View
              style={{
                padding: 35,
                backgroundColor: "#1A1C22",
                justifyContent: "center",
                alignItems: "center",
                borderRadius: 14,
              }}
            >
              <Text
                style={{
                  fontSize: 18,
                  color: "#D0D4DD",
                }}
              >
                Your Caller ID
              </Text>
              <View
                style={{
                  flexDirection: "row",
                  marginTop: 12,
                  alignItems: "center",
                }}
              >
                <Text
                  style={{
                    fontSize: 32,
                    color: "#ffff",
                    letterSpacing: 6,
                  }}
                >
                  {callerId}
                </Text>
              </View>
            </View>

            <View
              style={{
                backgroundColor: "#1A1C22",
                padding: 40,
                marginTop: 25,
                justifyContent: "center",
                borderRadius: 14,
              }}
            >
              <Text
                style={{
                  fontSize: 18,
                  color: "#D0D4DD",
                }}
              >
                Enter call id of another user
              </Text>
              <TextInputContainer
                placeholder={"Enter Caller ID"}
                value={otherUserId.current}
                setValue={(text) => {
                  otherUserId.current = text;
                  console.log("TEST", otherUserId.current);
                }}
                keyboardType={"number-pad"}
              />
              <TouchableOpacity
                onPress={() => {
                  setType("OUTGOING_CALL");
                  processCall();
                }}
                style={{
                  height: 50,
                  backgroundColor: "#5568FE",
                  justifyContent: "center",
                  alignItems: "center",
                  borderRadius: 12,
                  marginTop: 16,
                }}
              >
                <Text
                  style={{
                    fontSize: 16,
                    color: "#FFFFFF",
                  }}
                >
                  Call Now
                </Text>
              </TouchableOpacity>
            </View>
          </>
        </TouchableWithoutFeedback>
      </KeyboardAvoidingView>
    );
  };

  const OutgoingCallScreen = () => {
    return (
      <View
        style={{
          flex: 1,
          justifyContent: "space-around",
          backgroundColor: "#050A0E",
        }}
      >
        <View
          style={{
            padding: 35,
            justifyContent: "center",
            alignItems: "center",
            borderRadius: 14,
          }}
        >
          <Text
            style={{
              fontSize: 16,
              color: "#D0D4DD",
            }}
          >
            Calling to...
          </Text>

          <Text
            style={{
              fontSize: 36,
              marginTop: 12,
              color: "#ffff",
              letterSpacing: 6,
            }}
          >
            {otherUserId.current}
          </Text>
        </View>
        <View
          style={{
            justifyContent: "center",
            alignItems: "center",
          }}
        >
          <TouchableOpacity
            onPress={() => {
              setType("JOIN");
              otherUserId.current = null;
            }}
            style={{
              backgroundColor: "#FF5D5D",
              borderRadius: 30,
              height: 60,
              aspectRatio: 1,
              justifyContent: "center",
              alignItems: "center",
            }}
          >
            <CallEnd width={50} height={12} />
          </TouchableOpacity>
        </View>
      </View>
    );
  };

  const IncomingCallScreen = () => {
    return (
      <View
        style={{
          flex: 1,
          justifyContent: "space-around",
          backgroundColor: "#050A0E",
        }}
      >
        <View
          style={{
            padding: 35,
            justifyContent: "center",
            alignItems: "center",
            borderRadius: 14,
          }}
        >
          <Text
            style={{
              fontSize: 36,
              marginTop: 12,
              color: "#ffff",
            }}
          >
            {otherUserId.current} is calling..
          </Text>
        </View>
        <View
          style={{
            justifyContent: "center",
            alignItems: "center",
          }}
        >
          <TouchableOpacity
            onPress={() => {
              processAccept();
              setType("WEBRTC_ROOM");
            }}
            style={{
              backgroundColor: "green",
              borderRadius: 30,
              height: 60,
              aspectRatio: 1,
              justifyContent: "center",
              alignItems: "center",
            }}
          >
            <CallAnswer height={28} fill={"#fff"} />
          </TouchableOpacity>
        </View>
      </View>
    );
  };

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

  function toggleCamera() {
    localWebcamOn ? setlocalWebcamOn(false) : setlocalWebcamOn(true);
    localStream.getVideoTracks().forEach((track) => {
      localWebcamOn ? (track.enabled = false) : (track.enabled = true);
    });
  }

  function toggleMic() {
    localMicOn ? setlocalMicOn(false) : setlocalMicOn(true);
    localStream.getAudioTracks().forEach((track) => {
      localMicOn ? (track.enabled = false) : (track.enabled = true);
    });
  }

  function leave() {
    peerConnection.current.close();
    setlocalStream(null);
    setType("JOIN");
  }

  const WebrtcRoomScreen = () => {
    console.log("Entering WebrtcRoomScreen"); 
    console.log("localStream:", localStream); // Log localStream
  console.log("remoteStream:", remoteStream); // Log remoteStream
    return (
      <View
        style={{
          flex: 1,
          backgroundColor: "#050A0E",
          paddingHorizontal: 12,
          paddingVertical: 12,
        }}
      >
        {localStream ? (
          <RTCView
            objectFit={"cover"}
            style={{ flex: 1, backgroundColor: "#050A0E" }}
            streamURL={localStream.toURL()}
            
          />
        ) : null}
        {remoteStream ? (
          <RTCView
            objectFit={"cover"}
            style={{
              flex: 1,
              backgroundColor: "#050A0E",
              marginTop: 8,
            }}
            streamURL={remoteStream.toURL()}
          />
        ) : null}
        <View
          style={{
            marginVertical: 12,
            flexDirection: "row",
            justifyContent: "space-evenly",
          }}
        >
          <IconContainer
            backgroundColor={"red"}
            onPress={() => {
              leave();
              console.log("Exiting WebrtcRoomScreen");
            }}
            Icon={() => {
              return <CallEnd height={26} width={26} fill="#FFF" />;
            }}
          />
          <IconContainer
            style={{
              borderWidth: 1.5,
              borderColor: "#2B3034",
            }}
            backgroundColor={!localMicOn ? "#fff" : "transparent"}
            onPress={() => {
              toggleMic();
            }}
            Icon={() => {
              return localMicOn ? (
                <MicOn height={24} width={24} fill="#FFF" />
              ) : (
                <MicOff height={28} width={28} fill="#1D2939" />
              );
            }}
          />
          <IconContainer
            style={{
              borderWidth: 1.5,
              borderColor: "#2B3034",
            }}
            backgroundColor={!localWebcamOn ? "#fff" : "transparent"}
            onPress={() => {
              toggleCamera();
            }}
            Icon={() => {
              return localWebcamOn ? (
                <VideoOn height={24} width={24} fill="#FFF" />
              ) : (
                <VideoOff height={36} width={36} fill="#1D2939" />
              );
            }}
          />
          <IconContainer
            style={{
              borderWidth: 1.5,
              borderColor: "#2B3034",
            }}
            backgroundColor={"transparent"}
            onPress={() => {
              switchCamera();
            }}
            Icon={() => {
              return <CameraSwitch height={24} width={24} fill="#FFF" />;
            }}
          />
        </View>
      </View>
    );
  };

  switch (type) {
    case "JOIN":
      return JoinScreen();
    case "INCOMING_CALL":
      return IncomingCallScreen();
    case "OUTGOING_CALL":
      return OutgoingCallScreen();
    case "WEBRTC_ROOM":
      return WebrtcRoomScreen();
    default:
      return null;
  }
}

my localstream show log as

localStream: {
    "_id": "72e3117e-7d92-47eb-99b7-116f2eccfdd2",
    "_reactTag": "72e3117e-7d92-47eb-99b7-116f2eccfdd2",
    "_tracks": [
        {
            "_constraints": [Object
            ],
            "_enabled": true,
            "_muted": false,
            "_peerConnectionId": undefined,
            "_readyState": "live",
            "_settings": [Object
            ],
            "id": "5afba900-ive",
            "_settings": [Object
            ],
            "id": "5afba900-e6d9-4e52-8a7b-ca39e8f11c1b",
            "kind": "audio",
            "label": "",
            "remote": false
        },
        {
            "_constraints": [Object
            ],
            "_enabled": true,
            "_muted": false,
            "_peerConnectionId": undefined,
            "_readyState": "live",
            "_s-83e9-fc94fc807e2e",
            "kind": "video",
            "label"ettings": [Object], "id": "0247ede1-9d94-47e2-83e9-fc94fc807e2e", "kind": "video", "label": "", "remote": false}]}

and remotestream should be

remoteStream: {
                "_reactTag": "6d42bef0-e985-4a62-bd04-06165fd129f5",
                "_tracks": [
                    {
                        "_constraints": [Object
                        ],
                        "_enabled": true,
                        "_muted": false,
                        "_settings": [Object
                        ],
                        "id": "5bf76128-68f1-47a3-9123-de96e1d8139a",
                        "kind": "video",
                        "label": "Video",
                        "readyState": "live",
                        "remote": true
                    },
                    {
                        "_constraints": [Object
                        ],
                        "_enabled": true,
                        "_muted": false,
                        "_settings": [Object
                        ],
                        "id": "1edeb494-0445-4b98-828e-d025fe21c2f3",
                        "kind": "audio",
                        "label": "Audio",
                        "readyState": "live",
                        "remote": true
                    }
                ],
                "active": true,
                "id": "6645bf07-f49d-4bd4-81a1-52a23985019b"
            }

but after i upgrade version remotestream only show null in log pls help

i already read document WebRTC and socket.io but get confused so can someone give advice for what i need to looking for my project


Solution

  • in new version of WebRTC you must use addTrack instead of addStream