action-cable-signaling-server
action-cable-signaling-server copied to clipboard
Not working on different network
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.
Do you have any code to reference? Perhaps myself or anyone else could be of better assistance if we could take a look at the implementation
Please check the below code.
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>
and after a few seconds, it's auto disconnected. please reply ASAP.
Your demo also not working on a different network. https://action-cable-signaling-server.herokuapp.com/

