WebRTC - how to set always to use TURN server? - javascript

In the standard specs it says you can set ENUM value to "relay" : http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCIceServer
but, How do you set the enum to "relay" using Javascript of following?
if (tmp.indexOf("typ relay ") >= 0) { never occur in my test. how can i force it to enum "relay"?
or
is it a BUG? https://code.google.com/p/webrtc/issues/detail?id=1179
Any idea.
function createPeerConnection() {
try {
// Create an RTCPeerConnection via the polyfill (adapter.js).
pc = new RTCPeerConnection(pcConfig, pcConstraints);
pc.onicecandidate = onIceCandidate;
console.log('Created RTCPeerConnnection with:\n' +
' config: \'' + JSON.stringify(pcConfig) + '\';\n' +
' constraints: \'' + JSON.stringify(pcConstraints) + '\'.');
} catch (e) {
console.log('Failed to create PeerConnection, exception: ' + e.message);
alert('Cannot create RTCPeerConnection object; \
WebRTC is not supported by this browser.');
return;
}
pc.onaddstream = onRemoteStreamAdded;
pc.onremovestream = onRemoteStreamRemoved;
pc.onsignalingstatechange = onSignalingStateChanged;
pc.oniceconnectionstatechange = onIceConnectionStateChanged;
}
function onIceCandidate(event) {
if (event.candidate) {
var tmp = event.candidate.candidate;
if (tmp.indexOf("typ relay ") >= 0) {
/////////////////////////////////////////// NEVER happens
sendMessage({type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: tmp});
noteIceCandidate("Local", iceCandidateType(tmp));
}
} else {
console.log('End of candidates.');
}
}

There is a Chrome extension, WebRTC Network Limiter, that Configures how WebRTC's network traffic is routed by changing Chrome's privacy settings. You can force traffic to go through TURN without having to do any SDP mangling.
EDIT 1
The point of making this available in extensions, is for users that are worried about their security. You can check this excellent post about WebRTC security. The same as this extension does, can also be done in FF, by changing the flag media.peerconnection.ice.relay_only. This can be found in about:config. Don't know about the other two, but I'd wager they do have something similar.
On the other hand, if you are distributing an app and want all your clients to go through TURN, you won't have access to their browsers, and can't expect them to change those flags or install any extension. In that case, you'll have to mangle your SDP. You can use this code
function onIceCandidate(event) {
if (event.candidate) {
var type = event.candidate.candidate.split(" ")[7];
if (type != "relay") {
trace("ICE - Created " + type + " candidate ignored: TURN only mode.");
return;
} else {
sendCandidateMessage(candidate);
trace("ICE - Sending " + type + " candidate.");
}
} else {
trace("ICE - End of candidate generation.");
}
}
That code is taken from the discuss-webrtc list.
If you never get those candidates, it might very well be that your TURN server is not correctly configured. You can check your TURN server in this page. Don't forget to remove the existing STUN server configured in that demo.
EDIT 2
Even simpler: set iceTransportPolicy: "relay" in your WebRtcPeer config to force TURN:
var options = {
localVideo: videoInput, //if you want to see what you are sharing
onicecandidate: onIceCandidate,
mediaConstraints: constraints,
sendSource: 'screen',
iceTransportPolicy: 'relay',
iceServers: [{ urls: 'turn:XX.XX.XX.XX', username:'user', credential:'pass' }]
}
webRtcPeerScreencast = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, function(error) {
if (error) return onError(error) //use whatever you use for handling errors
this.generateOffer(onOffer)
});

This is working for me:
iceServers = [
{ "url": "turn:111.111.111.111:1234?transport=tcp",
"username": "xxxx",
"credential": "xxxxx"
}
]
optionsForWebRtc = {
remoteVideo : localVideoElement,
mediaConstraints : videoParams,
onicecandidate : onLocalIceCandidate,
configuration: {
iceServers: iceServers,
iceTransportPolicy: 'relay'
}
}

