Problem with STUN/TURN servers in WEBRTC video app made in MERN stack - reactjs

I have hosted a peer to peer meeting react app on netlify. I have used Peerjs for my video purpose. Everything is working as expected except the video. For some networks the video of the the remote person is working and for some others it is not working. I looked up and found out that it may be a STUN/TURN issue. I then implemented all the STUN/TURN servers in my code. However the video is still not getting setup in some cases. In some cases it is working fine, in others the video is not showing up. Herewith, I am attaching th code for the video and the link to the site.
import React,{useEffect,useState} from 'react';
import {io} from "socket.io-client";
import {useParams} from 'react-router-dom';
import {Grid} from "#material-ui/core";
import Peer from 'peerjs';
var connectionOptions = {
"force new connection" : true,
"reconnectionAttempts": "Infinity",
"timeout" : 10000,
"transports" : ["websocket"]
};
const Videobox = ({isVideoMute,isAudioMute}) => {
var myPeer = new Peer(
{
config: {'iceServers': [
{urls:'stun:stun01.sipphone.com'},
{urls:'stun:stun.ekiga.net'},
{urls:'stun:stun.fwdnet.net'},
{urls:'stun:stun.ideasip.com'},
{urls:'stun:stun.iptel.org'},
{urls:'stun:stun.rixtelecom.se'},
{urls:'stun:stun.schlund.de'},
{urls:'stun:stun.l.google.com:19302'},
{urls:'stun:stun1.l.google.com:19302'},
{urls:'stun:stun2.l.google.com:19302'},
{urls:'stun:stun3.l.google.com:19302'},
{urls:'stun:stun4.l.google.com:19302'},
{urls:'stun:stunserver.org'},
{urls:'stun:stun.softjoys.com'},
{urls:'stun:stun.voiparound.com'},
{urls:'stun:stun.voipbuster.com'},
{urls:'stun:stun.voipstunt.com'},
{urls:'stun:stun.voxgratia.org'},
{urls:'stun:stun.xten.com'},
{
urls: 'turn:numb.viagenie.ca',
credential: 'muazkh',
username: 'webrtc#live.com'
},
{
urls: 'turn:192.158.29.39:3478?transport=udp',
credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
username: '28224511:1379330808'
},
{
urls: 'turn:192.158.29.39:3478?transport=tcp',
credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
username: '28224511:1379330808'
}
]} /* Sample servers, please use appropriate ones */
}
);
const peers = {}
const [socket, setSocket] = useState()
const {id:videoId} = useParams();
const videoGrid = document.getElementById('video-grid')
useEffect(()=> {
const s=io("https://weconnectbackend.herokuapp.com",connectionOptions);
setSocket(s);
return () => {
s.disconnect();
}
},[])
// let myVideoStream;
const [myVideoStream, setmyVideoStream] = useState()
const muteUnmute = () => {
const enabled = myVideoStream.getAudioTracks()[0].enabled;
if (enabled) {
myVideoStream.getAudioTracks()[0].enabled = false;
//setUnmuteButton();
} else {
//setMuteButton();
myVideoStream.getAudioTracks()[0].enabled = true;
}
}
const playStop = () => {
//console.log('object')
let enabled = myVideoStream.getVideoTracks()[0].enabled;
if (enabled) {
myVideoStream.getVideoTracks()[0].enabled = false;
//setPlayVideo()
} else {
//setStopVideo()
myVideoStream.getVideoTracks()[0].enabled = true;
}
}
useEffect(() => {
if(myVideoStream)
playStop()
}, [isVideoMute])
useEffect(() => {
if(myVideoStream)
muteUnmute()
}, [isAudioMute])
useEffect(() => {
if(socket== null)
return;
myPeer.on('open',id=>{
socket.emit('join-room',videoId,id);
})
const myVideo = document.createElement('video')
myVideo.muted = true
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
// myVideoStream = stream;
window.localStream=stream;
setmyVideoStream(stream);
console.log(myVideoStream,"myvideostream");
addVideoStream(myVideo, stream)
myPeer.on('call', call => {
call.answer(stream)
const video = document.createElement('video')
call.on('stream', userVideoStream => {
addVideoStream(video, userVideoStream)
})
})
socket.on('user-connected',userId =>{
connectToNewUser(userId, stream)
})
socket.on('user-disconnected', userId => {
if (peers[userId]) peers[userId].close()
})
})
}, [socket,videoId])
function addVideoStream(video, stream) {
video.srcObject = stream
video.addEventListener('loadedmetadata', () => {
video.play()
})
videoGrid.append(video)
}
function connectToNewUser(userId, stream) {
const call = myPeer.call(userId, stream)
const video = document.createElement('video')
call.on('stream', userVideoStream => {
addVideoStream(video, userVideoStream)
})
call.on('close', () => {
video.remove()
})
peers[userId] = call
}
return (
<div id="video-grid" className="videoStyleFromDiv">
{/* <Video srcObject={srcObject}/> */}
</div>
)
}
export default Videobox
Website Link

