I've already looked over many posts in StackOverflow and their git issues. The problem with almost all of them is about header for HttpAuthorizer. But it's still not working for me. Can anyone help me to find what's wrong with my code? Thank you.
Note: I don't know if the error is on the server side, because when my team tested it with simple chat app from browser it works.
public class PusherService {
private static final String CHANNEL = "private-User." + accountId;
private static PusherService instance;
private Pusher pusher;
public static synchronized PusherService getInstance() {
if (instance == null) {
instance = new PusherService();
}
return instance;
}
private PusherService() {
pusher = initiatePusher();
}
private static Pusher initiatePusher() {
HttpAuthorizer httpAuthorizer = new HttpAuthorizer(BuildConfig.PUSHER_AUTH_URL);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", Session.getToken());
headers.put("Accept", "application/json");
headers.put("Content-Type", "application/json");
httpAuthorizer.setHeaders(headers);
PusherOptions options = new PusherOptions()
.setCluster(BuildConfig.PUSHER_CLUSTER)
.setEncrypted(true)
.setAuthorizer(httpAuthorizer);
Pusher pusher = new Pusher(BuildConfig.PUSHER_KEY, options);
pusher.connect(new ConnectionEventListener() {
#Override
public void onConnectionStateChange(ConnectionStateChange connectionStateChange) {
Log.i("Pusher", "Connection State Change: " + connectionStateChange.getCurrentState().name());
}
#Override
public void onError(String s, String s1, Exception e) {
Log.i("Pusher", String.format("Connection Error: [%s], exception: [%s]", s, e));
}
});
return pusher;
}
public PrivateChannel getChannel() {
PrivateChannel privateChannel = pusher.getPrivateChannel(CHANNEL);
if (privateChannel == null) {
privateChannel = pusher.subscribePrivate(CHANNEL, new PrivateChannelEventListener() {
#Override
public void onAuthenticationFailure(String s, Exception e) {
// Authentication failed
Log.i("Pusher", String.format("Authentication failure due to [%s], exception: [%s]", s, e));
}
#Override
public void onSubscriptionSucceeded(String s) {
Log.i("Pusher", "Private connection succeeded");
}
#Override
public void onEvent(String channel, String event, String data) {
Log.i("Pusher", data);
}
});
}
return privateChannel;
}
}
Then to test it I just called
PusherService.getInstance().getChannel();
After 2 days searching, we found the answer. I just need to remove the Content-Type header.
Related
Hello I have implemented pusher for realtime chat and subscribing to pusher channel , but I have many activities and fragments where i want to listen to pushr events . I have added this code in every activity/fragment but the problem is that it creates multiple subscriptions for every id . I know that i have to use Singleton for this can anyone point me in the right direction to achieve this ?
Here is the code i am writing in every activity/fragment
private PusherOptions options;
private Channel channel;
private Pusher pusher;
options = new PusherOptions();
options.setCluster("ap2");
pusher = new Pusher("afbfc1f591fd7b70190f", options);
pusher.connect();
profile_id = Global.shared().preferences.getString("PROFILE_ID", " ");
channel = pusher.subscribe(profile_id);
channel.bind("message",
new SubscriptionEventListener() {
#Override
public void onEvent(String s, String s1, final String data) {
runOnUiThread(new Runnable() {
#Override
public void run() {
try {
JSONObject result = new JSONObject(data);
String message = result.getString("message");
String time = result.getString("time");
String reId = result.getString("recieverId");
new_message = message;
getConvoData(k, message);
} catch (JSONException e) {
e.printStackTrace();
}
System.out.println("DATA ====>>" + data);
}
});
}
});
okay so after trying for a while i figured it out my self i created a global class and just added pusher code to it so that it maintains just one connection for the entire lifecycle of the app
public class Global extends MultiDexApplication {
#Override
public void onCreate() {
super.onCreate();
SharedPreferences preferences = sharedInstance.getSharedPreferences(sharedInstance.getString(R.string.shared_preferences), Context.MODE_PRIVATE);
sharedInstance.preferences = preferences;
connectTopusher();
}
public void connectTopusher() {
PusherOptions options;
Channel channel;
Pusher pusher;
options = new PusherOptions();
options.setCluster("ap2");
pusher = new Pusher("afbfc1f591fd7b70190f", options);
pusher.connect();
String profile = Global.shared().preferences.getString("PROFILE_ID", "");
channel = pusher.subscribe(profile);
channel.bind("message",
new SubscriptionEventListener() {
#Override
public void onEvent(String s, String s1, final String data) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
try {
JSONObject result = new JSONObject(data);
String message = result.getString("message");
String time = result.getString("time");
String reId = result.getString("recieverId");
} catch (JSONException e) {
e.printStackTrace();
}
System.out.println("DATA ====>>" + data);
}
});
}
});
channel.bind("status_change", new SubscriptionEventListener() {
#Override
public void onEvent(String s, String s1, final String data) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
try {
JSONObject result = new JSONObject(data);
} catch (JSONException e) {
e.printStackTrace();
}
System.out.println("DATA ====>>" + data);
}
});
}
});
}
You can expose channel in your Global class. That will allow you to call bind and unbind in your fragments, when they are in the foreground.
connectToPusher should just create a channel and subscribe to it.
In Global.java:
private Channel channel;
public void connectTopusher() {
PusherOptions options;
Pusher pusher;
options = new PusherOptions();
options.setCluster("ap2");
pusher = new Pusher("afbfc1f591fd7b70190f", options);
pusher.connect();
String profile = Global.shared().preferences.getString("PROFILE_ID", "");
this.channel = pusher.subscribe(profile);
}
public Channel getChannel(){
return this.channel;
}
And then in your activity/fragment you can bind/unbind your listeners to when they are resumed/paused - just keep a reference to it like this:
YourActivity.java (could also be your Fragment)
private SubscriptionEventListener messageListener = new SubscriptionEventListener(){
#Override
public void onEvent(String channel, String event, String data) {
//TODO: do something with events
}
}
//Bind when the listener comes into the foreground:
#Override
protected void onResume() {
super.onResume();
((Global) getActivity().getApplication()).getChannel().bind("message", messageListener);
}
//Make sure to unbind the event listener!
#Override
protected void onPause() {
super.onPause();
((Global) getActivity().getApplication()).getChannel().unbind("message", messageListener);
}
I hope this helps :)
I am trying to figure out how i can resubscribe the same observable for every hour when we have got an error from server for 5 attempts.I know about retryWhen but really not able to understand how i can use it in my case.I am using retrofit for server calls and rxjava to subscribe.
Here is the method where i am making a call using retrofit.Please help with this.
#Override
public Observable<Integer> uploadFileToServer(FileUploadData fileUploadData, File file) {
// log.i(TAG, "uploadFileToServer");
FileUploadEndpoint fileUploadEndpoint = null;
try {
fileUploadEndpoint = retrofitServiceFactory.getService(FileUploadEndpoint.class);
} catch (BaseUrlNotFoundException e) {
e.printStackTrace();
// log.i(TAG, "uploadFileToServer" + e.getMessage());
return Observable.just(FileUploadConstants.EXCEPTION_FILE_UPLOAD);
}
// create RequestBody instance from file
RequestBody requestFile =
RequestBody.create(okhttp3.MultipartBody.FORM, file);
// MultipartBody.Part is used to send also the actual file name
MultipartBody.Part body =
MultipartBody.Part.createFormData("uploadfile", file.getName(), requestFile);
// add another part within the multipart request
String descriptionString = "file upload";
RequestBody description =
RequestBody.create(
okhttp3.MultipartBody.FORM, descriptionString);
Map<String, String> queryMap = new HashMap<>();
queryMap.put("SENDER", fileUploadData.getSender());
queryMap.put("SOURCE", fileUploadData.getSource());
queryMap.put("SCHEMEID", fileUploadData.getSchemeId());
queryMap.put("ISPROCESSINGREQ", "false");
queryMap.put("ISENCRYPTED", "true");
queryMap.put("UID", fileUploadData.getSchemeId());
queryMap.put("METADATA", fileUploadData.getMetaData());
final Observable<FileUploadResponse> requestObservable = fileUploadEndpoint.upload(queryMap, description, body);
return requestObservable.map(new Function<FileUploadResponse, Integer>() {
#Override
public Integer apply(FileUploadResponse fileUploadResponse) throws Exception {
if (fileUploadResponse != null) {
int code = fileUploadResponse.getStatusCode();
switch (code) {
case 100:
return FileUploadConstants.FILE_UPLOAD_SUCCESSFUL;
}
}
return FileUploadConstants.EXCEPTION_FILE_UPLOAD;
}
}).retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
return throwableObservable.zipWith(Observable.range(1, 5), new BiFunction<Throwable, Integer, FileUploadResponse>() {
#Override
public FileUploadResponse apply(Throwable throwable, Integer integer) throws Exception {
return null;//not able to write the logic :(
}
});
}
});
}
#Override
public void setBaseUrl(String baseUrl) {
retrofitServiceFactory.setBaseUrl(baseUrl);
}
private interface FileUploadEndpoint {
#Multipart
#POST("da/appupload/file")
Observable<FileUploadResponse> upload(#QueryMap Map<String, String> additionValues,
#Part("description") RequestBody description,
#Part MultipartBody.Part file);
}
Here's a recipe for you.
public class RetryWithDelay implements
Func1<Observable<? extends Throwable>, Observable<?>> {
private static final String TAG = "RetryWithDelay";
private static final int DEFAULT_RETRY_COUNT = 5;
private static final int DEFAULT_RETRY_DELAY = 1000 * 60;
private final int maxRetries;
private final int retryDelayMillis;
private int retryCount;
public RetryWithDelay() {
this.maxRetries = DEFAULT_RETRY_COUNT;
this.retryDelayMillis = DEFAULT_RETRY_DELAY;
this.retryCount = 0;
}
public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
this.maxRetries = maxRetries;
this.retryDelayMillis = retryDelayMillis;
this.retryCount = 0;
}
#Override
public Observable<?> call(Observable<? extends Throwable> attempts) {
return attempts.flatMap(new Func1<Throwable, Observable<?>>() {
#Override
public Observable<?> call(Throwable throwable) {
if (throwable instanceof HttpException) {
LOGD(TAG, "Caught http exception.");
}
if (throwable instanceof IOException) {
LOGD(TAG, "Network error");
}
if (++retryCount < maxRetries) {
// When this Observable calls onNext, the original
// Observable will be retried (i.e. re-subscribed).
return Observable.timer(retryDelayMillis, TimeUnit.MILLISECONDS);
}
// Max retries hit. Just pass the error along.
return Observable.error(throwable);
}
});
}
}
Then in your code use it like this
// Leave constructor empty for default values
.retryWhen(new RetryWithDelay());
// Or setup different values
// In this case retry 3 times, with 5s delay
.retryWhen(new RetyryWithDelay(3, 5000));
I am trying to implement pusher chat in my Android Application, i am able to connect with pusher, got the response as CONNECTED. But not able to connect to Private channels(Local server). Can anyone help on this.
adding the code below.
Thanks in advance
final HttpAuthorizer authorizer = new HttpAuthorizer(myurl);
authorizer.setHeaders(getMapAuthorizationHeaders());
PusherOptions options = new PusherOptions().setAuthorizer(authorizer);
final Pusher pusher = new Pusher("pusher_key", options);
pusher.connect(new com.pusher.client.connection.ConnectionEventListener() {
#Override
public void onConnectionStateChange(ConnectionStateChange connectionStateChange) {
Log.e("connectionStateChange" , connectionStateChange.getCurrentState().toString());
if (connectionStateChange.getCurrentState() == ConnectionState.CONNECTED) {
SOCKET_ID = pusher.getConnection().getSocketId();
Log.e("SOCKET_ID" , ""+SOCKET_ID);
Channel channel = pusher.subscribePrivate(PUSH_CHANNEL, new PrivateChannelEventListener() {
#Override
public void onAuthenticationFailure(String s, Exception e) {
Log.e("PUSHER", "Channel subscription authorization failed");
}
#Override
public void onSubscriptionSucceeded(String s) {
Log.e("PUSHER", "Channel subscription authorization succeeded");
}
#Override
public void onEvent(String s, String s2, String s3) {
Log.e("PUSHER", "An event with name " + s2 + " was delivered!!");
}
}, "my-event");
}
}
#Override
public void onError(String s, String s1, Exception e) {
}
});
public static HashMap<String, String> getMapAuthorizationHeaders() {
try {
HashMap<String, String> authHeader = new HashMap<>();
authHeader.put("HeaderKey1", "HeaderValue1");
authHeader.put("HeaderKey2", "HeaderValue2");
return authHeader;
} catch (Exception e) {
return null;
}
}
Can I assume that where you've put "pusher_key", you've just added this for security purposes on here, and in your application you're actually using your key?
Other than that, where are you defining PUSH_CHANNEL for: pusher.subscribePrivate(PUSH_CHANNEL,
Each request to the server may return error_code. I want to handle these error in one place
when I was using AsyncTask I had a BaseAsyncTask like that
public abstract class BaseAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected Context context;
private ProgressDialog progressDialog;
private Result result;
protected BaseAsyncTask(Context context, ProgressDialog progressDialog) {
this.context = context;
this.progressDialog = progressDialog;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected void onPostExecute(Result result) {
super.onPostExecute(result);
HttpResponse<ErrorResponse> response = (HttpResponse<ErrorResponse>) result;
if(response.getData().getErrorCode() != -1) {
handleErrors(response.getData());
}else
onResult(result);
}
private void handleErrors(ErrorResponse errorResponse) {
}
public abstract void onResult(Result result);
}
But, using retrofit each request has its error handling callback:
git.getFeed(user,new Callback<gitmodel>() {
#Override
public void success(gitmodel gitmodel, Response response) {
}
#Override
public void failure(RetrofitError error) {
}
});
}
});
How can I handle all errors in one place?
If you need to get some 'logic' error, then you need some Java logic since it's not a Retrofit feature so basically:
Create a Your implementation Callback that implements the Retrofit Callback
Create a base object that define the method 'isError'
Modify Retrofit RestAdapter in order to get your Callback instead of the Retrofit One
MyCallback.java
import android.util.Log;
import retrofit.Callback;
import retrofit.client.Response;
public abstract class MyCallback<T extends MyObject> implements Callback<T> {
#Override
public final void success(T o, Response response) {
if (o.isError()) {
// [..do something with error]
handleLogicError(o);
}
else {
handleSuccess(o, response);
}
}
abstract void handleSuccess(T o, Response response);
void handleLogicError(T o) {
Log.v("TAG", "Error because userId is " + o.id);
}
}
MyObject.java (the base class for all your objects you get from Retrofit)
public class MyObject {
public long id;
public boolean isError() {
return id == 1;
}
}
MyRealObject.java - a class that extends the base object
public class MyRealObject extends MyObject {
public long userId;
public String title;
public String body;
}
RetroInterface.java - the interface used by retrofit you should be familiar with
import retrofit.http.GET;
import retrofit.http.Path;
public interface RetroInterface {
#GET("/posts/{id}")
void sendGet(#Path("id") int id, MyCallback<MyRealObject> callback);
}
And finally the piece of code where you use all the logic
RestAdapter adapter = new RestAdapter.Builder()
.setEndpoint("http://jsonplaceholder.typicode.com")
.build();
RetroInterface itf = adapter.create(RetroInterface.class);
itf.sendGet(2, new MyCallback<MyRealObject>() {
#Override
void handleSuccess(MyRealObject o, Response response) {
Log.v("TAG", "success");
}
#Override
public void failure(RetrofitError error) {
Log.v("TAG", "failure");
}
});
If you copy and paste this code, you'll get an error when you'll execute the itf.sendGet(1, new MyCallback..) and a success for itf.sendGet(2, new MyCallback...)
Not sure I understood it correctly, but you could create one Callback and pass it as a parameter to all of your requests.
Instead of:
git.getFeed(user,new Callback<gitmodel>() {
#Override
public void success(gitmodel gitmodel, Response response) {
}
#Override
public void failure(RetrofitError error) {
}
});
First define your Callback:
Callback<gitmodel> mCallback = new Callback<gitmodel>() {
#Override
public void success(gitmodel gitmodel, Response response) {
}
#Override
public void failure(RetrofitError error) {
// logic to handle error for all requests
}
};
Then:
git.getFeed(user, mCallback);
In Retrofit you can specify ErrorHandler to all requests.
public class ApiErrorHandler implements ErrorHandler {
#Override
public Throwable handleError(RetrofitError cause) {
//here place your logic for all errors
return cause;
}
}
Apply it to RestAdapter
RestAdapter.Builder()
.setClient(client)
.setEndpoint(endpoint)
.setErrorHandler(errorHandler)
.build();
I think that it is what you asked for.
In Retrofit2 you can't set an ErrorHandler with the method .setErrorHandler(), but you can create an interceptor to fork all possible errors centralised in one place of your application.
With this example you have one centralised place for your error handling with Retrofit2 and OkHttpClient. Just reuse the Retrofit object (retrofit).
You can try this standalone example with a custom interceptor for network and server errors. These both will be handled differently in Retrofit2, so you have to check the returned error code from the server over the response code (response.code()) and if the response was not successful (!response.isSuccessful()).
For the case that the user has no connection to the network or the server you have to catch an IOException of the method Response response = chain.proceed(chain.request()); and handle the network error in the catch block.
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
try {
Response response = chain.proceed(chain.request());
if (!response.isSuccessful()) {
Log.e("tag", "Failure central - response code: " + response.code());
Log.e("tag", "central server error handling");
// Central error handling for error responses here:
// e.g. 4XX and 5XX errors
switch (response.code()) {
case 401:
// do something when 401 Unauthorized happened
// e.g. delete credentials and forward to login screen
// ...
break;
case 403:
// do something when 403 Forbidden happened
// e.g. delete credentials and forward to login screen
// ...
break;
default:
Log.e("tag", "Log error or do something else with error code:" + response.code());
break;
}
}
return response;
} catch (IOException e) {
// Central error handling for network errors here:
// e.g. no connection to internet / to server
Log.e("tag", e.getMessage(), e);
Log.e("tag", "central network error handling");
throw e;
}
}
})
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://10.0.2.2:8000/api/v1/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
UserRepository backendRepository = retrofit.create(UserRepository.class);
backendRepository.getUser("userId123").enqueue(new Callback<UserModel>() {
#Override
public void onResponse(Call<UserModel> call, retrofit2.Response<UserModel> response) {
Log.d("tag", "onResponse");
if (!response.isSuccessful()) {
Log.e("tag", "onFailure local server error handling code:" + response.code());
} else {
// its all fine with the request
}
}
#Override
public void onFailure(Call<UserModel> call, Throwable t) {
Log.e("tag", "onFailure local network error handling");
Log.e("tag", t.getMessage(), t);
}
});
UserRepository example:
public interface UserRepository {
#GET("users/{userId}/")
Call<UserModel> getUser(#Path("userId") String userId);
}
UserModel example:
public class UserModel implements Parcelable {
#SerializedName("id")
#Expose
public String id = "";
#SerializedName("email")
#Expose
public String mail = "";
public UserModel() {
}
protected UserModel(Parcel in) {
id = in.readString();
mail = in.readString();
}
public static final Creator<UserModel> CREATOR = new Creator<UserModel>() {
#Override
public UserModel createFromParcel(Parcel in) {
return new UserModel(in);
}
#Override
public UserModel[] newArray(int size) {
return new UserModel[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(mail);
}
}
Fairly simply Retrofit custom error handling example. Is set up so that you don't need to do much work in the 'failure' handler of a retrofit call to get the user-visible error message to show. Works on all endpoints. There's lots of exception handling as our server folks like to keep us on our toes by sending all kinds of random stuff..!
// on error the server sends JSON
/*
{ "error": { "data": { "message":"A thing went wrong" } } }
*/
// create model classes..
public class ErrorResponse {
Error error;
public static class Error {
Data data;
public static class Data {
String message;
}
}
}
//
/**
* Converts the complex error structure into a single string you can get with error.getLocalizedMessage() in Retrofit error handlers.
* Also deals with there being no network available
*
* Uses a few string IDs for user-visible error messages
*/
private static class CustomErrorHandler implements ErrorHandler {
private final Context ctx;
public CustomErrorHandler(Context ctx) {
this.ctx = ctx;
}
#Override
public Throwable handleError(RetrofitError cause) {
String errorDescription;
if (cause.isNetworkError()) {
errorDescription = ctx.getString(R.string.error_network);
} else {
if (cause.getResponse() == null) {
errorDescription = ctx.getString(R.string.error_no_response);
} else {
// Error message handling - return a simple error to Retrofit handlers..
try {
ErrorResponse errorResponse = (ErrorResponse) cause.getBodyAs(ErrorResponse.class);
errorDescription = errorResponse.error.data.message;
} catch (Exception ex) {
try {
errorDescription = ctx.getString(R.string.error_network_http_error, cause.getResponse().getStatus());
} catch (Exception ex2) {
Log.e(TAG, "handleError: " + ex2.getLocalizedMessage());
errorDescription = ctx.getString(R.string.error_unknown);
}
}
}
}
return new Exception(errorDescription);
}
}
// When creating the Server...
retrofit.RestAdapter restAdapter = new retrofit.RestAdapter.Builder()
.setEndpoint(apiUrl)
.setLogLevel(retrofit.RestAdapter.LogLevel.FULL)
.setErrorHandler(new CustomErrorHandler(ctx)) // use error handler..
.build();
server = restAdapter.create(Server.class);
// Now when calling server methods, get simple error out like this:
server.postSignIn(login,new Callback<HomePageResponse>(){
#Override
public void success(HomePageResponse homePageResponse,Response response){
// Do success things!
}
#Override
public void failure(RetrofitError error){
error.getLocalizedMessage(); // <-- this is the message to show to user.
}
});
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/