To force the usage of a TURN server, you need to intercept the candidates found by the browser. Just like you are doing.
But if it never occurs in your testings, you may have some problems with your TURN server.
If you have a working TURN server, you'll get the candidates with typ relay.
Remember that you need to configure TURN server with authentication. It is mandatory and the browser will only use the candidates "relay" if the request is authenticated. Your iceServers config for PeerConnection must have the credentials defined for TURN servers.

Related

Safari iOS does not play video in offline mode

I am trying to build a service worker that retrieves a video from cache if available and fetches from online if it is not available. Here is my code for that:
self.addEventListener("fetch", function (event) {
if (event.request.headers.get("range")) {
caches.match(event.request.url).then(function (res) {
if (!res) {
log.debug(
`Range request NOT found in cache for ${event.request.url}, activating fetch...`
);
return fetch(event.request);
}
returnRangeRequest(event);
});
} else {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
}
});
function returnRangeRequest(event) {
var rangeHeader = event.request.headers.get("range");
var rangeMatch = rangeHeader.match(/^bytes\=(\d+)\-(\d+)?/);
var pos = Number(rangeMatch[1]);
var pos2 = rangeMatch[2];
if (pos2) {
pos2 = Number(pos2);
}
event.respondWith(
caches
.match(event.request.url)
.then(function (res) {
return res.arrayBuffer();
})
.then(function (ab) {
let responseHeaders = {
status: 206,
statusText: "Partial Content",
headers: [
["Content-Type", "video/mp4"],
[
"Content-Range",
"bytes " +
pos +
"-" +
(pos2 || ab.byteLength - 1) +
"/" +
ab.byteLength,
],
],
};
var abSliced = {};
if (pos2 > 0) {
abSliced = ab.slice(pos, pos2 + 1);
} else {
abSliced = ab.slice(pos);
}
log.debug(
`Returning range request response`
);
return new Response(abSliced, responseHeaders);
})
.catch(function (err) {
log.error(err);
})
);
}
When I am online and I try to play the video, it works fine and it prints the debug line Range request NOT found in cache for https://example.com/vid.mp4, activating fetch...
When I have cached the video url using cache.add("https://example.com/vid.mp4");, and I try to play it, the video plays fine.
The problem arises when I turn off the Wifi on the iPad. When I try to play the video after turning of wifi, the video stays at 0:00 with a total length of 0:00.
Some of my findings:
When I have wifi on and I have the video cached, there are two requests made with bytes bytes=0-1 and then bytes=0-4444000.
When I have wifi off, the request for bytes=0-1 is made, but it stops with that.
Where am I going wrong?
Safari appears to have a very strict approach to range requests and similar issues to your problem are sometimes seen with regular online web servers.
In particular, Safari expects to see a '206' response when it sends a request with a byte range. If the server responds with a '200' request it appears Safari cannot handle this. Some other browsers seem to be ok with this - for example Chrome.
Apple provide some info on hoe to check for this:
If you are not sure whether your media server supports byte-range requests, you can open the Terminal application in OS X and use the curl command-line tool to download a short segment from a file on the server:
curl --range 0-99 http://example.com/test.mov -o /dev/null
If the tool reports that it downloaded 100 bytes, the media server correctly handled the byte-range request. If it downloads the entire file, you may need to update the media server. For more information on curl, see OS X Man Pages.
See this answer for more detail and background: https://stackoverflow.com/a/32998689/334402

Find out which Network Interface is which via node or Electron

I use an electron app that needs to send its active IP address (in a LAN) to my server for ongoing communication. For that I use a brilliant script I found here which works great:
function getIPs() {
let ifaces = os.networkInterfaces();
console.log(ifaces);
let ipAdresse = '';
Object.keys(ifaces).forEach(function (ifname) {
let alias = 0;
ifaces[ifname].forEach(function (iface) {
if ('IPv4' !== iface.family || iface.internal !== false) {
return;
}
if (alias >= 1) {
console.log(ifname + ':' + alias, iface.address);
} else {
console.log(ifname, iface.address);
ipAdress = iface.address;
}
++alias;
});
});
return ipAdress;
}
The Problem I have is: if this Client is connected to more then one Network (sometimes the users connect to a WIFI as well to have internet), I cannot tell which one is my LAN and which belongs to the WIFI. As I have no Control over the used WIFI I cannot tell them apart by their IP-Range.
Is there a way to tell (in Node or Electron) if an Network Interface belongs to Cable or WIFI?

