webrtc each step success but no video share - webrtc

chrome version: 62.0.3202.94;
firefox version: 57.0.1;
I write a simple demo use webrtc and socket.io.
It works with pages. For example, I open one page to connect socket, and waiting for PeerConnection info from the main page(which get the local media). When I open the main, I create ice and sdp and then exchange them by socket.io to create connection.
Here is the code.
// The server side:
const express = require('express')
const app = express()
const path = require('path')
app.use(express.static(path.join(__dirname, 'public')))
app.get('/phone', function(req, res) {
res.sendfile(__dirname + '/phone.html')
})
app.get('/', function(req, res) {
res.sendfile(__dirname + '/index.html')
})
const server = require('http').createServer(app)
const io = require('socket.io')(server)
let clients = []
io.on('connection', function(socket) {
clients.push(socket)
const referer = socket.handshake.headers.referer
// socket connect from '/phone'
if (referer.match('/phone')) {
// send the ice from phone to others
socket.on('phone_ice_candidate', function(res) {
socket.broadcast.emit('pc_add_ice', {
ice: res.ice
})
})
// send the sdp from phone to others
socket.on('send_phone_sdp', function(data) {
socket.broadcast.emit('set_pc_remote_sdp', {
desc: data.desc
})
})
}
// phone add ice from web
socket.on('remote_ice_candidate', function(ice) {
socket.to(getId(clients, '/phone')).emit('send_ice_to_pc', {
ice: ice
})
})
// phone add sdp from web
socket.on('send_pc_sdp', function(data) {
// send to phone
socket.to(getId(clients, '/phone')).emit('set_phone_remote_sdp', {
desc: data
})
})
// socket disconnect and remove it from clients
socket.on('disconnect', () => {
let id = socket.id
clients.forEach((client, index) => {
if (client.id === id) {
clients.splice(index, 1)
}
})
})
})
// get the socket id to emit
function getId(sockets, exp) {
let id
sockets.forEach(socket => {
if (socket.handshake.headers.referer.match(exp)) {
id = socket.id
}
})
return id
}
server.listen(3000, function() {
console.log('port listening at 3000')
})
// --------------------------------------------- //
// web.js
var socket = io();
var server = {
// "iceServers": [{
// "url": "stun:stun.l.google.com:19302"
// }]
},
pc = new RTCPeerConnection(null),
v = document.querySelector('#video2')
// web onicecandidate
pc.onicecandidate = function(event) {
if (event.candidate) {
socket.emit('remote_ice_candidate', {
ice: event.candidate
})
}
}
// web addIceCandidate
socket.on('pc_add_ice', function(event) {
pc.addIceCandidate(new RTCIceCandidate(event.ice))
})
// didn't trigger
pc.ontrack = function(e) {
// v.srcObject = e.streams[0];
console.log(e, 'pc.ontrack')
}
// web setRemoteDescription and createAnswer
socket.on('set_pc_remote_sdp', function(e) {
pc.setRemoteDescription(e.desc).then(
function() {
console.log('remote setRemoteDescription success')
pc.createAnswer().then(function(desc) {
pc.setLocalDescription(desc).then(
function() {
socket.emit('send_pc_sdp', {
desc: desc
})
},
function(err) {
console.log(err)
}
);
})
},
function() {
console.log('pc setLocalDescription error')
}
)
})
// web iceConnectionState
pc.oniceconnectionstatechange = function() {
console.log('web oniceconnectionstatechange', pc.iceConnectionState)
// log checking -> connected
};
//---------------------------------------------//
// phone.js
var socket = io();
var server = {
// "iceServers": [{
// "url": "stun:stun.l.google.com:19302"
// }]
},
pc = new RTCPeerConnection(null),
v = document.querySelector('#video1')
// phone onicecandidate
pc.onicecandidate = function(event) {
if (event.candidate) {
socket.emit('phone_ice_candidate', {
ice: event.candidate
})
}
}
// phone addIceCandidate
socket.on('send_ice_to_pc', function(event) {
pc.addIceCandidate(new RTCIceCandidate(event.ice.ice))
})
// getUserMedia
navigator.mediaDevices.getUserMedia({
video: {
width: 400,
height: 300
},
audio: false
})
.then(function(stream) {
v.src = window.URL.createObjectURL(stream);
pc.addStream(stream);
})
.then(function() {
// create offer
pc.createOffer({
offerToReceiveVideo: 1
}).then(function(e) {
// pc setLocalDescription
pc.setLocalDescription(e).then(
function() {
socket.emit('send_phone_sdp', {
desc: e
})
},
function() {
console.log('pc setLocalDescription error')
}
)
});
})
.catch(function(err) {
console.log(err.name + ": " + err.message);
})
// phone setRemoteDescription
socket.on('set_phone_remote_sdp', function(e) {
pc.setRemoteDescription(e.desc.desc).then(
function() {
console.log('pc setRemoteDescription success')
},
function(err) {
console.log(err)
})
})
// phone iceConnectionState
pc.oniceconnectionstatechange = function() {
console.log('phone oniceconnectionstatechange', pc.iceConnectionState)
// log checking -> connected -> completed
};
When i use firefox to open it, there is an error ICE failed, add a STUN server and see about:webrtc for more details in console.
In chrome the 'phone iceConnectionState' changed checking -> connected -> completed, the 'web iceConnectionState' changed checking -> connected.

