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
Flowable<WebSocket.Event.OnConnectionClosed> observeOnConnectionClosedEvent();
}

View file

@ -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<SignalingOverall>() {
@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<SignalingOverall>() {
@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<String> strings = new ArrayList<>();
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;
}
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";
}

View file

@ -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;
}
}