Nodejs lookup of known bluetooth device

Is it possible to perform a lookup of a Bluetooth device given its address in a Nodejs script?
There are a few packages out there, the main one being Noble. However, they all focus around scanning, and not looking up a known address (as far as i can tell anyway!).
What i want to achieve, is to look up a known address, to see if the device can be found.
Much like PyBluez does for Python:
bluetooth.lookup_name('CC:20:E8:8F:3A:1D', timeout=5)
In Python, this can find the device even if it is undiscoverable, unlike a typical inquiry scan would.
I had this same problem and just found the btwatch lib, but it isn't working for me on the latest raspbian. But the source is just calling l2ping and looking for a string that I'm guessing no longer prints on success, so the modified code below works instead, similar to the lookup_name method, once you have l2ping installed (I think npm bluetooth or pybluez has it)
var Spawn = require('child_process').spawn;
function detectMacAddress(macAddress, callback)
{
//var macAddress = '72:44:56:05:79:A0';
var ls = Spawn('l2ping', ['-c', '1', '-t', '5', macAddress]);
ls.stdout.on('data', function (data) {
console.log("Found device in Range! " + macAddress);
callback(true);
});
ls.on('close', function () {
console.log("Could not find: " + macAddress);
callback(false);
});
}
Or, a synchronous way,
var execSync = require('child_process').execSync;
function detectMacAddressSync(macAddress)
{
var cmd = 'l2ping -c 1 -t 5 ' + macAddress;
try
{
var output = execSync(cmd );
console.log("output : "+ output );
return true;
}
catch(e)
{
console.log("caught: " + e);
return false;
}
}
As far as I have understood the problem you want to connect to the device using address. Then, I would suggest using node-bluetooth-serial-port.
var btSerial = new (require('bluetooth-serialport')).BluetoothSerialPort();
btSerial.on('found', function(address, name) {
btSerial.findSerialPortChannel(address, function(channel) {
btSerial.connect(address, channel, function() {
console.log('connected');
btSerial.write(new Buffer('my data', 'utf-8'), function(err, bytesWritten) {
if (err) console.log(err);
});
btSerial.on('data', function(buffer) {
console.log(buffer.toString('utf-8'));
});
}, function () {
console.log('cannot connect');
});
// close the connection when you're ready
btSerial.close();
}, function() {
console.log('found nothing');
});
});
BluetoothSerialPort.findSerialPortChannel(address, callback[, errorCallback])
Checks if a device has a serial port service running and if it is found it passes the channel id to use for the RFCOMM connection.
callback(channel) - called when finished looking for a serial port on the device.
errorCallback - called the search finished but no serial port channel was found on the device. Connects to a remote bluetooth device.
bluetoothAddress - the address of the remote Bluetooth device.
channel - the channel to connect to.
[successCallback] - called when a connection has been established.
[errorCallback(err)] - called when the connection attempt results in an error. The parameter is an Error object.

Failed to set local answer sdp: Called in wrong state: STATE_INPROGRESS

