vendredi 25 juin 2021

Action Cable Signaling Server not working on different networks

I have an issue with my Ruby on rails 6 app. I make a video chat app with signaling-server js (WebRTC JS) and action-cable. video chat working fine on the same network in production. but it's not working on a different network. connection is established but video not streaming. it's not showing some error.

signaling_server.js file.

import consumer from "./channels/consumer";

// Broadcast Types
const JOIN_ROOM = "JOIN_ROOM";
const EXCHANGE = "EXCHANGE";
const REMOVE_USER = "REMOVE_USER";

// DOM Elements
let currentUser;
let localVideo;
let remoteVideoContainer;
let user;

// Objects
let pcPeers = {};
let localstream;

window.onload = () => {
  user = document.getElementById("current-user");
  if(user) {
    currentUser = document.getElementById("current-user").innerHTML;
    localVideo = document.getElementById("local-video");
    remoteVideoContainer = document.getElementById("remote-video-container");
  }
};

// Ice Credentials
const ice = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };

// Add event listener's to buttons
document.addEventListener("DOMContentLoaded", () => {
  const joinButton = document.getElementById("join-button");
  const leaveButton = document.getElementById("leave-button");

  joinButton.onclick = handleJoinSession;
  leaveButton.onclick = handleLeaveSession;
});

// Initialize user's own video
document.onreadystatechange = () => {
  if (document.readyState === "interactive") {
    navigator.mediaDevices
      .getUserMedia({
        audio: true,
        video: true,
      })
      .then((stream) => {
        localstream = stream;
        localVideo.srcObject = stream;
        localVideo.muted = true;
      })
      .catch(logError);
  }
};

const handleJoinSession = async () => {
  consumer.subscriptions.create("VideoSessionChannel", {
    connected: () => {
      broadcastData({
        type: JOIN_ROOM,
        from: currentUser,
      });
    },
    received: (data) => {
      console.log("received", data);
      if (data.from === currentUser) return;
      switch (data.type) {
      case JOIN_ROOM:
        return joinRoom(data);
      case EXCHANGE:
        if (data.to !== currentUser) return;
        return exchange(data);
      case REMOVE_USER:
        return removeUser(data);
      default:
        return;
      }
    },
  });
};

const handleLeaveSession = () => {
  for (let user in pcPeers) {
    pcPeers[user].close();
  }
  pcPeers = {};

  consumer.unsubscribe();
  remoteVideoContainer.innerHTML = "";

  broadcastData({
    type: REMOVE_USER,
    from: currentUser,
  });
};

const joinRoom = (data) => {
  createPC(data.from, true);
};

const removeUser = (data) => {
  console.log("removing user", data.from);
  let video = document.getElementById(`remoteVideoContainer+${data.from}`);
  video && video.remove();
  delete pcPeers[data.from];
};

const createPC = (userId, isOffer) => {
  let pc = new RTCPeerConnection(ice);
  pcPeers[userId] = pc;

  console.log('createPC');
  // for (const track of localstream.getTracks()) {
  //   pc.addTrack(track, localstream);
  // }

  pc.addStream(localstream);

  isOffer &&
    pc
      .createOffer()
      .then((offer) => {
        console.log('createPC createOffer');
        return pc.setLocalDescription(offer);
      })
      .then(() => {
        broadcastData({
          type: EXCHANGE,
          from: currentUser,
          to: userId,
          sdp: JSON.stringify(pc.localDescription),
        });
      })
      .catch(logError);

  pc.onicecandidate = (event) => {
    event.candidate &&
      broadcastData({
        type: EXCHANGE,
        from: currentUser,
        to: userId,
        candidate: JSON.stringify(event.candidate),
      });
  };

  pc.onaddstream = (event) => {
    console.log(event.stream);
    const element = document.createElement("video");
    element.id = `remoteVideoContainer+${userId}`;
    element.autoplay = "autoplay";
    element.srcObject = event.stream;
    remoteVideoContainer.appendChild(element);
  };

  pc.oniceconnectionstatechange = () => {
    if (pc.iceConnectionState == "disconnected") {
      console.log("Disconnected:", userId);
      broadcastData({
        type: REMOVE_USER,
        from: userId,
      });
    }
  };

  return pc;
};

const exchange = (data) => {
  let pc;
  if (!pcPeers[data.from]) {
    pc = createPC(data.from, false);
  } else {
    pc = pcPeers[data.from];
  }

  if (data.candidate) {
    pc.addIceCandidate(new RTCIceCandidate(JSON.parse(data.candidate)))
      .then(() => console.log("Ice candidate added"))
      .catch(logError);
  }

  if (data.sdp) {
    const sdp = JSON.parse(data.sdp);
    pc.setRemoteDescription(new RTCSessionDescription(sdp))
      .then(() => {
        if (sdp.type === "offer") {
          pc.createAnswer()
            .then((answer) => {
              console.log('exchange createAnswer');
              return pc.setLocalDescription(answer);
            })
            .then(() => {
              broadcastData({
                type: EXCHANGE,
                from: currentUser,
                to: data.from,
                sdp: JSON.stringify(pc.localDescription),
              });
            });
        }
      })
      .catch(logError);
  }
};

const broadcastData = (data) => {
  /**
   * Add CSRF protection: https://stackoverflow.com/questions/8503447/rails-how-to-add-csrf-protection-to-forms-created-in-javascript
   */
  const csrfToken = document.querySelector("[name=csrf-token]").content;
  const headers = new Headers({
    "content-type": "application/json",
    "X-CSRF-TOKEN": csrfToken,
  });

  fetch("sessions", {
    method: "POST",
    body: JSON.stringify(data),
    headers,
  });
};

const logError = (error) => console.warn("Whoops! Error:", error);

routes.rb file

  mount ActionCable.server, at: "/cable"

Video Sessions Controller

class VideoSessionsController < ApplicationController
  
  def create
    head :no_content
    ActionCable.server.broadcast "video_session_channel", session_params
  end

  private

  def session_params
    params.require(:video_session).permit(:type, :from, :to, :sdp, :candidate)
  end

end

video channel file

class VideoSessionChannel < ApplicationCable::Channel
  def subscribed
    stream_from "video_session_channel"
  end
end

video chat HTML file.

<span id="current-user"><%= @random_number %></span>
                    <div class="video-chat-content">
                        <div class="" id="remote-video-container"> </div>
                        <video id="local-video" autoplay></video>
                    </div>

enter image description here

enter image description here

Aucun commentaire:

Enregistrer un commentaire