The TURN servers you are using have been out of commission for a couple of years in the case of the ones taken from https://www.html5rocks.com/en/tutorials/webrtc/infrastructure/
Copying credentials from random places is not how TURN works, you will need to run your own servers.

Related

Why doesn't the effect get current from the link?

I need to get localMediaStream in one effect, while it is set in another effect. Please tell me why in this context it is always null (if you do not set it in the same effect), but in this case I have a duplicate userMedia. Consequences - the camera does not go out when I call track.stop(). Based on this package
const peerConnections = useRef({});
const localMediaStream = useRef(null);
const peerMediaElements = useRef({
[LOCAL_VIDEO]: null,
});
useEffect(() => {
async function handleNewPeer({peerID, createOffer}) {
if (peerID in peerConnections.current) {
return console.warn(`Already connected to peer ${peerID}`);
}
peerConnections.current[peerID] = new RTCPeerConnection({
iceServers: freeice(),
});
peerConnections.current[peerID].onicecandidate = event => {
if (event.candidate) {
socket.emit(ACTIONS.RELAY_ICE, {
peerID,
iceCandidate: event.candidate,
});
}
}
let tracksNumber = 0;
peerConnections.current[peerID].ontrack = ({streams: [remoteStream]}) => {
tracksNumber++
if (tracksNumber === 2) { // video & audio tracks received
tracksNumber = 0;
addNewClient(peerID, () => {
if (peerMediaElements.current[peerID]) {
peerMediaElements.current[peerID].srcObject = remoteStream;
} else {
// FIX LONG RENDER IN CASE OF MANY CLIENTS
let settled = false;
const interval = setInterval(() => {
if (peerMediaElements.current[peerID]) {
peerMediaElements.current[peerID].srcObject = remoteStream;
settled = true;
}
if (settled) {
clearInterval(interval);
}
}, 1000);
}
});
}
}
/*localMediaStream.current = await navigator.mediaDevices.getUserMedia({
audio: audio,
video: video
})*/
localMediaStream.current.getTracks().forEach(track => { // localMediaStream null
peerConnections.current[peerID].addTrack(track, localMediaStream.current);
});
if (createOffer) {
const offer = await peerConnections.current[peerID].createOffer();
await peerConnections.current[peerID].setLocalDescription(offer);
socket.emit(ACTIONS.RELAY_SDP, {
peerID,
sessionDescription: offer,
});
}
}
socket.on(ACTIONS.ADD_PEER, handleNewPeer);
return () => {
socket.off(ACTIONS.ADD_PEER);
}
}, []);
// The installation, everything is as in the source, it did not work until I added the crutch above, but when it came to stopping the video stream, a bug appeared with the camera always on
useEffect(() => {
async function startCapture() {
console.log('start capture');
localMediaStream.current = await navigator.mediaDevices.getUserMedia({
audio: audio,
video: video
}).catch(console.log);
addNewClient(LOCAL_VIDEO, () => {
const localVideoElement = peerMediaElements.current[LOCAL_VIDEO];
if (localVideoElement) {
localVideoElement.volume = 0;
localVideoElement.srcObject = localMediaStream.current;
}
});
}
startCapture().then((data) => socket.emit(ACTIONS.JOIN, {room: roomID})).catch((e) => console.error(e)).finally(() => console.log('finally'));
console.log(roomID);
return () => {
localMediaStream.current.getTracks().forEach(track => track.stop());
socket.emit(ACTIONS.LEAVE);
};
}, [roomID]);
Thanks you very much.