I have two clients :
1) Chrome (version 50.0.2661.102 m) on Windows 7 PC
2) Chrome (version 50.0.2661.89) on Android tablet
Both are in the same network (so no need for STUN/TURN server).
I use my own signal server built with node.js (webSocket) on a VirtualBox VM with Centos 6.
The communication with video/sound between the clients works fine. Now I want to transfer a file from one client to another. As base of my code i use the code of this example
here
As this code suggess, I create the dataChannnel exactly after the creation of PeerConnection.
function createPeerConnection() {
....
myPeerConnection = new RTCPeerConnection(iceServers, optional);
myDataChannel = myPeerConnection.createDataChannel('myDataChannel');
// Set up event handlers for the ICE negotiation process.
myPeerConnection.onicecandidate = handleICECandidateEvent;
myPeerConnection.onaddstream = handleAddStreamEvent;
myPeerConnection.onnremovestream = handleRemoveStreamEvent;
myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent;
myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
myPeerConnection.ondatachannel = handleDataChannel;
myDataChannel.onmessage = handleDataChannelMessage;
myDataChannel.onopen = handleDataChannelOpen;
}
...
...
function invite(peerId) {
...
createPeerConnection();
...
}
...
...
function handleVideoOfferMsg(msg) {
thereIsNegotiation = true;
targetUsername = msg.name;
// Call createPeerConnection() to create the RTCPeerConnection.
log("Starting to accept invitation from " + targetUsername);
createPeerConnection();
// We need to set the remote description to the received SDP offer
// so that our local WebRTC layer knows how to talk to the caller.
var desc = new RTCSessionDescription(msg.sdp);
myPeerConnection.setRemoteDescription(desc)
.then(function(stream) {
log("-- Calling myPeerConnection.addStream()");
return myPeerConnection.addStream(localStream);
})
.then(function() {
log("------> Creating answer");
// Now that we've successfully set the remote description, we need to
// start our stream up locally then create an SDP answer. This SDP
// data describes the local end of our call, including the codec
// information, options agreed upon, and so forth.
return myPeerConnection.createAnswer();
})
.then(function(answer) {
log("------> Setting local description after creating answer");
// We now have our answer, so establish that as the local description.
// This actually configures our end of the call to match the settings
// specified in the SDP.
return myPeerConnection.setLocalDescription(answer);
})
.then(function() {
var msg = {
name: clientId,
room: roomId,
target: targetUsername,
type: "video-answer",
sdp: myPeerConnection.localDescription
};
// We've configured our end of the call now. Time to send our
// answer back to the caller so they know that we want to talk
// and how to talk to us.
log("Sending answer packet back to other peer");
sendToServer(msg);
})
.catch(handleGetUserMediaError);
}
When the second client makes the offer, the first client when tries to make the answer, I get the error
Error opening your camera and / or microphone : failed to set local answer
spd: Failed to push down transport description: Local fingerprint provided
but no identity available.
or
Error opening your camera and / or microphone : failed to set local answer
spd: Called in wrong state : STATE_INPROGRESS
Only one time the creation was successful.
Do I have to create DataChannel in other place? Like here :
function handleICEConnectionStateChangeEvent {
switch(myPeerConnection.iceConnectionState) {
...
case "connected":
createDataChannel();
break;
}
}
function createDataChannel(){
myDataChannel = myPeerConnection.createDataChannel('myDataChannel');
myPeerConnection.ondatachannel = handleDataChannel;
myDataChannel.onmessage = handleDataChannelMessage;
myDataChannel.onopen = handleDataChannelOpen;
}
Any suggestions?
The error in this code is that both sender and receiver create new datachannel. The right thing is, one to create the datachannel
myDataChannel = myPeerConnection.createDataChannel('myDataChannel')
and the other to wait for the creation of dataChannel:
myPeerConnection.ondatachannel = handleDataChannel;

Chrome packaged app UDP sockets not working

