*************************VIDEO CONFERENCE PAGE ***************************

 HERE, JUST LIKE THE PREVIOUS ONE WE MADE A VIDEO CALL FUNCTION USING THE SAME STEPS IN DIFFERENT FUNCTIONS.

TO GET MEDIA - GETPERMISSIONS()

TO MAKE RTC PEER CONNECTION, CREATING OFFER,EMITTING IT  -                                    CONNECTTOSOCKETSERVER()   

RECEIVING OFFER, CREATING ANSWER, SENDING ANSWER,HANDLING ICE CANDIDATE -

GOTMESSAGEFROMSERVER()

HERE I FACED A MAJOR ISSUE WHILE DISPLAYING THE STREAM (VIDEO). THE VIDEO OF OLDER TABS WAS VISIBLE ON THE NEWER ONES BUT THE NEWER TABS VIDEO WAS NOT VISIBLE ON THE OLDER TABS. THE AUDIO WAS AUDIBLE AND EVERYTHING WORKED FINE.

IT TOOK ALMOST A WEEK , HERE WE USED POLITE CONNECTION TO SOLVE THIS ISSUE (USED CHATGPT )

POLITE PEER CONNECTION

IF BOTH PEERS CREATE AN OFFER AT THE SAME TIME,OFFER COLLISION OCCURS, CAUSING ERRORS LIKE  Failed to set remote answer sdp: Called in wrong state: stable

TO SOLVE THIS WE MAKE ONE PEER AS POLITE AND THE OTHER AS IMPOLITE.

POLITE - IT WILL ACCEPT AND HANDLE INCOMING OFFER

IMPOLITE - IT WILL IGNORE ANY INCOMING OFFER IF IT IS MAKING AN OFFER.

TO ASSIGN WE USE LEXIOGRAPHICAL COMPARISON,

        isPolite[socketListId] = socketIdRef.current < socketListId;

IF THE STRINGS ARE : "7eLxtk6mOJc0eYozAAAD" < "uN92XLJGMoFqLHPUAAAH"

HERE "7" IS A NUMBER AND "u" IS AN ALPHABET, HOW CAN YOU COMPARE THEM.
IN LEXIOGRAPHICAL COMPARISON WE USE UNICODES TO COMPARE THEM. 
EACH OF THEM HAS AN UNIQUE UNICODE.

 UNICODE OF 7 = 55
UINCODE OF u = 117


