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;
Related
I'm using the new v2 Twilio Javascript SDK to make calls from the browser to other people.
This works fine but I've been asked to add volume controls for the incoming audio stream.
After some research it seems that I need to take the remote stream from the call and feed it through a gain node to reduce the volume.
Unfortunately the result from call.getRemoteStream is always null even when I can hear audio from the call.
I've tested this on latest Chrome and Edge and they have the same behavior.
Is there something else I need to do to access the remote stream?
Code:
async function(phoneNumber, token)
{
console.log("isSecureContext: " + window.isSecureContext); //check we can get the stream
var options = {
edge: 'ashburn', //us US endpoint
closeProtection: true // will warn user if you try to close browser window during an active call
};
var device = new Device(token, options);
const connectionParams = {
"phoneNumber": phoneNumber
};
var activeCall = await device.connect({ params: connectionParams });
//Setup gain (volume) control for incoming audio
//Note, getRemoteStream always returns null.
var remoteStream = activeCall.getRemoteStream();
if(remoteStream)
{
var audioCtx = new AudioContext();
var source = audioCtx.createMediaStreamSource(remoteStream);
var gainNode = audioCtx.createGain();
source.connect(gainNode)
gainNode.connect(audioCtx.destination);
}
else
{
console.log("No remote stream on call");
}
}
The log output is:
isSecureContext: true
then
No remote stream on call
Twilio support gave me the answer: you need to wait until you start receiving volume events before requesting the stream.
ie
call.on('volume', (inputVolume, outputVolume) => {
if(inputVolume > 0)
{
var remoteStream = activeCall.getRemoteStream();
....
}
});
I can make basic video call with temprory token from agora.io but when i create token from my server i get this error. I am using NgxAgora packet. I try to set areacode but there is no option for this in NgxAgora.
here is my angular code:
this.api.getmethod("appointment/gettoken/" + atob(this.acRouter.snapshot.params.id)).subscribe((data) => {
this.token = data['token']
this.client = this.ngxAgoraService.createClient({ mode: 'rtc', codec: 'h264' });
this.assignClientHandlers();
this.localStream = this.ngxAgoraService.createStream({ streamID: this.uid, audio: true, video: true, screen: false });
this.assignLocalStreamHandlers();
// Join and publish methods added in this step
this.initLocalStream(() => this.join(uid => this.publish(), error => console.error(error)));
})
and i use this function in web api side using C#:
public string createagoratoken(string appointmentUid,DateTime appointmentDate,int appointmentId)
{
var tokenbuilder = new AgoraEntegration.Media.AccessToken(AgoraEntegration.AgoraEnums.AppEnums.appId, AgoraEntegration.AgoraEnums.AppEnums.appCertificate, appointmentUid, appointmentId.ToString());
appointmentDate = appointmentDate.AddMinutes(20);
Int32 unixTimestamp = (Int32)(appointmentDate.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
tokenbuilder.addPrivilege(AgoraEntegration.Media.Privileges.kJoinChannel, (uint)unixTimestamp);
tokenbuilder.addPrivilege(AgoraEntegration.Media.Privileges.kInvitePublishAudioStream, (uint)unixTimestamp);
tokenbuilder.addPrivilege(AgoraEntegration.Media.Privileges.kInvitePublishVideoStream,(uint)unixTimestamp);
string token = tokenbuilder.build();
return token;
}
Also i use this library for acces token
Github access token for C#
CANNOT_MEET_AREA_DEMAND usually comes when: The connection fails because the user is outside the chosen region for connection. For example, if you set ClientConfig.areaCode as [AgoraRTC.AREAS.EUROPE], and a user tries to join the channel in North America, this error occurs. If ClientConfig.areaCode is not explicitly set, then by default the SDK requests servers across multiple regions and chooses an optimal connection, so the console log may print this error when a user joins the channel. In this case, you can ignore the error.
Try setting areaCode to GLOBAL:
this.rtc = AgoraRTC.createClient({
//
areaCode: ['GLOBAL']
});
I am using nativescript to build an app that will programmatically send a pre-built text to multiple preset parties in case of emergency.
I have an array of phone numbers and want to iterate over each one, using SMSmanager to send the text and the sentIntent argument seen in android docs to verify that the text was sent before moving on to the next array item.
I have created the pendingIntent variable to pass into "sms.sendTextMessage" as follows:
var sms = android.telephony.SmsManager.getDefault();
var utils = require("utils/utils");
//Gets application's current state
var context = utils.ad.getApplicationContext();
//Create a replica of Android's intent object
var intent = new android.content.Intent(context, com.tns.NativeScriptActivity.class);
//Create a replica of Android's pendingIntent object using context and intent
var pendingIntent = android.app.PendingIntent.getActivity(context, 1, intent, android.app.PendingIntent.FLAG_UPDATE_CURRENT);
I then send the text, passing in the pending intent var:
sms.sendTextMessage("5555555555", null, "hello", pendingIntent, null);
I then attempt to make a basic broadcast receiver using the information I found in the nativescript docs which should just log something to the console when it recieves the expected data.
app.android.registerBroadcastReceiver(pendingIntent, function() {
console.log("##### text sent #####");
});
The problem is: nothing happens. I'd expect to get ""##### text sent #####" logged to the console. I've googled a lot and am thinking maybe I need to add something about this broadcast reciever in the manifest, or perhaps my implimentation is wrong somewhere, but this is my first crack at an android app and I'm at a bit of a loss. Any help would be appreciated.
I'm going to answer my own question here in case anyone else runs into this.
The code that worked is:
var app = require("application");
var utils = require("utils/utils");
var context = utils.ad.getApplicationContext();
var sms = android.telephony.SmsManager.getDefault();
var SendMessages = {
init: function() {
var id = "messageSent";
this.sendText(id, this.pendingIntent(id));
},
sendText: function(id, pendingIntent) {
sms.sendTextMessage("5555555555", null, "Hello :)", pendingIntent, null);
this.broadcastReceiver(id, function() {
console.log("$$$$$ text sent $$$$$");
});
},
pendingIntent: function(id) {
var intent = new android.content.Intent(id);
return android.app.PendingIntent.getBroadcast(context, 0, intent, 0);
},
broadcastReceiver: function(id, callback) {
app.android.registerBroadcastReceiver(id, function() {
callback();
});
}
};
module.exports = SendMessages;
To explain: it seems as #Mike M mentioned each intent object needs some string as an id.
Then to make the "pendingIntent" object, again as #Mike M. mentioned I needed to hook to "getBroadcast" method, then I needed to pass pending intent the app context as the first argument, then 0, then the intent object with the id.
The pending intent then is receivable in a simple broadcast receiver function by simply passing the intent id as the first argument and the callback as the second. I've tested and it's working perfectly.
Following is a simplified version, no bs code approach to what you need to make it run. You need permissions and to make sure the user accepts those permissions. This is also one of the two ways (this one is the context-registered receiver way) to create a broadcast receiver, read more about both types here: https://developer.android.com/guide/components/broadcasts#receiving-broadcasts
Info on registering broadcast receiver: https://docs.nativescript.org/api-reference/classes/application.androidapplication.html#registerbroadcastreceiver
AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="__PACKAGE__"
android:versionCode="10000"
android:versionName="1.0">
<!-- ...more code -->
<uses-permission android:name="android.permission.SEND_SMS" />
<!-- ...more code -->
</manifest>
JavaScript:
import * as application from 'tns-core-modules/application';
import * as platform from 'tns-core-modules/platform';
import * as utils from 'tns-core-modules/utils/utils';
import * as permissions from 'nativescript-permissions';
// ...more code
try {
await permissions.requestPermission(
android.Manifest.permission.SEND_SMS,
'Need to send.'
);
console.log('SEND_SMS permission accepted.');
const text = 'Herro.';
const mobileNumber = '55555555';
const intentFilter = 'something_here';
const context = utils.ad.getApplicationContext();
const intent = new android.content.Intent(intentFilter);
const pendingIntent = android.app.PendingIntent.getBroadcast(context, 0, intent, 0);
const sms = android.telephony.SmsManager.getDefault();
application.android.registerBroadcastReceiver(intentFilter, function() {
console.log(`Text has been sent: ${text}`);
});
setTimeout(() => {
console.log('Sending text.');
sms.sendTextMessage(mobileNumber, null, text, pendingIntent, null);
}, 5000);
} catch (error) {
console.log('Permission error:', error);
}
The code above is created inside the activity and uses the main ui thread. This means that if the user exits the activity, the broadcast receiver will linger in limbo and android can destroy it.
I am new to node.js. How to detect client is disconnected from node.js server .
Here is my code:
var net = require('net');
var http = require('http');
var host = '192.168.1.77';
var port = 12345;//
var server = net.createServer(function (stream) {
stream.setEncoding('utf8');
stream.on('data', function (data) {
var comm = JSON.parse(data);
if (comm.action == "Join_Request" && comm.gameId =="game1") // join request getting from client
{
var reply0 = new Object();
reply0.message = "WaitRoom";
stream.write(JSON.stringify(reply0) + "\0");
}
});
stream.on('disconnect', function() {
});
stream.on('close', function () {
console.log("Close");
});
stream.on('error', function () {
console.log("Error");
});
});
server.listen(port,host);
How to know client side internet disconnection.
The best way to detect "dead sockets" is to send periodic application-level ping/keepalive messages. What that message looks like depends on the protocol you're using for communicating over the socket. Then it's just a matter of using a timer or other means of checking if you've received a "ping response" within a certain period of time after you sent the ping/keepalive message to the client.
On a semi-related note, it looks like you're using JSON messages for communication, but you're assuming a complete JSON string on every data event which is a bad assumption. Try using a delimiter (a newline is pretty common for something like this, and it makes debugging the communication more human-readable) instead.
Here is a simple example of how to achieve this:
var PING_TIMEOUT = 5000, // how long to wait for client to respond
WAIT_TIMEOUT = 5000; // duration of "silence" from client until a ping is sent
var server = net.createServer(function(stream) {
stream.setEncoding('utf8');
var buffer = '',
pingTimeout,
waitTimeout;
function send(obj) {
stream.write(JSON.stringify(obj) + '\n');
}
stream.on('data', function(data) {
// stop our timers if we've gotten any kind of data
// from the client, whether it's a ping response or
// not, we know their connection is still good.
clearTimeout(waitTimeout);
clearTimeout(pingTimeout);
buffer += data;
var idx;
// because `data` can be a chunk of any size, we could
// have multiple messages in our buffer, so we check
// for that here ...
while (~(idx = buffer.indexOf('\n'))) {
try {
var comm = JSON.parse(buffer.substring(0, idx));
// join request getting from client
if (comm.action === "Join_Request" && comm.gameId === "game1") {
send({ message: 'WaitRoom' });
}
} catch (ex) {
// some error occurred, probably from trying to parse invalid JSON
}
// update our buffer
buffer = buffer.substring(idx + 1);
}
// we wait for more data, if we don't see anything in
// WAIT_TIMEOUT milliseconds, we send a ping message
waitTimeout = setTimeout(function() {
send({ message: 'Ping' });
// we sent a ping, now we wait for a ping response
pingTimeout = setTimeout(function() {
// if we've gotten here, we are assuming the
// connection is dead because the client did not
// at least respond to our ping message
stream.destroy(); // or stream.end();
}, PING_TIMEOUT);
}, WAIT_TIMEOUT);
});
// other event handlers and logic ...
});
You could also just have one interval instead of two timers that checks a "last data received" timestamp against the current timestamp and if it exceeds some length of time and we have sent a ping message recently, then you assume the socket/connection is dead. You could also instead send more than one ping message and if after n ping messages are sent and no response is received, close the connection at that point (this is basically what OpenSSH does).
There are many ways to go about it. However you may also think about doing the same on the client side, so that you know the server didn't lose its connection either.
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);
});
});