Electron/react application shows extension server error

I've been using an electron/react boilerplate for my application, yet I find myself facing a dreaded empty white screen as the console spits the following error message at me repeatedly:
[31308:0413/104846.234:ERROR:CONSOLE(1)] "Extension server error: Operation failed: Permission denied", source: devtools://devtools/bundled/models/extensions/extensions.js (1)
This error suddenly popped up without much warning as it was working fine earlier, though I suspect it might have something to do with changes I made to the ipcRenderer.
Along with this, the following errors pop up in the console: https://gyazo.com/1c3e3f22f65fd6f7db0fd9549969581b
Here's my preload.ts:
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: {
myPing() {
ipcRenderer.send('ipc-example', 'ping');
},
on(channel: string, func: (...args: unknown[]) => void) {
const validChannels = ['ipc-example'];
if (validChannels.includes(channel)) {
const subscription = (_event: IpcRendererEvent, ...args: unknown[]) =>
func(...args);
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, subscription);
return () => ipcRenderer.removeListener(channel, subscription);
}
return undefined;
},
once(channel: string, func: (...args: unknown[]) => void) {
const validChannels = ['ipc-example'];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.once(channel, (_event, ...args) => func(...args));
}
},
},
});
And here's my main.ts:
/**
* This module executes inside of electron's main process. You can start
* electron renderer process from here and communicate with the other processes
* through IPC.
*
* When running `npm run build` or `npm run build:main`, this file is compiled to
* `./src/main.js` using webpack. This gives us some performance wins.
*/
import path from 'path';
import { app, BrowserWindow, shell, ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
import MenuBuilder from './components/containers/menu';
import { resolveHtmlPath } from './main/util';
export default class AppUpdater {
constructor() {
log.transports.file.level = 'info';
autoUpdater.logger = log;
autoUpdater.checkForUpdatesAndNotify();
}
}
let mainWindow: BrowserWindow | null = null;
ipcMain.on('ipc-example', async (event, arg) => {
const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`;
console.log(msgTemplate(arg));
event.reply('ipc-example', msgTemplate('pong'));
});
if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support');
sourceMapSupport.install();
}
const isDevelopment =
process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';
if (isDevelopment) {
require('electron-debug')();
}
const installExtensions = async () => {
const installer = require('electron-devtools-installer');
const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
const extensions = ['REACT_DEVELOPER_TOOLS'];
return installer
.default(
extensions.map((name) => installer[name]),
forceDownload
)
.catch(console.log);
};
const createWindow = async () => {
if (isDevelopment) {
await installExtensions();
}
const RESOURCES_PATH = app.isPackaged
? path.join(process.resourcesPath, 'assets')
: path.join(__dirname, '../../assets');
const getAssetPath = (...paths: string[]): string => {
return path.join(RESOURCES_PATH, ...paths);
};
mainWindow = new BrowserWindow({
show: false,
width: 1024,
height: 728,
icon: getAssetPath('icon.png'),
webPreferences: {
contextIsolation: true,
preload: app.isPackaged
? path.join(__dirname, 'preload.js')
: path.join(__dirname, '../../.erb/dll/preload.js'),
},
});
mainWindow.loadURL(resolveHtmlPath('index.html'));
mainWindow.on('ready-to-show', () => {
if (!mainWindow) {
throw new Error('"mainWindow" is not defined');
}
if (process.env.START_MINIMIZED) {
mainWindow.minimize();
} else {
mainWindow.show();
}
});
mainWindow.on('closed', () => {
mainWindow = null;
});
const menuBuilder = new MenuBuilder(mainWindow);
menuBuilder.buildMenu();
// Open urls in the user's browser
mainWindow.webContents.setWindowOpenHandler((edata) => {
shell.openExternal(edata.url);
return { action: 'deny' };
});
// Remove this if your app does not use auto updates
// eslint-disable-next-line
new AppUpdater();
};
/**
* Add event listeners...
*/
app.on('window-all-closed', () => {
// Respect the OSX convention of having the application in memory even
// after all windows have been closed
if (process.platform !== 'darwin') {
app.quit();
}
});
app
.whenReady()
.then(() => {
createWindow();
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) createWindow();
});
})
.catch(console.log);
Does this error seem familiar to anyone? I've no clue how to go about solving this.

local audio is not getting stream on remote side

I am trying to make an web app with audio, video call using WebRTC.
Problem is that local audio/video working properly in my web app, but remote audio/video is not getting stream on remote side. in console there is no error. you can join room but you can't hear others audio or see video.
here's code:
useEffect(() => {
const initRoom = async () => {
socket.current = socketInit();
//Get User Audio
await captureLocalMedia();
socket.current.emit(ACTIONS.JOIN, {roomId, user});
socket.current.on(ACTIONS.ADD_PEER, handleNewPeerConnection);
async function captureLocalMedia() {
localMediaStream.current =
await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
}
async function handleNewPeerConnection({peerId, createOffer, user: newUser}) {
if(peerId in connections.current) {
return console.warn(`You are already joined with ${user.username}`)
}
var configuration = {
offerToReceiveAudio: true
}
connections.current[peerId] = new RTCPeerConnection({
iceServers: [
{
urls: "stun:stun.l.google.com:19302"
},
{
urls: "stun:stun1.l.google.com:19302"
},
{
urls: "stun:stun2.l.google.com:19302"
},
{
urls: "stun:stun3.l.google.com:19302"
},
{
urls: "stun:stun4.l.google.com:19302"
}
],
configuration: configuration
})
connections.current[peerId].ontrack = (event) => {
addNewClients(newUser, () => {
if(audioElements.current[newUser.id]) {
audioElements.current[newUser.id].srcObject = event.streams[0];
} else {
let settled = false;
const interval = setInterval(() => {
if(audioElements.current[newUser.id]) {
const [remoteStream] = event.streams;
audioElements.current[newUser.id].srcObject=remoteStream
settled = true;
}
if (settled) {
clearInterval(interval)
}
}, 600)
}
})
}
localMediaStream.current.getTracks().forEach((track) => {
connections.current[peerId].addTrack(
track,
localMediaStream.current
)
});
if(createOffer) {
const offer = await connections.current[peerId].createOffer()
await connections.current[peerId].setLocalDescription(offer)
socket.current.emit(ACTIONS.RELAY_SDP, {
peerId,
sessionDescription: offer
})
}
}
}
initRoom();
return () => {
localMediaStream.current
.getTracks()
.forEach((track) => track.stop());
socket.current.emit(ACTIONS.LEAVE, { roomId });
for (let peerId in connections.current) {
connections.current[peerId].close();
delete connections.current[peerId];
delete audioElements.current[peerId];
}
socket.current.off(ACTIONS.ADD_PEER);
}
}, [])
this is socketInit function:
import {io} from 'socket.io-client';
const socketInit = () => {
const options = {
'force new connection': true,
reconnectionAttempts: 'Infinity',
timeout: 10000,
transports: ['websocket'],
};
return io('http://localhost:5500', options)
};
export default socketInit;
You should check whether the offer's SDP contains information about media tracks. For example:
sdp v=0
o=- 4748410946812024893 2 IN IP4 127.0.0.1
............
a=sendrecv
**a=msid:Eei3sKzfsiJybxa4TYhANjGsFMuWe2lAxadS f798f673-566e-4a8e-9760-8d657d031acf**
............
a=rtpmap:126 telephone-event/8000
a=ssrc:3563088629 cname:0j/yv49mmBxgcAbW
a=ssrc:3563088629 msid:Eei3sKzfsiJybxa4TYhANjGsFMuWe2lAxadS f798f673-566e-4a8e-9760-8d657d031acf
a=ssrc:3563088629 mslabel:Eei3sKzfsiJybxa4TYhANjGsFMuWe2lAxadS
a=ssrc:3563088629 label:f798f673-566e-4a8e-9760-8d657d031acf
............
a=max-message-size:262144
If remote peer got information about media tracks and it doesn't work, then the problem is probably with the playing of HTMLMediaElement. Try to add the line:
audioElements.current[newUser.id].autoplay = true

WebRTC react stream multiple tracks issue. Camera + Screen Share

I am working on a new website. You can join room calls and make a voicecall so use your webcam but you can also share your screen.
The problem is when i add the track to the stream the peer doesn't work more... I don't know how to solve this. I also tried to change the code from adding the track to adding a new stream or anything else but cannot find a solution. Can anyone help me?
Client:
export default function Meeting(props) {
const userVideo = useRef();
const partnerVideo = useRef();
const screenShare = useRef();
const peerRef = useRef();
const socketRef = useRef();
const otherUser = useRef();
const userStream = useRef();
const userScreen = useRef();
useEffect(() => {
navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(stream => {
userVideo.current.srcObject = stream;
userStream.current = stream;
socketRef.current = socketIOClient.connect(ENDPOINT);
socketRef.current.emit("join room", props.room);
socketRef.current.on('other user', userID => {
callUser(userID);
otherUser.current = userID;
});
socketRef.current.on("user joined", userID => {
otherUser.current = userID;
});
socketRef.current.on("offer", handleRecieveCall);
socketRef.current.on("answer", handleAnswer);
socketRef.current.on("ice-candidate", handleNewICECandidateMsg);
});
}, []);
function callUser(userID) {
console.log("call user - - -d-s-d-s-d--sd-sdd-sd-ssd-sd-sd--");
peerRef.current = createPeer(userID);
userStream.current.getTracks().forEach(track => peerRef.current.addTrack(track, userStream.current));
}
function createPeer(userID) {
const peer = new RTCPeerConnection({
//sdpSemantics: 'unified-plan',
iceServers: [
{
urls: "stun:stun.stunprotocol.org"
},
{
urls: 'turn:numb.viagenie.ca',
credential: 'muazkh',
username: 'webrtc#live.com'
},
]
});
peer.onicecandidate = handleICECandidateEvent;
peer.ontrack = handleTrackEvent;
peer.onnegotiationneeded = () => {
if (peer.signalingState != "stable") return;
handleNegotiationNeededEvent(userID);
}
return peer;
}
function handleNegotiationNeededEvent(userID) {
console.log("negotiationsad-das-d-as-d-asd--asd-a-sd-a-sd-");
peerRef.current.createOffer().then(offer => {
return peerRef.current.setLocalDescription(offer);
}).then(() => {
const payload = {
target: userID,
type: "video-offer",
caller: socketRef.current.id,
sdp: peerRef.current.localDescription
};
socketRef.current.emit("offer", payload);
}).catch(e => console.log(e));
}
function handleRecieveCall(incoming) {
peerRef.current = createPeer();
const desc = new RTCSessionDescription(incoming.sdp);
peerRef.current.setRemoteDescription(desc).then(() => {
userStream.current.getTracks().forEach(track => peerRef.current.addTrack(track, userStream.current));
}).then(() => {
return peerRef.current.createAnswer();
}).then(answer => {
return peerRef.current.setLocalDescription(answer);
}).then(() => {
const payload = {
target: incoming.caller,
type: "video-offer",
caller: socketRef.current.id,
sdp: peerRef.current.localDescription
}
socketRef.current.emit("answer", payload);
})
}
function handleAnswer(message) {
const desc = new RTCSessionDescription(message.sdp);
peerRef.current.setRemoteDescription(desc).catch(e => console.log(e));
}
function handleICECandidateEvent(e) {
if (e.candidate) {
const payload = {
target: otherUser.current,
candidate: e.candidate,
}
socketRef.current.emit("ice-candidate", payload);
}
}
function handleNewICECandidateMsg(incoming) {
const candidate = new RTCIceCandidate(incoming);
if (peerRef.current && candidate) {
peerRef.current.addIceCandidate(candidate).catch(e => console.log(e));
}
}
function handleTrackEvent(e) {
var stream = e.streams[0];
var tracks = stream.getTracks();
var lun = tracks.length;
console.log(tracks);
if (lun === 2) {
partnerVideo.current.srcObject = stream;
} else if (lun === 1) {
screenShare.current.srcObject = stream;
}
};
function shareScreen() {
navigator.mediaDevices.getDisplayMedia({ cursor: true }).then(stream => {
screenShare.current.srcObject = stream;
userScreen.current = stream;
const screenTrack = stream.getTracks()[0];
callUser(otherUser.current);
peerRef.current.addTrack(screenTrack, stream);
screenTrack.onended = function () {
peerRef.current.removeTrack(screenTrack);
}
})
}
return (
<div>
<video controls style={{ height: 500, width: 500 }} autoPlay ref={userVideo} />
<video controls style={{ height: 500, width: 500 }} autoPlay ref={partnerVideo} />
<video controls style={{ height: 500, width: 500 }} autoPlay ref={screenShare} />
<button onClick={shareScreen}>Share screen</button>
</div>
);
};
In shareScreen you're adding a second track to the existing peer connection with addTrack, which triggers renegotiation (causes your onnegotiationneeded handler to run again). This is correct so far, and triggers a second offer/answer exchange, which is needed for media track additions.
You're just not set up to handle renegotiation correctly, because you've mixed initialization code in with your (re)negotiation code, causing a new RTCPeerConnection object to be created when you already have one.
Renegotiation is going to happen whenever you add tracks or stop their underlying transceivers, so you don't want initialization code in your negotiation code. Instead do this:
Move the creation of your RTCPeerConnection out of handleReceiveCall. Create it on page load instead.
Move the addition of tracks out of handleReceiveCall as well if you can, or at least skip re-adding them if they've already been added.
Your ontrack is going to fire again with the screen-sharing track, so you need to add code to handle that. You can differentiate it from the camera track by event.streams[0].id and event.transceiver.mid being different, or simply from this being the third track.

Twilio Video - Video disappears after switching camera on iOS

I am using the Twilio Video JS SDK (https://www.twilio.com/docs/video) in a React app. I want to allow users to switch video cameras on mobile devices using the following function:
switchCamera = (isRear = false) => {
const { videoToken } = this.props;
const { videoCameras } = this.state;
if (videoToken) {
let videoInput;
if (isRear) {
videoInput = videoCameras.filter(camera => camera.label.toLowerCase().includes('back'));
} else {
videoInput = videoCameras.filter(camera =>camera.label.toLowerCase().includes('front'));
}
createLocalVideoTrack({
facingMode: isRear ? 'environment' : 'user',
deviceId: { exact: videoInput[0].deviceId },
}).then(async localVideoTrack => {
if (this.state.room) {
const { localParticipant } = this.state.room;
const tracks = Array.from(localParticipant.videoTracks.values())
.map(trackPublication => trackPublication.track);
localParticipant.unpublishTracks(tracks);
tracks.forEach(track => track.detach().forEach(element => element.remove()));
await localParticipant.publishTrack(localVideoTrack);
localVideoTrack.attach();
}
});
}
};
However, when the localParticipant switches cameras, his own video feed disappears, but all other participants can see the switched camera feed.
How do I keep the localParticipant's video feed from disappearing?

Resources