i can connect ios app with kurento room for conference call without any issue but i cannot connect it with android, here i am following tutorial Kurento WebRTC Peer For Android to make android client to connect with kurento room.
Here is the code what i am trying
public class MainActivity extends AppCompatActivity implements
RoomListener,NBMWebRTCPeer.Observer {
private LooperExecutor executor;
private static KurentoRoomAPI kurentoRoomAPI;
private EglBase rootEglBase;
private NBMWebRTCPeer nbmWebRTCPeer;
private SurfaceViewRenderer localView;
private SurfaceViewRenderer remoteView;
private VideoRenderer.Callbacks localRender;
private SessionDescription localSdp;
private SessionDescription remoteSdp;
NBMMediaConfiguration.NBMVideoFormat receiverVideoFormat = new NBMMediaConfiguration.NBMVideoFormat(1280, 720, ImageFormat.YUV_420_888, 30);
NBMMediaConfiguration mediaConfiguration = new NBMMediaConfiguration(NBMMediaConfiguration.NBMRendererType.OPENGLES, NBMMediaConfiguration.NBMAudioCodec.OPUS, 0, NBMMediaConfiguration.NBMVideoCodec.VP8, 0, receiverVideoFormat, NBMMediaConfiguration.NBMCameraPosition.FRONT);
private boolean isMyVideoPublished = false;
private boolean isMyIceCandidateSent = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int hasCameraPermission = checkSelfPermission(Manifest.permission.CAMERA);
List<String> permissions = new ArrayList<String>();
if (hasCameraPermission != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.CAMERA);
}
if (!permissions.isEmpty()) {
requestPermissions(permissions.toArray(new String[permissions.size()]), 111);
}
}
executor = new LooperExecutor();
executor.requestStart();
String wsRoomUri = "wss://172.16.1.9:8443/room";
kurentoRoomAPI = new KurentoRoomAPI(executor, wsRoomUri, this);
kurentoRoomAPI.connectWebSocket();
localView = (SurfaceViewRenderer) findViewById(R.id.gl_surface_local);
remoteView = (SurfaceViewRenderer) findViewById(R.id.gl_surface_remote);
localView.init(EglBase.create().getEglBaseContext(), null);
localView.setZOrderMediaOverlay(true);
localView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
localView.setMirror(true);
localView.requestLayout();
remoteView.init(EglBase.create().getEglBaseContext(), null);
remoteView.setZOrderMediaOverlay(true);
remoteView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
remoteView.setMirror(true);
remoteView.requestLayout();
nbmWebRTCPeer = new NBMWebRTCPeer(mediaConfiguration, this, localView, this);
nbmWebRTCPeer.initialize();
//nbmWebRTCPeer.generateOffer("local", true);
//nbmWebRTCPeer.generateOffer("MyRemotePeer", false);
}
#Override
public void onRoomResponse(RoomResponse response) {
Log.d("onRoomResponse", "******** onRoomResponse : "+response.toString());
if (response.getId() == 123) {
Log.d("onRoomResponse", "Successfully connected to the room!");
nbmWebRTCPeer.generateOffer("Jeeva", true);
//kurentoRoomAPI.sendMessage("112233", "Jeeva Moto", "Hello room!", 125);
} else if (response.getId() == 125) {
Log.d("onRoomResponse", "The server received my message!");
} else if (response.getId() == 126) {
}else if (response.getId() == 129) {
}
}
#Override
public void onRoomError(RoomError error) {
Log.d("onRoomError", "******** onRoomError : "+error.toString());
}
#Override
public void onRoomNotification(RoomNotification notification) {
if(notification.getMethod().equals(RoomListener.METHOD_SEND_MESSAGE)) {
final String username = notification.getParam("user").toString();
final String message = notification.getParam("message").toString();
Log.d("onRoomNotification", "Oh boy! " + username + " sent me a message: " + message);
}
Log.d("onRoomNotification", "******** RoomNotification : " + notification);
if(notification.getMethod().equals("iceCandidate"))
{
Map<String, Object> map = notification.getParams();
Log.d("onRoomNotification", "******** Map RoomNotification : " + map);
String sdpMid = map.get("sdpMid").toString();
int sdpMLineIndex = Integer.valueOf(map.get("sdpMLineIndex").toString());
String sdp = map.get("candidate").toString();
IceCandidate ic = new IceCandidate(sdpMid, sdpMLineIndex, sdp);
nbmWebRTCPeer.addRemoteIceCandidate(ic, "remote");
}
}
#Override
public void onRoomConnected() {
Log.d("onRoomConnected","******** Called");
kurentoRoomAPI.sendJoinRoom("Jeeva", "112233", true, 123);
}
#Override
public void onRoomDisconnected() {
}
#Override
public void onInitialize() {
}
#Override
public void onLocalSdpOfferGenerated(SessionDescription localSdpOffer, NBMPeerConnection connection) {
Log.d("onLclSdpOfrGen","******** localSdpOffer : "+localSdpOffer.description+" connection : "+connection.getConnectionId());
if (!isMyVideoPublished) {
kurentoRoomAPI.sendPublishVideo(localSdpOffer.description,false,129);
//String username = "qwerty";
//kurentoRoomAPI.sendReceiveVideoFrom(username, "webcam", localSdpOffer.description, 129);
isMyVideoPublished = true;
}else {
String username = "qwerty";
kurentoRoomAPI.sendReceiveVideoFrom(username, "webcam", localSdpOffer.description, 129);
}
}
#Override
public void onLocalSdpAnswerGenerated(SessionDescription localSdpAnswer, NBMPeerConnection connection) {
Log.d("onLclSdpAnsGen","******** localSdpAnswer : "+localSdpAnswer.description+" connection : "+connection.getConnectionId());
}
#Override
public void onIceCandidate(IceCandidate iceCandidate, NBMPeerConnection nbmPeerConnection) {
Log.d("onIceCandidate", "******** iceCandidate : " + iceCandidate.sdp + " nbmPeerConnection : " + nbmPeerConnection.getConnectionId());
if (!isMyIceCandidateSent){
isMyIceCandidateSent = true;
kurentoRoomAPI.sendOnIceCandidate("Jeeva", iceCandidate.sdp, iceCandidate.sdpMid, Integer.toString(iceCandidate.sdpMLineIndex),129);
} else {
kurentoRoomAPI.sendOnIceCandidate("qwerty", iceCandidate.sdp,
iceCandidate.sdpMid, Integer.toString(iceCandidate.sdpMLineIndex), 129);
nbmWebRTCPeer.addRemoteIceCandidate(iceCandidate, iceCandidate.sdp);
}
}
#Override
public void onIceStatusChanged(PeerConnection.IceConnectionState state, NBMPeerConnection connection) {
Log.d("onIceStatusChanged","******** state : "+state+" connection : "+connection);
}
#Override
public void onRemoteStreamAdded(MediaStream stream, NBMPeerConnection connection) {
Log.d("onRemoteStreamAdded","******** stream : "+stream+" connection : "+connection);
nbmWebRTCPeer.attachRendererToRemoteStream(remoteView, stream);
}
#Override
public void onRemoteStreamRemoved(MediaStream stream, NBMPeerConnection connection) {
Log.d("onRemoteStreamRemoved","******** stream : "+stream+" connection : "+connection);
}
#Override
public void onPeerConnectionError(String error) {
Log.d("onPeerConnectionError","******** error : "+error);
}
#Override
public void onDataChannel(DataChannel dataChannel, NBMPeerConnection connection) {
Log.d("onDataChannel","******** dataChannel : "+dataChannel+" connection : "+connection);
}
#Override
public void onBufferedAmountChange(long l, NBMPeerConnection connection, DataChannel channel) {
Log.d("onBufferedAmountChange","******** channel : "+channel+" connection : "+connection);
}
#Override
public void onStateChange(NBMPeerConnection connection, DataChannel channel) {
Log.d("onStateChange","******** channel : "+channel+" connection : "+connection);
}
#Override
public void onMessage(DataChannel.Buffer buffer, NBMPeerConnection connection, DataChannel channel) {
Log.d("onMessage","******** channel : "+channel+" buffer : "+buffer+" connection : "+connection);
}}
I am looking for any working sample for android client which connects kurento room for conference call.
Kurento also has completed Android client with simple UI.
It allows to connect to "kurento-room" server using of their KurentoAPI.
It has only 2p2, but contain implementation of all needed signaling, so it may be a good start point.
More details are there.
Related
I'm attempting to stream my webcam locally from a webpage on my computer to an Android app (native WebRTC). I'm using WebRTC for the peer connection and NodeJS with Socket.io for signaling. When I initiate the video stream all sdp appears to be set correctly, but no track plays on my Andorid surface view. All I get is a black screen and this console output:
2020-08-19 13:23:19.667 30492-31575/com.example.demowebrtcclient I/org.webrtc.Logging: EglRenderer: video_viewDuration: 4050 ms. Frames received: 0. Dropped: 0. Rendered: 0. Render fps: .0. Average render time: NA. Average swapBuffer time: NA.
Here's my code, sorry there's so much I just don't know where the root of the problem is.
Peer 1 (Webpage, JS)
navigator.mediaDevices.getUserMedia({audio: true, video: true})
.then(function(s) {
stream = s;
video.srcObject = stream;
video.play();
});
function startStream() {
socket.emit('broadcaster');
}
socket.on('answer', function(id, description) {
let RTCDescription = description;
if (isAndroid) {
RTCDescription = new RTCSessionDescription();
RTCDescription.sdp = description;
RTCDescription.type = "answer";
console.log("Answer received");
}
console.log(RTCDescription);
peerConnection.setRemoteDescription(RTCDescription);
});
socket.on('watcher', function(id) {
peerConnection = new RTCPeerConnection(config);
// peerConnections[id] = peerConnection;
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
peerConnection.createOffer()
.then(function(sdp) {
peerConnection.setLocalDescription(sdp);
})
.then(function() {
socket.emit('offer', id, peerConnection.localDescription);
})
peerConnection.onicecandidate = function(event) {
if (event.candidate) {
socket.emit('candidate', id, event.candidate);
}
};
});
socket.on('candidate', function(id, candidate) {
console.log("Candidate recieved");
peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
});
socket.on('close', function(id) {
peerConnection.close();
delete peerConnection;
});
Peer 2 (Android, Java)
private void initializePeerConnectionFactory() {
PeerConnectionFactory.InitializationOptions initOptions = PeerConnectionFactory.InitializationOptions.builder(getApplicationContext())
.setEnableInternalTracer(true)
.setFieldTrials("WebRTC-H264HighProfile/Enabled/")
.createInitializationOptions();
PeerConnectionFactory.initialize(initOptions);
VideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(rootEglBaseContext, /* enableIntelVp8Encoder */true, /* enableH264HighProfile */true);
VideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(rootEglBaseContext);
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
options.disableEncryption = true;
options.disableNetworkMonitor = true;
factory = PeerConnectionFactory.builder()
.setVideoEncoderFactory(defaultVideoEncoderFactory)
.setVideoDecoderFactory(defaultVideoDecoderFactory)
.setOptions(options)
.createPeerConnectionFactory();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rootEglBase = EglBase.create();
rootEglBaseContext = rootEglBase.getEglBaseContext();
videoView = findViewById(R.id.video_view);
videoView.setMirror(true);
videoView.setEnableHardwareScaler(true);
videoView.init(rootEglBaseContext, null);
videoSink = new ProxyVideoSink();
iceServers = new ArrayList<>();
stunServer = (PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());
iceServers.add(stunServer);
executor = Executors.newSingleThreadScheduledExecutor();
mediaConstraints = new MediaConstraints();
mSocket.on(Socket.EVENT_CONNECT, onConnect);
mSocket.on("offer", handleOffer);
mSocket.on("broadcaster", onBroadcast);
mSocket.on("candidate", onCandidate);
mSocket.connect();
initializePeerConnectionFactory();
}
private Emitter.Listener handleOffer = new Emitter.Listener() {
#Override
public void call(final Object... args) {
Log.d("socket", "Offer recieved");
peerConnection = factory.createPeerConnection(iceServers, observer);
String id = (String) args[0];
JSONObject data = (JSONObject) args[1];
peerConnection.setRemoteDescription(new SdpAdapter("setremote") {
#Override
public void onSetSuccess() {
peerConnection.createAnswer(new SdpAdapter("createanswer") {
#Override
public void onCreateSuccess(final SessionDescription sdp) {
super.onCreateSuccess(sdp);
Log.d("socket", "Session description " + sdp.toString() + " created");
SdpAdapter local = new SdpAdapter("setlocal") {
#Override
public void onSetSuccess() {
super.onSetSuccess();
mSocket.emit("answer" , id, peerConnection.getLocalDescription().description);
}
};
peerConnection.setLocalDescription(local, sdp);
}
}, mediaConstraints);
}
}, new SessionDescription(SessionDescription.Type.OFFER, data.optString("sdp")));
}
};
private Emitter.Listener onConnect = new Emitter.Listener() {
#Override
public void call(final Object... args) {
Log.d("socket", "Socket connected");
mSocket.emit("watcher");
}
};
private Emitter.Listener onBroadcast = new Emitter.Listener() {
#Override
public void call(final Object... args) {
Log.d("socket", "Received broadcast request");
mSocket.emit("watcher");
}
};
private Emitter.Listener onCandidate = new Emitter.Listener() {
#Override
public void call(final Object... args) {
Log.d("ice", "New ice candidate recieved");
JSONObject data = (JSONObject) args[1];
IceCandidate candidate = new IceCandidate(data.optString("sdpMid"), Integer.parseInt(data.optString("sdpMLineIndex")), data.optString("candidate"));
peerConnection.addIceCandidate(candidate);
}
};
public class SdpAdapter implements SdpObserver {
private String name;
public SdpAdapter(String name) {
this.name = name;
}
#Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.d("socket", "SDP create success for " + name );
}
#Override
public void onSetSuccess() {
Log.d("socket", "SDP set success for " + name);
}
#Override
public void onCreateFailure(String s) {
Log.d("socket", "SDP create failure for" + s);
}
#Override
public void onSetFailure(String s) {
Log.d("socket", "SDP set failure for" + s);
}
};
public static class ProxyVideoSink implements VideoSink {
private VideoSink mTarget;
#Override
synchronized public void onFrame(VideoFrame frame) {
if (mTarget == null) {
Log.d("socket", "Dropping frame in proxy because target is null.");
return;
}
mTarget.onFrame(frame);
}
synchronized void setTarget(VideoSink target) {
this.mTarget = target;
}
}
Observer observer = new Observer() {
#Override
public void onIceCandidate(IceCandidate iceCandidate) {
Log.d("ice", iceCandidate.toString());
mSocket.emit("candidate", iceCandidate);
}
#Override
public void onAddStream(MediaStream stream) {
executor.execute(new Runnable() {
#Override
public void run() {
Log.d("socket", "Stream added");
Log.d("tracks", "Getting tracks");
VideoTrack remoteVideoTrack = (VideoTrack)stream.videoTracks.get(0);
AudioTrack remoteAudioTrack = (AudioTrack)stream.audioTracks.get(0);
Log.d("tracks", "Enabling tracks");
remoteAudioTrack.setEnabled(true);
remoteVideoTrack.setEnabled(true);
Log.d("tracks", "Adding sink");
videoSink.setTarget(videoView);
remoteVideoTrack.addSink(videoSink);
peerConnection.getStats(reports -> {
for (StatsReport report : reports) {
Log.d("Stats", "Stats: " + report.toString());
}
}, null);
}
});
}
};
}
Disabling encryption in Android will break interoperability with the browser, unless you start them in a special (insecure) mode.
options.disableEncryption = true;
A good point to get started with the Android native WebRTC API is by looking at the example App code:
https://chromium.googlesource.com/external/webrtc/+/refs/heads/master/examples/androidapp/src/org/appspot/apprtc/PeerConnectionClient.java
I'll recommend to add more logs and extend the description / question.
Recommended procedure when debugging a WebRTC issue:
Did ICE succeed? (iceConnectionState is connected) If not, look at the gathered and received candidates. Check involved firewall configurations, NAT and TURN.
Do you receive packets but no frames can be rendered? Check your codecs and encoders / decoders.
I am trying to integrate the Sinch API in android .
My VOIPClient. Java is like this
public class VOIPClient {
private static final String TAG = "VOIPClient";
private SinchClient mSinch;
private TTSHelper mTTS;
private Call mCurrentCall;
private BootService mContext;
private SinchClientBuilder mBuilder;
private NsdController mNsdController;
private final CallListener mCallListener = new CallListener() {
#Override
public void onCallProgressing(Call call) {
Log.d(TAG, "Call established at " + " Thusee");
}
#Override
public void onCallEstablished(Call call) {
Log.d(TAG, "Call established at " + call.getDetails().getEstablishedTime());
mTTS.speak("Call started", TTSHelper.UTTERANCE_VOIP_START);
JsonObject payload = new JsonObject();
payload.addProperty("Status", 0);
payload.addProperty("Call_status", 1);
if (mNsdController != null) {
mNsdController.sendCommand(20, payload);
}
}
#Override
public void onCallEnded(Call call) {
Log.d(TAG, "Call ended at " + call.getDetails().getEndedTime() + "caused by " + call.getDetails().getEndCause().toString());
mTTS.speak("Call ended", TTSHelper.UTTERANCE_VOIP_END);
mCurrentCall.hangup();
JsonObject payload = new JsonObject();
payload.addProperty("Status", 0);
payload.addProperty("Call_status", 1);
if (mNsdController != null) {
mNsdController.sendCommand(21, payload);
}
}
#Override
public void onShouldSendPushNotification(Call call, List<PushPair> list) {
}
};
public VOIPClient(BootService context) {
mContext = context;
mTTS = TTSHelper.getInstance(context);
mBuilder = Sinch.getSinchClientBuilder().context(mContext.getApplicationContext())
.applicationKey(CloudConfig.SINCH_APP_KEY)
.applicationSecret(CloudConfig.SINCH_APP_SECRET)
.environmentHost(CloudConfig.SINCH_ENVIRONMENT);
if (mNsdController != null)
mNsdController.initialize();
}
public void start() {
SharedPreferences prefs = mContext.getPreferences();
int userId = prefs.getInt(MerryClient.PREF_USER_ID, 0);
String mUserId;
if (userId > 0) {
mUserId = String.valueOf(userId);
mSinch = Sinch.getSinchClientBuilder().context(mContext.getApplicationContext()).userId(mUserId)
.applicationKey(CloudConfig.SINCH_APP_KEY)
.applicationSecret(CloudConfig.SINCH_APP_SECRET)
.environmentHost(CloudConfig.SINCH_ENVIRONMENT).build();
mSinch.setSupportCalling(true);
mSinch.setSupportManagedPush(false);
SinchClientListener sinchClientListener = new SinchClientListener() {
#Override
public void onClientStarted(SinchClient sinchClient) {
Log.d(TAG, "Sinch Client starts: " + sinchClient.getLocalUserId());
mTTS.speak("Call ready", TTSHelper.UTTERANCE_VOIP_READY);
}
#Override
public void onClientStopped(SinchClient sinchClient) {
Log.d(TAG, "Sinch Client stops");
}
#Override
public void onClientFailed(SinchClient sinchClient, SinchError sinchError) {
Log.e(TAG, String.format("Sinch Client error %d: %s", sinchError.getCode(), sinchError.getMessage()));
mSinch.terminate();
mTTS.speak("Voice Over IP failed", TTSHelper.UTTERANCE_VOIP_FAIL);
}
#Override
public void onRegistrationCredentialsRequired(SinchClient sinchClient, ClientRegistration clientRegistration) {
Log.d(TAG, "Sinch Client requires registration");
}
#Override
public void onLogMessage(int i, String s, String s1) {
Log.d(TAG, s1);
}
};
mSinch.addSinchClientListener(sinchClientListener);
mSinch.getCallClient().setRespectNativeCalls(false);
mSinch.getCallClient().addCallClientListener(new SinchCallClientListener());
mCurrentCall = null;
mSinch.startListeningOnActiveConnection();
mSinch.start();
}
}
public void tearDown() {
if (mSinch != null) {
mSinch.stopListeningOnActiveConnection();
mSinch.terminate();
mSinch = null;
}
}
public void restart() {
tearDown();
start();
}
public void initiateCall(final String targetUserName) {
new Thread(new Runnable() {
public void run() {
Looper.prepare();
if (targetUserName != null) {
try {
Call call = callUser(targetUserName);
call.addCallListener(mCallListener);
mCurrentCall = call;
} catch (Exception e) {
Log.e(TAG, "Initiate VOIP call failed", e);
}
}
Looper.loop();
}
}).start();
}
public void answerCall() {
if (mCurrentCall != null) {
mCurrentCall.answer();
}
}
public void hangUpCall() {
if (mCurrentCall != null) {
mCurrentCall.hangup();
}
}
private class SinchCallClientListener implements CallClientListener {
#Override
public void onIncomingCall(CallClient callClient, Call call) {
Log.d(TAG, "Incoming call");
mTTS.speak("Incoming call from " + call.getRemoteUserId(), TTSHelper.UTTERANCE_VOIP_INCOMING);
call.addCallListener(mCallListener);
mCurrentCall = call;
// For testing only
answerCall();
}
}
public Call callUser(String userId) {
if (mSinch != null && mSinch.isStarted()) {
start();
}
if (mSinch == null) {
return null;
}
return mSinch.getCallClient().callUser(userId);
}
class CallerThread implements Runnable {
public String mtargetUserName;
CallerThread(String targetUserName) {
this.mtargetUserName = targetUserName;
}
#Override
public void run() {
Looper.prepare();
if (mtargetUserName != null) {
try {
Call call = callUser(mtargetUserName);
call.addCallListener(mCallListener);
mCurrentCall = call;
} catch (Exception e) {
Log.e(TAG, "Initiate VOIP call failed", e);
mContext.getAlexa().start();
}
}
Looper.loop();
}
}
}
When I try to call to other device then I am getting these kind of exceptions
Initiate VOIP call failed
java.lang.IllegalStateException: SinchClient not started
at com.sinch.android.rtc.internal.client.calling.DefaultCallClient.throwUnlessStarted(Unknown Source)
at com.sinch.android.rtc.internal.client.calling.DefaultCallClient.call(Unknown Source)
at com.sinch.android.rtc.internal.client.calling.DefaultCallClient.callUser(Unknown Source)
at com.sinch.android.rtc.internal.client.calling.DefaultCallClient.callUser(Unknown Source)
at tw.com.test.cloud.VOIPClient.callUser(VOIPClient.java:272)
at tw.com.test.cloud.VOIPClient$CallerThread.run(VOIPClient.java:293)
at java.lang.Thread.run(Thread.java:818)
Also some times I am getting this exception
FATAL EXCEPTION: Thread-75
Process: tw.com.test.wear, PID: 1123
java.lang.IllegalThreadStateException: A Looper must be associated with this thread.
at com.sinch.android.rtc.internal.AndroidLooperCallbackHandler.<init>(Unknown Source)
at com.sinch.android.rtc.internal.client.InternalSinchClientFactory.createSinchClient(Unknown Source)
at com.sinch.android.rtc.DefaultSinchClientBuilder.build(Unknown Source)
at tw.com.test.cloud.VOIPClient.start(VOIPClient.java:109)
at tw.com.test.cloud.VOIPClient$2.onClientStopped(VOIPClient.java:124)
at com.sinch.android.rtc.internal.client.DefaultSinchClient.shutdown(Unknown Source)
at com.sinch.android.rtc.internal.client.DefaultSinchClient.terminate(Unknown Source)
at tw.com.test.cloud.VOIPClient.tearDown(VOIPClient.java:160)
at tw.com.test.nsd.NsdController.messageReceived(NsdController.java:570)
at tw.com.test.nsd.NsdConnection.run(NsdConnection.java:115)
at java.lang.Thread.run(Thread.java:818)
2 Days I tried my self, I can't able to solve this yet,
I am always getting these exception. Sometimes it will work for one time then I need to restart the app.
You have to start SinchClient sinchClient.start(); and take NOTE during production mode do not place in a plain text form your SINCH_APP_SECRET, because its a secret key, hackers will easily read or decompile your code.
public VOIPClient(BootService context) {
mContext = context;
mTTS = TTSHelper.getInstance(context);
mBuilder = Sinch.getSinchClientBuilder().context(mContext.getApplicationContext())
.applicationKey(CloudConfig.SINCH_APP_KEY)
.applicationSecret(CloudConfig.SINCH_APP_SECRET)
.environmentHost(CloudConfig.SINCH_ENVIRONMENT);
sinchClient.setSupportCalling(true);
sinchClient.start();
if (mNsdController != null)
mNsdController.initialize();
}
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
I'm trying to work with Twilio Client SDK for android with very less experience with Twilio service.
All I want to achieve here is to make voice call from an app to registered numbers using the Twilio number.
Here's my client side implementation
public class AudioCallActivity extends AppCompatActivity implements Twilio.InitListener, View.OnClickListener {
private static final int MIC_PERMISSION_REQUEST_CODE = 1;
private CoordinatorLayout coordinatorLayout;
private String contact;
private String name;
private String profile_pic;
private ImageView profile;
private TextView user;
private TextView timer;
private Device device;
private Connection connection;
private String TAG = AudioCallActivity.this.getClass().getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_call);
if (ActivityCompat.checkSelfPermission(AudioCallActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(AudioCallActivity.this, Manifest.permission.MODIFY_AUDIO_SETTINGS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(AudioCallActivity.this,
new String[]{Manifest.permission.RECORD_AUDIO}, MIC_PERMISSION_REQUEST_CODE);
ActivityCompat.requestPermissions(AudioCallActivity.this,
new String[]{Manifest.permission.MODIFY_AUDIO_SETTINGS}, 2);
}
if (getIntent() != null) {
contact = getIntent().getStringExtra("contact").trim();
patient_name = getIntent().getStringExtra("name");
profile_pic = getIntent().getStringExtra("profile_pic").trim();
loadUI();
} else AudioCallActivity.this.finish();
if (!Twilio.isInitialized())
Twilio.initialize(getApplicationContext(), this);
coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinatorlayout);
}
private void loadUI() {
profile = (ImageView) findViewById(R.id.profile_pic);
((Button) findViewById(R.id.end_call)).setOnClickListener(this);
user = (TextView) findViewById(R.id.userName);
timer = (TextView) findViewById(R.id.timer);
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.end_call:
if (connection != null)
connection.disconnect();
device.release();
AudioCallActivity.this.finish();
break;
}
}
#Override
public void onInitialized() {
user.setText("Calling " + name);
new GlideOperations(getApplicationContext(), profile).glideImages(profile_pic);
NetworkManager.getInstance(getApplicationContext()).StringRequest(TAG, new HashMap<String, String>(), AppConfig.CAPABILITY_TOKEN, new VolleyResponse<String>() {
#Override
public void success(String response) {
if (response != null && !response.isEmpty()) {
device = Twilio.createDevice(response, null);
HashMap<String, String> param = new HashMap<>();
param.put("PhoneNumber", "XXXXXXXXXX");
if (connection == null) {
connection = device.connect(param, new ConnectionListener() {
#Override
public void onConnecting(Connection connection) {
Log.d(TAG, "Connecting...");
}
#Override
public void onConnected(Connection connection) {
timer.setVisibility(View.VISIBLE);
Log.d(TAG, "Connected");
}
#Override
public void onDisconnected(Connection connection) {
Log.d(TAG, "Disconnected");
}
#Override
public void onDisconnected(Connection connection, int i, String s) {
}
});
}
}
}
#Override
public void error(VolleyError error) {
Log.d(TAG, "VOLLEY ERROR");
}
}, Request.Method.POST);
}
#Override
public void onError(Exception e) {
Toast.makeText(getApplicationContext(), "ERROR", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == MIC_PERMISSION_REQUEST_CODE && resultCode != RESULT_OK) {
Snackbar snackbar = Snackbar.make(coordinatorLayout, "Lyphe Call need the following permission to operate.", Snackbar.LENGTH_LONG);
snackbar.show();
}
}
}
In my TwiMl application I have the following voice request url {xyz.com}/outgoing
Here's the server side script for the same
require_once dirname(__FILE__) . "/include/config.php";
header('Content-type: text/xml');
$callerId = TWILO_NUMBER;
$number = "XXXXXXX";
if (isset($_POST['PhoneNumber'])) {
$number = htmlspecialchars($_POST['PhoneNumber']);
}
if (preg_match("/^[\d\+\-\(\) ]+$/", $number)) {
$numberOrClient = "<Number>" . $number . "</Number>";
} else {
$numberOrClient = "<Client>" . $number . "</Client>";
}
?>
<Response>
<Dial callerId="<?php echo $callerId ?>">
<?php echo $numberOrClient ?>
</Dial>
</Response>
Please let me know where I'm making the mistake. TIA
I am trying to use the DataApi but I am not receiving the data on the wearable side. Here is my code :
Mobile side :
public class Moto360Service implements GoogleApiClient.ConnectionCallbacks, MessageApi.MessageListener {
private static final String TAG = Moto360Service.class.getSimpleName();
private static Moto360Service moto360ServiceInstance = null;
private GoogleApiClient mApiClient;
public Moto360Service() {
Log.v(TAG, "Moto360Service instance Created");
//Favourites
mSelectionOrangeContent = new ArrayList<WatchData>();
mMyRadiosContent = new ArrayList<WatchData>();
mMyPodcastsContent = new ArrayList<WatchData>();
//
initGoogleApiClient();
}
public static Moto360Service getInstance() {
synchronized (TAG) {
if (moto360ServiceInstance == null) {
moto360ServiceInstance = new Moto360Service();
}
}
return moto360ServiceInstance;
}
private void initGoogleApiClient() {
mApiClient = new GoogleApiClient.Builder(OrangeradioApplication.getInstance().getApplicationContext())
.addApi( Wearable.API )
.build();
mApiClient.connect();
}
#Override
public void onConnected(Bundle bundle) {
Log.d(TAG, "connect to watch");
String[] data = {"c","d","e" + + System.currentTimeMillis()};
new DataTask (data).execute();
}
class DataTask extends AsyncTask<Node, Void, Void> {
private final String[] content;
public DataTask (String [] content) {
this.content = content;
}
#Override
protected Void doInBackground(Node... nodes) {
PutDataMapRequest dataMap = PutDataMapRequest.create ("/watch/data");
dataMap.getDataMap().putStringArray("content", content);
PutDataRequest request = dataMap.asPutDataRequest();
PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(mApiClient, request);
pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult> () {
#Override
public void onResult(DataApi.DataItemResult dataItemResult) {
// something
Log.d(TAG, "SOMETHING");
Log.d(TAG, "dataItemResult : " + dataItemResult.getDataItem());
if (dataItemResult.getStatus().isSuccess()) {
Log.d(TAG, "message successfully sent");
} else if (dataItemResult.getStatus().isInterrupted()) {
Log.e(TAG, "couldn't send data to watch (interrupted)");
} else if (dataItemResult.getStatus().isCanceled()) {
Log.e(TAG, "couldn't send data to watch (canceled)");
}
}
} );
Log.d(TAG, "sending data");
return null;
}
}
Wear side :
public class MainActivity extends Activity implements MessageApi.MessageListener, GoogleApiClient.ConnectionCallbacks,DataApi.DataListener, WearableListView.ClickListener {
private GoogleApiClient mApiClient;
private SimpleListAdapter mAdapter;
private static final String TAG = MainActivity.class.getSimpleName();
private ArrayList<String> list = new ArrayList<>();
private WearableListView mListView;
private ProgressDialog dialog;
private String showReceived;
private Float showSize;
private Context mContext;
private Handler myHandler = new Handler();
private int progressValue = 0;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
...
initGoogleApiClient();
}
private void initGoogleApiClient() {
mApiClient = new GoogleApiClient.Builder( this )
.addApi( Wearable.API )
.addConnectionCallbacks( this )
.build();
if( mApiClient != null && !( mApiClient.isConnected() || mApiClient.isConnecting() ) )
mApiClient.connect();
}
#Override
public void onConnected(Bundle bundle) {
Log.d(TAG, "connected");
Wearable.DataApi.addListener(mApiClient, this);
Wearable.MessageApi.addListener(mApiClient, this);
}
#Override
public void onDataChanged(DataEventBuffer dataEventBuffer) {
Log.d(TAG, "data changed");
}
}
The message isn't received on the wearable side, but the message is sent from the mobile.
private void sendMessage(final String path, final String text) {
new Thread( new Runnable() {
#Override
public void run() {
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes( mApiClient ).await();
for (Node node : nodes.getNodes()) {
Log.d(TAG, "name : " + node.getDisplayName() + "id : " + node.getId());
Log.d(TAG, "mApiClient : " + mApiClient);
Log.d(TAG, "sending message");
Wearable.MessageApi.sendMessage(mApiClient, node.getId(), path, text.getBytes());
}
}
}).start();
}
In the logs, I see that the node is found :
D/Moto360Serviceļ¹ name : Moto 360 E109id : ca292f00
Wear manifest : http://pastebin.com/C5BuXWx8
EDIT :
I tried the following (to make a change at each request) :
String[] data = {"a","b","c" + System.currentTimeMillis()};
new DataTask (data).execute();
But it didn't change anything.
onDataChanged() is called only if a new data is added, so if you add the same data (i.e. data items with the same content and path), it won't trigger that method. One way to see if that is the case is to add a timestamp to your data and see if that triggers the callback.