const gotMessageFromServer = async (fromId, message) => {
    const signal = JSON.parse(message);
    const pc = connections[fromId];
    const polite = isPolite[fromId];

try {
      if (signal.sdp) {
        const desc = new RTCSessionDescription(signal.sdp);
        const offerCollision =
          signal.sdp.type === "offer" &&
          (makingOffer[fromId] || pc.signalingState !== "stable");

ignoreOffer[fromId] = !polite && offerCollision;
        i
f (ignoreOffer[fromId]) return;
await pc.setRemoteDescription(desc);

if (signal.sdp.type === "offer") {
          const answer = await pc.createAnswer();
          await pc.setLocalDescription(answer);
          socketRef.current.emit(
            "signal",
            fromId,
            JSON.stringify({ sdp: pc.localDescription })
          );
        }
      }


WE GOT ANOTHER THIS TIME ON CHROME SAYING  Error code: STATUS_BREAKPOINT

This error is thrown when:

  • Chrome hits an invalid CPU instruction (like a breakpoint instruction in compiled code).

  • There’s a native crash in Chrome itself, usually due to a bug in:

    • The browser engine (V8, WebRTC internals)

    • A bad extension

    • A memory or threading issue

  • Occasionally happens when WebRTC is misused (e.g., misconfiguring tracks, renegotiation bugs).


TO SOLVE WE COMMENT A PART OF OUR CODE AND ADD ANOTHER BLOCK
f (id !== socketIdRef.current) {
        // conn.getSenders().forEach((sender) => sender.replaceTrack(null));
        // stream.getTracks().forEach((track) => conn.addTrack(track, stream));
        const senders = conn.getSenders();
        stream.getTracks().forEach((track) => {
          const sender = senders.find((s) => s.track?.kind === track.kind);
          if (sender) sender.replaceTrack(track);
          else conn.addTrack(track, stream);
        });

WE CREATED A BLACK() AND SILENCE() FUNCTION , THEY ARE USED WHEN THE USER DISABLES AUDIO AND VIDEO.

VIDEOCONFERENCE.JSX





import React, { useEffect, useRef, useState } from "react";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import { io } from "socket.io-client";
import "../styles/VC.css";

const server_Url = "http://localhost:8080";
let connections = {};
let makingOffer = {};
let ignoreOffer = {};
let isPolite = {};

const peerConfigConnections = {
  iceServers: [{ urls: "stun:stun1.l.google.com:19302" }],
};

export default function VideoConference() {
  const socketRef = useRef();
  const socketIdRef = useRef();
  const localVideoRef = useRef();
  const videoRef = useRef([]);

  const [videoAvailable, setVideoAvailable] = useState(true);
  const [audioAvailable, setAudioAvailable] = useState(true);
  const [video, setVideo] = useState([]);
  const [audio, setAudio] = useState();
  const [screenAvailable, setScreenAvailable] = useState();
  const [askForUsername, setAskForUsername] = useState(true);
  const [username, setUsername] = useState("");
  const [videos, setVideos] = useState([]);

  useEffect(() => {
    getPermissions();
  }, []);

  const getPermissions = async () => {
    try {
      const videoStream = await navigator.mediaDevices.getUserMedia({
        video: true,
      });
      setVideoAvailable(true);
      console.log("video permission granted!");

      const audioStream = await navigator.mediaDevices.getUserMedia({
        audio: true,
      });
      setAudioAvailable(true);
      console.log("audio permission granted!");

      setScreenAvailable(!!navigator.mediaDevices.getDisplayMedia);

      const userMediaStream = await navigator.mediaDevices.getUserMedia({
        video: true,
        audio: true,
      });

      window.localStream = userMediaStream;
      if (localVideoRef.current) {
        localVideoRef.current.srcObject = userMediaStream;
      }
    } catch (e) {
      console.log("Permission error:", e);
      setVideoAvailable(false);
      setAudioAvailable(false);
    }
  };

  useEffect(() => {
    if (video !== undefined || audio !== undefined) {
      getUserMedia();
    }
  }, [audio, video]);

  const silence = () => {
    let ctx = new AudioContext();
    let oscillator = ctx.createOscillator();
    oscillator.connect(ctx.destination);
    oscillator.start();
    ctx.resume();
    return Object.assign(new MediaStreamTrack(), { enabled: false });
  };

  const black = ({ width = 333, height = 600 } = {}) => {
    let canvas = Object.assign(document.createElement("canvas"), {
      width,
      height,
    });
    canvas.getContext("2d").fillRect(0, 0, width, height);
    let stream = canvas.captureStream();
    return Object.assign(stream.getVideoTracks()[0], { enabled: false });
  };

  const connect = () => {
    setAskForUsername(false);
    setVideo(videoAvailable);
    setAudio(audioAvailable);
    connectToSocketServer();
    getUserMedia();
  };

  const connectToSocketServer = () => {
    socketRef.current = io(server_Url);

    socketRef.current.on("connect", () => {
      socketIdRef.current = socketRef.current.id;
      socketRef.current.emit("join-call", window.location.href);
    });

    socketRef.current.on("signal", gotMessageFromServer);

    socketRef.current.on("user-left", (id) => {
      setVideos((videos) => videos.filter((video) => video.socketId !== id));
    });

    socketRef.current.on("user-joined", (id, clients) => {
      clients.forEach((socketListId) => {
        if (connections[socketListId]) return;
// CREATING CONNECTION
        connections[socketListId] = new RTCPeerConnection(
          peerConfigConnections
        );
        makingOffer[socketListId] = false;
        ignoreOffer[socketListId] = false;
        isPolite[socketListId] = socketIdRef.current < socketListId;

        connections[socketListId].onicecandidate = (event) => {
          if (event.candidate) {
            socketRef.current.emit(
              "signal",
              socketListId,
              JSON.stringify({ ice: event.candidate })
            );
          }
        };

        connections[socketListId].onnegotiationneeded = async () => {
          try {
            makingOffer[socketListId] = true;
            const offer = await connections[socketListId].createOffer();
            await connections[socketListId].setLocalDescription(offer);
            socketRef.current.emit(
              "signal",
              socketListId,
              JSON.stringify({
                sdp: connections[socketListId].localDescription,
              })
            );
          } catch (err) {
            console.error(err);
          } finally { WITHOUT THIS BLOCK OUR APP ASSUMES THAT WE ARE STILL
makingOffer[socketListId] = false; SENDING SIGNAL CAUSING OFFER COLLISION
          }
        };

        connections[socketListId].ontrack = (event) => {
          // TO AVOID DUPLICATES
setVideos((prev) => {
            const updated = prev.filter((v) => v.socketId !== socketListId);
            return [
              ...updated,
              { socketId: socketListId, stream: event.streams[0] },
            ];
          });
        };

        // const localStream = window.localStream || new MediaStream([black(), silence()]);
        const localStream = window.localStream;
        localStream
          .getTracks()
          .forEach((track) =>
            connections[socketListId].addTrack(track, localStream)
          );
      });
    });
  };

  const gotMessageFromServer = async (fromId, message) => {
    const signal = JSON.parse(message);
    const pc = connections[fromId];
    const polite = isPolite[fromId];

    try {
      if (signal.sdp) {
        const desc = new RTCSessionDescription(signal.sdp);
        const offerCollision =
          signal.sdp.type === "offer" &&
          (makingOffer[fromId] || pc.signalingState !== "stable");

        ignoreOffer[fromId] = !polite && offerCollision;
        if (ignoreOffer[fromId]) return; AVOIDING COLLISION

        await pc.setRemoteDescription(desc);

        if (signal.sdp.type === "offer") {
          const answer = await pc.createAnswer();
          await pc.setLocalDescription(answer);
          socketRef.current.emit(
            "signal",
            fromId,
            JSON.stringify({ sdp: pc.localDescription })
          );
        }
      }

      if (signal.ice) {
        try {
          await pc.addIceCandidate(new RTCIceCandidate(signal.ice));
        } catch (err) {
          if (!ignoreOffer[fromId]) console.error("Error adding ICE", err);
        }
      }
    } catch (err) {
      console.error("Signal error from", fromId, err);
    }
  };

  const getUserMediaSuccess = (stream) => {
    try {
      window.localStream?.getTracks().forEach((track) => track.stop());
    } catch {}

    window.localStream = stream;
    if (localVideoRef.current) localVideoRef.current.srcObject = stream;

    Object.entries(connections).forEach(([id, conn]) => {
      if (id !== socketIdRef.current) {
        // conn.getSenders().forEach((sender) => sender.replaceTrack(null));
        // stream.getTracks().forEach((track) => conn.addTrack(track, stream));
        const senders = conn.getSenders();
        stream.getTracks().forEach((track) => {
          const sender = senders.find((s) => s.track?.kind === track.kind);
          if (sender) sender.replaceTrack(track);
          else conn.addTrack(track, stream);
        });
      }
    });
  };

  const getUserMedia = () => {
    if ((video && videoAvailable) || (audio && audioAvailable)) {
      navigator.mediaDevices
        .getUserMedia({ video, audio })
        .then(getUserMediaSuccess)
        .catch((e) => console.log("getUserMedia error", e));
    } else {
      try {
        // if (
        //   localVideoRef.current &&
        //   localVideoRef.current.srcObject &&
        //   typeof localVideoRef.current.srcObject.getTracks === 'function'
        // ) {
        //   localVideoRef.current.srcObject.getTracks().forEach((track) => track.stop());
        // } we can write this as
        localVideoRef.current?.srcObject
          ?.getTracks()
          .forEach((track) => track.stop());
      } catch {}
    }
  };

  return (
    <>
      {askForUsername ? (
        <div>
          <h2>Enter into lobby</h2>
          <TextField
            className="inputsRequired"
            label="Username"
            value={username}
            variant="outlined"
            onChange={(e) => setUsername(e.target.value)}
          />
          <Button variant="contained" onClick={connect}>
            Connect
          </Button>
          <div>
            <video ref={localVideoRef} autoPlay muted></video>
          </div>
        </div>
      ) : (
        <div>
          <video ref={localVideoRef} autoPlay muted></video>
          {videos.map((video) => (
            <div key={video.socketId}>
              <h2>{video.socketId}</h2>
              <video
                data-socket={video.socketId}
                ref={(ref) => {
                  if (ref && video.stream) {
                    ref.srcObject = video.stream;
                  }
                }}
                autoPlay
                playsInline
              />
            </div>
          ))}
        </div>
      )}
    </>
  );
}
















Comments

Popular posts from this blog

MIDDLEWARE.JS

MODELS

AUTHENTICATION PAGE