I'm trying to get UDP sockets working for a packaged app using Chrome Canary (currently version 25). I am pretty confused by the fact the UDP example here conflicts with the reference documentation here.
The official example uses this line:
chrome.socket.create('udp', '127.0.0.1', 1337, { onEvent: handleDataEvent }, ...
In Canary using this line results in the error:
Uncaught Error: Invocation of form socket.create(string, string,
integer, object, function) doesn't match definition
socket.create(string type, optional object options, function callback)
Not surprising since that matches the documented form of the function. (I guess the example is out of date?) OK, so I try this...
chrome.socket.create('udp', { onEvent: handleDataEvent }, ...
Canary complains:
Uncaught Error: Invalid value for argument 2. Property 'onEvent':
Unexpected property.
Now I'm confused, especially since this parameter is undocumented in the reference. So I just go with this:
chrome.socket.create('udp', {}, ...
Now it creates OK, but the following call to connect...
chrome.socket.connect(socketId, function(result) ...
...fails with this:
Uncaught Error: Invocation of form socket.connect(integer, function)
doesn't match definition socket.connect(integer socketId, string
hostname, integer port, function callback)
...which is not surprising, since now my code doesn't mention a host or port anywhere, so I guess it needs to be in connect. So I change it to the form:
chrome.socket.connect(socketId, address, port, function (result) ...
At last I can connect and write to the socket OK. But this doesn't cover reading.
Can someone show me a working example based on UDP that can send & receive, so I can work from that?
How do I receive data since the example's onEvent handler does not work? How do I ensure I receive any data on-demand as soon as it arrives without blocking?
The Network Communications doc is not up-to-date. See the latest API doc: https://developer.chrome.com/trunk/apps/socket.html. But the doc doesn't state everything clearly.
I looked into Chromium source code and found some useful comments here: https://code.google.com/searchframe#OAMlx_jo-ck/src/net/udp/udp_socket.h&q=file:(%5E%7C/)net/udp/udp_socket%5C.h$&exact_package=chromium
// Client form:
// In this case, we're connecting to a specific server, so the client will
// usually use:
// Connect(address) // Connect to a UDP server
// Read/Write // Reads/Writes all go to a single destination
//
// Server form:
// In this case, we want to read/write to many clients which are connecting
// to this server. First the server 'binds' to an addres, then we read from
// clients and write responses to them.
// Example:
// Bind(address/port) // Binds to port for reading from clients
// RecvFrom/SendTo // Each read can come from a different client
// // Writes need to be directed to a specific
// // address.
For the server UDP socket, call chrome.socket.bind and chrome.socket.recvFrom/chrome.socket.sendTo to interact with clients. For the client UDP socket, call chrome.socket.connect and chrome.socket.read/chrome.socket.write to interact with the server.
Here's an example:
var serverSocket;
var clientSocket;
// From https://developer.chrome.com/trunk/apps/app_hardware.html
var str2ab=function(str) {
var buf=new ArrayBuffer(str.length);
var bufView=new Uint8Array(buf);
for (var i=0; i<str.length; i++) {
bufView[i]=str.charCodeAt(i);
}
return buf;
}
// From https://developer.chrome.com/trunk/apps/app_hardware.html
var ab2str=function(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
};
// Server
chrome.socket.create('udp', null, function(createInfo){
serverSocket = createInfo.socketId;
chrome.socket.bind(serverSocket, '127.0.0.1', 1345, function(result){
console.log('chrome.socket.bind: result = ' + result.toString());
});
function read()
{
chrome.socket.recvFrom(serverSocket, 1024, function(recvFromInfo){
console.log('Server: recvFromInfo: ', recvFromInfo, 'Message: ',
ab2str(recvFromInfo.data));
if(recvFromInfo.resultCode >= 0)
{
chrome.socket.sendTo(serverSocket,
str2ab('Received message from client ' + recvFromInfo.address +
':' + recvFromInfo.port.toString() + ': ' +
ab2str(recvFromInfo.data)),
recvFromInfo.address, recvFromInfo.port, function(){});
read();
}
else
console.error('Server read error!');
});
}
read();
});
// A client
chrome.socket.create('udp', null, function(createInfo){
clientSocket = createInfo.socketId;
chrome.socket.connect(clientSocket, '127.0.0.1', 1345, function(result){
console.log('chrome.socket.connect: result = ' + result.toString());
});
chrome.socket.write(clientSocket, str2ab('Hello server!'), function(writeInfo){
console.log('writeInfo: ' + writeInfo.bytesWritten +
'byte(s) written.');
});
chrome.socket.read(clientSocket, 1024, function(readInfo){
console.log('Client: received response: ' + ab2str(readInfo.data), readInfo);
});
});

Categories