More progress towards external signaling support

This commit is contained in:
Mario Danic 2018-10-10 16:31:04 +02:00
parent dc16c7b91d
commit 7bce24a850
10 changed files with 301 additions and 46 deletions

View File

@ -34,4 +34,5 @@ public interface ExternalSignaling {
@Receive @Receive
Flowable<WebSocket.Event.OnConnectionClosed> observeOnConnectionClosedEvent(); Flowable<WebSocket.Event.OnConnectionClosed> observeOnConnectionClosedEvent();
} }

View File

@ -29,9 +29,11 @@ import android.graphics.Color;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -48,6 +50,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.bitmap.CircleCrop; import com.bumptech.glide.load.resource.bitmap.CircleCrop;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.api.ExternalSignaling;
import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.base.BaseController; import com.nextcloud.talk.controllers.base.BaseController;
@ -55,6 +58,7 @@ import com.nextcloud.talk.events.ConfigurationChangeEvent;
import com.nextcloud.talk.events.MediaStreamEvent; import com.nextcloud.talk.events.MediaStreamEvent;
import com.nextcloud.talk.events.PeerConnectionEvent; import com.nextcloud.talk.events.PeerConnectionEvent;
import com.nextcloud.talk.events.SessionDescriptionSendEvent; import com.nextcloud.talk.events.SessionDescriptionSendEvent;
import com.nextcloud.talk.models.ExternalSignalingServer;
import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.call.CallOverall; import com.nextcloud.talk.models.json.call.CallOverall;
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall; import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
@ -197,6 +201,7 @@ public class CallController extends BaseController {
private MediaConstraints audioConstraints; private MediaConstraints audioConstraints;
private MediaConstraints videoConstraints; private MediaConstraints videoConstraints;
private MediaConstraints sdpConstraints; private MediaConstraints sdpConstraints;
private MediaConstraints sdpConstraintsForMCU;
private MagicAudioManager audioManager; private MagicAudioManager audioManager;
private VideoSource videoSource; private VideoSource videoSource;
private VideoTrack localVideoTrack; private VideoTrack localVideoTrack;
@ -238,6 +243,8 @@ public class CallController extends BaseController {
private SpotlightView spotlightView; private SpotlightView spotlightView;
private ExternalSignalingServer externalSignalingServer;
public CallController(Bundle args) { public CallController(Bundle args) {
super(args); super(args);
NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
@ -351,6 +358,7 @@ public class CallController extends BaseController {
//create sdpConstraints //create sdpConstraints
sdpConstraints = new MediaConstraints(); sdpConstraints = new MediaConstraints();
sdpConstraintsForMCU = new MediaConstraints();
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")); sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
String offerToReceiveVideoString = "true"; String offerToReceiveVideoString = "true";
@ -358,14 +366,21 @@ public class CallController extends BaseController {
offerToReceiveVideoString = "false"; offerToReceiveVideoString = "false";
} }
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", offerToReceiveVideoString));
offerToReceiveVideoString));
sdpConstraintsForMCU.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "false"));
sdpConstraintsForMCU.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "false"));
sdpConstraintsForMCU.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true"));
sdpConstraintsForMCU.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true")); sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true"));
sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true")); sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
if (!isVoiceOnlyCall) { if (!isVoiceOnlyCall) {
cameraInitialization(); cameraInitialization();
} }
microphoneInitialization(); microphoneInitialization();
} }
@ -893,6 +908,13 @@ public class CallController extends BaseController {
IceServer iceServer; IceServer iceServer;
if (signalingSettingsOverall != null && signalingSettingsOverall.getOcs() != null && if (signalingSettingsOverall != null && signalingSettingsOverall.getOcs() != null &&
signalingSettingsOverall.getOcs().getSettings() != null) { signalingSettingsOverall.getOcs().getSettings() != null) {
if (!TextUtils.isEmpty(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingServer()) &&
!TextUtils.isEmpty(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingTicket())) {
externalSignalingServer.setExternalSignalingServer(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingServer());
externalSignalingServer.setExternalSignalingTicket(signalingSettingsOverall.getOcs().getSettings().getExternalSignalingTicket());
}
if (signalingSettingsOverall.getOcs().getSettings().getStunServers() != null) { if (signalingSettingsOverall.getOcs().getSettings().getStunServers() != null) {
for (int i = 0; i < signalingSettingsOverall.getOcs().getSettings().getStunServers().size(); for (int i = 0; i < signalingSettingsOverall.getOcs().getSettings().getStunServers().size();
i++) { i++) {
@ -1088,44 +1110,48 @@ public class CallController extends BaseController {
NotificationUtils.cancelExistingNotifications(getApplicationContext(), conversationUser); NotificationUtils.cancelExistingNotifications(getApplicationContext(), conversationUser);
ncApi.pullSignalingMessages(credentials, ApiUtils.getUrlForSignaling(baseUrl, urlToken)) if (externalSignalingServer == null) {
.subscribeOn(Schedulers.newThread()) ncApi.pullSignalingMessages(credentials, ApiUtils.getUrlForSignaling(baseUrl, urlToken))
.observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.newThread())
.repeatWhen(observable -> observable) .observeOn(AndroidSchedulers.mainThread())
.takeWhile(observable -> inCall) .repeatWhen(observable -> observable)
.retry(3, observable -> inCall) .takeWhile(observable -> inCall)
.subscribe(new Observer<SignalingOverall>() { .retry(3, observable -> inCall)
@Override .subscribe(new Observer<SignalingOverall>() {
public void onSubscribe(Disposable d) { @Override
signalingDisposable = d; public void onSubscribe(Disposable d) {
} signalingDisposable = d;
}
@Override @Override
public void onNext(SignalingOverall signalingOverall) { public void onNext(SignalingOverall signalingOverall) {
if (signalingOverall.getOcs().getSignalings() != null) { if (signalingOverall.getOcs().getSignalings() != null) {
for (int i = 0; i < signalingOverall.getOcs().getSignalings().size(); i++) { for (int i = 0; i < signalingOverall.getOcs().getSignalings().size(); i++) {
try { try {
receivedSignalingMessage(signalingOverall.getOcs().getSignalings().get(i)); receivedSignalingMessage(signalingOverall.getOcs().getSignalings().get(i));
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Failed to process received signaling" + Log.e(TAG, "Failed to process received signaling" +
" message"); " message");
}
} }
} }
} }
}
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
dispose(signalingDisposable); dispose(signalingDisposable);
} }
@Override @Override
public void onComplete() { public void onComplete() {
dispose(signalingDisposable); dispose(signalingDisposable);
} }
}); });
} else {
}
} }
@Override @Override
@ -1497,7 +1523,7 @@ public class CallController extends BaseController {
.PeerConnectionEventType.SENSOR_FAR) || .PeerConnectionEventType.SENSOR_FAR) ||
peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
.PeerConnectionEventType.SENSOR_NEAR)) { .PeerConnectionEventType.SENSOR_NEAR)) {
if (!isVoiceOnlyCall) { if (!isVoiceOnlyCall) {
boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
.PeerConnectionEventType.SENSOR_FAR) && videoOn; .PeerConnectionEventType.SENSOR_FAR) && videoOn;
@ -1559,14 +1585,14 @@ public class CallController extends BaseController {
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("{") stringBuilder.append("{")
.append("\"fn\":\"") .append("\"fn\":\"")
.append(StringEscapeUtils.escapeJson(LoganSquare.serialize(ncMessageWrapper.getSignalingMessage()))).append("\"") .append(StringEscapeUtils.escapeJson(LoganSquare.serialize(ncMessageWrapper.getSignalingMessage()))).append("\"")
.append(",") .append(",")
.append("\"sessionId\":") .append("\"sessionId\":")
.append("\"").append(StringEscapeUtils.escapeJson(callSession)).append("\"") .append("\"").append(StringEscapeUtils.escapeJson(callSession)).append("\"")
.append(",") .append(",")
.append("\"ev\":\"message\"") .append("\"ev\":\"message\"")
.append("}"); .append("}");
List<String> strings = new ArrayList<>(); List<String> strings = new ArrayList<>();
String stringToSend = stringBuilder.toString(); String stringToSend = stringBuilder.toString();

View File

@ -0,0 +1,29 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models;
import lombok.Data;
@Data
public class ExternalSignalingServer {
String externalSignalingServer;
String externalSignalingTicket;
}

View File

@ -0,0 +1,37 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.websocket;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.squareup.moshi.Json;
import lombok.Data;
@Data
@JsonObject
public class AuthParametersWebSocketMessage {
@JsonField(name = "userid")
String userid;
@Json(name = "ticket")
String ticket;
}

View File

@ -0,0 +1,37 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.websocket;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.squareup.moshi.Json;
import lombok.Data;
@Data
@JsonObject
public class AuthWebSocketMessage {
@JsonField(name = "url")
String url;
@JsonField(name = "params")
AuthParametersWebSocketMessage authParametersWebSocketMessage;
}

View File

@ -0,0 +1,33 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.websocket;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import lombok.Data;
@Data
@JsonObject
public class BaseWebSocketMessage {
@JsonField(name = "type")
String type;
}

View File

@ -0,0 +1,34 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.websocket;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.squareup.moshi.Json;
import lombok.Data;
@Data
@JsonObject
public class HelloOverallWebSocketMessage extends BaseWebSocketMessage {
@JsonField(name = "hello")
HelloWebSocketMessage helloWebSocketMessage;
}

View File

@ -0,0 +1,40 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.websocket;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.squareup.moshi.Json;
import lombok.Data;
@Data
@JsonObject
public class HelloWebSocketMessage {
@JsonField(name = "version")
String version;
@JsonField(name = "resumeid")
String resumeid;
@JsonField(name = "auth")
AuthWebSocketMessage authWebSocketMessage;
}

View File

@ -138,6 +138,10 @@ public class ApiUtils {
return baseUrl + ocsApiVersion + spreedApiVersion + "/chat/" + token; return baseUrl + ocsApiVersion + spreedApiVersion + "/chat/" + token;
} }
public static String getUrlForExternalServerAuthBackend(String baseUrl) {
return baseUrl + ocsApiVersion + spreedApiVersion + "/signaling/backend";
}
public static String getUrlForMentionSuggestions(String baseUrl, String token) { public static String getUrlForMentionSuggestions(String baseUrl, String token) {
return getUrlForChat(baseUrl, token) + "/mentions"; return getUrlForChat(baseUrl, token) + "/mentions";
} }

View File

@ -47,18 +47,32 @@ public class ScarletHelper {
NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
} }
public ExternalSignaling getExternalSignalingInstanceForServer(String url) { private String getExternalSignalingServerUrlFromSettingsUrl(String url) {
if (externalSignalingMap.containsKey(url)) { String generatedURL = url.replace("https://", "wss://").replace("http://", "ws://");
return externalSignalingMap.get(url);
if (generatedURL.endsWith("/")) {
generatedURL += "spreed";
} else {
generatedURL += "/spreed";
}
return generatedURL;
}
public ExternalSignaling getExternalSignalingInstanceForServer(String url, boolean forceReconnect) {
String connectionUrl = getExternalSignalingServerUrlFromSettingsUrl(url);
if (externalSignalingMap.containsKey(connectionUrl) && !forceReconnect) {
return externalSignalingMap.get(connectionUrl);
} else { } else {
Scarlet scarlet = new Scarlet.Builder() Scarlet scarlet = new Scarlet.Builder()
.backoffStrategy(new LinearBackoffStrategy(500)) .backoffStrategy(new LinearBackoffStrategy(500))
.webSocketFactory(OkHttpClientUtils.newWebSocketFactory(okHttpClient, url)) .webSocketFactory(OkHttpClientUtils.newWebSocketFactory(okHttpClient, connectionUrl))
.addMessageAdapterFactory(new MoshiMessageAdapter.Factory()) .addMessageAdapterFactory(new MoshiMessageAdapter.Factory())
.addStreamAdapterFactory(new RxJava2StreamAdapterFactory()) .addStreamAdapterFactory(new RxJava2StreamAdapterFactory())
.build(); .build();
ExternalSignaling externalSignaling = scarlet.create(ExternalSignaling.class); ExternalSignaling externalSignaling = scarlet.create(ExternalSignaling.class);
externalSignalingMap.put(url, externalSignaling); externalSignalingMap.put(connectionUrl, externalSignaling);
return externalSignaling; return externalSignaling;
} }
} }