have you set autoplay in your html? I have the same issue, and it turns out I should have set autoplay in my html tag. Namely:
<video autoplay></video>
Hope this helps!

getUserMedia is an async function. You are calling createOffer before you call pc.addStream which means there is nothing to negotiate.
Make the promise callback return your pc.createOffer() after pc.addStream(stream);

PTAL at https://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-signaling and compare your code to the example and see if you can figure it out.

Related

React native Expo how to keep tcp socket alive on background

I have a tcp socket client connection and server in my app. My main goal is taking data from server and send it to client connection. It works great while my app is running but if i get app to background it just sends one data and sends other datas after opening app again. I tried expo-task-manager but it just sends first data and doesnt sends after it as before. I was using my function inside useEffect in a context component. My function with task manager is below.
const BACKGROUND_CLIENT_TASK = "background-client-task";
TaskManager.defineTask(BACKGROUND_CLIENT_TASK, async () => {
const now = Date.now();
console.log(
`Got background fetch call at date: ${new Date(now).toISOString()}`
);
const mainFunction = async () => {
const server = TcpSocket.createServer(function (socket) {
socket.on("data", (takenData) => {
const jsonStringData = String.fromCharCode(...takenData);
const data = JSON.parse(jsonStringData);
const clientOptions = {
port: 1500,
host: "localhost",
};
const dataToClientArray = [
`DataProcess1${data}`,
`DataProcess2${data}`,
`DataProcess3${data}`,
];
dataToClientArray.forEach((dataToClient, index) => {
setTimeout(() => {
const client = TcpSocket.createConnection(
clientOptions,
() => {
// Write on the socket
client.write(`${dataToClient}`, "hex");
console.log("client started");
console.log(dataToClient);
// Close socket
client.destroy();
}
);
client.on("data", (data) => {
console.log("Received: ", data.toString());
});
client.on("error", (error) => {
console.log("Error: ", error);
});
client.on("close", () => {
console.log("Connection closed");
});
}, 300 * index);
});
});
socket.on("error", (error) => {
console.log("An error ocurred with client socket ", error);
});
socket.on("close", (error) => {
console.log("Closed connection with ", socket.address());
});
}).listen({ port: 1754, host: "0.0.0.0" });
server.on("error", (error) => {
console.log("An error ocurred with the server", error);
});
server.on("close", () => {
console.log("Server closed connection");
});
};
mainFunction();
// Be sure to return the successful result type!
return BackgroundFetch.BackgroundFetchResult.NewData;
});
BTW It doesnt start if i dont add the mainFunction in useEffect
The code that i register my defined Task. (result returns undefined)
async function registerBackgroundFetchAsync() {
return BackgroundFetch.registerTaskAsync(BACKGROUND_CLIENT_TASK, {
minimumInterval: 5, // seconds
stopOnTerminate: false, // android only,
startOnBoot: true, // android only
});
}
const registerFunction = async () => {
const result = await registerBackgroundFetchAsync();
console.log(result);
console.log("resultup");
const status = await BackgroundFetch.getStatusAsync();
const isRegistered = await TaskManager.isTaskRegisteredAsync(
BACKGROUND_CLIENT_TASK
);
setStatus(status);
setIsRegistered(isRegistered);
};
registerFunction();

