Recently I have been creating a new Android app to make/receive VoIP calls using Linphone lib. The first version of the app worked almost ok, I updated the lib and decided to do some code arrange, and for some reason, the app does not longer receive calls.
All the VoIP functionality is inside an android service:
package com.test.voice.linphone;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;
import com.test.voice.voice.R;
import org.linphone.core.AVPFMode;
import org.linphone.core.Address;
import org.linphone.core.AuthInfo;
import org.linphone.core.AuthMethod;
import org.linphone.core.Call;
import org.linphone.core.CallLog;
import org.linphone.core.CallStats;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatRoom;
import org.linphone.core.ConfiguringState;
import org.linphone.core.Content;
import org.linphone.core.Core;
import org.linphone.core.CoreException;
import org.linphone.core.CoreListener;
import org.linphone.core.EcCalibratorStatus;
import org.linphone.core.Event;
import org.linphone.core.Factory;
import org.linphone.core.Friend;
import org.linphone.core.FriendList;
import org.linphone.core.GlobalState;
import org.linphone.core.InfoMessage;
import org.linphone.core.PayloadType;
import org.linphone.core.PresenceModel;
import org.linphone.core.ProxyConfig;
import org.linphone.core.PublishState;
import org.linphone.core.Reason;
import org.linphone.core.RegistrationState;
import org.linphone.core.SubscriptionState;
import org.linphone.core.Transports;
import org.linphone.core.VersionUpdateCheckResult;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;
public class LinphoneService extends Service {
// *************************************** CONSTANTS *************************************** //
private static final String TAG = "linphone_service";
private static final String WAKE_LOG_TAG = ":voicewakelock";
private static final String LIN_TAG = "libcore";
private static final String USER_AGENT = "VoiceAgent";
private static final long TIME_ITERATE = 200;
// ****************************************** VARS ***************************************** //
private final IBinder mBinder = new LocalBinder();
private PowerManager.WakeLock mWakeLock;
private Core mCore;
private String mLpConfig = null;
private String mConfigFactoryFile = null;
public String mLinphoneConfigFile = null;
private String mRootCaFile = null;
private String mRingSoundFile = null;
private String mRingBackSoundFile = null;
private String mPauseSoundFile = null;
private AuthInfo mAuthInfo;
private ProxyConfig mProxyConfig;
private Timer mIterateTimer;
private LinphoneCallback mCallback;
private RegistrationState mRegistrationState = RegistrationState.None;
// ************************************* INNER CLASSES ************************************* //
public class LocalBinder extends Binder {
public LinphoneService getService() {
return LinphoneService.this;
}
}
// *************************************** LIFECYCLE *************************************** //
#Override
public void onCreate() {
super.onCreate();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOG_TAG);
mWakeLock.acquire();
String basePath = getFilesDir().getAbsolutePath();
mLpConfig = basePath + "/lpconfig.xsd";
mConfigFactoryFile = basePath + "/linphonerc";
mLinphoneConfigFile = basePath + "/.linphonerc";
mRootCaFile = basePath + "/rootca.pem";
mRingSoundFile = basePath + "/oldphone_mono.wav";
mRingBackSoundFile = basePath + "/ringback.wav";
mPauseSoundFile = basePath + "/toy_mono.wav";
}
#Override
public void onDestroy() {
super.onDestroy();
mWakeLock.release();
cancelIterateTimer();
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// ************************************* PUBLIC METHODS ************************************ //
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
/**
* Set a {#link LinphoneCallback} to receive event callbacks.
*
* #param callback {#link LinphoneCallback} to set.
*/
public void setCallback(LinphoneCallback callback) {
mCallback = callback;
}
/**
* Initiate Linphone library, set configuration.
*
* #throws Exception
*/
public void initLibrary() throws Exception {
copyAssetsFromPackage();
Factory.instance().setDebugMode(true, LIN_TAG);
mCore = Factory.instance().createCore(
mLinphoneConfigFile, mConfigFactoryFile, this);
mCore.addListener(getCoreListener());
mCore.enableIpv6(false);
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
mCore.setUserAgent(USER_AGENT, packageInfo.versionName);
mCore.setRemoteRingbackTone(mRingSoundFile);
mCore.setRing(mRingSoundFile);
mCore.setRootCa(mRootCaFile);
mCore.setPlayFile(mPauseSoundFile);
mCore.setNetworkReachable(true);
mCore.enableEchoCancellation(true);
mCore.enableEchoLimiter(true);
mCore.enableAdaptiveRateControl(true);
mCore.clearAllAuthInfo();
mCore.clearProxyConfig();
enablePayloads();
startIterateTimer();
}
/**
* Login agains the VoIP server.
*
* #param name Username.
* #param password Password.
* #param host Server.
* #param tcpPort Specify the TCP port to use, only TCP available.
* #throws CoreException
*/
public void login(String name, String password, String host, int tcpPort)
throws CoreException {
String identity = "sip:" + name + "#" + host;
String proxy = "sip:" + host;
Address proxyAddress = Factory.instance().createAddress(proxy);
Address identityAddress = Factory.instance().createAddress(identity);
if (proxyAddress == null || identityAddress == null) {
throw new CoreException("Proxy or Identity address is null.");
}
mProxyConfig = mCore.createProxyConfig();
mProxyConfig.setIdentityAddress(identityAddress);
mProxyConfig.setServerAddr(proxyAddress.asStringUriOnly());
mProxyConfig.setAvpfMode(AVPFMode.Disabled);
mProxyConfig.setAvpfRrInterval(0);
mProxyConfig.enableQualityReporting(false);
mProxyConfig.setQualityReportingCollector(null);
mProxyConfig.setQualityReportingInterval(0);
// mProxyConfig.setRoute(proxyAddress.asStringUriOnly());
mProxyConfig.enableRegister(true);
mAuthInfo = Factory.instance().createAuthInfo(
name, null, password, null, null, host);
Transports transports = mCore.getTransports();
transports.setUdpPort(-1);
transports.setTlsPort(-1);
transports.setTcpPort(tcpPort);
mCore.setTransports(transports);
mCore.addProxyConfig(mProxyConfig);
mCore.addAuthInfo(mAuthInfo);
mCore.setDefaultProxyConfig(mProxyConfig);
}
/**
* Disconnect from remote VoIP server.
*/
public void logout() {
if (mProxyConfig != null) {
mProxyConfig.edit();
mProxyConfig.enableRegister(false);
mProxyConfig.done();
}
}
/**
* Accept incoming call.
*/
public void acceptCall() {
mCore.acceptCall(mCore.getCurrentCall());
}
/**
* Decline current call.
*/
public void declineCall() {
mCore.declineCall(mCore.getCurrentCall(), Reason.Declined);
}
/**
* Hang up the current call.
*/
public void hangUp() {
Call currentCall = mCore.getCurrentCall();
if (currentCall != null) {
mCore.terminateCall(currentCall);
} else if (mCore.isInConference()) {
mCore.terminateConference();
} else {
mCore.terminateAllCalls();
}
}
// ************************************ PRIVATE METHODS ************************************ //
/**
* Copy resource files.
*
* #throws IOException If an I/O error occurrs.
*/
private void copyAssetsFromPackage() throws IOException {
copyIfNotExist(this, R.raw.oldphone_mono, mRingSoundFile);
copyIfNotExist(this, R.raw.ringback, mRingBackSoundFile);
copyIfNotExist(this, R.raw.toy_mono, mPauseSoundFile);
copyIfNotExist(this, R.raw.linphonerc_default, mLinphoneConfigFile);
copyIfNotExist(this, R.raw.linphonerc_factory, (
new File(this.mConfigFactoryFile)).getName());
copyIfNotExist(this, R.raw.lpconfig, mLpConfig);
copyIfNotExist(this, R.raw.rootca, mRootCaFile);
}
private void copyIfNotExist(Context context, int resourceId, String target) throws IOException {
File fileToCopy = new File(target);
if (!fileToCopy.exists()) {
copyFromPackage(context, resourceId, fileToCopy.getName());
}
}
private void copyFromPackage(Context context, int resourceId, String target) throws
IOException {
FileOutputStream outputStream = context.openFileOutput(target, 0);
InputStream inputStream = context.getResources().openRawResource(resourceId);
byte[] buff = new byte[8048];
int readByte;
while ((readByte = inputStream.read(buff)) != -1) {
outputStream.write(buff, 0, readByte);
}
outputStream.flush();
outputStream.close();
inputStream.close();
}
/**
* Get the {#link CoreListener}.
*
* #return Instance of {#link CoreListener}.
*/
private CoreListener getCoreListener() {
return new CoreListener() {
#Override
public void onGlobalStateChanged(Core core, GlobalState globalState, String s) {
Log.d(TAG, "Core listener - Global State Changed: " + s);
mCallback.onGlobalStateChanged(globalState);
}
#Override
public void onRegistrationStateChanged(Core core, ProxyConfig proxyConfig,
RegistrationState registrationState,
String state) {
Log.d(TAG, "Core listener - On Registration State Changed: " +
state);
if (registrationState != mRegistrationState) {
mCallback.onRegistrationStateChanged(registrationState);
}
mRegistrationState = registrationState;
}
#Override
public void onCallStateChanged(Core core, Call call, Call.State state, String s) {
Log.d(TAG, "Core listener - On Call State Changed");
mCallback.onCallStateChanged(call, state);
}
#Override
public void onNotifyPresenceReceived(Core core, Friend friend) {
Log.d(TAG, "Core listener - On Notify Presence Received");
}
#Override
public void onNotifyPresenceReceivedForUriOrTel(Core core, Friend friend, String s,
PresenceModel presenceModel) {
Log.d(TAG, "Core listener - On Notify Presence Received For Uri Or Tel");
}
#Override
public void onNewSubscriptionRequested(Core core, Friend friend, String s) {
Log.d(TAG, "Core listener - On New Subscription Requested");
}
#Override
public void onAuthenticationRequested(Core core, AuthInfo authInfo, AuthMethod
authMethod) {
Log.d(TAG, "Core listener - On Authentication Requested");
}
#Override
public void onCallLogUpdated(Core core, CallLog callLog) {
Log.d(TAG, "Core listener - On Call Log Updated");
}
#Override
public void onMessageReceived(Core core, ChatRoom chatRoom, ChatMessage chatMessage) {
Log.d(TAG, "Core listener - On Message Received");
}
#Override
public void onMessageReceivedUnableDecrypt(Core core, ChatRoom chatRoom, ChatMessage
chatMessage) {
Log.d(TAG, "Core listener - On Message Received Unable Decrypt");
}
#Override
public void onIsComposingReceived(Core core, ChatRoom chatRoom) {
Log.d(TAG, "Core listener - On Is Composing Received");
}
#Override
public void onDtmfReceived(Core core, Call call, int i) {
Log.d(TAG, "Core listener - On Dtmf Received");
}
#Override
public void onReferReceived(Core core, String s) {
Log.d(TAG, "Core listener - On Refer Received");
}
#Override
public void onCallEncryptionChanged(Core core, Call call, boolean b, String s) {
Log.d(TAG, "Core listener - On Call Encrypted Changed");
}
#Override
public void onTransferStateChanged(Core core, Call call, Call.State state) {
Log.d(TAG, "Core listener - On Transfer State Changed");
}
#Override
public void onBuddyInfoUpdated(Core core, Friend friend) {
Log.d(TAG, "Core listener - On Buddy Info Updated");
}
#Override
public void onCallStatsUpdated(Core core, Call call, CallStats callStats) {
Log.d(TAG, "Core listener - On Call Stats Updated");
}
#Override
public void onInfoReceived(Core core, Call call, InfoMessage infoMessage) {
Log.d(TAG, "Core listener - On Info Received");
}
#Override
public void onSubscriptionStateChanged(Core core, Event event, SubscriptionState
subscriptionState) {
Log.d(TAG, "Core listener - On Subscription State Changed");
}
#Override
public void onNotifyReceived(Core core, Event event, String s, Content content) {
Log.d(TAG, "Core listener - On Notify Received");
}
#Override
public void onSubscribeReceived(Core core, Event event, String s, Content content) {
Log.d(TAG, "Core listener - On Subscribe Received");
}
#Override
public void onPublishStateChanged(Core core, Event event, PublishState publishState) {
Log.d(TAG, "Core listener - On Publish State Changed");
}
#Override
public void onConfiguringStatus(Core core, ConfiguringState configuringState, String
s) {
Log.d(TAG, "Core listener - On Configuring Status");
}
#Override
public void onNetworkReachable(Core core, boolean b) {
Log.d(TAG, "Core listener - On Network Reachable");
}
#Override
public void onLogCollectionUploadStateChanged(Core core, Core
.LogCollectionUploadState logCollectionUploadState, String s) {
Log.d(TAG, "Core listener - On Log Collection Upload StateChanged");
}
#Override
public void onLogCollectionUploadProgressIndication(Core core, int i, int i1) {
Log.d(TAG, "Core listener - On Log Collection Upload ProgressIndication");
}
#Override
public void onFriendListCreated(Core core, FriendList friendList) {
Log.d(TAG, "Core listener - On Friend List Created");
}
#Override
public void onFriendListRemoved(Core core, FriendList friendList) {
Log.d(TAG, "Core listener - On Friend List Removed");
}
#Override
public void onCallCreated(Core core, Call call) {
Log.d(TAG, "Core listener - On Call Created");
}
#Override
public void onVersionUpdateCheckResultReceived(Core core, VersionUpdateCheckResult
versionUpdateCheckResult, String s, String s1) {
Log.d(TAG, "Core listener - On Version Update Check ResultReceived");
}
#Override
public void onChatRoomStateChanged(Core core, ChatRoom chatRoom, ChatRoom.State state) {
Log.d(TAG, "Core listener - On Chat Room State Changed");
}
#Override
public void onQrcodeFound(Core core, String s) {
Log.d(TAG, "Core listener - On QR Code Found");
}
#Override
public void onEcCalibrationResult(Core core, EcCalibratorStatus ecCalibratorStatus,
int i) {
Log.d(TAG, "Core listener - On EC Calibration Result");
}
#Override
public void onEcCalibrationAudioInit(Core core) {
Log.d(TAG, "Core listener - On EC Calibration Audio Init");
}
#Override
public void onEcCalibrationAudioUninit(Core core) {
Log.d(TAG, "Core listener - On EC Calibration Audio Uninit");
}
};
}
private void startIterateTimer() {
cancelIterateTimer();
mIterateTimer = new Timer();
mIterateTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
if (mCore != null) mCore.iterate();
}
}, 0, TIME_ITERATE);
}
/**
* Remove iterate timer.
*/
private void cancelIterateTimer() {
if (mIterateTimer != null) mIterateTimer.cancel();
mIterateTimer = null;
}
private void enablePayloads() {
PayloadType[] audioPayloads = mCore.getAudioPayloadTypes();
for (int i = 0; i < audioPayloads.length; ++i) {
PayloadType payloadType = audioPayloads[i];
payloadType.enable(true);
}
mCore.setAudioPayloadTypes(audioPayloads);
}
}
All the configurations and audio files are ok since they were working in the first implementation, but I guess I am doing something wrong on this class.
Any help?
Finally I solved the issue, I was missing Core.start():
/**
* Initiate Linphone library, set configuration.
*
* #throws Exception
*/
public void initLibrary() throws Exception {
copyAssetsFromPackage();
Factory.instance().setDebugMode(false, LIN_TAG);
mCore = Factory.instance().createCore(
mLinphoneConfigFile, mConfigFactoryFile, this);
mCore.addListener(getCoreListener());
mCore.start();
mCore.enableIpv6(false);
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
mCore.setUserAgent(USER_AGENT, packageInfo.versionName);
mCore.setRemoteRingbackTone(mRingSoundFile);
mCore.setRing(mRingSoundFile);
mCore.setRootCa(mRootCaFile);
mCore.setPlayFile(mPauseSoundFile);
mCore.setNetworkReachable(true);
mCore.enableEchoCancellation(true);
mCore.enableEchoLimiter(true);
mCore.enableAdaptiveRateControl(true);
mCore.clearAllAuthInfo();
mCore.clearProxyConfig();
enablePayloads();
startIterateTimer();
}
Related
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
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
I have a AndroidService to listen MQTT messages. Below is the code. For some reason the service is able to connect and subscribe to channel, but is unable is read messages. messageArrived is never called.
public class FollowService extends Service implements MqttCallback{
private final IBinder localBinder = new FollowServiceBinder();
private final String TAG = "Service";
private MqttClient mqClient;
public class FollowServiceBinder extends Binder {
public FollowService getService() {
return FollowService.this;
}
}
public FollowService() {
}
#Override
public IBinder onBind(Intent intent) {
return localBinder;
}
#Override
public void onCreate() {
super.onCreate();
try {
mqClient = new MqttClient("tcp://192.168.1.46:1883", "sadfsfi", new MemoryPersistence());
mqClient.connect();
Log.i(TAG, "Connected to client");
}
catch(MqttException me){
Log.e(TAG, "MqttClient Exception Occured in on create!!!", me);
}
}
#Keep
public void beginFollowing(){
try {
mqClient.subscribe("test");
Log.i(TAG, "Subscribed test");
}
catch (MqttException me){
Log.e(TAG, "MqttClient Exception Occured in following!!!", me);
}
}
#Override
public void connectionLost(Throwable cause) {
Log.i(TAG, "ConnectionLost");
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
Log.i(TAG, "Delivered");
}
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
Log.i(TAG, "Received update: " + topic + ":" + message.toString());
}
}
There is Eclipse Paho Android Service which is dedicated to Android you can use instead of the regular MqttClient, it may solves your problem (if your are sure the problem is not on your MQTT server side) & some other problems you may have in the future if you want to settle an Android MQTT service :
If you want to give it a try :
in build.gradle :
compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.0.3-SNAPSHOT'
compile ('org.eclipse.paho:org.eclipse.paho.android.service:1.0.3-SNAPSHOT'){
exclude module: 'support-v4'
}
compile 'com.android.support:support-v4:22.1.0'
In AndroidManifest.xml :
<uses-permission android:name="android.permission.INTERNET" />
and in your <application></application> :
<service android:name="org.eclipse.paho.android.service.MqttService" />
Here is an example MqttHandler.java :
public class MqttHandler {
protected final static String TAG = DeviceHandler.class.getSimpleName();
/**
* MQTT client
*/
private MqttAndroidClient mClient = null;
/**
* client ID used to authenticate
*/
protected String mClientId = "";
/**
* Android context
*/
private Context mContext = null;
/**
* callback for MQTT events
*/
private MqttCallback mClientCb = null;
/**
* callback for MQTT connection
*/
private IMqttActionListener mConnectionCb = null;
/**
* Sets whether the client and server should remember state across restarts and reconnects
*/
protected boolean mCleanSessionDefault = false;
/**
* Sets the connection timeout value (in seconds)
*/
protected int mTimeoutDefault = 30;
/**
* Sets the "keep alive" interval (in seconds)
*/
protected int mKeepAliveDefault = 60;
/**
* connection state
*/
private boolean connected = false;
/**
* list of message callbacks
*/
private List<IMessageCallback> mMessageCallbacksList = new ArrayList<>();
private final static String SERVER_URI = "192.168.1.46";
private final static int SERVER_PORT = 1883;
public MqttHandler(Context context) {
this.mContext = context;
this.mClientCb = new MqttCallback() {
#Override
public void connectionLost(Throwable cause) {
connected = false;
for (int i = 0; i < mMessageCallbacksList.size(); i++) {
mMessageCallbacksList.get(i).connectionLost(cause);
}
}
#Override
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
for (int i = 0; i < mMessageCallbacksList.size(); i++) {
mMessageCallbacksList.get(i).messageArrived(topic, mqttMessage);
}
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
for (int i = 0; i < mMessageCallbacksList.size(); i++) {
mMessageCallbacksList.get(i).deliveryComplete(token);
}
}
};
}
public boolean isConnected() {
if (mClient == null)
return false;
else
return connected;
}
public void connect() {
try {
if (!isConnected()) {
MqttConnectOptions options = new MqttConnectOptions();
String serverURI = "";
options.setCleanSession(mCleanSessionDefault);
options.setConnectionTimeout(mTimeoutDefault);
options.setKeepAliveInterval(mKeepAliveDefault);
mClient = new MqttAndroidClient(mContext, "tcp://" + SERVER_URI + ":" + SERVER_PORT, mClientId);
mClient.setCallback(mClientCb);
mConnectionCb = new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken iMqttToken) {
connected = true;
for (int i = 0; i < mMessageCallbacksList.size(); i++) {
mMessageCallbacksList.get(i).onConnectionSuccess(iMqttToken);
}
}
#Override
public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
connected = false;
for (int i = 0; i < mMessageCallbacksList.size(); i++) {
mMessageCallbacksList.get(i).onConnectionFailure(iMqttToken, throwable);
}
}
};
try {
mClient.connect(options, mContext, mConnectionCb);
} catch (MqttException e) {
e.printStackTrace();
}
} else {
Log.v(TAG, "cant connect - already connected");
}
} catch (IllegalArgumentException e) {
Log.v(TAG, "parameters error. cant connect");
}
}
public void disconnect() {
if (isConnected()) {
try {
mClient.disconnect(mContext, mConnectionCb);
} catch (MqttException e) {
e.printStackTrace();
}
} else {
Log.v(TAG, "cant disconnect - already disconnected");
}
}
/**
* Publish a message to MQTT server
*
* #param topic message topic
* #param message message body
* #param isRetained define if message should be retained on MQTT server
* #param listener completion listener (null allowed)
* #return
*/
public IMqttDeliveryToken publishMessage(String topic, String message, boolean isRetained, IMqttActionListener listener) {
if (isConnected()) {
MqttMessage mqttMessage = new MqttMessage(message.getBytes());
mqttMessage.setRetained(isRetained);
mqttMessage.setQos(0);
try {
return mClient.publish(topic, mqttMessage, mContext, listener);
} catch (MqttPersistenceException e) {
e.printStackTrace();
} catch (MqttException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, "cant publish message. Not connected");
}
return null;
}
/**
* Subscribe to topic
*
* #param topic topic to subscribe
* #param listener completion listener (null allowed)
* #return
*/
public void subscribe(String topic, IMqttActionListener listener) {
if (isConnected()) {
try {
mClient.subscribe(topic, 0, mContext, listener);
} catch (MqttException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, "cant publish message. Not connected");
}
}
/**
* Unsubscribe a topic
*
* #param topic topic to unsubscribe
* #param listener completion listener (null allowed)
*/
public void unsubscribe(String topic, IMqttActionListener listener) {
if (isConnected()) {
try {
mClient.unsubscribe(topic, mContext, listener);
} catch (MqttException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, "cant publish message. Not connected");
}
}
public void addCallback(IMessageCallback callback) {
mMessageCallbacksList.add(callback);
}
}
With this listener IMessageCallback.java :
public interface IMessageCallback {
/**
* This method is called when the connection to the server is lost.
*
* #param cause the reason behind the loss of connection.
*/
void connectionLost(Throwable cause);
/**
* This method is called when a message arrives from the server.
*
* #param topic name of the topic on the message was published to
* #param mqttMessage the actual message
* #throws Exception
*/
void messageArrived(String topic, MqttMessage mqttMessage) throws Exception;
/**
* Called when delivery for a message has been completed, and all acknowledgments have been received.
*
* #param messageToken he delivery token associated with the message.
*/
void deliveryComplete(IMqttDeliveryToken messageToken);
/**
* Called when connection is established
*
* #param iMqttToken token for this connection
*/
void onConnectionSuccess(IMqttToken iMqttToken);
/**
* Called when connection has failed
*
* #param iMqttToken token when failure occured
* #param throwable exception
*/
void onConnectionFailure(IMqttToken iMqttToken, Throwable throwable);
/**
* Called when disconnection is successfull
*
* #param iMqttToken token for this connection
*/
void onDisconnectionSuccess(IMqttToken iMqttToken);
/**
* Called when disconnection failed
*
* #param iMqttToken token when failure occured
* #param throwable exception
*/
void onDisconnectionFailure(IMqttToken iMqttToken, Throwable throwable);
}
You can call it like that :
final MqttHandler mqttHandler = new MqttHandler(mContext);
mqttHandler.addCallback(new IMessageCallback() {
#Override
public void connectionLost(Throwable cause) {
Log.v(TAG, "connectionLost");
}
#Override
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
Log.v(TAG, "messageArrived : " + topic + " : " + new String(mqttMessage.getPayload()));
}
#Override
public void deliveryComplete(IMqttDeliveryToken messageToken) {
try {
Log.v(TAG, "deliveryComplete : " + new String(messageToken.getMessage().getPayload()));
} catch (MqttException e) {
e.printStackTrace();
}
}
#Override
public void onConnectionSuccess(IMqttToken iMqttToken) {
Log.v(TAG, "connection success");
mqttHandler.subscribe("test", new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.v(TAG, "subscribe success");
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.e(TAG, "subscribe failure");
}
});
}
#Override
public void onConnectionFailure(IMqttToken iMqttToken, Throwable throwable) {
Log.v(TAG, "connection failure");
}
#Override
public void onDisconnectionSuccess(IMqttToken iMqttToken) {
Log.v(TAG, "disconnection success");
}
#Override
public void onDisconnectionFailure(IMqttToken iMqttToken, Throwable throwable) {
Log.v(TAG, "disconnection failure");
}
});
mqttHandler.connect();
You can find a complete working usecase with Paho Mqtt Android client here
From looking at okHttp source code, when call.execute() is called the body being transferred from server to the client.
It doesn't make sense because it makes impossible to set deadline to okio which means i cannot give timeout to the whole request but only readTimeout and connectTimeout which have effect only until the first byte is ready for read.
Am i missing something here?
There’s no way to give a deadline to the entire request. You should open a feature request on this! OkHttp’s use of Okio is one of it’s differentiating features, and exposing more Okio functionality through OkHttp’s API is a great way to put more power in OkHttp’s users.
This is on the schedule for the next version of okhttp (https://github.com/square/okhttp/issues/2840), but for now we successfully implemented a deadline for both the request and response body reading by subclassing Call in our application in production:
package com.pushd.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http2.StreamResetException;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
/**
* An okhttp3.Call with a deadline timeout from the start of isExecuted until ResponseBody.source() is closed or unused.
*/
public class DeadlineCall implements Call {
private final static Logger LOGGER = Logger.getLogger(DeadlineCall.class.getName());
private static AtomicInteger sFutures = new AtomicInteger();
private static final ScheduledExecutorService sHTTPCancelExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
#Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "DeadlineCallCancel");
t.setDaemon(true);
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
});
private final Call mUnderlying;
private final int mDeadlineTimeout;
private volatile ScheduledFuture mDeadline;
private volatile boolean mDeadlineHit;
private volatile boolean mCancelled;
private volatile BufferedSource mBodySource;
DeadlineCall(Call underlying, int deadlineTimeout) {
mUnderlying = underlying;
mDeadlineTimeout = deadlineTimeout;
}
/**
* Factory wrapper for OkHttpClient.newCall(request) to create a new DeadlineCall scheduled to cancel its underlying Call after the deadline.
* #param client
* #param request
* #param deadlineTimeout in ms
* #return Call
*/
public static DeadlineCall newDeadlineCall(#NonNull OkHttpClient client, #NonNull Request request, int deadlineTimeout) {
final Call underlying = client.newCall(request);
return new DeadlineCall(underlying, deadlineTimeout);
}
/**
* Shuts down thread that cancels calls when their deadline is hit.
*/
public static void shutdownNow() {
sHTTPCancelExecutorService.shutdownNow();
}
#Override
public Request request() {
return mUnderlying.request();
}
/**
* Response MUST be closed to clean up deadline even if body is not read, e.g. on !isSuccessful
* #return
* #throws IOException
*/
#Override
public Response execute() throws IOException {
startDeadline();
try {
return wrapResponse(mUnderlying.execute());
} catch (IOException e) {
cancelDeadline();
throw wrapIfDeadline(e);
}
}
/**
* Deadline is removed when onResponse returns unless response.body().source() or a method using
* it is called synchronously from onResponse to indicate caller's committment to close it themselves.
* This includes peekBody so prefer DeadlineResponseBody.peek unless you explicitly close after peekBody.
* #param responseCallback
*/
#Override
public void enqueue(final Callback responseCallback) {
startDeadline();
mUnderlying.enqueue(new Callback() {
#Override
public void onFailure(Call underlying, IOException e) {
cancelDeadline(); // there is no body to read so no need for deadline anymore
responseCallback.onFailure(DeadlineCall.this, wrapIfDeadline(e));
}
#Override
public void onResponse(Call underlying, Response response) throws IOException {
try {
responseCallback.onResponse(DeadlineCall.this, wrapResponse(response));
if (mBodySource == null) {
cancelDeadline(); // remove deadline if body was never opened
}
} catch (IOException e) {
cancelDeadline();
throw wrapIfDeadline(e);
}
}
});
}
private IOException wrapIfDeadline(IOException e) {
if (mDeadlineHit && isCancellationException(e)) {
return new DeadlineException(e);
}
return e;
}
public class DeadlineException extends IOException {
public DeadlineException(Throwable cause) {
super(cause);
}
}
/**
* Wraps response to cancelDeadline when response closed and throw correct DeadlineException when deadline happens during response reading.
* #param response
* #return
*/
private Response wrapResponse(final Response response) {
return response.newBuilder().body(new DeadlineResponseBody(response)).build();
}
public class DeadlineResponseBody extends ResponseBody {
private final Response mResponse;
DeadlineResponseBody(final Response response) {
mResponse = response;
}
#Override
public MediaType contentType() {
return mResponse.body().contentType();
}
#Override
public long contentLength() {
return mResponse.body().contentLength();
}
/**
* #return the body source indicating it will be closed later by the caller to cancel the deadline
*/
#Override
public BufferedSource source() {
if (mBodySource == null) {
mBodySource = Okio.buffer(new ForwardingSource(mResponse.body().source()) {
#Override
public long read(Buffer sink, long byteCount) throws IOException {
try {
return super.read(sink, byteCount);
} catch (IOException e) {
throw wrapIfDeadline(e);
}
}
#Override
public void close() throws IOException {
cancelDeadline();
super.close();
}
});
}
return mBodySource;
}
/**
* #return the body source without indicating it will be closed later by caller, e.g. to peekBody on unsucessful requests
*/
public BufferedSource peekSource() {
return mResponse.body().source();
}
/**
* Copy of https://square.github.io/okhttp/3.x/okhttp/okhttp3/Response.html#peekBody-long- that uses peekSource() since Response class is final
* #param byteCount
* #return
* #throws IOException
*/
public ResponseBody peek(long byteCount) throws IOException {
BufferedSource source = peekSource();
source.request(byteCount);
Buffer copy = source.buffer().clone();
// There may be more than byteCount bytes in source.buffer(). If there is, return a prefix.
Buffer result;
if (copy.size() > byteCount) {
result = new Buffer();
result.write(copy, byteCount);
copy.clear();
} else {
result = copy;
}
return ResponseBody.create(mResponse.body().contentType(), result.size(), result);
}
}
private void startDeadline() {
mDeadline = sHTTPCancelExecutorService.schedule(new Runnable() {
#Override
public void run() {
mDeadlineHit = true;
mUnderlying.cancel(); // calls onFailure or causes body read to throw
LOGGER.fine("Deadline hit for " + request()); // should trigger a subsequent wrapIfDeadline but if we see this log line without that it means the caller orphaned us without closing
}
}, mDeadlineTimeout, TimeUnit.MILLISECONDS);
LOGGER.fine("started deadline for " + request());
if (sFutures.incrementAndGet() == 1000) {
LOGGER.warning("1000 pending DeadlineCalls, may be leaking due to not calling close()");
}
}
private void cancelDeadline() {
if (mDeadline != null) {
mDeadline.cancel(false);
mDeadline = null;
sFutures.decrementAndGet();
LOGGER.fine("canceled deadline for " + request());
} else {
LOGGER.info("deadline already canceled for " + request());
}
}
#Override
public void cancel() {
mCancelled = true;
// should trigger onFailure or raise from execute or responseCallback.onResponse which will cancelDeadline
mUnderlying.cancel();
}
#Override
public boolean isExecuted() {
return mUnderlying.isExecuted();
}
#Override
public boolean isCanceled() {
return mCancelled;
}
#Override
public Call clone() {
return new DeadlineCall(mUnderlying.clone(), mDeadlineTimeout);
}
private static boolean isCancellationException(IOException e) {
// okhttp cancel from HTTP/2 calls
if (e instanceof StreamResetException) {
switch (((StreamResetException) e).errorCode) {
case CANCEL:
return true;
}
}
// https://android.googlesource.com/platform/external/okhttp/+/master/okhttp/src/main/java/com/squareup/okhttp/Call.java#281
if (e instanceof IOException &&
e.getMessage() != null && e.getMessage().equals("Canceled")) {
return true;
}
return false;
}
}
Note that we also have a separate interceptor to timeout DNS since even our deadline doesn't cover that:
/**
* Based on http://stackoverflow.com/questions/693997/how-to-set-httpresponse-timeout-for-android-in-java/31643186#31643186
* as per https://github.com/square/okhttp/issues/95
*/
private static class DNSTimeoutInterceptor implements Interceptor {
long mTimeoutMillis;
public DNSTimeoutInterceptor(long timeoutMillis) {
mTimeoutMillis = timeoutMillis;
}
#Override
public Response intercept(final Chain chain) throws IOException {
Request request = chain.request();
Log.SplitTimer timer = (request.tag() instanceof RequestTag ? ((RequestTag) request.tag()).getTimer() : null);
// underlying call should timeout after 2 tries of 5s: https://android.googlesource.com/platform/bionic/+/android-5.1.1_r38/libc/dns/include/resolv_private.h#137
// could use our own Dns implementation that falls back to public DNS servers: https://garage.easytaxi.com/tag/dns-android-okhttp/
if (!DNSResolver.isDNSReachable(request.url().host(), mTimeoutMillis)) {
throw new UnknownHostException("DNS timeout");
}
return chain.proceed(request);
}
private static class DNSResolver implements Runnable {
private String mDomain;
private InetAddress mAddress;
public static boolean isDNSReachable(String domain, long timeoutMillis) {
try {
DNSResolver dnsRes = new DNSResolver(domain);
Thread t = new Thread(dnsRes, "DNSResolver");
t.start();
t.join(timeoutMillis);
return dnsRes.get() != null;
} catch(Exception e) {
return false;
}
}
public DNSResolver(String domain) {
this.mDomain = domain;
}
public void run() {
try {
InetAddress addr = InetAddress.getByName(mDomain);
set(addr);
} catch (UnknownHostException e) {
}
}
public synchronized void set(InetAddress inetAddr) {
this.mAddress = inetAddr;
}
public synchronized InetAddress get() {
return mAddress;
}
}
}
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/