From 7bce24a850e7f7ac17b68eaa9f462c191a8d29d2 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Wed, 10 Oct 2018 16:31:04 +0200 Subject: [PATCH] More progress towards external signaling support --- .../nextcloud/talk/api/ExternalSignaling.java | 1 + .../talk/controllers/CallController.java | 108 +++++++++++------- .../talk/models/ExternalSignalingServer.java | 29 +++++ .../AuthParametersWebSocketMessage.java | 37 ++++++ .../json/websocket/AuthWebSocketMessage.java | 37 ++++++ .../json/websocket/BaseWebSocketMessage.java | 33 ++++++ .../HelloOverallWebSocketMessage.java | 34 ++++++ .../json/websocket/HelloWebSocketMessage.java | 40 +++++++ .../com/nextcloud/talk/utils/ApiUtils.java | 4 + .../nextcloud/talk/webrtc/ScarletHelper.java | 24 +++- 10 files changed, 301 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/websocket/AuthParametersWebSocketMessage.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/websocket/AuthWebSocketMessage.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/websocket/BaseWebSocketMessage.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloOverallWebSocketMessage.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloWebSocketMessage.java diff --git a/app/src/main/java/com/nextcloud/talk/api/ExternalSignaling.java b/app/src/main/java/com/nextcloud/talk/api/ExternalSignaling.java index aecfb3595..351fec7c2 100644 --- a/app/src/main/java/com/nextcloud/talk/api/ExternalSignaling.java +++ b/app/src/main/java/com/nextcloud/talk/api/ExternalSignaling.java @@ -34,4 +34,5 @@ public interface ExternalSignaling { @Receive Flowable observeOnConnectionClosedEvent(); + } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallController.java b/app/src/main/java/com/nextcloud/talk/controllers/CallController.java index 280bf0f06..0ca6ab4a1 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallController.java @@ -29,9 +29,11 @@ import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.os.Handler; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; + import android.text.TextUtils; import android.util.Log; 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.request.RequestOptions; import com.nextcloud.talk.R; +import com.nextcloud.talk.api.ExternalSignaling; import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.application.NextcloudTalkApplication; 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.PeerConnectionEvent; import com.nextcloud.talk.events.SessionDescriptionSendEvent; +import com.nextcloud.talk.models.ExternalSignalingServer; import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.json.call.CallOverall; import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall; @@ -197,6 +201,7 @@ public class CallController extends BaseController { private MediaConstraints audioConstraints; private MediaConstraints videoConstraints; private MediaConstraints sdpConstraints; + private MediaConstraints sdpConstraintsForMCU; private MagicAudioManager audioManager; private VideoSource videoSource; private VideoTrack localVideoTrack; @@ -238,6 +243,8 @@ public class CallController extends BaseController { private SpotlightView spotlightView; + private ExternalSignalingServer externalSignalingServer; + public CallController(Bundle args) { super(args); NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); @@ -351,6 +358,7 @@ public class CallController extends BaseController { //create sdpConstraints sdpConstraints = new MediaConstraints(); + sdpConstraintsForMCU = new MediaConstraints(); sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")); String offerToReceiveVideoString = "true"; @@ -358,14 +366,21 @@ public class CallController extends BaseController { offerToReceiveVideoString = "false"; } - sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", - offerToReceiveVideoString)); + sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", 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("DtlsSrtpKeyAgreement", "true")); if (!isVoiceOnlyCall) { cameraInitialization(); } + microphoneInitialization(); } @@ -893,6 +908,13 @@ public class CallController extends BaseController { IceServer iceServer; if (signalingSettingsOverall != null && signalingSettingsOverall.getOcs() != 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) { for (int i = 0; i < signalingSettingsOverall.getOcs().getSettings().getStunServers().size(); i++) { @@ -1088,44 +1110,48 @@ public class CallController extends BaseController { NotificationUtils.cancelExistingNotifications(getApplicationContext(), conversationUser); - ncApi.pullSignalingMessages(credentials, ApiUtils.getUrlForSignaling(baseUrl, urlToken)) - .subscribeOn(Schedulers.newThread()) - .observeOn(AndroidSchedulers.mainThread()) - .repeatWhen(observable -> observable) - .takeWhile(observable -> inCall) - .retry(3, observable -> inCall) - .subscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - signalingDisposable = d; - } + if (externalSignalingServer == null) { + ncApi.pullSignalingMessages(credentials, ApiUtils.getUrlForSignaling(baseUrl, urlToken)) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .repeatWhen(observable -> observable) + .takeWhile(observable -> inCall) + .retry(3, observable -> inCall) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + signalingDisposable = d; + } - @Override - public void onNext(SignalingOverall signalingOverall) { - if (signalingOverall.getOcs().getSignalings() != null) { - for (int i = 0; i < signalingOverall.getOcs().getSignalings().size(); i++) { - try { - receivedSignalingMessage(signalingOverall.getOcs().getSignalings().get(i)); - } catch (IOException e) { - Log.e(TAG, "Failed to process received signaling" + - " message"); + @Override + public void onNext(SignalingOverall signalingOverall) { + if (signalingOverall.getOcs().getSignalings() != null) { + for (int i = 0; i < signalingOverall.getOcs().getSignalings().size(); i++) { + try { + receivedSignalingMessage(signalingOverall.getOcs().getSignalings().get(i)); + } catch (IOException e) { + Log.e(TAG, "Failed to process received signaling" + + " message"); + } } } } - } - @Override - public void onError(Throwable e) { - dispose(signalingDisposable); - } + @Override + public void onError(Throwable e) { + dispose(signalingDisposable); + } - @Override - public void onComplete() { - dispose(signalingDisposable); - } - }); + @Override + public void onComplete() { + dispose(signalingDisposable); + } + }); + } else { + + } } @Override @@ -1497,7 +1523,7 @@ public class CallController extends BaseController { .PeerConnectionEventType.SENSOR_FAR) || peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent .PeerConnectionEventType.SENSOR_NEAR)) { - + if (!isVoiceOnlyCall) { boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent .PeerConnectionEventType.SENSOR_FAR) && videoOn; @@ -1559,14 +1585,14 @@ public class CallController extends BaseController { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("{") - .append("\"fn\":\"") - .append(StringEscapeUtils.escapeJson(LoganSquare.serialize(ncMessageWrapper.getSignalingMessage()))).append("\"") - .append(",") - .append("\"sessionId\":") - .append("\"").append(StringEscapeUtils.escapeJson(callSession)).append("\"") - .append(",") - .append("\"ev\":\"message\"") - .append("}"); + .append("\"fn\":\"") + .append(StringEscapeUtils.escapeJson(LoganSquare.serialize(ncMessageWrapper.getSignalingMessage()))).append("\"") + .append(",") + .append("\"sessionId\":") + .append("\"").append(StringEscapeUtils.escapeJson(callSession)).append("\"") + .append(",") + .append("\"ev\":\"message\"") + .append("}"); List strings = new ArrayList<>(); String stringToSend = stringBuilder.toString(); diff --git a/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.java b/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.java new file mode 100644 index 000000000..502270985 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.java @@ -0,0 +1,29 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 . + */ + +package com.nextcloud.talk.models; + +import lombok.Data; + +@Data +public class ExternalSignalingServer { + String externalSignalingServer; + String externalSignalingTicket; +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/AuthParametersWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/AuthParametersWebSocketMessage.java new file mode 100644 index 000000000..cf4739904 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/AuthParametersWebSocketMessage.java @@ -0,0 +1,37 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 . + */ + +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; +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/AuthWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/AuthWebSocketMessage.java new file mode 100644 index 000000000..17956b73a --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/AuthWebSocketMessage.java @@ -0,0 +1,37 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 . + */ + +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; +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/BaseWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/BaseWebSocketMessage.java new file mode 100644 index 000000000..0af035388 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/BaseWebSocketMessage.java @@ -0,0 +1,33 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 . + */ + +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; +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloOverallWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloOverallWebSocketMessage.java new file mode 100644 index 000000000..33b1522cc --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloOverallWebSocketMessage.java @@ -0,0 +1,34 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 . + */ + +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; +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloWebSocketMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloWebSocketMessage.java new file mode 100644 index 000000000..edc655ca5 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/HelloWebSocketMessage.java @@ -0,0 +1,40 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * 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 . + */ + +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; +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index 3d516eedb..7e22c9345 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -138,6 +138,10 @@ public class ApiUtils { 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) { return getUrlForChat(baseUrl, token) + "/mentions"; } diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/ScarletHelper.java b/app/src/main/java/com/nextcloud/talk/webrtc/ScarletHelper.java index 33be69a92..b99aeb590 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/ScarletHelper.java +++ b/app/src/main/java/com/nextcloud/talk/webrtc/ScarletHelper.java @@ -47,18 +47,32 @@ public class ScarletHelper { NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); } - public ExternalSignaling getExternalSignalingInstanceForServer(String url) { - if (externalSignalingMap.containsKey(url)) { - return externalSignalingMap.get(url); + private String getExternalSignalingServerUrlFromSettingsUrl(String url) { + String generatedURL = url.replace("https://", "wss://").replace("http://", "ws://"); + + 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 { Scarlet scarlet = new Scarlet.Builder() .backoffStrategy(new LinearBackoffStrategy(500)) - .webSocketFactory(OkHttpClientUtils.newWebSocketFactory(okHttpClient, url)) + .webSocketFactory(OkHttpClientUtils.newWebSocketFactory(okHttpClient, connectionUrl)) .addMessageAdapterFactory(new MoshiMessageAdapter.Factory()) .addStreamAdapterFactory(new RxJava2StreamAdapterFactory()) .build(); ExternalSignaling externalSignaling = scarlet.create(ExternalSignaling.class); - externalSignalingMap.put(url, externalSignaling); + externalSignalingMap.put(connectionUrl, externalSignaling); return externalSignaling; } }