The stream event is not working in PeerJS

The application is a Zoom-like program where users are connected P2P using PeerJS calling feature.
This is the PeerJS object set up:
var peer = new Peer(undefined, {
config: {
'iceServers': [
{
url: 'stun:relay.metered.ca:80'
},
{
url: 'turn:relay.metered.ca:80',
username: '*********',
credential: '**********',
},
{
url: 'turn:relay.metered.ca:443',
username: '*********',
credential: '*********'
}
]},
host: '/',
port: '3001'
})
Playing the current user's stream, and listening for other peers' calls:
const myVideo = document.createElement('video')
myVideo.muted = true
const peers = {}
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
addVideoStream(myVideo, stream)
// listen for external calls on peer server
peer.on('call', call => {
call.answer(stream)
const video = document.createElement('video')
call.on('stream', userVideoStream => {
addVideoStream(video, userVideoStream)
})
})
socket.on('user-connected', userId => {
const call = peer.call(userId, stream)
const video = document.createElement('video')
call.on('stream', function(videoStream) {
addVideoStream(video, videoStream)
})
call.on('close', () => {
video.remove()
})
peers[userId] = call
})
})
peer.on('open', id => {
socket.emit('join-room', ROOM_ID, id)
})
function addVideoStream(video, stream) {
video.srcObject = stream
// once the video's ready play it
video.addEventListener('loadedmetadata', () => {
video.play()
})
videoGrid.append(video)
}
The server file:
const express = require('express')
const app = express()
const server = require('http').Server(app)
const io = require('socket.io')(server)
const { v4: uuidV4 } = require('uuid')
app.set('view engine', 'ejs')
app.use(express.static('public'))
app.get('/', (req, res) => {
res.redirect(`/${uuidV4()}`)
})
app.get('/:room', (req, res) => {
res.render('room', { roomId: req.params.room })
})
io.on('connection', socket => {
socket.on('join-room', (roomId, userId) => {
socket.join(roomId)
socket.to(roomId).emit('user-connected', userId)
socket.on('disconnect', () => {
socket.to(roomId).emit('user-disconnected', userId)
})
})
})
server.listen(3000)
I connect the app successfully, and I can make calls when I open the the same room's URL in another browser window (Chrome), but the call.on('stream') does not execute. I tried to see if there is an error using peer.on(error), and console.log() in the stream event's callback function, but there were no errors. I don't know what I'm missing here! Any help would be appreciated. Thank you.

Can't get WebRTC to work in different networks

