How to Implement Conference Call with jsSIP Library - Mixing Media Streams

89 Views Asked by At

I am working on a web application where I am using the jsSIP library for handling SIP communication. My goal is to implement a conference call feature where I can mix the media streams of multiple sessions on the client side. Essentially, I want to create an "add-to-call" feature to enable a conference call experience.

I've successfully handled incoming and outgoing calls, but I'm struggling with efficiently managing multiple sessions and mixing their media streams. My questions are:

  1. How can I retrieve all active sessions on the client side using jsSIP?

  2. Once I have the sessions, how can I mix their media streams to create a conference call experience?

  const attachRemoteStream = (
    session: RTCPeerConnectionDeprecated,
    elementId: string
  ) => {
    const mediaElement = getVideoElement(elementId);
    session.ontrack = (event) => {
      if (event.track.kind === "video") {
        console.log("event.streams RemoteStream", event.streams);
        if (event.streams.length > 0 && mediaElement) {
          console.log("RemoteStream If block is called");
          mediaElement.srcObject = event.streams[0];
          mediaElement.play();
        } else {
          console.log("RemoteStream Else block is called");
          const stream = new MediaStream([event.track]);
          if (mediaElement) {
            mediaElement.srcObject = stream;
            mediaElement.play();
          }
        }
      }
    };
  };

  const attachLocalStream = (session: RTCSession, elementId: string) => {
    const mediaElement = getVideoElement(elementId);
    session.connection.ontrack = (event) => {
      if (event.track.kind === "audio") {
        console.log("event.streams LocalStream", event.streams);
        if (event.streams.length > 0 && mediaElement) {
          console.log("LocalStream If block is called");
          mediaElement.srcObject = event.streams[0];
          mediaElement.play();
        } else {
          console.log("Else block is called");
          const stream = new MediaStream([event.track]);
          if (mediaElement) {
            mediaElement.srcObject = stream;
            mediaElement.play();
          }
        }
      }
    };
  };

  const attachIncommingLocalStream = (
    session: RTCSession,
    elementId: string
  ) => {
    const mediaElement = getVideoElement(elementId);

    if (!mediaElement) {
      console.error(`Media element with id ${elementId} not found.`);
      return;
    }

    // Assuming there is a local video track, add it to the local stream
    const localVideoSender = session.connection.getSenders().find((sender) => {
      return sender.track?.kind === "video";
    });
    if (localVideoSender && localVideoSender.track) {
      const localStream = new MediaStream([localVideoSender.track]);

      // Attach the local stream to the video element
      mediaElement.srcObject = localStream;
      mediaElement.play();
    } else {
      console.error("No local video track found in the session.");
    }
  };

  const outgoingCall = async (session: RTCSession) => {
    attachLocalStream(session, "videoRemote");
    session.on("progress", () => {
      console.log("outgoingCall call is in progress");
      setOutgoingCall(true);
    });
    session.on("connecting", () => {
      console.log("outgoingCall call is in connecting ...");
    });
    session.on("ended", () => {
      console.log("outgoingCall call has ended");
      navigate("/");
      setOutgoingCall(false);
    });
    session.on("confirmed", () => {
      console.log("outgoingCall is confirmed");
      setOutgoingCall(false);
    });
    session.on("failed", () => {
      console.log("outgoingCall unable to establish the call");
      navigate("/");
      setOutgoingCall(false);
    });
    session.on("accepted", (e: any) => {
      attachIncommingLocalStream(session, "videoLocal");
      navigate("/answer");
    });
  };

  const inCommingCall = (session: RTCSession) => {
    session.on("progress", () => {
      console.log("inCommingCall is in progress");
      setIncommingCall(true);
    });
    session.on("accepted", () => {
      console.log("inCommingCall call has answered");
      navigate("/answer");
      setIncommingCall(false);
    });
    session.on("connecting", () => {
      console.log("inCommingCall call is in connecting ...");
    });
    session.on("confirmed", () => {
      console.log(
        "inCommingCall handler will be called for incoming calls too"
      );
      attachIncommingLocalStream(session, "videoLocal");
      setIncommingCall(false);
    });
    session.on("ended", () => {
      console.log("inCommingCall call has ended");
      setIncommingCall(false);
      navigate("/");
    });
    session.on("failed", () => {
      console.log("inCommingCall unable to establish the call");
      setIncommingCall(false);
    });
    session.on("peerconnection", (e) => {
      attachRemoteStream(e.peerconnection, "videoRemote");
    });
    session.on("sdp", (e: SDPEvent) => {
      console.log("SDP Events are", e);
    });
  };

  useEffect(() => {
    sipClient?.on("newRTCSession", (data: RTCSessionEvent) => {
      const session: RTCSession = data.session;
      setSession(session);
      const direction = session.direction;
      console.log("direction", direction);
      setSession(session);
      if (direction === "incoming") {
        inCommingCall(session);
      } else if (direction === "outgoing") {
        outgoingCall(session);
      }
    });
  }, [sipClient]);
0

There are 0 best solutions below