Hi I'm trying to save the remote stream received from the webrtc, I followed some code sample from git, tried various approach to get remote stream but,not able to get stream from socket or any other way, if some one have any idea please suggest here is snippet of my Class of WebRTCClient
here is my class:
package fr.pchab.webrtcclient;
import android.app.Activity;
import android.util.Log;
import android.widget.Toast;
import com.github.nkzawa.emitter.Emitter;
import com.github.nkzawa.socketio.client.IO;
import com.github.nkzawa.socketio.client.Socket;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.AudioSource;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoCapturerAndroid;
import org.webrtc.VideoSource;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.LinkedList;
public class WebRtcClient {
private final static String TAG = "WebRtcClient";
private final static int MAX_PEER = 2;
private boolean[] endPoints = new boolean[MAX_PEER];
private PeerConnectionFactory factory;
private HashMap<String, Peer> peers = new HashMap<>();
private LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<>();
private PeerConnectionParameters pcParams;
private MediaConstraints pcConstraints = new MediaConstraints() {
};
private MediaStream localMS;
private VideoSource videoSource;
private RtcListener mListener;
private Socket client;
/**
* Implement this interface to be notified of events.
*/
public interface RtcListener {
void onCallReady(String callId);
void onStatusChanged(String newStatus);
void onLocalStream(MediaStream localStream);
void onAddRemoteStream(MediaStream remoteStream, int endPoint);
void onRemoveRemoteStream(int endPoint);
}
private interface Command {
void execute(String peerId, JSONObject payload) throws JSONException;
}
private class CreateOfferCommand implements Command {
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.e(TAG, "CreateOfferCommand");
Peer peer = peers.get(peerId);
peer.pc.createOffer(peer, pcConstraints);
}
}
private class CreateAnswerCommand implements Command {
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.e(TAG, "CreateAnswerCommand");
Peer peer = peers.get(peerId);
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(payload.getString("type")),
payload.getString("sdp")
);
peer.pc.setRemoteDescription(peer, sdp);
peer.pc.createAnswer(peer, pcConstraints);
}
}
private class SetRemoteSDPCommand implements Command {
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.e(TAG, "SetRemoteSDPCommand");
Peer peer = peers.get(peerId);
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(payload.getString("type")),
payload.getString("sdp")
);
peer.pc.setRemoteDescription(peer, sdp);
}
}
private class AddIceCandidateCommand implements Command {
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.e(TAG, "AddIceCandidateCommand");
PeerConnection pc = peers.get(peerId).pc;
if (pc.getRemoteDescription() != null) {
IceCandidate candidate = new IceCandidate(
payload.getString("id"),
payload.getInt("label"),
payload.getString("candidate")
);
pc.addIceCandidate(candidate);
}
}
}
/**
* Send a message through the signaling server
*
* #param to id of recipient
* #param type type of message
* #param payload payload of message
* #throws JSONException
*/
public void sendMessage(String to, String type, JSONObject payload) throws JSONException {
JSONObject message = new JSONObject();
message.put("to", to);
message.put("type", type);
message.put("payload", payload);
client.emit("message", message);
}
private class MessageHandler {
private HashMap<String, Command> commandMap;
private MessageHandler() {
this.commandMap = new HashMap<>();
commandMap.put("init", new CreateOfferCommand());
commandMap.put("offer", new CreateAnswerCommand());
commandMap.put("answer", new SetRemoteSDPCommand());
commandMap.put("candidate", new AddIceCandidateCommand());
}
private Emitter.Listener onMessage = new Emitter.Listener() {
#Override
public void call(Object... args) {
JSONObject data = (JSONObject) args[0];
try {
String from = data.getString("from");
String type = data.getString("type");
JSONObject payload = null;
if (!type.equals("init")) {
payload = data.getJSONObject("payload");
}
// if peer is unknown, try to add him
if (!peers.containsKey(from)) {
// if MAX_PEER is reach, ignore the call
int endPoint = findEndPoint();
if (endPoint != MAX_PEER) {
Peer peer = addPeer(from, endPoint);
peer.pc.addStream(localMS);
commandMap.get(type).execute(from, payload);
}
} else {
commandMap.get(type).execute(from, payload);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
};
private Emitter.Listener onId = new Emitter.Listener() {
#Override
public void call(Object... args) {
String id = (String) args[0];
mListener.onCallReady(id);
}
};
}
private class Peer implements SdpObserver, PeerConnection.Observer, DataChannel.Observer {
private PeerConnection pc;
private String id;
private int endPoint;
#Override
public void onCreateSuccess(final SessionDescription sdp) {
// TODO: modify sdp to use pcParams prefered codecs
JSONObject payload = null;
try {
payload = new JSONObject();
payload.put("type", sdp.type.canonicalForm());
payload.put("sdp", sdp.description);
sendMessage(id, sdp.type.canonicalForm(), payload);
pc.setLocalDescription(Peer.this, sdp);
} catch (JSONException e) {
e.printStackTrace();
}
Log.e("WebRtcClient", "WebRtcClient onCreateSuccess:" + payload);
}
#Override
public void onSetSuccess() {
Log.e("WebRtcClient", "WebRtcClient onSetSuccess:");
}
#Override
public void onCreateFailure(String s) {
Log.e("WebRtcClient", "WebRtcClient onCreateFailure:" + s);
}
#Override
public void onSetFailure(String s) {
Log.e("WebRtcClient", "WebRtcClient onSetFailure:" + s);
}
#Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
Log.e("WebRtcClient", "WebRtcClient onSignalingChange:" + signalingState.name());
}
#Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
removePeer(id);
mListener.onStatusChanged("DISCONNECTED");
} else if (iceConnectionState == PeerConnection.IceConnectionState.CONNECTED) {
}
Log.e("WebRtcClient", "WebRtcClient onIceConnectionChange:" + iceConnectionState);
}
#Override
public void onIceConnectionReceivingChange(boolean b) {
Log.e("WebRtcClient", "WebRtcClient onIceConnectionReceivingChange:" + b);
}
#Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
Log.e("WebRtcClient", "WebRtcClient onIceGatheringChange:" + iceGatheringState);
}
#Override
public void onIceCandidate(final IceCandidate candidate) {
JSONObject payload = null;
try {
payload = new JSONObject();
payload.put("label", candidate.sdpMLineIndex);
payload.put("id", candidate.sdpMid);
payload.put("candidate", candidate.sdp);
sendMessage(id, "candidate", payload);
} catch (JSONException e) {
e.printStackTrace();
}
Log.e("WebRtcClient", "WebRtcClient onIceCandidate:" + payload);
}
#Override
public void onAddStream(MediaStream mediaStream) {
Log.e(TAG, "onAddStream " + mediaStream.label());
// remote streams are displayed from 1 to MAX_PEER (0 is localStream)
mListener.onAddRemoteStream(mediaStream, endPoint + 1);
Log.e("WebRtcClient", "WebRtcClient onAddStream:" + mediaStream.label());
}
#Override
public void onRemoveStream(MediaStream mediaStream) {
Log.e(TAG, "onRemoveStream " + mediaStream.label());
Log.e("WebRtcClient", "WebRtcClient onRemoveStream:" + mediaStream.label());
removePeer(id);
}
#Override
public void onRenegotiationNeeded() {
Log.e("WebRtcClient", "WebRtcClient onRenegotiationNeeded:");
}
#Override
public void onDataChannel(final DataChannel dataChannel) {
Log.e("onDataChannel", "onDataChannel:" + dataChannel.label());
dataChannel.registerObserver(this);
}
#Override
public void onMessage(DataChannel.Buffer buffer) {
ByteBuffer data = buffer.data;
byte[] bytes = new byte[data.remaining()];
data.get(bytes);
String command = new String(bytes);
Log.e(TAG, " onDataChannel-DcObserver " + command);
}
#Override
public void onStateChange() {
Log.e(TAG, "onDataChannel -DcObserver " + "onStateChange");
}
#Override
public void onBufferedAmountChange(long arg0) {
Log.e(TAG, " DcObserver " + arg0);
}
public Peer(String id, int endPoint) {
Log.e(TAG, "WebRtcClient new Peer: " + id + " " + endPoint);
this.pc = factory.createPeerConnection(iceServers, pcConstraints, this);
createDataChannel();
this.id = id;
this.endPoint = endPoint;
pc.addStream(localMS); //, new MediaConstraints()
mListener.onStatusChanged("CONNECTING");
}
private void createDataChannel() {
DataChannel.Init dcInit = new DataChannel.Init();
dcInit.id = 1;
dataChannel = pc.createDataChannel("sendDataChannel", dcInit);
dataChannel.registerObserver(this);
}
}
DataChannel dataChannel;
private Peer addPeer(String id, int endPoint) {
Peer peer = new Peer(id, endPoint);
peers.put(id, peer);
endPoints[endPoint] = true;
return peer;
}
private void removePeer(String id) {
Peer peer = peers.get(id);
mListener.onRemoveRemoteStream(peer.endPoint);
peer.pc.close();
peers.remove(peer.id);
endPoints[peer.endPoint] = false;
}
private Activity mContext;
public WebRtcClient(RtcListener listener, String host, PeerConnectionParameters params, Activity mContext) {
mListener = listener;
pcParams = params;
this.mContext = mContext;
PeerConnectionFactory.initializeAndroidGlobals(listener, true, true, true
/*params.videoCodecHwAcceleration, mEGLcontext*/);
factory = new PeerConnectionFactory();
MessageHandler messageHandler = new MessageHandler();
try {
client = IO.socket(host);
} catch (URISyntaxException e) {
e.printStackTrace();
}
client.on("id", messageHandler.onId);
client.on("message", messageHandler.onMessage);
client.connect();
iceServers.add(new PeerConnection.IceServer("stun:23.21.150.121"));
iceServers.add(new PeerConnection.IceServer("stun:stun.l.google.com:19302"));
pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
pcConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
// pcConstraints.optional.add(new MediaConstraints.KeyValuePair("RtpDataChannels", "false"));
}
/**
* Call this method in Activity.onPause()
*/
public void onPause() {
if (videoSource != null) videoSource.stop();
}
/**
* Call this method in Activity.onResume()
*/
public void onResume() {
if (videoSource != null) videoSource.restart();
}
/**
* Call this method in Activity.onDestroy()
*/
public void onDestroy() {
for (Peer peer : peers.values()) {
peer.pc.dispose();
}
videoSource.dispose();
factory.dispose();
client.disconnect();
client.close();
}
private int findEndPoint() {
for (int i = 0; i < MAX_PEER; i++) if (!endPoints[i]) return i;
return MAX_PEER;
}
/**
* Start the client.
* <p>
* Set up the local stream and notify the signaling server.
* Call this method after onCallReady.
*
* #param name client name
*/
public void start(String name) {
setCamera();
try {
JSONObject message = new JSONObject();
message.put("name", name);
client.emit("readyToStream", message);
} catch (JSONException e) {
e.printStackTrace();
}
}
private void setCamera() {
localMS = factory.createLocalMediaStream("ARDAMS");
if (pcParams.videoCallEnabled) {
MediaConstraints videoConstraints = new MediaConstraints();
videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxHeight", Integer.toString(pcParams.videoHeight)));
videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxWidth", Integer.toString(pcParams.videoWidth)));
videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxFrameRate", Integer.toString(pcParams.videoFps)));
videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minFrameRate", Integer.toString(pcParams.videoFps)));
VideoCapturer videoCapturer = getVideoCapturer();
if (videoCapturer != null) {
videoSource = factory.createVideoSource(videoCapturer, videoConstraints);
localMS.addTrack(factory.createVideoTrack("ARDAMSv0", videoSource));
} else {
}
}
AudioSource audioSource = factory.createAudioSource(new MediaConstraints());
localMS.addTrack(factory.createAudioTrack("ARDAMSa0", audioSource));
mListener.onLocalStream(localMS);
}
private void showMessage(final String msg) {
mContext.runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
}
});
}
private VideoCapturer getVideoCapturer() {
//Camera name empty will call the back cam bydefualt
return VideoCapturerAndroid.create("", new VideoCapturerAndroid.CameraEventsHandler() {
#Override
public void onCameraError(String s) {
Log.e("WebRtcClient", "WebRtcClient onCameraError:" + s);
}
#Override
public void onCameraFreezed(String s) {
Log.e("WebRtcClient", "WebRtcClient onCameraFreezed:" + s);
}
#Override
public void onCameraOpening(int i) {
Log.e("WebRtcClient", "WebRtcClient onCameraOpening:" + i);
// showMessage("Opening Camera id " + i);
}
#Override
public void onFirstFrameAvailable() {
Log.e("WebRtcClient", "WebRtcClient onFirstFrameAvailable:");
// showMessage("Camera onFirstFrameAvailable:");
}
#Override
public void onCameraClosed() {
Log.e("WebRtcClient", "WebRtcClient onCameraClosed:");
showMessage("Camera onCameraClosed");
}
});
}
}
Referenced from link
Currently there is no option to save remote media stream on android.You will have to implement a media server which would save media stream and pass stream between two devices. Kurento is an open source media server which I know provides the functionality but I haven't used it
Related
Version 4.2 of the Smack library seems to drop the message body, as sent by FCM.
When I set the debugger on, I can see the following incoming message:
<message><data:gcm xmlns:data="google:mobile:data">{"message_type":"nack","from":"xxx","message_id":"aaaa1","error":"BAD_REGISTRATION","error_description":""}</data:gcm></message>
However, when Smack 4.2 parses this message, it drops the JSON body inside the message and gives me the following in my packet listener:
<message><gcm xmlns="google:mobile:data"></gcm></message>
Here's my test class:
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.DefaultPacketExtension;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.StringUtils;
import javax.net.ssl.SSLSocketFactory;
public class CcsClient {
private static final String HOST = "fcm-xmpp.googleapis.com";
private static final int PORT = 5235;
private final XMPPConnection conn;
public CcsClient(String senderId, String serverKey) {
SASLAuthentication.supportSASLMechanism("PLAIN", 0);
ConnectionConfiguration conf = new ConnectionConfiguration(HOST, PORT);
conf.setSASLAuthenticationEnabled(true);
conf.setSocketFactory(SSLSocketFactory.getDefault());
conf.setServiceName(HOST);
conf.setSendPresence(false);
conf.setCompressionEnabled(false);
conf.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled);
conf.setDebuggerEnabled(true);
this.conn = new XMPPConnection(conf);
conn.addConnectionListener(new AbstractConnectionListener() {
#Override
public void connectionClosed() {
super.connectionClosed();
}
#Override
public void connectionClosedOnError(Exception e) {
super.connectionClosedOnError(e);
}
#Override
public void reconnectingIn(int seconds) {
super.reconnectingIn(seconds);
}
#Override
public void reconnectionFailed(Exception e) {
super.reconnectionFailed(e);
}
#Override
public void reconnectionSuccessful() {
super.reconnectionSuccessful();
}
});
try {
conn.connect();
conn.login(senderId + "#gcm.googleapis.com", serverKey);
System.out.println("connected!");
} catch (XMPPException e) {
e.printStackTrace();
}
}
private static final class GcmPacketExtension extends DefaultPacketExtension {
private final String json;
public GcmPacketExtension(String json) {
super("gcm", "google:mobile:data");
this.json = json;
}
public String getJson() {
return json;
}
#Override
public String toXML() {
return "<gcm xmlns=\"google:mobile:data\">" + StringUtils.escapeForXML(json) + "</gcm>";
}
public Message toPacket() {
Message message = new Message();
message.addExtension(this);
return message;
}
}
public static void main(String[] args) throws InterruptedException {
final CcsClient c = new CcsClient("xxx", "xxx");
c.conn.addPacketListener(new PacketListener() {
#Override
public void processPacket(Packet packet) {
System.out.println("incoming!" + packet.toString());
}
}, new PacketFilter() {
#Override
public boolean accept(Packet packet) {
return true;
}
});
for (int i = 0; i < 1; i++) {
c.conn.sendPacket(new GcmPacketExtension("{\"to\":\"xxx\", \"message_id\":\"aaaa"+i+"\", \"delivery_receipt_requested\":true}").toPacket());
}
Thread.sleep(1000000);
}
}
What am I doing wrong?
So I finally got it working. An example client is here: https://gist.github.com/judepereira/fd8dc0a5321179b699f5c5e54812770c
So I'm badly stuck in creating a datachannel between android app client and Web App client running on Chrome.
Following is my complete code for which Audio and Video is streaming fine. But When I try to send the message using DataChannel it returns False and State as CONNECTING. I'm trying to resolve since 1 week but couldn't make it run.
EDIT: Issue resolved. Here is the final Working Code. Android Do not support RTPDataChannel anymore
Home.java
public class Home extends Activity {
public List<PeerConnection.IceServer> iceServers;
private GLSurfaceView videoView;
public static SocketIO socket;
ArrayList<String> userIDs = new ArrayList<>();
private static final String FIELD_TRIAL_VP9 = "WebRTC-SupportVP9/Enabled/";
String RoomId = "";
String sreverURL = "http://xx.xx.xx.xx:xxxx/";
private EditText roomid;
private VideoRenderer.Callbacks remote_view;
private VideoRenderer.Callbacks local_view;
protected PeerConnectionFactory factory;
PeerConnectionFactory.Options options = null;
Events pc_events;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
videoView = (GLSurfaceView) findViewById(R.id.glview_call_remote);
VideoRendererGui.setView(videoView, new Runnable() {
#Override
public void run() {
createPeerConnectionFactory();
}
});
remote_view = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FIT, false);
local_view = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FILL, true);
iceServers = new ArrayList<>();
IceServer icc = new IceServer("stun:stun.l.google.com:19302", "", "");
iceServers.add(icc);
roomid = (EditText) findViewById(R.id.roomId);
Random rand = new Random();
roomid.setText("" + rand.nextInt(9999));
pc_events = new peerEventHandler();
}
private void createPeerConnectionFactory() {
runOnUiThread(new Runnable() {
#Override
public void run() {
PeerConnectionFactory.initializeFieldTrials(FIELD_TRIAL_VP9);
PeerConnectionFactory.initializeAndroidGlobals(Home.this, true, true, true, VideoRendererGui.getEGLContext());
try {
factory = new PeerConnectionFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public void ondail(View view) {
try {
try {
SocketIO.setDefaultSSLSocketFactory(SSLContext.getDefault());
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
}
socket = new SocketIO();
//startCall();
socket.connect(sreverURL, new IOCallback() {
#Override
public void onMessage(JSONObject json, IOAcknowledge ack) {
}
#Override
public void onMessage(String data, IOAcknowledge ack) {
}
#Override
public void onError(SocketIOException socketIOException) {
socketIOException.printStackTrace();
}
#Override
public void onDisconnect() {
}
#Override
public void onConnect() {
showToast("Connected to " + sreverURL);
}
#Override
public void on(final String event, IOAcknowledge ack, final Object... args) {
String argument = "";
try {
if (args instanceof Object[]) {
argument = args[0].toString();
} else {
argument = args.toString();
}
} catch (Exception e) {
argument = args.toString();
}
Log.e("Socked.on", event + ", " + argument);
switch (getEvent(event)) {
case LOG:
break;
case MESSAGE:
if (args instanceof Object[]) {
pc_events.setMessage(args[0].toString());
} else {
pc_events.setMessage(args.toString());
}
break;
case CREATED:
runOnUiThread(new Runnable() {
public void run() {
showToast("Room Created " + args[0]);
}
});
break;
case BROADCAST:
break;
case JOIN:
break;
case EMIT:
Log.e("Socked.onEMIT", argument);
startCall();
pc_events.createOffer();
break;
case ERROR:
Log.e("Socked.onERROR", argument);
break;
default:
break;
}
}
});
try {
RoomId = roomid.getText().toString();
} catch (Exception e) {
}
socket.emit("create or join", RoomId);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public void oncancel(View view) {
}
public SocketEvent getEvent(String eventString) {
SocketEvent eventType;
try {
if (eventString.contains("log")) {
eventType = SocketEvent.LOG;
} else if (eventString.contains("created")) {
eventType = SocketEvent.CREATED;
} else if (eventString.contains("emit():")) {
eventType = SocketEvent.EMIT;
} else if (eventString.contains("broadcast():")) {
eventType = SocketEvent.BROADCAST;
} else if (eventString.contains("message")) {
eventType = SocketEvent.MESSAGE;
} else if (eventString.toLowerCase().substring(0, 20).contains("join")) {
eventType = SocketEvent.JOIN;
} else {
eventType = SocketEvent.ERROR;
}
} catch (Exception e) {
eventType = SocketEvent.ERROR;
}
return eventType;
}
public static interface Events {
public void peerConnectionEvent(VideoRenderer.Callbacks localRender, VideoRenderer.Callbacks remoteRender);
public void setFactory(PeerConnectionFactory factory);
public void setMessage(String message);
public void createOffer();
public void sendMessage(String msg);
}
private void startCall() {
pc_events.setFactory(factory);
pc_events.peerConnectionEvent(remote_view, local_view);
}
public void showToast(final String message) {
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(Home.this, message, Toast.LENGTH_SHORT).show();
}
});
}
public void sendMessage(View v) {
pc_events.sendMessage("Hello");
}
}
peerEventHandler.java
public class peerEventHandler implements Events {
private PeerConnection peerConnection;
private PeerConnectionFactory factory;
PCObserver pcObserver = new PCObserver();
public LooperExecutor executor;
private MediaStream mediaStream;
private VideoSource videoSource;
private DcObserver dc_observer;
public static final String VIDEO_TRACK_ID = "ARDAMSv0";
public static final String AUDIO_TRACK_ID = "ARDAMSa0";
private VideoCapturerAndroid videoCapturer;
private VideoTrack localVideoTrack;
private VideoTrack remoteVideoTrack;
public boolean preferIsac = false;
public boolean videoCallEnabled = true;
public boolean preferH264 = false;
private SessionDescription localSdp;
private final SDPObserver sdpObserver = new SDPObserver();
public boolean isInitiator = false;
private MediaConstraints sdpMediaConstraints;
private VideoRenderer.Callbacks remote_view;
private VideoRenderer.Callbacks local_view;
private DataChannel dataChannel;
#Override
public void peerConnectionEvent(Callbacks remoteRender, Callbacks localRender) {
this.remote_view = remoteRender;
this.local_view = localRender;
creatPeerConnection();
}
public void creatPeerConnection() {
executor = new LooperExecutor();
executor.requestStart();
MediaConstraints pcConstraints = new MediaConstraints();
MediaConstraints videoConstraints = new MediaConstraints();
MediaConstraints audioConstraints = new MediaConstraints();
sdpMediaConstraints = new MediaConstraints();
creatvideoConstraints(videoConstraints);
creataudioConstraints(audioConstraints);
creatsdpMediaConstraints(sdpMediaConstraints);
List<PeerConnection.IceServer> iceServers = new ArrayList<PeerConnection.IceServer>();
IceServer iceServer = new IceServer("stun:stun.l.google.com:19302", "", "");
iceServers.add(iceServer);
PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.BALANCED;
rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE;
pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "false"));
pcConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true"));
peerConnection = factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver);
Logging.enableTracing("logcat:", EnumSet.of(Logging.TraceLevel.TRACE_DEFAULT), Logging.Severity.LS_WARNING);
mediaStream = factory.createLocalMediaStream("ARDAMS");
Log.e("DC Add", "Adding Data Channel");
dataChannel = peerConnection.createDataChannel("sendDataChannel", new DataChannel.Init());
dc_observer = new DcObserver();
dataChannel.registerObserver(dc_observer);
Log.e("DC Add", "Data Channel added");
String cameraDeviceName = CameraEnumerationAndroid.getDeviceName(0);
String frontCameraDeviceName = CameraEnumerationAndroid.getNameOfFrontFacingDevice();
cameraDeviceName = frontCameraDeviceName;
videoCapturer = VideoCapturerAndroid.create(cameraDeviceName, null);
videoSource = factory.createVideoSource(videoCapturer, videoConstraints);
localVideoTrack = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
localVideoTrack.setEnabled(true);
localVideoTrack.addRenderer(new VideoRenderer(local_view));
mediaStream.addTrack(factory.createAudioTrack(AUDIO_TRACK_ID, factory.createAudioSource(audioConstraints)));
mediaStream.addTrack(localVideoTrack);
peerConnection.addStream(mediaStream);
}
#Override
public void createOffer() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection != null) {
isInitiator = true;
peerConnection.createOffer(sdpObserver, sdpMediaConstraints);
}
}
});
}
public void createAnswer() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection != null) {
isInitiator = false;
peerConnection.createAnswer(sdpObserver, sdpMediaConstraints);
}
}
});
}
private class PCObserver implements PeerConnection.Observer {
#Override
public void onAddStream(final MediaStream stream) {
Log.e("onAddStream", "onAddStream");
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
if (stream.audioTracks.size() > 1 || stream.videoTracks.size() > 1) {
// /reportError("Weird-looking stream: " + stream);
return;
}
if (stream.videoTracks.size() == 1) {
remoteVideoTrack = stream.videoTracks.get(0);
remoteVideoTrack.setEnabled(true);
remoteVideoTrack.addRenderer(new VideoRenderer(remote_view));
VideoRendererGui.update(local_view, 75, 70, 60, 60, ScalingType.SCALE_ASPECT_FIT, true);
VideoRendererGui.update(remote_view, 0, 0, 200, 200, ScalingType.SCALE_ASPECT_FILL, false);
}
}
});
}
#Override
public void onDataChannel(final DataChannel dc) {
executor.execute(new Runnable() {
#Override
public void run() {
dataChannel = dc;
String channelName = dataChannel.label();
dc_observer = new DcObserver();
dataChannel.registerObserver(dc_observer);
}
});
}
#Override
public void onIceCandidate(IceCandidate candidate) {
SocketIO socket = Home.socket;
JSONObject json;
try {
/*
json.putOpt("label", candidate.sdpMLineIndex);
json.putOpt("id", candidate.sdpMid);
json.putOpt("candidate", candidate.sdp);*/
Gson gson = new Gson();
String json_str = gson.toJson(candidate);
json = new JSONObject(json_str);
json.putOpt("type", "candidate");
socket.emit("message", json);
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onIceConnectionChange(IceConnectionState state) {
}
#Override
public void onIceConnectionReceivingChange(boolean arg0) {
}
#Override
public void onIceGatheringChange(IceGatheringState arg0) {
}
#Override
public void onRemoveStream(MediaStream arg0) {
}
#Override
public void onRenegotiationNeeded() {
}
#Override
public void onSignalingChange(SignalingState arg0) {
}
}
public void creatPcConstrains(MediaConstraints pcConstraints) {
}
public void creatvideoConstraints(MediaConstraints videoConstraints) {
String MAX_VIDEO_WIDTH_CONSTRAINT = "maxWidth";
String MIN_VIDEO_WIDTH_CONSTRAINT = "minWidth";
String MAX_VIDEO_HEIGHT_CONSTRAINT = "maxHeight";
String MIN_VIDEO_HEIGHT_CONSTRAINT = "minHeight";
String MAX_VIDEO_FPS_CONSTRAINT = "maxFrameRate";
String MIN_VIDEO_FPS_CONSTRAINT = "minFrameRate";
int videoWidth = 0;
int videoHeight = 0;
if ((videoWidth == 0 || videoHeight == 0) && true && MediaCodecVideoEncoder.isVp8HwSupported()) {
videoWidth = 1280;
videoHeight = 1280;
}
if (videoWidth > 0 && videoHeight > 0) {
videoWidth = Math.min(videoWidth, 1280);
videoHeight = Math.min(videoHeight, 1280);
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
}
int videoFps = 30;
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
}
public void creataudioConstraints(MediaConstraints audioConstraints) {
audioConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true"));
pcConstraints.optional.add(new KeyValuePair("internalSctpDataChannels", "true"));
}
public void creatsdpMediaConstraints(MediaConstraints sdpMediaConstraints) {
sdpMediaConstraints.mandatory.add(new KeyValuePair("OfferToReceiveAudio", "true"));
sdpMediaConstraints.mandatory.add(new KeyValuePair("OfferToReceiveVideo", "true"));
}
private class SDPObserver implements SdpObserver {
#Override
public void onCreateFailure(String arg0) {
System.out.print(arg0);
}
#Override
public void onCreateSuccess(SessionDescription origSdp) {
if (localSdp != null) {
return;
}
localSdp = origSdp;
setLocalDescription(origSdp);
}
#Override
public void onSetFailure(String arg0) {
}
#Override
public void onSetSuccess() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
if (isInitiator) {
if (peerConnection != null) {
JSONObject json = new JSONObject();
try {
json.putOpt("type", localSdp.type.toString().toLowerCase());
json.putOpt("sdp", localSdp.description);
} catch (JSONException e) {
e.printStackTrace();
}
Home.socket.emit("message", json);
}
} else {
// createAnswer();
}
}
});
}
}
public void addRemoteIceCandidate(final IceCandidate candidate) {
executor.execute(new Runnable() {
#Override
public void run() {
peerConnection.addIceCandidate(candidate);
}
});
}
public void setLocalDescription(final SessionDescription sdp) {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
peerConnection.setLocalDescription(sdpObserver, sdp);
}
});
}
public void setRemoteDescription(final SessionDescription sdp) {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
peerConnection.setRemoteDescription(sdpObserver, sdp);
}
});
}
#Override
public void setFactory(PeerConnectionFactory factory) {
this.factory = factory;
}
public void onWebSocketMessage(final String msg) {
try {
Log.e("onWebSocketMessage", msg);
JSONObject json = new JSONObject(msg);
json = new JSONObject(msg);
String type = json.optString("type");
if (type.equals("candidate")) {
IceCandidate candidate = new IceCandidate(json.getString("id"), json.getInt("label"), json.getString("candidate"));
addRemoteIceCandidate(candidate);
} else if (type.equals("answer")) {
isInitiator = false;
SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
setRemoteDescription(sdp);
} else if (type.equals("offer")) {
SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
setRemoteDescription(sdp);
} else if (type.equals("bye")) {
} else {
}
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void setMessage(String message) {
if (message.toString().contains("got user media") || message.toString().contains("bye")) {
} else
onWebSocketMessage(message);
}
private class DcObserver implements DataChannel.Observer {
#Override
public void onMessage(DataChannel.Buffer buffer) {
ByteBuffer data = buffer.data;
byte[] bytes = new byte[data.remaining()];
data.get(bytes);
String command = new String(bytes);
Log.e("onMessage ", command);
}
#Override
public void onStateChange() {
Log.e("onStateChange ", "onStateChange");
}
#Override
public void onBufferedAmountChange(long arg0) {
Log.e("onMessage ", "" + arg0);
}
}
#Override
public void sendMessage(String msg) {
Log.e("DataChannel State", "" + dataChannel.state());
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
boolean sent = dataChannel.send(new DataChannel.Buffer(buffer, false));
Log.e("Message sent", "" + sent);
}
}
Edit: There is no issue at web client as similar app in ios is working fine in similar scenario.
I have an android app running as a client of WebRTC server running at Node.js server.
The current state of the app is I can make video calls but can't send the message on DataChannel.
Here is my complete code for the android app.
Home.java
public class Home extends Activity {
public List<PeerConnection.IceServer> iceServers;
private GLSurfaceView videoView;
public static SocketIO socket;
ArrayList<String> userIDs = new ArrayList<>();
private static final String FIELD_TRIAL_VP9 = "WebRTC-SupportVP9/Enabled/";
String RoomId = "";
String sreverURL = "http://xx.xx.xx.xx:xxxx/";
private EditText roomid;
private VideoRenderer.Callbacks remote_view;
private VideoRenderer.Callbacks local_view;
protected PeerConnectionFactory factory;
PeerConnectionFactory.Options options = null;
Events pc_events;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
videoView = (GLSurfaceView) findViewById(R.id.glview_call_remote);
VideoRendererGui.setView(videoView, new Runnable() {
#Override
public void run() {
createPeerConnectionFactory();
}
});
remote_view = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FIT, false);
local_view = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FILL, true);
iceServers = new ArrayList<>();
IceServer icc = new IceServer("stun:stun.l.google.com:19302", "", "");
iceServers.add(icc);
roomid = (EditText) findViewById(R.id.roomId);
Random rand = new Random();
roomid.setText("" + rand.nextInt(9999));
pc_events = new peerEventHandler();
}
private void createPeerConnectionFactory() {
runOnUiThread(new Runnable() {
#Override
public void run() {
PeerConnectionFactory.initializeFieldTrials(FIELD_TRIAL_VP9);
PeerConnectionFactory.initializeAndroidGlobals(Home.this, true, true, true, VideoRendererGui.getEGLContext());
try {
factory = new PeerConnectionFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public void ondail(View view) {
try {
try {
SocketIO.setDefaultSSLSocketFactory(SSLContext.getDefault());
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
}
socket = new SocketIO();
socket.connect(sreverURL, new IOCallback() {
#Override
public void onMessage(JSONObject json, IOAcknowledge ack) {
}
#Override
public void onMessage(String data, IOAcknowledge ack) {
}
#Override
public void onError(SocketIOException socketIOException) {
socketIOException.printStackTrace();
}
#Override
public void onDisconnect() {
}
#Override
public void onConnect() {
showToast("Connected to " + sreverURL);
}
#Override
public void on(final String event, IOAcknowledge ack, final Object... args) {
Log.e("Socked.on", event + ", " + args);
switch (getEvent(event)) {
case LOG :
break;
case MESSAGE :
if (args instanceof Object[]) {
pc_events.setMessage(args[0].toString());
} else {
pc_events.setMessage(args.toString());
}
break;
case CREATED :
runOnUiThread(new Runnable() {
public void run() {
showToast("Room Created " + args[0]);
}
});
break;
case BROADCAST :
break;
case JOIN :
break;
case EMIT :
Log.e("Socked.onEMIT", args.toString());
startCall();
pc_events.createOffer();
break;
case ERROR :
Log.e("Socked.onERROR", args.toString());
break;
default :
break;
}
}
});
try {
RoomId = roomid.getText().toString();
} catch (Exception e) {
}
socket.emit("create or join", RoomId);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public void oncancel(View view) {
}
public SocketEvent getEvent(String eventString) {
SocketEvent eventType;
try {
if (eventString.contains("log")) {
eventType = SocketEvent.LOG;
} else if (eventString.contains("created")) {
eventType = SocketEvent.CREATED;
} else if (eventString.contains("emit():")) {
eventType = SocketEvent.EMIT;
}
else if (eventString.contains("broadcast():")) {
eventType = SocketEvent.BROADCAST;
} else if (eventString.contains("message")) {
eventType = SocketEvent.MESSAGE;
} else if (eventString.toLowerCase().substring(0, 20).contains("join")) {
eventType = SocketEvent.JOIN;
} else {
eventType = SocketEvent.ERROR;
}
} catch (Exception e) {
eventType = SocketEvent.ERROR;
}
return eventType;
}
public static interface Events {
public void peerConnectionEvent(VideoRenderer.Callbacks localRender, VideoRenderer.Callbacks remoteRender);
public void setFactory(PeerConnectionFactory factory);
public void setMessage(String message);
public void createOffer();
public void sendMessage(String msg);
}
private void startCall() {
pc_events.setFactory(factory);
pc_events.peerConnectionEvent(remote_view, local_view);
}
public void showToast(final String message) {
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(Home.this, message, Toast.LENGTH_SHORT).show();
}
});
}
public void makeOffer(View v) {
pc_events.sendMessage("Hello");
}
}
peerEventHandler.java
public class peerEventHandler implements Events {
private PeerConnection peerConnection;
private PeerConnectionFactory factory;
PCObserver pcObserver = new PCObserver();
public LooperExecutor executor;
private MediaStream mediaStream;
private VideoSource videoSource;
private DcObserver dc_observer;
public static final String VIDEO_TRACK_ID = "ARDAMSv0";
public static final String AUDIO_TRACK_ID = "ARDAMSa0";
private VideoCapturerAndroid videoCapturer;
private VideoTrack localVideoTrack;
private VideoTrack remoteVideoTrack;
public boolean preferIsac = false;
public boolean videoCallEnabled = true;
public boolean preferH264 = false;
private SessionDescription localSdp;
private final SDPObserver sdpObserver = new SDPObserver();
public boolean isInitiator = false;
private MediaConstraints sdpMediaConstraints;
private VideoRenderer.Callbacks remote_view;
private VideoRenderer.Callbacks local_view;
private DataChannel dataChannel;
#Override
public void peerConnectionEvent(Callbacks remoteRender, Callbacks localRender) {
this.remote_view = remoteRender;
this.local_view = localRender;
creatPeerConnection();
}
public void creatPeerConnection() {
executor = new LooperExecutor();
executor.requestStart();
MediaConstraints pcConstraints = new MediaConstraints();
MediaConstraints videoConstraints = new MediaConstraints();
MediaConstraints audioConstraints = new MediaConstraints();
sdpMediaConstraints = new MediaConstraints();
creatPcConstrains(pcConstraints);
creatvideoConstraints(videoConstraints);
creatsdpMediaConstraints(sdpMediaConstraints);
List<PeerConnection.IceServer> iceServers = new ArrayList<PeerConnection.IceServer>();
IceServer iceServer = new IceServer("stun:stun.l.google.com:19302", "", "");
iceServers.add(iceServer);
PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.BALANCED;
rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE;
peerConnection = factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver);
Logging.enableTracing("logcat:", EnumSet.of(Logging.TraceLevel.TRACE_DEFAULT), Logging.Severity.LS_WARNING);
mediaStream = factory.createLocalMediaStream("ARDAMS");
String cameraDeviceName = CameraEnumerationAndroid.getDeviceName(0);
String frontCameraDeviceName = CameraEnumerationAndroid.getNameOfFrontFacingDevice();
cameraDeviceName = frontCameraDeviceName;
videoCapturer = VideoCapturerAndroid.create(cameraDeviceName, null);
videoSource = factory.createVideoSource(videoCapturer, videoConstraints);
localVideoTrack = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
localVideoTrack.setEnabled(true);
localVideoTrack.addRenderer(new VideoRenderer(local_view));
mediaStream.addTrack(factory.createAudioTrack(AUDIO_TRACK_ID, factory.createAudioSource(audioConstraints)));
mediaStream.addTrack(localVideoTrack);
peerConnection.addStream(mediaStream);
dataChannel = peerConnection.createDataChannel("sendDataChannel", new DataChannel.Init());
dc_observer = new DcObserver();
dataChannel.registerObserver(dc_observer);
}
#Override
public void createOffer() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection != null) {
isInitiator = true;
peerConnection.createOffer(sdpObserver, sdpMediaConstraints);
}
}
});
}
public void createAnswer() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection != null) {
isInitiator = false;
peerConnection.createAnswer(sdpObserver, sdpMediaConstraints);
}
}
});
}
private class PCObserver implements PeerConnection.Observer {
#Override
public void onAddStream(final MediaStream stream) {
Log.e("onAddStream", "onAddStream");
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
if (stream.audioTracks.size() > 1 || stream.videoTracks.size() > 1) {
// /reportError("Weird-looking stream: " + stream);
return;
}
if (stream.videoTracks.size() == 1) {
remoteVideoTrack = stream.videoTracks.get(0);
remoteVideoTrack.setEnabled(true);
remoteVideoTrack.addRenderer(new VideoRenderer(remote_view));
VideoRendererGui.update(local_view, 75, 70, 60, 60, ScalingType.SCALE_ASPECT_FIT, true);
VideoRendererGui.update(remote_view, 0, 0, 200, 200, ScalingType.SCALE_ASPECT_FILL, false);
}
}
});
}
#Override
public void onDataChannel(final DataChannel dc) {
executor.execute(new Runnable() {
#Override
public void run() {
dataChannel = dc;
String channelName = dataChannel.label();
dataChannel.registerObserver(new DcObserver());
}
});
}
#Override
public void onIceCandidate(IceCandidate candidate) {
SocketIO socket = Home.socket;
JSONObject json = new JSONObject();
try {
json.putOpt("type", "candidate");
json.putOpt("label", candidate.sdpMLineIndex);
json.putOpt("id", candidate.sdpMid);
json.putOpt("candidate", candidate.sdp);
} catch (JSONException e) {
e.printStackTrace();
}
socket.emit("message", json);
}
#Override
public void onIceConnectionChange(IceConnectionState arg0) {
}
#Override
public void onIceConnectionReceivingChange(boolean arg0) {
}
#Override
public void onIceGatheringChange(IceGatheringState arg0) {
}
#Override
public void onRemoveStream(MediaStream arg0) {
}
#Override
public void onRenegotiationNeeded() {
}
#Override
public void onSignalingChange(SignalingState arg0) {
}
}
public void creatPcConstrains(MediaConstraints pcConstraints) {
pcConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true"));
pcConstraints.optional.add(new KeyValuePair("RtpDataChannels", "true"));
pcConstraints.optional.add(new KeyValuePair("internalSctpDataChannels", "true"));
}
public void creatvideoConstraints(MediaConstraints videoConstraints) {
String MAX_VIDEO_WIDTH_CONSTRAINT = "maxWidth";
String MIN_VIDEO_WIDTH_CONSTRAINT = "minWidth";
String MAX_VIDEO_HEIGHT_CONSTRAINT = "maxHeight";
String MIN_VIDEO_HEIGHT_CONSTRAINT = "minHeight";
String MAX_VIDEO_FPS_CONSTRAINT = "maxFrameRate";
String MIN_VIDEO_FPS_CONSTRAINT = "minFrameRate";
int videoWidth = 0;
int videoHeight = 0;
if ((videoWidth == 0 || videoHeight == 0) && true && MediaCodecVideoEncoder.isVp8HwSupported()) {
videoWidth = 1280;
videoHeight = 1280;
}
if (videoWidth > 0 && videoHeight > 0) {
videoWidth = Math.min(videoWidth, 1280);
videoHeight = Math.min(videoHeight, 1280);
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
}
int videoFps = 30;
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
}
public void creataudioConstraints(MediaConstraints pcConstraints) {
pcConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true"));
pcConstraints.optional.add(new KeyValuePair("RtpDataChannels", "true"));
pcConstraints.optional.add(new KeyValuePair("internalSctpDataChannels", "true"));
}
public void creatsdpMediaConstraints(MediaConstraints sdpMediaConstraints) {
sdpMediaConstraints.mandatory.add(new KeyValuePair("OfferToReceiveAudio", "true"));
sdpMediaConstraints.mandatory.add(new KeyValuePair("OfferToReceiveVideo", "true"));
}
private class SDPObserver implements SdpObserver {
#Override
public void onCreateFailure(String arg0) {
System.out.print(arg0);
}
#Override
public void onCreateSuccess(SessionDescription origSdp) {
if (localSdp != null) {
return;
}
localSdp = origSdp;
setLocalDescription(origSdp);
}
#Override
public void onSetFailure(String arg0) {
}
#Override
public void onSetSuccess() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
if (isInitiator) {
if (peerConnection != null) {
JSONObject json = new JSONObject();
try {
json.putOpt("type", localSdp.type.toString().toLowerCase());
json.putOpt("sdp", localSdp.description);
} catch (JSONException e) {
e.printStackTrace();
}
Home.socket.emit("message", json);
}
} else {
// createAnswer();
}
}
});
}
}
public void addRemoteIceCandidate(final IceCandidate candidate) {
executor.execute(new Runnable() {
#Override
public void run() {
peerConnection.addIceCandidate(candidate);
}
});
}
public void setLocalDescription(final SessionDescription sdp) {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
peerConnection.setLocalDescription(sdpObserver, sdp);
}
});
}
public void setRemoteDescription(final SessionDescription sdp) {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
peerConnection.setRemoteDescription(sdpObserver, sdp);
}
});
}
#Override
public void setFactory(PeerConnectionFactory factory) {
this.factory = factory;
}
public void onWebSocketMessage(final String msg) {
try {
Log.e("onWebSocketMessage", msg);
JSONObject json = new JSONObject(msg);
json = new JSONObject(msg);
String type = json.optString("type");
if (type.equals("candidate")) {
IceCandidate candidate = new IceCandidate(json.getString("id"), json.getInt("label"), json.getString("candidate"));
addRemoteIceCandidate(candidate);
} else if (type.equals("answer")) {
isInitiator = false;
SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
setRemoteDescription(sdp);
} else if (type.equals("offer")) {
SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
setRemoteDescription(sdp);
} else if (type.equals("bye")) {
} else {
}
} catch (JSONException e) {
}
}
#Override
public void setMessage(String message) {
if (message.toString().contains("got user media") || message.toString().contains("bye")) {
} else
onWebSocketMessage(message);
}
private class DcObserver implements DataChannel.Observer {
#Override
public void onMessage(DataChannel.Buffer buffer) {
ByteBuffer data = buffer.data;
byte[] bytes = new byte[data.remaining()];
data.get(bytes);
String command = new String(bytes);
Log.e("onMessage ", command);
}
#Override
public void onStateChange() {
Log.e("onStateChange ", "onStateChange");
}
#Override
public void onBufferedAmountChange(long arg0) {
Log.e("onMessage ", "" + arg0);
}
}
#Override
public void sendMessage(String msg) {
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
boolean sent = dataChannel.send(new DataChannel.Buffer(buffer, false));
if (sent) {
Log.e("Message sent", "" + sent);
}
}
}
Any comments and suggestions are welcomed ;)
WebRTC data channel works through SCTP now so you can remove the RtpDataChannels constraint.
So I'm trying to connect Android to Browser via webRTC via socket.io and libjingle and server is running on Node.js .
Issue I'm facing is weired.
When 1 client is at Android(native app) and other is at Ipad(native app), Everything works fine.
When 1 client is at iPad(Native app) and other is WebApp, Everyting works fine.
But When 1 Client is at Android(native app) and other is WebPage, everyting works fine except the audio and video is not streaming to that end.
Following are the two major classes i've used for the purpose:
PS. The method makeOffer(View v) called by the button.
MainActivity.java
public List<PeerConnection.IceServer> iceServers;
private GLSurfaceView videoView;
public static SocketIO socket;
ArrayList<String> userIDs = new ArrayList<>();
private static final String FIELD_TRIAL_VP9 = "WebRTC-SupportVP9/Enabled/";
String RoomId = "";
String sreverURL = "http://xx.xx.xx.xx:xxxx/";
private EditText roomid;
private VideoRenderer.Callbacks localRender;
private VideoRenderer.Callbacks remoteRender;
protected PeerConnectionFactory factory;
PeerConnectionFactory.Options options = null;
Events pc_events;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
videoView = (GLSurfaceView) findViewById(R.id.glview_call);
VideoRendererGui.setView(videoView, new Runnable() {
#Override
public void run() {
createPeerConnectionFactory();
}
});
remoteRender = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FILL, false);
localRender = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FILL, true);
iceServers = new ArrayList<>();
IceServer icc = new IceServer("stun:stun.l.google.com:19302", "", "");
iceServers.add(icc);
roomid = (EditText) findViewById(R.id.roomId);
Random rand = new Random();
roomid.setText("" + rand.nextInt(9999));
pc_events = new peerEventHandler();
}
private void createPeerConnectionFactory() {
runOnUiThread(new Runnable() {
#Override
public void run() {
PeerConnectionFactory.initializeFieldTrials(FIELD_TRIAL_VP9);
PeerConnectionFactory.initializeAndroidGlobals(Home.this, true, true, true, VideoRendererGui.getEGLContext());
try {
factory = new PeerConnectionFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public void ondail(View view) {
try {
try {
SocketIO.setDefaultSSLSocketFactory(SSLContext.getDefault());
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
}
socket = new SocketIO();
socket.connect(sreverURL, new IOCallback() {
#Override
public void onMessage(JSONObject json, IOAcknowledge ack) {
Log.e("json Object", json.toString());
}
#Override
public void onMessage(String data, IOAcknowledge ack) {
Log.e("json Object", data.toString());
}
#Override
public void onError(SocketIOException socketIOException) {
socketIOException.printStackTrace();
System.out.println(socketIOException.toString());
}
#Override
public void onDisconnect() {
}
#Override
public void onConnect() {
showToast("Connected to " + sreverURL);
startCall("");
}
#Override
public void on(final String event, IOAcknowledge ack, final Object... args) {
Log.e("Socked.on", event + ", " + args);
switch (getEvent(event)) {
case LOG :
break;
case MESSAGE :
if (args instanceof Object[]) {
if (args[0].toString().contains("offer")) {
// startCall("");
}
pc_events.setMessage(args[0].toString());
} else {
pc_events.setMessage(args.toString());
}
break;
case CREATED :
runOnUiThread(new Runnable() {
public void run() {
showToast("Room Created " + args[0]);
}
});
break;
case BROADCAST :
Log.e("Socked.onBROADCAST", args.toString());
break;
case JOIN :
Log.e("Socked.onJOIN", args.toString());
break;
case EMIT :
Log.e("Socked.onEMIT", args.toString());
Log.e("Socked.onBROADCOST", args.toString());
break;
case ERROR :
Log.e("Socked.onERROR", args.toString());
break;
default :
break;
}
}
});
try {
RoomId = roomid.getText().toString();
} catch (Exception e) {
}
socket.emit("create or join", RoomId);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public void oncancel(View view) {
}
public SocketEvent getEvent(String eventString) {
SocketEvent eventType;
try {
if (eventString.contains("log")) {
eventType = SocketEvent.LOG;
} else if (eventString.contains("created")) {
eventType = SocketEvent.CREATED;
} else if (eventString.contains("emit():")) {
eventType = SocketEvent.EMIT;
}
else if (eventString.contains("broadcast():")) {
eventType = SocketEvent.BROADCAST;
} else if (eventString.contains("message")) {
eventType = SocketEvent.MESSAGE;
} else if (eventString.toLowerCase().substring(0, 20).contains("join")) {
eventType = SocketEvent.JOIN;
} else {
eventType = SocketEvent.ERROR;
}
} catch (Exception e) {
eventType = SocketEvent.ERROR;
}
return eventType;
}
public static interface Events {
public void peerConnectionEvent(String clintID, VideoRenderer.Callbacks localRender, VideoRenderer.Callbacks remoteRender);
public void setFactory(PeerConnectionFactory factory);
public void setMessage(String message);
public void createOffer();
}
private void startCall(String clientID) {
pc_events.setFactory(factory);
pc_events.peerConnectionEvent(clientID, localRender, remoteRender);
}
public void showToast(final String message) {
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(Home.this, message, Toast.LENGTH_SHORT).show();
}
});
}
public void makeOffer(View v) {
pc_events.createOffer();
}
}
PeerEventHandler.java
public class peerEventHandler implements Events {
private PeerConnection peerConnection;
private PeerConnectionFactory factory;
PCObserver pcObserver = new PCObserver();
public LooperExecutor executor;
private MediaStream mediaStream;
private VideoSource videoSource;
public static final String VIDEO_TRACK_ID = "ARDAMSv0";
public static final String AUDIO_TRACK_ID = "ARDAMSa0";
private VideoCapturerAndroid videoCapturer;
private VideoTrack localVideoTrack;
private VideoTrack remoteVideoTrack;
public boolean preferIsac = false;
public boolean videoCallEnabled = true;
public boolean preferH264 = true;
private static final String VIDEO_CODEC_H264 = "VP8";
private static final String AUDIO_CODEC_ISAC = "ISAC";
private SessionDescription localSdp;
private final SDPObserver sdpObserver = new SDPObserver();
private LinkedList<IceCandidate> queuedRemoteCandidates;
public boolean isInitiator = false;
private MediaConstraints sdpMediaConstraints;
private VideoRenderer.Callbacks localRender;
private VideoRenderer.Callbacks remoteRender;
#Override
public void peerConnectionEvent(String clintID, Callbacks localRender, Callbacks remoteRender) {
this.localRender = localRender;
this.remoteRender = remoteRender;
creatPeerConnection();
}
public void creatPeerConnection() {
queuedRemoteCandidates = new LinkedList<IceCandidate>();
executor = new LooperExecutor();
executor.requestStart();
MediaConstraints pcConstraints = new MediaConstraints();
MediaConstraints videoConstraints = new MediaConstraints();
MediaConstraints audioConstraints = new MediaConstraints();
sdpMediaConstraints = new MediaConstraints();
creatPcConstrains(pcConstraints);
creatvideoConstraints(videoConstraints);
creatsdpMediaConstraints(sdpMediaConstraints);
List<PeerConnection.IceServer> iceServers = new ArrayList<PeerConnection.IceServer>();
IceServer iceServer = new IceServer("stun:stun.l.google.com:19302", "", "");
iceServers.add(iceServer);
PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
// TCP candidates are only useful when connecting to a server that
// supports
// ICE-TCP.
rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.BALANCED;
rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE;
peerConnection = factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver);
Logging.enableTracing("logcat:", EnumSet.of(Logging.TraceLevel.TRACE_DEFAULT), Logging.Severity.LS_WARNING);
mediaStream = factory.createLocalMediaStream("ARDAMS");
String cameraDeviceName = CameraEnumerationAndroid.getDeviceName(0);
String frontCameraDeviceName = CameraEnumerationAndroid.getNameOfFrontFacingDevice();
cameraDeviceName = frontCameraDeviceName;
videoCapturer = VideoCapturerAndroid.create(cameraDeviceName, null);
videoSource = factory.createVideoSource(videoCapturer, videoConstraints);
localVideoTrack = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
localVideoTrack.setEnabled(true);
localVideoTrack.addRenderer(new VideoRenderer(localRender));
mediaStream.addTrack(factory.createAudioTrack(AUDIO_TRACK_ID, factory.createAudioSource(audioConstraints)));
mediaStream.addTrack(localVideoTrack);
peerConnection.addStream(mediaStream);
}
#Override
public void createOffer() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection != null) {
isInitiator = true;
peerConnection.createOffer(sdpObserver, sdpMediaConstraints);
}
}
});
}
public void createAnswer() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection != null) {
isInitiator = false;
peerConnection.createAnswer(sdpObserver, sdpMediaConstraints);
}
}
});
}
private class PCObserver implements PeerConnection.Observer {
#Override
public void onAddStream(final MediaStream stream) {
Log.e("onAddStream", "onAddStream");
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
if (stream.audioTracks.size() > 1 || stream.videoTracks.size() > 1) {
// /reportError("Weird-looking stream: " + stream);
return;
}
if (stream.videoTracks.size() == 1) {
remoteVideoTrack = stream.videoTracks.get(0);
remoteVideoTrack.setEnabled(true);
remoteVideoTrack.addRenderer(new VideoRenderer(remoteRender));
}
}
});
}
#Override
public void onDataChannel(DataChannel arg0) {
Log.e("On data channel", "Data Channel");
}
#Override
public void onIceCandidate(IceCandidate candidate) {
SocketIO socket = Home.socket;
JSONObject json = new JSONObject();
try {
json.putOpt("type", "candidate");
json.putOpt("label", candidate.sdpMLineIndex);
json.putOpt("id", candidate.sdpMid);
json.putOpt("candidate", candidate.sdp);
} catch (JSONException e) {
}
socket.emit("message", json);
}
#Override
public void onIceConnectionChange(IceConnectionState arg0) {
Log.e("onIceConnectionChange", "onIceConnectionChange");
}
#Override
public void onIceConnectionReceivingChange(boolean arg0) {
Log.e("onIceConnectionReceivingChange", "onIceConnectionReceivingChange");
}
#Override
public void onIceGatheringChange(IceGatheringState arg0) {
Log.e("onIceGatheringChange", "onIceGatheringChange");
}
#Override
public void onRemoveStream(MediaStream arg0) {
Log.e("onRemoveStream", "onRemoveStream");
}
#Override
public void onRenegotiationNeeded() {
Log.e("onRenegotiationNeeded", "onRenegotiationNeeded");
}
#Override
public void onSignalingChange(SignalingState arg0) {
Log.e("onSignalingChange", "onSignalingChange");
}
}
public void creatPcConstrains(MediaConstraints pcConstraints) {
pcConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true"));
pcConstraints.optional.add(new KeyValuePair("RtpDataChannels", "true"));
}
public void creatvideoConstraints(MediaConstraints videoConstraints) {
String MAX_VIDEO_WIDTH_CONSTRAINT = "maxWidth";
String MIN_VIDEO_WIDTH_CONSTRAINT = "minWidth";
String MAX_VIDEO_HEIGHT_CONSTRAINT = "maxHeight";
String MIN_VIDEO_HEIGHT_CONSTRAINT = "minHeight";
String MAX_VIDEO_FPS_CONSTRAINT = "maxFrameRate";
String MIN_VIDEO_FPS_CONSTRAINT = "minFrameRate";
int videoWidth = 0;
int videoHeight = 0;
// If VP8 HW video encoder is supported and video resolution is not
// specified force it to HD.
if ((videoWidth == 0 || videoHeight == 0) && true && MediaCodecVideoEncoder.isVp8HwSupported()) {
videoWidth = 1280;
videoHeight = 1280;
}
// Add video resolution constraints.
if (videoWidth > 0 && videoHeight > 0) {
videoWidth = Math.min(videoWidth, 1280);
videoHeight = Math.min(videoHeight, 1280);
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
}
// Add fps constraints.
int videoFps = 30;
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
}
public void creataudioConstraints(MediaConstraints pcConstraints) {
pcConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true"));
pcConstraints.optional.add(new KeyValuePair("RtpDataChannels", "true"));
}
public void creatsdpMediaConstraints(MediaConstraints sdpMediaConstraints) {
sdpMediaConstraints.mandatory.add(new KeyValuePair("OfferToReceiveAudio", "true"));
sdpMediaConstraints.mandatory.add(new KeyValuePair("OfferToReceiveVideo", "true"));
}
private class SDPObserver implements SdpObserver {
#Override
public void onCreateFailure(String arg0) {
System.out.print(arg0);
}
#Override
public void onCreateSuccess(SessionDescription origSdp) {
if (localSdp != null) {
return;
}
String sdpDescription = origSdp.description;
if (preferIsac) {
sdpDescription = preferCodec(sdpDescription, AUDIO_CODEC_ISAC, true);
}
if (videoCallEnabled && preferH264) {
sdpDescription = preferCodec(sdpDescription, VIDEO_CODEC_H264, false);
}
final SessionDescription sdp = new SessionDescription(origSdp.type, sdpDescription);
localSdp = sdp;
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection != null) {
JSONObject json = new JSONObject();
try {
// peerConnection.setLocalDescription(null, localSdp);
json.putOpt("type", "offer");
json.putOpt("sdp", localSdp.description);
} catch (JSONException e) {
e.printStackTrace();
}
Home.socket.emit("message", json);
}
}
});
}
#Override
public void onSetFailure(String arg0) {
Log.e("onSetFailure", arg0);
}
#Override
public void onSetSuccess() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
if (isInitiator) {
createOffer();
} else {
createAnswer();
}
}
});
}
}
private static String preferCodec(String sdpDescription, String codec, boolean isAudio) {
String[] lines = sdpDescription.split("\r\n");
int mLineIndex = -1;
String codecRtpMap = null;
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding
// parameters>]
String regex = "^a=rtpmap:(\\d+) " + codec + "(/\\d+)+[\r]?$";
Pattern codecPattern = Pattern.compile(regex);
String mediaDescription = "m=video ";
if (isAudio) {
mediaDescription = "m=audio ";
}
for (int i = 0; (i < lines.length) && (mLineIndex == -1 || codecRtpMap == null); i++) {
if (lines[i].startsWith(mediaDescription)) {
mLineIndex = i;
continue;
}
Matcher codecMatcher = codecPattern.matcher(lines[i]);
if (codecMatcher.matches()) {
codecRtpMap = codecMatcher.group(1);
continue;
}
}
if (mLineIndex == -1) {
// Log.w(TAG, "No " + mediaDescription + " line, so can't prefer " +
// codec);
return sdpDescription;
}
if (codecRtpMap == null) {
// Log.w(TAG, "No rtpmap for " + codec);
return sdpDescription;
}
// Log.d(TAG, "Found " + codec + " rtpmap " + codecRtpMap +
// ", prefer at "
// + lines[mLineIndex]);
String[] origMLineParts = lines[mLineIndex].split(" ");
if (origMLineParts.length > 3) {
StringBuilder newMLine = new StringBuilder();
int origPartIndex = 0;
// Format is: m=<media> <port> <proto> <fmt> ...
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
newMLine.append(codecRtpMap);
for (; origPartIndex < origMLineParts.length; origPartIndex++) {
if (!origMLineParts[origPartIndex].equals(codecRtpMap)) {
newMLine.append(" ").append(origMLineParts[origPartIndex]);
}
}
lines[mLineIndex] = newMLine.toString();
// Log.d(TAG, "Change media description: " + lines[mLineIndex]);
} else {
// Log.e(TAG, "Wrong SDP media description format: " +
// lines[mLineIndex]);
}
StringBuilder newSdpDescription = new StringBuilder();
for (String line : lines) {
newSdpDescription.append(line).append("\r\n");
}
return newSdpDescription.toString();
}
private void drainCandidates() {
if (queuedRemoteCandidates != null) {
// Log.d(TAG, "Add " + queuedRemoteCandidates.size() +
// " remote candidates");
for (IceCandidate candidate : queuedRemoteCandidates) {
// if(!candidate.sdpMid.contains("video"))
peerConnection.addIceCandidate(candidate);
}
queuedRemoteCandidates = null;
}
}
public void addRemoteIceCandidate(final IceCandidate candidate) {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection != null) {
if (queuedRemoteCandidates != null) {
queuedRemoteCandidates.add(candidate);
} else {
peerConnection.addIceCandidate(candidate);
}
}
}
});
}
public void setRemoteDescription(final SessionDescription sdp) {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
String sdpDescription = sdp.description;
if (preferIsac) {
sdpDescription = preferCodec(sdpDescription, AUDIO_CODEC_ISAC, true);
}
if (videoCallEnabled && preferH264) {
sdpDescription = preferCodec(sdpDescription, VIDEO_CODEC_H264, false);
}
SessionDescription sdpRemote = new SessionDescription(sdp.type, sdpDescription);
peerConnection.setRemoteDescription(sdpObserver, sdpRemote);
}
});
}
#Override
public void setFactory(PeerConnectionFactory factory) {
this.factory = factory;
}
public void onWebSocketMessage(final String msg) {
try {
Log.e("onWebSocketMessage", msg);
JSONObject json = new JSONObject(msg);
json = new JSONObject(msg);
String type = json.optString("type");
if (type.equals("candidate")) {
IceCandidate candidate = new IceCandidate(json.getString("id"), json.getInt("label"), json.getString("candidate"));
addRemoteIceCandidate(candidate);
} else if (type.equals("answer")) {
if (isInitiator) {
SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
setRemoteDescription(sdp);
} else {
}
} else if (type.equals("offer")) {
if (!isInitiator) {
SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
setRemoteDescription(sdp);
} else {
}
} else if (type.equals("bye")) {
} else {
}
} catch (JSONException e) {
}
}
#Override
public void setMessage(String message) {
if (message.toString().contains("got user media") || message.toString().contains("bye")) {
} else
onWebSocketMessage(message);
}
}
I don't know why but I changed target=android-19 to target=android-21 and everything worked fine as desired (Except Datachannel).
Final Working Code
PS: You can use this code without any royalty and Guarantees :p
Home.java
public class Home extends Activity {
public List<PeerConnection.IceServer> iceServers;
private GLSurfaceView videoView;
public static SocketIO socket;
ArrayList<String> userIDs = new ArrayList<>();
private static final String FIELD_TRIAL_VP9 = "WebRTC-SupportVP9/Enabled/";
String RoomId = "";
String sreverURL = "http://xx.xx.xx.xx:xxxx/";
private EditText roomid;
private VideoRenderer.Callbacks remote_view;
private VideoRenderer.Callbacks local_view;
protected PeerConnectionFactory factory;
PeerConnectionFactory.Options options = null;
Events pc_events;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
videoView = (GLSurfaceView) findViewById(R.id.glview_call_remote);
VideoRendererGui.setView(videoView, new Runnable() {
#Override
public void run() {
createPeerConnectionFactory();
}
});
remote_view = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FIT, false);
local_view = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FILL, true);
iceServers = new ArrayList<>();
IceServer icc = new IceServer("stun:stun.l.google.com:19302", "", "");
iceServers.add(icc);
roomid = (EditText) findViewById(R.id.roomId);
Random rand = new Random();
roomid.setText("" + rand.nextInt(9999));
pc_events = new peerEventHandler();
}
private void createPeerConnectionFactory() {
runOnUiThread(new Runnable() {
#Override
public void run() {
PeerConnectionFactory.initializeFieldTrials(FIELD_TRIAL_VP9);
PeerConnectionFactory.initializeAndroidGlobals(Home.this, true, true, true, VideoRendererGui.getEGLContext());
try {
factory = new PeerConnectionFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public void ondail(View view) {
try {
try {
SocketIO.setDefaultSSLSocketFactory(SSLContext.getDefault());
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
}
socket = new SocketIO();
socket.connect(sreverURL, new IOCallback() {
#Override
public void onMessage(JSONObject json, IOAcknowledge ack) {
}
#Override
public void onMessage(String data, IOAcknowledge ack) {
}
#Override
public void onError(SocketIOException socketIOException) {
socketIOException.printStackTrace();
}
#Override
public void onDisconnect() {
}
#Override
public void onConnect() {
showToast("Connected to " + sreverURL);
}
#Override
public void on(final String event, IOAcknowledge ack, final Object... args) {
Log.e("Socked.on", event + ", " + args);
switch (getEvent(event)) {
case LOG :
break;
case MESSAGE :
if (args instanceof Object[]) {
pc_events.setMessage(args[0].toString());
} else {
pc_events.setMessage(args.toString());
}
break;
case CREATED :
runOnUiThread(new Runnable() {
public void run() {
showToast("Room Created " + args[0]);
}
});
break;
case BROADCAST :
break;
case JOIN :
break;
case EMIT :
Log.e("Socked.onEMIT", args.toString());
startCall();
pc_events.createOffer();
break;
case ERROR :
Log.e("Socked.onERROR", args.toString());
break;
default :
break;
}
}
});
try {
RoomId = roomid.getText().toString();
} catch (Exception e) {
}
socket.emit("create or join", RoomId);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public void oncancel(View view) {
}
public SocketEvent getEvent(String eventString) {
SocketEvent eventType;
try {
if (eventString.contains("log")) {
eventType = SocketEvent.LOG;
} else if (eventString.contains("created")) {
eventType = SocketEvent.CREATED;
} else if (eventString.contains("emit():")) {
eventType = SocketEvent.EMIT;
}
else if (eventString.contains("broadcast():")) {
eventType = SocketEvent.BROADCAST;
} else if (eventString.contains("message")) {
eventType = SocketEvent.MESSAGE;
} else if (eventString.toLowerCase().substring(0, 20).contains("join")) {
eventType = SocketEvent.JOIN;
} else {
eventType = SocketEvent.ERROR;
}
} catch (Exception e) {
eventType = SocketEvent.ERROR;
}
return eventType;
}
public static interface Events {
public void peerConnectionEvent(VideoRenderer.Callbacks localRender, VideoRenderer.Callbacks remoteRender);
public void setFactory(PeerConnectionFactory factory);
public void setMessage(String message);
public void createOffer();
public void sendMessage(String msg);
}
private void startCall() {
pc_events.setFactory(factory);
pc_events.peerConnectionEvent(remote_view, local_view);
}
public void showToast(final String message) {
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(Home.this, message, Toast.LENGTH_SHORT).show();
}
});
}
public void makeOffer(View v) {
pc_events.sendMessage("Hello");
}
}
peerEventHandler.java
public class peerEventHandler implements Events {
private PeerConnection peerConnection;
private PeerConnectionFactory factory;
PCObserver pcObserver = new PCObserver();
public LooperExecutor executor;
private MediaStream mediaStream;
private VideoSource videoSource;
private DcObserver dc_observer;
public static final String VIDEO_TRACK_ID = "ARDAMSv0";
public static final String AUDIO_TRACK_ID = "ARDAMSa0";
private VideoCapturerAndroid videoCapturer;
private VideoTrack localVideoTrack;
private VideoTrack remoteVideoTrack;
public boolean preferIsac = false;
public boolean videoCallEnabled = true;
public boolean preferH264 = false;
private SessionDescription localSdp;
private final SDPObserver sdpObserver = new SDPObserver();
public boolean isInitiator = false;
private MediaConstraints sdpMediaConstraints;
private VideoRenderer.Callbacks remote_view;
private VideoRenderer.Callbacks local_view;
private DataChannel dataChannel;
#Override
public void peerConnectionEvent(Callbacks remoteRender, Callbacks localRender) {
this.remote_view = remoteRender;
this.local_view = localRender;
creatPeerConnection();
}
public void creatPeerConnection() {
executor = new LooperExecutor();
executor.requestStart();
MediaConstraints pcConstraints = new MediaConstraints();
MediaConstraints videoConstraints = new MediaConstraints();
MediaConstraints audioConstraints = new MediaConstraints();
sdpMediaConstraints = new MediaConstraints();
creatPcConstrains(pcConstraints);
creatvideoConstraints(videoConstraints);
creatsdpMediaConstraints(sdpMediaConstraints);
List<PeerConnection.IceServer> iceServers = new ArrayList<PeerConnection.IceServer>();
IceServer iceServer = new IceServer("stun:stun.l.google.com:19302", "", "");
iceServers.add(iceServer);
PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.BALANCED;
rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE;
peerConnection = factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver);
Logging.enableTracing("logcat:", EnumSet.of(Logging.TraceLevel.TRACE_DEFAULT), Logging.Severity.LS_WARNING);
mediaStream = factory.createLocalMediaStream("ARDAMS");
String cameraDeviceName = CameraEnumerationAndroid.getDeviceName(0);
String frontCameraDeviceName = CameraEnumerationAndroid.getNameOfFrontFacingDevice();
cameraDeviceName = frontCameraDeviceName;
videoCapturer = VideoCapturerAndroid.create(cameraDeviceName, null);
videoSource = factory.createVideoSource(videoCapturer, videoConstraints);
localVideoTrack = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
localVideoTrack.setEnabled(true);
localVideoTrack.addRenderer(new VideoRenderer(local_view));
mediaStream.addTrack(factory.createAudioTrack(AUDIO_TRACK_ID, factory.createAudioSource(audioConstraints)));
mediaStream.addTrack(localVideoTrack);
peerConnection.addStream(mediaStream);
dataChannel = peerConnection.createDataChannel("sendDataChannel", new DataChannel.Init());
dc_observer = new DcObserver();
dataChannel.registerObserver(dc_observer);
}
#Override
public void createOffer() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection != null) {
isInitiator = true;
peerConnection.createOffer(sdpObserver, sdpMediaConstraints);
}
}
});
}
public void createAnswer() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection != null) {
isInitiator = false;
peerConnection.createAnswer(sdpObserver, sdpMediaConstraints);
}
}
});
}
private class PCObserver implements PeerConnection.Observer {
#Override
public void onAddStream(final MediaStream stream) {
Log.e("onAddStream", "onAddStream");
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
if (stream.audioTracks.size() > 1 || stream.videoTracks.size() > 1) {
// /reportError("Weird-looking stream: " + stream);
return;
}
if (stream.videoTracks.size() == 1) {
remoteVideoTrack = stream.videoTracks.get(0);
remoteVideoTrack.setEnabled(true);
remoteVideoTrack.addRenderer(new VideoRenderer(remote_view));
VideoRendererGui.update(local_view, 75, 70, 60, 60, ScalingType.SCALE_ASPECT_FIT, true);
VideoRendererGui.update(remote_view, 0, 0, 200, 200, ScalingType.SCALE_ASPECT_FILL, false);
}
}
});
}
#Override
public void onDataChannel(final DataChannel dc) {
executor.execute(new Runnable() {
#Override
public void run() {
dataChannel = dc;
String channelName = dataChannel.label();
dataChannel.registerObserver(new DcObserver());
}
});
}
#Override
public void onIceCandidate(IceCandidate candidate) {
SocketIO socket = Home.socket;
JSONObject json = new JSONObject();
try {
json.putOpt("type", "candidate");
json.putOpt("label", candidate.sdpMLineIndex);
json.putOpt("id", candidate.sdpMid);
json.putOpt("candidate", candidate.sdp);
} catch (JSONException e) {
e.printStackTrace();
}
socket.emit("message", json);
}
#Override
public void onIceConnectionChange(IceConnectionState arg0) {
}
#Override
public void onIceConnectionReceivingChange(boolean arg0) {
}
#Override
public void onIceGatheringChange(IceGatheringState arg0) {
}
#Override
public void onRemoveStream(MediaStream arg0) {
}
#Override
public void onRenegotiationNeeded() {
}
#Override
public void onSignalingChange(SignalingState arg0) {
}
}
public void creatPcConstrains(MediaConstraints pcConstraints) {
pcConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true"));
pcConstraints.optional.add(new KeyValuePair("RtpDataChannels", "true"));
pcConstraints.optional.add(new KeyValuePair("internalSctpDataChannels", "true"));
}
public void creatvideoConstraints(MediaConstraints videoConstraints) {
String MAX_VIDEO_WIDTH_CONSTRAINT = "maxWidth";
String MIN_VIDEO_WIDTH_CONSTRAINT = "minWidth";
String MAX_VIDEO_HEIGHT_CONSTRAINT = "maxHeight";
String MIN_VIDEO_HEIGHT_CONSTRAINT = "minHeight";
String MAX_VIDEO_FPS_CONSTRAINT = "maxFrameRate";
String MIN_VIDEO_FPS_CONSTRAINT = "minFrameRate";
int videoWidth = 0;
int videoHeight = 0;
if ((videoWidth == 0 || videoHeight == 0) && true && MediaCodecVideoEncoder.isVp8HwSupported()) {
videoWidth = 1280;
videoHeight = 1280;
}
if (videoWidth > 0 && videoHeight > 0) {
videoWidth = Math.min(videoWidth, 1280);
videoHeight = Math.min(videoHeight, 1280);
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
}
int videoFps = 30;
videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
}
public void creataudioConstraints(MediaConstraints pcConstraints) {
pcConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true"));
pcConstraints.optional.add(new KeyValuePair("RtpDataChannels", "true"));
pcConstraints.optional.add(new KeyValuePair("internalSctpDataChannels", "true"));
}
public void creatsdpMediaConstraints(MediaConstraints sdpMediaConstraints) {
sdpMediaConstraints.mandatory.add(new KeyValuePair("OfferToReceiveAudio", "true"));
sdpMediaConstraints.mandatory.add(new KeyValuePair("OfferToReceiveVideo", "true"));
}
private class SDPObserver implements SdpObserver {
#Override
public void onCreateFailure(String arg0) {
System.out.print(arg0);
}
#Override
public void onCreateSuccess(SessionDescription origSdp) {
if (localSdp != null) {
return;
}
localSdp = origSdp;
setLocalDescription(origSdp);
}
#Override
public void onSetFailure(String arg0) {
}
#Override
public void onSetSuccess() {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
if (isInitiator) {
if (peerConnection != null) {
JSONObject json = new JSONObject();
try {
json.putOpt("type", localSdp.type.toString().toLowerCase());
json.putOpt("sdp", localSdp.description);
} catch (JSONException e) {
e.printStackTrace();
}
Home.socket.emit("message", json);
}
} else {
// createAnswer();
}
}
});
}
}
public void addRemoteIceCandidate(final IceCandidate candidate) {
executor.execute(new Runnable() {
#Override
public void run() {
peerConnection.addIceCandidate(candidate);
}
});
}
public void setLocalDescription(final SessionDescription sdp) {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
peerConnection.setLocalDescription(sdpObserver, sdp);
}
});
}
public void setRemoteDescription(final SessionDescription sdp) {
executor.execute(new Runnable() {
#Override
public void run() {
if (peerConnection == null) {
return;
}
peerConnection.setRemoteDescription(sdpObserver, sdp);
}
});
}
#Override
public void setFactory(PeerConnectionFactory factory) {
this.factory = factory;
}
public void onWebSocketMessage(final String msg) {
try {
Log.e("onWebSocketMessage", msg);
JSONObject json = new JSONObject(msg);
json = new JSONObject(msg);
String type = json.optString("type");
if (type.equals("candidate")) {
IceCandidate candidate = new IceCandidate(json.getString("id"), json.getInt("label"), json.getString("candidate"));
addRemoteIceCandidate(candidate);
} else if (type.equals("answer")) {
isInitiator = false;
SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
setRemoteDescription(sdp);
} else if (type.equals("offer")) {
SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
setRemoteDescription(sdp);
} else if (type.equals("bye")) {
} else {
}
} catch (JSONException e) {
}
}
#Override
public void setMessage(String message) {
if (message.toString().contains("got user media") || message.toString().contains("bye")) {
} else
onWebSocketMessage(message);
}
private class DcObserver implements DataChannel.Observer {
#Override
public void onMessage(DataChannel.Buffer buffer) {
ByteBuffer data = buffer.data;
byte[] bytes = new byte[data.remaining()];
data.get(bytes);
String command = new String(bytes);
Log.e("onMessage ", command);
}
#Override
public void onStateChange() {
Log.e("onStateChange ", "onStateChange");
}
#Override
public void onBufferedAmountChange(long arg0) {
Log.e("onMessage ", "" + arg0);
}
}
#Override
public void sendMessage(String msg) {
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
boolean sent = dataChannel.send(new DataChannel.Buffer(buffer, false));
if (sent) {
Log.e("Message sent", "" + sent);
}
}
}
I am using the following open-source webrtc android application:
https://github.com/pchab/AndroidRTC
I have just modified this application to use my socket.io server instead of using the following one which is given by same author:
https://github.com/pchab/ProjectRTC
To do this, I needed to do some changes in the two classes of the above AndroidRTC Application. After this, when I started the application it did not call the 'createOffer()' or 'createAnswer()' function which is part of libjingle_peerconnection library. I am confused whether these two functions are not getting called or they are not able to use 'sendMessage()' function.
From debugging, I came to know that line which calls 'createAnswer()' function is successfully reached. After this, I expect the 'createAnswer()' function to use my 'sendMessage()' function to send the answer back to other party by using my socket.io server. I am not able to peek inside this 'createAnswer()' function as it is part of the library.
Before changing the above application to use my own server, I had tested it with the server given by auhtor. It ran successfully. I don't know what is wrong when I use my own server to make calls and do handshaking. I just modified few lines to support the way I do signalling on the server.
My server code is already used for webrtc web application. Web Applications are successful in making calls using this server. It should work for this android application too with little modification on the application.
I modified the following two classes in android application:
RTCActivity.java
package fr.pchab.AndroidRTC;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Window;
import android.widget.Toast;
import org.json.JSONException;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.VideoRenderer;
import java.util.List;
public class RTCActivity extends Activity implements WebRtcClient.RTCListener{
private final static int VIDEO_CALL_SENT = 666;
private VideoStreamsView vsv;
private WebRtcClient client;
private String mSocketAddress;
private String callerId;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
mSocketAddress = "https://" + getResources().getString(R.string.host);
mSocketAddress += (":"+getResources().getString(R.string.port)+"/");
PeerConnectionFactory.initializeAndroidGlobals(this);
// Camera display view
Point displaySize = new Point();
getWindowManager().getDefaultDisplay().getSize(displaySize);
vsv = new VideoStreamsView(this, displaySize);
client = new WebRtcClient(this, mSocketAddress);
final Intent intent = getIntent();
final String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
final List<String> segments = intent.getData().getPathSegments();
callerId = segments.get(0);
}
}
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
#Override
public void onPause() {
super.onPause();
vsv.onPause();
}
#Override
public void onResume() {
super.onResume();
vsv.onResume();
}
#Override
public void onCallReady(String callId) {
startCam();
}
public void answer(String callerId) throws JSONException {
client.sendMessage(callerId, "init", null);
startCam();
}
public void call(String callId) {
Intent msg = new Intent(Intent.ACTION_SEND);
msg.putExtra(Intent.EXTRA_TEXT, mSocketAddress + callId);
msg.setType("text/plain");
startActivityForResult(Intent.createChooser(msg, "Call someone :"), VIDEO_CALL_SENT);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == VIDEO_CALL_SENT) {
startCam();
}
}
public void startCam() {
setContentView(vsv);
// Camera settings
client.setCamera("front", "640", "480");
client.start("android_test", true);
}
#Override
public void onStatusChanged(final String newStatus) {
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), newStatus, Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onLocalStream(MediaStream localStream) {
localStream.videoTracks.get(0).addRenderer(new VideoRenderer(new VideoCallbacks(vsv, 0)));
}
#Override
public void onAddRemoteStream(MediaStream remoteStream, int endPoint) {
remoteStream.videoTracks.get(0).addRenderer(new VideoRenderer(new VideoCallbacks(vsv, endPoint)));
vsv.shouldDraw[endPoint] = true;
}
#Override
public void onRemoveRemoteStream(MediaStream remoteStream, int endPoint) {
remoteStream.videoTracks.get(0).dispose();
vsv.shouldDraw[endPoint] = false;
}
// Implementation detail: bridge the VideoRenderer.Callbacks interface to the
// VideoStreamsView implementation.
private class VideoCallbacks implements VideoRenderer.Callbacks {
private final VideoStreamsView view;
private final int stream;
public VideoCallbacks(VideoStreamsView view, int stream) {
this.view = view;
this.stream = stream;
}
#Override
public void setSize(final int width, final int height) {
view.queueEvent(new Runnable() {
public void run() {
view.setSize(stream, width, height);
}
});
}
#Override
public void renderFrame(VideoRenderer.I420Frame frame) {
view.queueFrame(stream, frame);
}
}
}
WebRTCClient.java
package fr.pchab.AndroidRTC;
import java.util.HashMap;
import java.util.LinkedList;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoSource;
import android.os.Handler;
import android.util.Log;
import com.koushikdutta.async.http.socketio.Acknowledge;
import com.koushikdutta.async.http.socketio.ConnectCallback;
import com.koushikdutta.async.http.socketio.EventCallback;
import com.koushikdutta.async.http.socketio.SocketIOClient;
class WebRtcClient {
private final static int MAX_PEER = 2;
private boolean[] endPoints = new boolean[MAX_PEER];
private PeerConnectionFactory factory;
private HashMap<String, Peer> peers = new HashMap<String, Peer>();
private LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<PeerConnection.IceServer>();
private MediaConstraints pcConstraints = new MediaConstraints();
private MediaStream lMS;
private RTCListener mListener;
private SocketIOClient client;
private final MessageHandler messageHandler = new MessageHandler();
private final static String TAG = WebRtcClient.class.getCanonicalName();
public interface RTCListener{
void onCallReady(String callId);
void onStatusChanged(String newStatus);
void onLocalStream(MediaStream localStream);
void onAddRemoteStream(MediaStream remoteStream, int endPoint);
void onRemoveRemoteStream(MediaStream remoteStream, int endPoint);
}
private interface Command{
void execute(String peerId, JSONObject payload) throws JSONException;
}
private class CreateOfferCommand implements Command{
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.d(TAG,"CreateOfferCommand");
Peer peer = peers.get(peerId);
peer.pc.createOffer(peer, pcConstraints);
}
}
private class CreateAnswerCommand implements Command{
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.d(TAG,"CreateAnswerCommand");
Peer peer = peers.get(peerId);
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(payload.getString("type")),
payload.getString("sdp")
);
peer.pc.setRemoteDescription(peer, sdp);
peer.pc.createAnswer(peer, pcConstraints);
}
}
private class SetRemoteSDPCommand implements Command{
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.d(TAG,"SetRemoteSDPCommand");
Peer peer = peers.get(peerId);
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(payload.getString("type")),
payload.getString("sdp")
);
peer.pc.setRemoteDescription(peer, sdp);
}
}
private class AddIceCandidateCommand implements Command{
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.d(TAG,"AddIceCandidateCommand");
PeerConnection pc = peers.get(peerId).pc;
if (pc.getRemoteDescription() != null) {
IceCandidate candidate = new IceCandidate(
payload.getString("id"),
payload.getInt("label"),
payload.getString("candidate")
);
pc.addIceCandidate(candidate);
}
}
}
public void sendMessage(String to, String type, JSONObject payload) throws JSONException {
JSONObject message = new JSONObject();
//message.put("room", to);
message.put("type", type);
message.put("msg", payload);
message.put("room", "sojharo");
client.emit("message", new JSONArray().put(message));
}
private class MessageHandler implements EventCallback {
private HashMap<String, Command> commandMap;
public MessageHandler() {
this.commandMap = new HashMap<String, Command>();
commandMap.put("init", new CreateOfferCommand());
commandMap.put("offer", new CreateAnswerCommand());
commandMap.put("answer", new SetRemoteSDPCommand());
commandMap.put("candidate", new AddIceCandidateCommand());
}
#Override
public void onEvent(String s, JSONArray jsonArray, Acknowledge acknowledge) {
try {
Log.d(TAG,"MessageHandler.onEvent() "+ (s == null ? "nil" : s));
if(s.equals("id")) {
JSONObject message = new JSONObject();
message.put("room", "sojharo");
message.put("username", "android");
client.emit("create or join livehelp",
new JSONArray().put(message));
} else if (s.equals("joined")) {
mListener.onCallReady("Not Initiator");
} else {
JSONObject json = jsonArray.getJSONObject(0);
try{
if(json.getString("msg").equals("got user media"))
return ;
}catch(JSONException e){}
String from = json.getString("from");
String type = null;
try{
type = json.getString("type");
}catch(JSONException e){}
// if peer is unknown, try to add him
if(!peers.containsKey(from)) {
// if MAX_PEER is reach, ignore the call
int endPoint = findEndPoint();
if(endPoint != MAX_PEER) {
addPeer(from, endPoint);
commandMap.get(type).execute(from, json);
}
} else {
commandMap.get(type).execute(from, json);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
private class Peer implements SdpObserver, PeerConnection.Observer{
private PeerConnection pc;
private String id;
private int endPoint;
#Override
public void onCreateSuccess(final SessionDescription sdp) {
try {
JSONObject payload = new JSONObject();
payload.put("type", sdp.type.canonicalForm());
payload.put("sdp", sdp.description);
sendMessage(id, sdp.type.canonicalForm(), payload);
pc.setLocalDescription(Peer.this, sdp);
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onSetSuccess() {}
#Override
public void onCreateFailure(String s) {}
#Override
public void onSetFailure(String s) {}
#Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {}
#Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
if(iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
removePeer(id);
mListener.onStatusChanged("DISCONNECTED");
}
}
#Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {}
#Override
public void onIceCandidate(final IceCandidate candidate) {
try {
JSONObject payload = new JSONObject();
payload.put("label", candidate.sdpMLineIndex);
payload.put("id", candidate.sdpMid);
payload.put("candidate", candidate.sdp);
sendMessage(id, "candidate", payload);
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onError() {}
#Override
public void onAddStream(MediaStream mediaStream) {
Log.d(TAG,"onAddStream "+mediaStream.label());
// remote streams are displayed from 1 to MAX_PEER (0 is localStream)
mListener.onAddRemoteStream(mediaStream, endPoint+1);
}
#Override
public void onRemoveStream(MediaStream mediaStream) {
mListener.onRemoveRemoteStream(mediaStream, endPoint);
removePeer(id);
}
#Override
public void onDataChannel(DataChannel dataChannel) {}
public Peer(String id, int endPoint) {
Log.d(TAG,"new Peer: "+id + " " + endPoint);
this.pc = factory.createPeerConnection(iceServers, pcConstraints, this);
this.id = id;
this.endPoint = endPoint;
pc.addStream(lMS, new MediaConstraints());
mListener.onStatusChanged("CONNECTING");
}
}
public WebRtcClient(RTCListener listener, String host) {
mListener = listener;
factory = new PeerConnectionFactory();
SocketIOClient.connect(host, new ConnectCallback() {
#Override
public void onConnectCompleted(Exception ex, SocketIOClient socket) {
if (ex != null) {
Log.e(TAG,"WebRtcClient connect failed: "+ex.getMessage());
return;
}
Log.d(TAG,"WebRtcClient connected.");
client = socket;
// specify which events you are interested in receiving
client.addListener("id", messageHandler);
client.addListener("message", messageHandler);
client.addListener("joined", messageHandler);
}
}, new Handler());
iceServers.add(new PeerConnection.IceServer("stun:23.21.150.121"));
iceServers.add(new PeerConnection.IceServer("stun:stun.l.google.com:19302"));
pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
}
public void setCamera(String cameraFacing, String height, String width){
MediaConstraints videoConstraints = new MediaConstraints();
videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxHeight", height));
videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxWidth", width));
VideoSource videoSource = factory.createVideoSource(getVideoCapturer(cameraFacing), videoConstraints);
lMS = factory.createLocalMediaStream("ARDAMS");
lMS.addTrack(factory.createVideoTrack("ARDAMSv0", videoSource));
lMS.addTrack(factory.createAudioTrack("ARDAMSa0"));
mListener.onLocalStream(lMS);
}
private int findEndPoint() {
for(int i = 0; i < MAX_PEER; i++) {
if(!endPoints[i]) return i;
}
return MAX_PEER;
}
public void start(String name, boolean privacy){
try {
JSONObject message = new JSONObject();
message.put("msg", new JSONObject().put("msg", "got user media"));
message.put("room", "sojharo");
client.emit("message", new JSONArray().put(message));
} catch (JSONException e) {
e.printStackTrace();
}
}
/*
Cycle through likely device names for the camera and return the first
capturer that works, or crash if none do.
*/
private VideoCapturer getVideoCapturer(String cameraFacing) {
int[] cameraIndex = { 0, 1 };
int[] cameraOrientation = { 0, 90, 180, 270 };
for (int index : cameraIndex) {
for (int orientation : cameraOrientation) {
String name = "Camera " + index + ", Facing " + cameraFacing +
", Orientation " + orientation;
VideoCapturer capturer = VideoCapturer.create(name);
if (capturer != null) {
return capturer;
}
}
}
throw new RuntimeException("Failed to open capturer");
}
private void addPeer(String id, int endPoint) {
Peer peer = new Peer(id, endPoint);
peers.put(id, peer);
endPoints[endPoint] = true;
}
private void removePeer(String id) {
Peer peer = peers.get(id);
peer.pc.close();
peer.pc.dispose();
peers.remove(peer.id);
endPoints[peer.endPoint] = false;
}
}
The code is able to receive the offer and candidates from other party. It is not able to send the answer or candidates to that party in return.
I have not modified other two classes which can be found on the above link for android application.
Here is snippet of my socket.io server code written in nodejs:
socket.on('create or join livehelp', function (room) {
var numClients = socketio.sockets.clients(room.room).length;
if (numClients === 0){
socket.join(room.room);
socket.set('nickname', room.username);
socket.emit('created', room);
} else if (numClients < 2) {
socket.join(room.room);
socket.set('nickname', room.username);
socket.emit('joined', room);
socket.broadcast.to(room.room).emit('join', room);
} else { // max three clients
socket.emit('full', room.room);
}
console.log(socketio.sockets.manager.rooms)
console.log(room)
});
socket.on('message', function (message) {
//console.log('Got message:', message);
//socket.broadcast.emit('message', message);
message.msg.from = socket.id;
//socketio.sockets.in(message.room).emit('message', message.msg);
socket.broadcast.to(message.room).emit('message', message.msg);
//console.log('Got message:', message.msg);
//console.log(socketio.sockets.manager.rooms)
});
I am confused if there is any error why I am not able to find it in debugging. Log for this is very difficult to read as it runs very fast and I am not able to catch each and every line. But apparently, it looked fine at a glance.
Please help. Thanks.
I think you are not able to generate answer but you are able to generate offer?. If this is the case try adding
pcConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
to your pc constraints.
Hope this will help..
client.on('message', function (details) {
console.log('message',details.to);
console.log(details.type);
if(details.type !== 'init'){
var otherClient = io.sockets.connected[details.to];
if (!otherClient) {
return;
}
delete details.to;
details.from = client.id;
otherClient.emit('message', details);
}
else
{
if (io.sockets.adapter.rooms[client.room] !== undefined ) {
for(var member in io.sockets.adapter.rooms[client.room]){
console.log(member);
if(member !== client.id){
var otherClient = io.sockets.connected[member];
if (!otherClient) {
return;
}
delete details.to;
details.from = client.id;
otherClient.emit('message', details);
}
else{
console.log("no need to send self again!");
}
}
} else {
client.emit("update", "Please connect to a room.");
}
}
});
Please download latest libjingle from here
http://repo.spring.io/libs-release-remote/io/pristine/libjingle/