I am trying to transfer a video streaming from one browser to another with WebRTC and socket.io. It works just fine in the same network. No image is getting through across different ones.
I use socket-io as a signal server. I register two browsers in a "room" and then start sending signals.
The code which is executed in the browser from which the streaming is sent:
function joinRoom(room) {
if (room === '') {
alert('Please type a room ID')
} else {
data = { room: room};
socket.emit('join', data);
}
}
// SOCKET EVENT CALLBACKS =====================================================
socket.on('room_created', async () => {
console.log('Socket event callback: room_created')
await setLocalStream(mediaConstraints)
socket.emit('startc', {room: roomId, clientip: clientip})
isRoomCreator = true
})
socket.on('full_room', () => {
console.log('Socket event callback: full_room')
alert('The room is full, please try another one')
})
socket.on('startc', async () => {
console.log('Socket event callback: start_call')
if (isRoomCreator) {
rtcPeerConnection = new RTCPeerConnection(iceServers)
addLocalTracks(rtcPeerConnection)
rtcPeerConnection.ontrack = setRemoteStream
rtcPeerConnection.onicecandidate = sendIceCandidate
await createOffer(rtcPeerConnection)
}
})
socket.on('offer', async (event) => {
console.log('Socket event callback: offer')
if (!isRoomCreator) {
rtcPeerConnection = new RTCPeerConnection(iceServers)
addLocalTracks(rtcPeerConnection)
rtcPeerConnection.ontrack = setRemoteStream
rtcPeerConnection.onicecandidate = sendIceCandidate
rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(event))
await createAnswer(rtcPeerConnection)
}
})
socket.on('answer', (event) => {
console.log('answer');
console.log('Socket event callback: webrtc_answer')
rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(event))
})
socket.on('webrtc_ice_candidate', (event) => {
console.log('Socket event callback: webrtc_ice_candidate')
// ICE candidate configuration.
var candidate = new RTCIceCandidate({
sdpMLineIndex: event.label,
candidate: event.candidate,
})
rtcPeerConnection.addIceCandidate(candidate)
})
The code that accepts the streamed media (vuejs):
socket.on("offer", (data) => {
this.$emit("closeWaitingToConnect");
this.createAnswer(data);
});
joinMeToRoom() {
console.log("joinToRoom: ", this.room);
this.$socket.emit("join", this.room);
}, //joinMeToRoom ()
createAnswer: function(event) {
var roomId = this.room.room;
let sessionDescription
this.peer.ontrack = this.setRemoteStream
this.peer.onicecandidate = this.sendIceCandidate
this.peer.setRemoteDescription(new RTCSessionDescription(event))
try {
sessionDescription = this.peer.createAnswer().then((answer) => {
var anwer =
console.log('sessionDescription');
console.log(answer);
this.$socket.emit('answer', {
type: 'webrtc_answer',
sdp: answer,
sessionDescription: JSON.stringify(answer),
roomId,
})
return this.peer.setLocalDescription(answer)
});
} catch (error) {
console.error('cae: '+error)
}
},
getScreenPosition() {
const right = this.$refs.screen.getBoundingClientRect().right;
const bottom = this.$refs.screen.getBoundingClientRect().bottom;
return { bottom: bottom, right: right };
},
setRemoteStream(event) {
console.log('event setRemoteStream');
console.log(event);
var stream_screen = document.querySelector("video");
stream_screen.srcObject = event.streams[0];
stream_screen.play();
var remoteStream = event.stream
},
I have setup my own TURN server and tried paid versions. Still can't get the stream across different networks.
What am I missing?

onRemoteStreamListener doesn't get triggered on the Initiator's Side (Web App)

