I am trying to write a chat application using WebRTC and I can send the messages over the dataChannel using a code like bellow one:
const peerConnection = new RTCPeerConnection();
const dataChannel =
peerConnection.createDataChannel("myLabel", dataChannelOptions);
dataChannel.onerror = (error) => {
console.log("Data Channel Error:", error);
};
dataChannel.onmessage = (event) => {
console.log("Got Data Channel Message:", event.data);
};
dataChannel.onopen = () => {
dataChannel.send("Hello World!");
};
dataChannel.onclose = () => {
console.log("The Data Channel is Closed");
};
with dataChannel.send() I can send data over the channel correctly. but I am wondering to know, is there any way to determine that the sent message is delivered to another side or not?
This simplest answer is: send a reply.
But you may not need to, if you use an ordered, reliable datachannel (which is the default).
An ordered reliable datachannel
With one of these, you determine a message was sent, by waiting for the bufferedAmount to go down:
const pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();
const channel = pc1.createDataChannel("chat");
chat.onkeypress = async e => {
if (e.keyCode != 13) return;
const before = channel.bufferedAmount;
channel.send(chat.value);
const after = channel.bufferedAmount;
console.log(`Queued ${after - before} bytes`);
channel.bufferedAmountLowThreshold = before; // set floor trigger and wait
await new Promise(r => channel.addEventListener("bufferedamountlow", r));
console.log(`Sent ${after - channel.bufferedAmount} bytes`);
chat.value = "";
};
pc2.ondatachannel = e => e.channel.onmessage = e => console.log(`> ${e.data}`);
pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
pc1.oniceconnectionstatechange = e => console.log(pc1.iceConnectionState);
pc1.onnegotiationneeded = async e => {
await pc1.setLocalDescription(await pc1.createOffer());
await pc2.setRemoteDescription(pc1.localDescription);
await pc2.setLocalDescription(await pc2.createAnswer());
await pc1.setRemoteDescription(pc2.localDescription);
}
Chat: <input id="chat"><br>
Since the channel is reliable, it won't give up sending this message until it's been received.
Since the channel is ordered, it won't send a second message before this message has been received.
This lets you send a bunch of messages in a row without waiting on a reply. As long as the bufferedAmount keeps goes down, you know it's being sent and received.
In short, to determine a message was received, send a second message, or have the other side send a reply.
An unreliable datachannel
If you're using an unreliable datachannel, then sending a reply is the only way. But since there's no guarantee the reply will make it back, this may produce false negatives, causing duplicate messages on the receiving end.
Unreliable one way, reliable the other
Using the negotiated constructor argument, it's possible to create a datachannel that's unreliable in one direction, yet reliable in the other. This can be used to solve the unreliable reply, to avoid duplicate messages on the receiving (pc2) end.
dc1 = pc1.createDataChannel("chat", {negotiated: true, id: 0, maxRetransmits: 0});
dc2 = pc2.createDataChannel("chat", {negotiated: true, id: 0});
Related
I'm currently following this example and I'm tring to extend upon it with my own logic.
I can get the socket to connect, but I see that there is a problem in having the client reconnect continuously - Hence why I want to have a limit on the number of retries that the client can perform.
socket.pipe(
tap((data => console.log(data))),
retryWhen((errors) =>
errors.pipe(
take(this.retryCount),
delayWhen(val =>
timer(val * 1000)
)
)
)
).subscribe()
I looked up the documentation and figured that I could use the take() operator from rxjs. However, given that I set my retryCount to 5, the following scenario can be assumed:
Client connects to socket successfully
Client disconnects from the socket
Client reconnects after 4 retries
Client disconnects
Client retries 1 times, and stops retrying (take(5) has been reached)
Is there a way, in which I can "reset" the amount of times that take() will retry? i.e. so that every time the client disconnects from the socket, it will always have 5 retries?
The retryWhen logic from your example doesn't take into account all the possible intertwined flows of connect/disconnect/reconnect. Also the reset of the reconnection attempts doesn't happen.
Beside that, you're touching the inner Observables for open and close of the webSocket directly (which is not technically wrong), while you could put all the logic and handlers inside the subject's pipe and subscription code. I've forked your example from StackBlitz and adjusted it so that any connect / disconnect and reconnect events are handled properly.
As you can see in the screenshot below, it's trying to reconnect five time before it reaches the limit and quit.
The number of reconnect retries and the delay between each trial can be tweaked via this two constants:
const maxReconnectAttempts = 5; // -1 stands for 'forever'
const reconnectAttemptDelay = 1000; // in ms
As written in the comment, if you put const maxReconnectAttempts = -1 the reconnection trials will go on forever.
You can find below the whole code taken from index.js, or here in the forked StackBlitz repo for the full reference.
import { iif, of, throwError } from 'rxjs';
import { concatMap, delay, retryWhen, tap } from 'rxjs/operators';
import { webSocket } from 'rxjs/webSocket';
// get elements
const btn1 = document.getElementById('btn1');
const btn2 = document.getElementById('btn2');
const display = document.getElementById('app');
const msg = document.getElementById('msg');
// bind click actions
btn1.onclick = connect;
btn2.onclick = disconnect;
// create a webSocketSubject
const wsUrl = 'wss://www.gasnow.org/ws/gasprice';
const maxReconnectAttempts = 5; // -1 stands for 'forever'
const reconnectAttemptDelay = 1000; // in ms
let wsSubject$;
let wsSubscription;
let disconnected = false;
let reconnectRetryCount = 0;
// display live data on view
const bindDataToView = ({ data }) => {
console.log(data);
display.innerText = Number(data.rapid);
};
// connect/subscribe to websocket url
function connect() {
if (!this.wsSubject || this.wsSubject.closed) {
wsSubject$ = webSocket(wsUrl); // create a fresh instance
console.log(`Initializing WebSocket connection to ${wsUrl}...`);
wsSubscription = wsSubject$
.pipe(
retryWhen((errors) =>
errors.pipe(
concatMap((error) =>
iif(
() =>
maxReconnectAttempts !== -1 &&
reconnectRetryCount++ >= maxReconnectAttempts,
throwError('WebSocket reconnecting retry limit exceeded!'),
of(error).pipe(
tap(() => {
disconnected = true;
console.warn('Trying to reconnect to WebSocket server...');
}),
delay(reconnectAttemptDelay)
)
)
)
)
),
tap(() => {
if (disconnected) {
disconnected = false;
reconnectRetryCount = 0;
msg.innerText = 'Streaming ...';
console.log('Successfully reconnected to the WebSocket server.');
}
})
)
.subscribe(
(data) => {
msg.innerText = 'Streaming ...';
bindDataToView(data);
},
(err) => {
reconnectRetryCount = 0;
console.error(err);
},
() => {
reconnectRetryCount = 0;
msg.innerText = 'Connection closed';
console.warn('Connection to the WebSocket server was closed!');
}
);
}
}
// close websocket connection
function disconnect() {
if (wsSubject$) {
wsSubject$.complete(); // this will trigger closingObserver and closeObserver
wsSubject$.unsubscribe();
wsSubject$ = null;
disconnected = true;
reconnectRetryCount = 0;
if (wsSubscription) {
wsSubscription.unsubscribe();
wsSubscription = null;
}
console.log('Disconnected from the WebSocket server.');
}
}
I'm currently developing a NodeJS WebSocket server. To detect broken connections I've followed this guide here:
https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
The server side works really good but the client makes problems because I can't find a ping function.
Does anyone has an idea how I can get the client part done without the library?
const WebSocket = require('ws');
function heartbeat() {
clearTimeout(this.pingTimeout);
// Use `WebSocket#terminate()`, which immediately destroys the connection,
// instead of `WebSocket#close()`, which waits for the close timer.
// Delay should be equal to the interval at which your server
// sends out pings plus a conservative assumption of the latency.
this.pingTimeout = setTimeout(() => {
this.terminate();
}, 30000 + 1000);
}
const client = new WebSocket('wss://echo.websocket.org/');
client.on('open', heartbeat);
client.on('ping', heartbeat);
client.on('close', function clear() {
clearTimeout(this.pingTimeout);
});
One main problem is that there is no ping method I think:
client.on('open') -> client.onopen available in JavaScript
client.on('close') -> client.onclose available in JavaScript
client.on('ping') -> How? Just how?
There is no Javascript API to send ping frames or receive pong frames. This is either supported by your browser, or not. There is also no API to enable, configure or detect whether the browser supports and is using ping/pong frames.
https://stackoverflow.com/a/10586583/7377682
Sad but true, in case of the ping frame, the API does not support it as mentioned in previous answers.
The most popular workaround is to listen to the close event and try to reconnect to the server using an interval.
This tutorial is easy to understand and contains most use-cases to begin with WS:
var ws = new WebSocket("ws://localhost:3000/ws");
let that = this; // cache the this
var connectInterval;
var check = () => {
const { ws } = this.state;
if (!ws || ws.readyState == WebSocket.CLOSED) this.connect(); //check if websocket instance is closed, if so call `connect` function.
};
// websocket onopen event listener
ws.onopen = () => {
console.log("connected websocket main component");
this.setState({ ws: ws });
that.timeout = 250; // reset timer to 250 on open of websocket connection
clearTimeout(connectInterval); // clear Interval on on open of websocket connection
};
// websocket onclose event listener
ws.onclose = e => {
console.log(
`Socket is closed. Reconnect will be attempted in ${Math.min(
10000 / 1000,
(that.timeout + that.timeout) / 1000
)} second.`,
e.reason
);
that.timeout = that.timeout + that.timeout; //increment retry interval
connectInterval = setTimeout(this.check, Math.min(10000, that.timeout)); //call check function after timeout
};
// websocket onerror event listener
ws.onerror = err => {
console.error(
"Socket encountered error: ",
err.message,
"Closing socket"
);
ws.close();
};
I think what you are look for on the client is onmessage:
client.onmessage = function (event) {
console.log(event.data);
}
All messages sent from the server can be listened to this way. See https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications
hello i am newbie in WebRTC and i tried this code
const yourVideo = document.querySelector("#face_cam_vid");
const theirVideo = document.querySelector("#thevid");
(async () => {
if (!("mediaDevices" in navigator) || !("RTCPeerConnection" in window)) {
alert("Sorry, your browser does not support WebRTC.");
return;
}
const stream = await navigator.mediaDevices.getUserMedia({video: true,
audio: true});
yourVideo.srcObject = stream;
const configuration = {
iceServers: [{urls: "stun:stun.1.google.com:19302"}]
};
const yours = new RTCPeerConnection(configuration);
const theirs = new RTCPeerConnection(configuration);
for (const track of stream.getTracks()) {
yours.addTrack(track, stream);
}
theirs.ontrack = e => theirVideo.srcObject = e.streams[0];
yours.onicecandidate = e => theirs.addIceCandidate(e.candidate);
theirs.onicecandidate = e => yours.addIceCandidate(e.candidate);
const offer = await yours.createOffer();
await yours.setLocalDescription(offer);
await theirs.setRemoteDescription(offer);
const answer = await theirs.createAnswer();
await theirs.setLocalDescription(answer);
await yours.setRemoteDescription(answer);
})();
and it works but partly https://imgur.com/a/nG7Xif6 . in short i am creating ONE-to-ONE random video chatting like in omegle but this code displays both "remote"(stranger's) and "local"("mine") video with my local stream but all i want is , user wait for second user to have video chat and when third user enters it should wait for fourth and etc. i hope someone will help me with that
You're confusing a local-loop demo—what you have—with a chat room.
A local-loop demo is a short-circuit client-only proof-of-concept, linking two peer connections on the same page to each other. Utterly useless, except to prove the API works and the browser can talk to itself.
It contains localPeerConnection and remotePeerConnection—or pc1 and pc2—and is not how one would typically write WebRTC code. It leaves out signaling.
Signaling is hard to demo without a server, but I show people this tab demo. Right-click and open it in two adjacent windows, and click the Call! button on one to call the other. It uses localSocket, a non-production hack I made using localStorage for signaling.
Just as useless, a tab-demo looks more like real code:
const pc = new RTCPeerConnection();
call.onclick = async () => {
video.srcObject = await navigator.mediaDevices.getUserMedia({video:true, audio:true});
for (const track of video.srcObject.getTracks()) {
pc.addTrack(track, video.srcObject);
}
};
pc.ontrack = e => video.srcObject = e.streams[0];
pc.oniceconnectionstatechange = e => console.log(pc.iceConnectionState);
pc.onicecandidate = ({candidate}) => sc.send({candidate});
pc.onnegotiationneeded = async e => {
await pc.setLocalDescription(await pc.createOffer());
sc.send({sdp: pc.localDescription});
}
const sc = new localSocket();
sc.onmessage = async ({data: {sdp, candidate}}) => {
if (sdp) {
await pc.setRemoteDescription(sdp);
if (sdp.type == "offer") {
await pc.setLocalDescription(await pc.createAnswer());
sc.send({sdp: pc.localDescription});
}
} else if (candidate) await pc.addIceCandidate(candidate);
}
There's a single pc—your half of the call—and there's an onmessage signaling callback to handle the timing-critical asymmetric offer/answer negotiation correctly. Same JS on both sides.
This still isn't a chat-room. For that you need server logic to determine how people meet, and a web socket server for signaling. Try this tutorial on MDN which culminates in a chat demo.
I'm trying to add some functionality for rabbitmq with delay messages. Actually I need to get this message after 2 weeks. As I know we do not need any plugin. Also when this message invokes, how should I reschedule new x delay exchanger to invoke again over 2 weeks. Where shoul I added this x delay message.
config
"messageQueue": {
"connectionString": "amqp://guest:guest#localhost:5672?heartbeat=5",
"queueName": "history",
"exchange": {
"type": "headers",
"prefix": "history."
},
"reconnectTimeout": 5000
},
service:
import amqplib from 'amqplib'
import config from 'config'
import logger from './logger'
const {reconnectTimeout, connectionString, exchange: {prefix, type: exchangeType}, queueName} = config.messageQueue
const onConsume = (expectedMessages, channel, onMessage) => async message => {
const {fields: {exchange}, properties: {correlationId, replyTo}, content} = message
logger.silly(`consumed message from ${exchange}`)
const messageTypeName = exchange.substring(exchange.startsWith(prefix) ? prefix.length : 0)
const messageType = expectedMessages[messageTypeName]
if (!messageType) {
logger.warn(`Unexpected message of type ${messageTypeName} received. The service only accepts messages of types `, Object.keys(expectedMessages))
return
}
const deserializedMessage = messageType.decode(content)
const object = deserializedMessage.toJSON()
const result = await onMessage(messageTypeName, object)
if (correlationId && replyTo) {
const {type, response} = result
const encoded = type.encode(response).finish()
channel.publish('', replyTo, encoded, {correlationId})
}
}
const startService = async (expectedMessages, onMessage) => {
const restoreOnFailure = e => {
logger.warn('connection with message bus lost due to error', e)
logger.info(`reconnecting in ${reconnectTimeout} milliseconds`)
setTimeout(() => startService(expectedMessages, onMessage), reconnectTimeout)
}
const exchanges = Object.keys(expectedMessages).map(m => `${prefix}${m}`)
try {
const connection = await amqplib.connect(connectionString)
connection.on('error', restoreOnFailure)
const channel = await connection.createChannel()
const handleConsume = onConsume(expectedMessages, channel, onMessage)
const queue = await channel.assertQueue(queueName)
exchanges.forEach(exchange => {
channel.assertExchange(exchange, exchangeType, {durable: true})
channel.bindQueue(queue.queue, exchange, '')
})
logger.debug(`start listening messages from ${exchanges.join(', ')}`)
channel.consume(queue.queue, handleConsume, {noAck: true})
}
catch (e) {
logger.warn('error while subscribing for messages message', e)
restoreOnFailure(e)
}
}
export default startService
RabbitMQ has a plug-in for scheduling messages. You can use it, subject to an important design caveat which I explain below.
Use Steps
You must first install it:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
Then, you have to set up a delayed exchange:
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-delayed-type", "direct");
channel.exchangeDeclare("my-exchange", "x-delayed-message", true, false, args);
Finally, you can set the x-delay parameter (where delay is in milliseconds).
byte[] messageBodyBytes = "delayed payload".getBytes();
AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder();
headers = new HashMap<String, Object>();
headers.put("x-delay", 5000);
props.headers(headers);
channel.basicPublish("my-exchange", "", props.build(), messageBodyBytes);
Two weeks is equal to (7*24*60*60*1000 = 604,800,000) milliseconds.
Important Caveat
As I explained in this answer, this is a really bad thing to ask the message broker to do.
It's important to keep in mind, when dealing with message queues, they perform a very specific function in a system: to hold messages while the processor(s) are busy processing earlier messages. It is expected that a properly-functioning message queue will deliver messages as soon as reasonable. Basically, the fundamental expectation is that as soon as a message reaches the head of the queue, the next pull on the queue will yield the message -- no delay.
Delay becomes a result of how a system with a queue processes messages. In fact, Little's Law offers some interesting insights into this. If you're going to stick an arbitrary delay in there, you really have no need of a message queue to begin with - all your work is scheduled up front.
So, in a system where a delay is necessary (for example, to join/wait for a parallel operation to complete), you should be looking at other methods. Typically a queryable database would make sense in this particular instance. If you find yourself keeping messages in a queue for a pre-set period of time, you're actually using the message queue as a database - a function it was not designed to provide. Not only is this risky, but it also has a high likelihood of hurting the performance of your message broker.
I am trying to setup a peer to peer file sharing system using WebRTC. I'm able to open a data channel on each side, but I can't send messages from one user to another. Moreover, if one peer closes the channel, the other, the onclose event is only triggered for this user.
What's the proper way to setup and use a data channel with webRTC?
Could you tell me what's wrong or missing in my code?
//create RTC peer objet.
var RTCPeerConnection = webkitRTCPeerConnection;
var RTCIceCandidate = window.RTCIceCandidate;
var RTCSessionDescription = window.RTCSessionDescription;
var iceServers = {
iceServers: [{
url: 'stun:stun.l.google.com:19302'
}]
};
var p2p_connection = new RTCPeerConnection({
iceServers: [
{ 'url': (IS_CHROME ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121') }
]
});
// send offer (only executes in one browser)
function initiateConnection() {
p2p_connection.createOffer(function (description) {
p2p_connection.setLocalDescription(description);
server_socket.emit('p2p request', description,my_username);
});
};
// receive offer and send answer
server_socket.on('p2p request', function(description,sender){
console.log('received p2p request');
p2p_connection.setRemoteDescription(new RTCSessionDescription(description));
p2p_connection.createAnswer(function (description) {
p2p_connection.setLocalDescription(description);
server_socket.emit('p2p reply', description,sender);
});
});
// receive answer
server_socket.on('p2p reply', function(description,sender){
console.log('received p2p reply');
p2p_connection.setRemoteDescription(new RTCSessionDescription(description));
});
// ICE candidates
p2p_connection.onicecandidate = onicecandidate; // sent event listener
// locally generated
function onicecandidate(event) {
if (!p2p_connection || !event || !event.candidate) return;
var candidate = event.candidate;
server_socket.emit('add candidate',candidate,my_username);
}
// sent by other peer
server_socket.on('add candidate', function(candidate,my_username){
p2p_connection.addIceCandidate(new RTCIceCandidate({
sdpMLineIndex: candidate.sdpMLineIndex,
candidate: candidate.candidate
}));
});
// data channel
var dataChannel = p2p_connection.createDataChannel('label');
dataChannel.onmessage = function (event) {
var data = event.data;
console.log("I got data channel message: ", data);
};
dataChannel.onopen = function (event) {
console.log("Data channel ready");
dataChannel.send("Hello World!");
};
dataChannel.onclose = function (event) {
console.log("Data channel closed.");
};
dataChannel.onerror = function (event) {
console.log("Data channel error!");
}
Update:
Found the solution there: http://www.html5rocks.com/en/tutorials/webrtc/basics/
p2p_connection.ondatachannel = function (event) {
receiveChannel = event.channel;
receiveChannel.onmessage = function(event){
console.log(event.data);
};
};
You might consider using the simple-peer library to avoid dealing with these complexities in the future. The WebRTC API calls are confusing and the ordering is sometimes hard to get right.
simple-peer supports video/voice streams, data channel (text and binary data), and you can even use the data channel as a node.js-style duplex stream. It also supports advanced options like disabling trickle ICE candidates (so each client only needs to send one offer/answer message instead of many repeated ice candidate messages). It's un-opinionated and works with any backend.
https://github.com/feross/simple-peer
Abstractions!
https://github.com/feross/simple-peer (noted above by #Feross)
https://github.com/rtc-io/rtc-mesh
https://github.com/dominictarr/scuttlebutt
https://github.com/mafintosh/peervision
https://github.com/muaz-khan/DataChannel
Gigantic list of related projects...
https://github.com/kgryte/awesome-peer-to-peer