I am trying to build a video chat application between 2 users using the sdk provided by Connecty Cube everything works fine so far till.
User 1 (caller) : initiates the call
User 2 (opponent) : receives the call and accepts it
User 1 & User 2 : should get a call back function onRemoteStreamListener in order to start the Video Session between them
What actually happens User 2 only gets the Event for OnRemoteStreamListener and could view/hear User 1, while onRemoteStreamListener doesn't get triggered in User 1 side. I am not sure what is the problem as I have been going through the documentation provided by Connecty Cube and having it as my reference for the integration
link: https://developers.connectycube.com/js/videocalling?id=accept-a-call
ConnectyCube SDK Version : 3.9.1
Here are samples for the code:
User 1 (Caller Code)
async startVideoSession() {
try {
const {
dispatch,
getters: {
getConnectyCubeSessionInfo,
getUserData,
getSelectedVideoAppointement
}
} = this.$store;
const patientConnectyCubeUserResponse = await dispatch(
"getUserFromConnectyCube",
{
sessionInfo: getConnectyCubeSessionInfo,
email: getSelectedVideoAppointement.patient.email
}
);
const doctorConnectyCubeUserResponse = await dispatch(
"getUserFromConnectyCube",
{
sessionInfo: getConnectyCubeSessionInfo,
email: getUserData.email
}
);
const {
Credentials: { appId }
} = connectyCubeClient;
const client = new ConnectyCube();
await client.init({ appId, token: getConnectyCubeSessionInfo.token });
await client.createSession({
login: ########,
password: ##########
});
const token = client.service.sdkInstance.session.token;
await client.chat.connect({
userId: doctorConnectyCubeUserResponse.user.id,
password: token
});
const isConnected = await client.chat.isConnected;
if (isConnected) {
const calleesIds = [patientConnectyCubeUserResponse.user.id]; // User's ids
const sessionType = client.videochat.CallType.VIDEO; // AUDIO is also possible
const additionalOptions = { bandwidth: 256 };
const session = await client.videochat.createNewSession(
calleesIds,
sessionType,
additionalOptions
);
const mediaParams = {
audio: true,
video: true,
options: {
muted: true,
mirror: true
}
};
await session
.getUserMedia(mediaParams)
.then(localStream => {
session.attachMediaStream("doctor-video", localStream);
const extension = {};
session.call(extension, error => {
console.log(error);
});
})
.catch(err => {
console.error(err);
});
console.log("last console.log");
client.videochat.onAcceptCallListener = this.onAcceptCallListener;
// client.videochat.onUserNotAnswerListener = function(session, userId) {
// console.log("call refused");
// };
client.videochat.onSessionConnectionStateChangedListener = function(
session,
userID,
connectionState
) {
console.log("Connection state => \n", connectionState);
};
client.videochat.onRemoteStreamListener = this.OnRemoteStreamListener;
}
} catch (err) {
console.log("ERRRRRROR", err);
}
}
OnRemoteStreamListener(session, userId, remoteStream) {
// attach the remote stream to DOM element
console.log("STREAM FROM DOCTOR");
session.attachMediaStream("patient-video", remoteStream);
},
User 2 (opponent code)
async initalizeConnectyCube() {
const {
getters: { getPatientData }
} = this.$store;
const client = new ConnectyCube();
const { Credentials, Config } = connectyCubeClient;
await client.init(Credentials, Config);
const sessionInfo = await client.createSession();
await client.init({ appId: Credentials.appId, token: sessionInfo.token });
const sessionStatus = await client.createSession({
login: ########,
password: ########
});
const token = client.service.sdkInstance.session.token;
await client.chat.connect({
userId: sessionStatus.user.id,
password: token
});
const isUserConnected = await client.chat.isConnected;
if (isUserConnected) {
client.videochat.onCallListener = this.handleOnCallListener;
client.videochat.onRemoteStreamListener = this.handleOnRemoteStreamListener;
}
}
handleOnRemoteStreamListener(session, userID, remoteStream) {
// attach the remote stream to DOM element
console.log("STREAM FROM CALLER");
session.attachMediaStream("patient-video", remoteStream);
}

WebRTC state checking

I would like connect 2 devices with WebRTC on localhost. All devices have no internet access. They are connected to a same local wifi.
I try this on React Native App.
In this context offline, do I need to trickle ICE candidates and addIceCandidate ? If I understund correctly, ICE candidates is for iceServer. But my case, iceServer is null (because i'm offline only, connected on same localhost wifi) :
const configuration = { iceServers: [{ urls: [] }] };
So actualty i exchange offer and answer, but after setRemoteDescription the answer, the connectionState stay on checking.
You can see my React Component :
constructor(props) {
super(props);
this.pc = new RTCPeerConnection(configuration);
}
state = initialState;
componentDidMount() {
const { pc } = this;
if (pc) {
this.setState({
peerCreated: true
});
}
this.setConnectionState();
pc.oniceconnectionstatechange = () => this.setConnectionState();
pc.onaddstream = ({ stream }) => {
if (stream) {
this.setState({
receiverVideoURL: stream.toURL()
});
}
};
pc.onnegotiationneeded = () => {
if (this.state.initiator) {
this.createOffer();
}
};
pc.onicecandidate = ({ candidate }) => {
if (candidate === null) {
const { offer } = this.state;
const field = !offer ? 'offer' : 'data';
setTimeout(() => {
alert('setTimeout started');
this.setState({
[field]: JSON.stringify(pc.localDescription)
});
}, 2000);
}
};
}
#autobind
setConnectionState() {
this.setState({
connectionState: this.pc.iceConnectionState
});
}
getUserMedia() {
MediaStreamTrack.getSources(() => {
getUserMedia(
{
audio: false,
video: true
},
this.getUserMediaSuccess,
this.getUserMediaError
);
});
}
#autobind
async getUserMediaSuccess(stream) {
const { pc } = this;
pc.addStream(stream);
await this.setState({ videoURL: stream.toURL() });
if (this.state.initiator) {
return this.createOffer();
}
return this.createAnswer();
}
getUserMediaError(error) {
console.log(error);
}
#autobind
logError(error) {
const errorArray = [...this.state.error, error];
return this.setState({
error: errorArray
});
}
/**
* Create offer
*
* #memberof HomeScreen
*/
#autobind
createOffer() {
const { pc } = this;
pc.createOffer()
.then(offer => pc.setLocalDescription(offer))
.then(() => {
this.setState({
offerCreated: true
});
})
.catch(this.logError);
}
/**
* Create anwser
*
* #memberof HomeScreen
*/
#autobind
async createAnswer() {
const { pc } = this;
const { data } = this.state;
if (data) {
const sd = new RTCSessionDescription(JSON.parse(data));
await this.setState({
offerImported: true
});
pc.setRemoteDescription(sd)
.then(() => pc.createAnswer())
.then(answer => pc.setLocalDescription(answer))
.then(() => {
this.setState({
answerCreated: true
});
})
.catch(this.logError);
}
}
#autobind
receiveAnswer() {
const { pc } = this;
const { data } = this.state;
const sd = new RTCSessionDescription(JSON.parse(data));
return pc
.setRemoteDescription(sd)
.then(() => {
this.setState({
answerImported: true
});
})
.catch(this.logError);
}
/**
* Start communication
*
* #param {boolean} [initiator=true]
* #returns
* #memberof HomeScreen
*/
#autobind
async start(initiator = true) {
if (!initiator) {
await this.setState({
initiator: false
});
}
return this.getUserMedia();
}
Anyone can help me?
No iceServers is fine on a LAN, but peers must still exchange at least one candidate: their host candidate (based on their machine's LAN IP address).
Either:
Trickle candidates using onicecandidate -> signaling -> addIceCandidate as usual, or
Out-wait the ICE process (a few seconds) before exchanging pc.localDescription.
It looks like you're attempting the latter. This approach works because...
Trickle ICE is an optimization.
The signaling (trickling) of individual ice candidates using onicecandidate, is an optimization meant to speed up negotiation. Once setLocalDescription succeeds, the browser's internal ICE Agent starts, inserting ICE candidates, as they're discovered, into the localDescription itself. Wait a few seconds to negotiate, and trickling isn't necessary at all: all ICE candidates will be in the offer and answer transmitted.
Your code
From your onicecandidate code it looks like you're already attempting to gather the localDescription after ICE completion (and you've written it to work from both ends):
pc.onicecandidate = ({ candidate }) => {
if (!candidate) {
const { offer } = this.state;
const field = !offer ? 'offer' : 'data';
this.setState({
[field]: JSON.stringify(pc.localDescription)
});
}
};
In the offerer side you've correctly commented out the equivalent code in createOffer:
pc.createOffer()
.then(offer => pc.setLocalDescription(offer))
.catch(this.logError);
// .then(() => {
// this.setState({
// offer: JSON.stringify(pc.localDescription)
// });
// });
But on the answerer side, you have not, and that's likely the problem:
createAnswer() {
const { pc } = this;
const { data } = this.state;
if (data) {
const sd = new RTCSessionDescription(JSON.parse(data));
pc.setRemoteDescription(sd)
.then(() => pc.createAnswer())
.then(answer => pc.setLocalDescription(answer))
.then(() => {
this.setState({
offer: JSON.stringify(pc.localDescription)
});
})
.catch(this.logError);
}
}
This means it sends an answer back immediately, before the answerer's ICE agent has had time to insert any candidates into the answer. This is probably why it fails.
On a side-note: Nothing appears to wait for getUserMedia to finish either, so answers likely won't contain any video either, depending on the timing of your getUserMediaSuccess function, which fails to add any tracks or streams to the connection. But assuming you're just doing data channels, this should work with my recommended fixes.