Merge pull request #2543 from nextcloud/add-interface-for-sending-signaling-messages

Add interface for sending signaling messages
This commit is contained in:
Andy Scherzinger 2022-12-27 15:44:40 +01:00 committed by GitHub
commit e4e9a51889
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 200 additions and 428 deletions

View File

@ -66,7 +66,6 @@ import com.nextcloud.talk.events.ConfigurationChangeEvent;
import com.nextcloud.talk.events.MediaStreamEvent; import com.nextcloud.talk.events.MediaStreamEvent;
import com.nextcloud.talk.events.NetworkEvent; import com.nextcloud.talk.events.NetworkEvent;
import com.nextcloud.talk.events.PeerConnectionEvent; import com.nextcloud.talk.events.PeerConnectionEvent;
import com.nextcloud.talk.events.SessionDescriptionSendEvent;
import com.nextcloud.talk.events.WebSocketCommunicationEvent; import com.nextcloud.talk.events.WebSocketCommunicationEvent;
import com.nextcloud.talk.models.ExternalSignalingServer; import com.nextcloud.talk.models.ExternalSignalingServer;
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall; import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
@ -79,13 +78,13 @@ import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage; import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick; import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
import com.nextcloud.talk.models.json.signaling.NCMessagePayload; import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
import com.nextcloud.talk.models.json.signaling.NCMessageWrapper;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage; import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import com.nextcloud.talk.models.json.signaling.Signaling; import com.nextcloud.talk.models.json.signaling.Signaling;
import com.nextcloud.talk.models.json.signaling.SignalingOverall; import com.nextcloud.talk.models.json.signaling.SignalingOverall;
import com.nextcloud.talk.models.json.signaling.settings.IceServer; import com.nextcloud.talk.models.json.signaling.settings.IceServer;
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall; import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
import com.nextcloud.talk.signaling.SignalingMessageReceiver; import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import com.nextcloud.talk.signaling.SignalingMessageSender;
import com.nextcloud.talk.ui.dialog.AudioOutputDialog; import com.nextcloud.talk.ui.dialog.AudioOutputDialog;
import com.nextcloud.talk.users.UserManager; import com.nextcloud.talk.users.UserManager;
import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.ApiUtils;
@ -262,6 +261,9 @@ public class CallActivity extends CallBaseActivity {
private InternalSignalingMessageReceiver internalSignalingMessageReceiver = new InternalSignalingMessageReceiver(); private InternalSignalingMessageReceiver internalSignalingMessageReceiver = new InternalSignalingMessageReceiver();
private SignalingMessageReceiver signalingMessageReceiver; private SignalingMessageReceiver signalingMessageReceiver;
private InternalSignalingMessageSender internalSignalingMessageSender = new InternalSignalingMessageSender();
private SignalingMessageSender signalingMessageSender;
private Map<String, SignalingMessageReceiver.CallParticipantMessageListener> callParticipantMessageListeners = private Map<String, SignalingMessageReceiver.CallParticipantMessageListener> callParticipantMessageListeners =
new HashMap<>(); new HashMap<>();
@ -1369,6 +1371,7 @@ public class CallActivity extends CallBaseActivity {
signalingMessageReceiver = internalSignalingMessageReceiver; signalingMessageReceiver = internalSignalingMessageReceiver;
signalingMessageReceiver.addListener(participantListMessageListener); signalingMessageReceiver.addListener(participantListMessageListener);
signalingMessageReceiver.addListener(offerMessageListener); signalingMessageReceiver.addListener(offerMessageListener);
signalingMessageSender = internalSignalingMessageSender;
joinRoomAndCall(); joinRoomAndCall();
} }
} }
@ -1572,6 +1575,7 @@ public class CallActivity extends CallBaseActivity {
signalingMessageReceiver = webSocketClient.getSignalingMessageReceiver(); signalingMessageReceiver = webSocketClient.getSignalingMessageReceiver();
signalingMessageReceiver.addListener(participantListMessageListener); signalingMessageReceiver.addListener(participantListMessageListener);
signalingMessageReceiver.addListener(offerMessageListener); signalingMessageReceiver.addListener(offerMessageListener);
signalingMessageSender = webSocketClient.getSignalingMessageSender();
} else { } else {
if (webSocketClient.isConnected() && currentCallStatus == CallStatus.PUBLISHER_FAILED) { if (webSocketClient.isConnected() && currentCallStatus == CallStatus.PUBLISHER_FAILED) {
webSocketClient.restartWebSocket(); webSocketClient.restartWebSocket();
@ -1615,11 +1619,6 @@ public class CallActivity extends CallBaseActivity {
performCall(); performCall();
} }
break; break;
case "peerReadyForRequestingOffer":
Log.d(TAG, "onMessageEvent 'peerReadyForRequestingOffer'");
webSocketClient.requestOfferForSessionIdWithType(
webSocketCommunicationEvent.getHashMap().get("sessionId"), "video");
break;
} }
} }
@ -1955,7 +1954,8 @@ public class CallActivity extends CallBaseActivity {
true, true,
true, true,
type, type,
signalingMessageReceiver); signalingMessageReceiver,
signalingMessageSender);
} else if (hasMCU) { } else if (hasMCU) {
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory, peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
@ -1967,7 +1967,8 @@ public class CallActivity extends CallBaseActivity {
false, false,
true, true,
type, type,
signalingMessageReceiver); signalingMessageReceiver,
signalingMessageSender);
} else { } else {
if (!"screen".equals(type)) { if (!"screen".equals(type)) {
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory, peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
@ -1979,7 +1980,8 @@ public class CallActivity extends CallBaseActivity {
false, false,
false, false,
type, type,
signalingMessageReceiver); signalingMessageReceiver,
signalingMessageSender);
} else { } else {
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory, peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
iceServers, iceServers,
@ -1990,7 +1992,8 @@ public class CallActivity extends CallBaseActivity {
false, false,
false, false,
type, type,
signalingMessageReceiver); signalingMessageReceiver,
signalingMessageSender);
} }
} }
@ -2234,80 +2237,6 @@ public class CallActivity extends CallBaseActivity {
} }
} }
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEvent(SessionDescriptionSendEvent sessionDescriptionSend) throws IOException {
NCMessageWrapper ncMessageWrapper = new NCMessageWrapper();
ncMessageWrapper.setEv("message");
ncMessageWrapper.setSessionId(callSession);
NCSignalingMessage ncSignalingMessage = new NCSignalingMessage();
ncSignalingMessage.setTo(sessionDescriptionSend.getPeerId());
ncSignalingMessage.setRoomType(sessionDescriptionSend.getVideoStreamType());
ncSignalingMessage.setType(sessionDescriptionSend.getType());
NCMessagePayload ncMessagePayload = new NCMessagePayload();
ncMessagePayload.setType(sessionDescriptionSend.getType());
if (!"candidate".equals(sessionDescriptionSend.getType())) {
ncMessagePayload.setSdp(sessionDescriptionSend.getSessionDescription().description);
ncMessagePayload.setNick(conversationUser.getDisplayName());
} else {
ncMessagePayload.setIceCandidate(sessionDescriptionSend.getNcIceCandidate());
}
// Set all we need
ncSignalingMessage.setPayload(ncMessagePayload);
ncMessageWrapper.setSignalingMessage(ncSignalingMessage);
if (!hasExternalSignalingServer) {
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("}");
List<String> strings = new ArrayList<>();
String stringToSend = stringBuilder.toString();
strings.add(stringToSend);
int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, new int[]{ApiUtils.APIv3, 2, 1});
ncApi.sendSignalingMessages(credentials, ApiUtils.getUrlForSignaling(apiVersion, baseUrl, roomToken),
strings.toString())
.retry(3)
.subscribeOn(Schedulers.io())
.subscribe(new Observer<SignalingOverall>() {
@Override
public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
// unused atm
}
@Override
public void onNext(@io.reactivex.annotations.NonNull SignalingOverall signalingOverall) {
receivedSignalingMessages(signalingOverall.getOcs().getSignalings());
}
@Override
public void onError(@io.reactivex.annotations.NonNull Throwable e) {
Log.e(TAG, "", e);
}
@Override
public void onComplete() {
// unused atm
}
});
} else {
webSocketClient.sendCallMessage(ncMessageWrapper);
}
}
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) { @NonNull int[] grantResults) {
@ -2644,6 +2573,93 @@ public class CallActivity extends CallBaseActivity {
} }
} }
private class InternalSignalingMessageSender implements SignalingMessageSender {
@Override
public void send(NCSignalingMessage ncSignalingMessage) {
addLocalParticipantNickIfNeeded(ncSignalingMessage);
String serializedNcSignalingMessage;
try {
serializedNcSignalingMessage = LoganSquare.serialize(ncSignalingMessage);
} catch (IOException e) {
Log.e(TAG, "Failed to serialize signaling message", e);
return;
}
// The message wrapper can not be defined in a JSON model to be directly serialized, as sent messages
// need to be serialized twice; first the signaling message, and then the wrapper as a whole. Received
// messages, on the other hand, just need to be deserialized once.
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append('{')
.append("\"fn\":\"")
.append(StringEscapeUtils.escapeJson(serializedNcSignalingMessage))
.append('\"')
.append(',')
.append("\"sessionId\":")
.append('\"').append(StringEscapeUtils.escapeJson(callSession)).append('\"')
.append(',')
.append("\"ev\":\"message\"")
.append('}');
List<String> strings = new ArrayList<>();
String stringToSend = stringBuilder.toString();
strings.add(stringToSend);
int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, new int[]{ApiUtils.APIv3, 2, 1});
ncApi.sendSignalingMessages(credentials, ApiUtils.getUrlForSignaling(apiVersion, baseUrl, roomToken),
strings.toString())
.retry(3)
.subscribeOn(Schedulers.io())
.subscribe(new Observer<SignalingOverall>() {
@Override
public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
}
@Override
public void onNext(@io.reactivex.annotations.NonNull SignalingOverall signalingOverall) {
// When sending messages to the internal signaling server the response has been empty since
// Talk v2.9.0, so it is not really needed to process it, but there is no harm either in
// doing that, as technically messages could be returned.
receivedSignalingMessages(signalingOverall.getOcs().getSignalings());
}
@Override
public void onError(@io.reactivex.annotations.NonNull Throwable e) {
Log.e(TAG, "", e);
}
@Override
public void onComplete() {
}
});
}
/**
* Adds the local participant nick to offers and answers.
*
* For legacy reasons the offers and answers sent when the internal signaling server is used are expected to
* provide the nick of the local participant.
*
* @param ncSignalingMessage the message to add the nick to
*/
private void addLocalParticipantNickIfNeeded(NCSignalingMessage ncSignalingMessage) {
String type = ncSignalingMessage.getType();
if (!"offer".equals(type) && !"answer".equals(type)) {
return;
}
NCMessagePayload payload = ncSignalingMessage.getPayload();
if (payload == null) {
// Broken message, this should not happen
return;
}
payload.setNick(conversationUser.getDisplayName());
}
}
private class MicrophoneButtonTouchListener implements View.OnTouchListener { private class MicrophoneButtonTouchListener implements View.OnTouchListener {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")

View File

@ -1,129 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 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.events;
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
import org.webrtc.SessionDescription;
import androidx.annotation.Nullable;
public class SessionDescriptionSendEvent {
@Nullable
private final SessionDescription sessionDescription;
private final String peerId;
private final String type;
@Nullable
private final NCIceCandidate ncIceCandidate;
private final String videoStreamType;
public SessionDescriptionSendEvent(@Nullable SessionDescription sessionDescription, String peerId, String type,
@Nullable NCIceCandidate ncIceCandidate, @Nullable String videoStreamType) {
this.sessionDescription = sessionDescription;
this.peerId = peerId;
this.type = type;
this.ncIceCandidate = ncIceCandidate;
this.videoStreamType = videoStreamType;
}
@Nullable
public SessionDescription getSessionDescription() {
return this.sessionDescription;
}
public String getPeerId() {
return this.peerId;
}
public String getType() {
return this.type;
}
@Nullable
public NCIceCandidate getNcIceCandidate() {
return this.ncIceCandidate;
}
public String getVideoStreamType() {
return this.videoStreamType;
}
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(o instanceof SessionDescriptionSendEvent)) {
return false;
}
final SessionDescriptionSendEvent other = (SessionDescriptionSendEvent) o;
if (!other.canEqual((Object) this)) {
return false;
}
final Object this$sessionDescription = this.getSessionDescription();
final Object other$sessionDescription = other.getSessionDescription();
if (this$sessionDescription == null ? other$sessionDescription != null : !this$sessionDescription.equals(other$sessionDescription)) {
return false;
}
final Object this$peerId = this.getPeerId();
final Object other$peerId = other.getPeerId();
if (this$peerId == null ? other$peerId != null : !this$peerId.equals(other$peerId)) {
return false;
}
final Object this$type = this.getType();
final Object other$type = other.getType();
if (this$type == null ? other$type != null : !this$type.equals(other$type)) {
return false;
}
final Object this$ncIceCandidate = this.getNcIceCandidate();
final Object other$ncIceCandidate = other.getNcIceCandidate();
if (this$ncIceCandidate == null ? other$ncIceCandidate != null : !this$ncIceCandidate.equals(other$ncIceCandidate)) {
return false;
}
final Object this$videoStreamType = this.getVideoStreamType();
final Object other$videoStreamType = other.getVideoStreamType();
return this$videoStreamType == null ? other$videoStreamType == null : this$videoStreamType.equals(other$videoStreamType);
}
protected boolean canEqual(final Object other) {
return other instanceof SessionDescriptionSendEvent;
}
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $sessionDescription = this.getSessionDescription();
result = result * PRIME + ($sessionDescription == null ? 43 : $sessionDescription.hashCode());
final Object $peerId = this.getPeerId();
result = result * PRIME + ($peerId == null ? 43 : $peerId.hashCode());
final Object $type = this.getType();
result = result * PRIME + ($type == null ? 43 : $type.hashCode());
final Object $ncIceCandidate = this.getNcIceCandidate();
result = result * PRIME + ($ncIceCandidate == null ? 43 : $ncIceCandidate.hashCode());
final Object $videoStreamType = this.getVideoStreamType();
result = result * PRIME + ($videoStreamType == null ? 43 : $videoStreamType.hashCode());
return result;
}
public String toString() {
return "SessionDescriptionSendEvent(sessionDescription=" + this.getSessionDescription() + ", peerId=" + this.getPeerId() + ", type=" + this.getType() + ", ncIceCandidate=" + this.getNcIceCandidate() + ", videoStreamType=" + this.getVideoStreamType() + ")";
}
}

View File

@ -1,42 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Andy Scherzinger
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017 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.signaling
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
@Parcelize
@JsonObject
data class NCMessageWrapper(
@JsonField(name = ["fn"])
var signalingMessage: NCSignalingMessage? = null,
/** always a "message" */
@JsonField(name = ["ev"])
var ev: String? = null,
@JsonField(name = ["sessionId"])
var sessionId: String? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null, null)
}

View File

@ -34,7 +34,7 @@ import kotlinx.android.parcel.TypeParceler
data class Signaling( data class Signaling(
@JsonField(name = ["type"]) @JsonField(name = ["type"])
var type: String? = null, var type: String? = null,
/** can be NCMessageWrapper or List<HashMap<String,String>> */ /** can be NCSignalingMessage (encoded as a String) or List<Map<String, Object>> */
@JsonField(name = ["data"]) @JsonField(name = ["data"])
var messageWrapper: Any? = null var messageWrapper: Any? = null
) : Parcelable { ) : Parcelable {

View File

@ -1,39 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Andy Scherzinger
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* 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 android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
@Parcelize
@JsonObject
data class RequestOfferOverallWebSocketMessage(
@JsonField(name = ["type"])
var type: String? = null,
@JsonField(name = ["message"])
var requestOfferOverallWebSocketMessage: RequestOfferSignalingMessage? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
}

View File

@ -1,39 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Andy Scherzinger
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* 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 android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
@Parcelize
@JsonObject
class RequestOfferSignalingMessage(
@JsonField(name = ["recipient"])
var actorWebSocketMessage: ActorWebSocketMessage? = null,
@JsonField(name = ["data"])
var signalingDataWebSocketMessageForOffer: SignalingDataWebSocketMessageForOffer? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
}

View File

@ -1,39 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Andy Scherzinger
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* 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 android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
@Parcelize
@JsonObject
class SignalingDataWebSocketMessageForOffer(
@JsonField(name = ["type"])
var type: String? = null,
@JsonField(name = ["roomType"])
var roomType: String? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
}

View File

@ -0,0 +1,36 @@
/*
* Nextcloud Talk application
*
* @author Daniel Calviño Sánchez
* Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.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.signaling;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
/**
* Interface to send signaling messages.
*/
public interface SignalingMessageSender {
/**
* Sends the given signaling message.
*
* @param ncSignalingMessage the message to send
*/
void send(NCSignalingMessage ncSignalingMessage);
}

View File

@ -31,7 +31,6 @@ import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.events.NetworkEvent; import com.nextcloud.talk.events.NetworkEvent;
import com.nextcloud.talk.events.WebSocketCommunicationEvent; import com.nextcloud.talk.events.WebSocketCommunicationEvent;
import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.models.json.signaling.NCMessageWrapper;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage; import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import com.nextcloud.talk.models.json.websocket.BaseWebSocketMessage; import com.nextcloud.talk.models.json.websocket.BaseWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.ByeWebSocketMessage; import com.nextcloud.talk.models.json.websocket.ByeWebSocketMessage;
@ -41,6 +40,7 @@ import com.nextcloud.talk.models.json.websocket.EventOverallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.HelloResponseOverallWebSocketMessage; import com.nextcloud.talk.models.json.websocket.HelloResponseOverallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.JoinedRoomOverallWebSocketMessage; import com.nextcloud.talk.models.json.websocket.JoinedRoomOverallWebSocketMessage;
import com.nextcloud.talk.signaling.SignalingMessageReceiver; import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import com.nextcloud.talk.signaling.SignalingMessageSender;
import com.nextcloud.talk.utils.bundle.BundleKeys; import com.nextcloud.talk.utils.bundle.BundleKeys;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
@ -102,6 +102,8 @@ public class MagicWebSocketInstance extends WebSocketListener {
private final ExternalSignalingMessageReceiver signalingMessageReceiver = new ExternalSignalingMessageReceiver(); private final ExternalSignalingMessageReceiver signalingMessageReceiver = new ExternalSignalingMessageReceiver();
private final ExternalSignalingMessageSender signalingMessageSender = new ExternalSignalingMessageSender();
MagicWebSocketInstance(User conversationUser, String connectionUrl, String webSocketTicket) { MagicWebSocketInstance(User conversationUser, String connectionUrl, String webSocketTicket) {
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
@ -344,9 +346,9 @@ public class MagicWebSocketInstance extends WebSocketListener {
} }
} }
public void sendCallMessage(NCMessageWrapper ncMessageWrapper) { private void sendCallMessage(NCSignalingMessage ncSignalingMessage) {
try { try {
String message = LoganSquare.serialize(webSocketConnectionHelper.getAssembledCallMessageModel(ncMessageWrapper)); String message = LoganSquare.serialize(webSocketConnectionHelper.getAssembledCallMessageModel(ncSignalingMessage));
if (!connected || reconnecting) { if (!connected || reconnecting) {
messagesQueue.add(message); messagesQueue.add(message);
} else { } else {
@ -357,19 +359,6 @@ public class MagicWebSocketInstance extends WebSocketListener {
} }
} }
public void requestOfferForSessionIdWithType(String sessionIdParam, String roomType) {
try {
String message = LoganSquare.serialize(webSocketConnectionHelper.getAssembledRequestOfferModel(sessionIdParam, roomType));
if (!connected || reconnecting) {
messagesQueue.add(message);
} else {
internalWebSocket.send(message);
}
} catch (IOException e) {
Log.e(TAG, "Failed to offer request. sessionIdParam: " + sessionIdParam + " roomType:" + roomType, e);
}
}
void sendBye() { void sendBye() {
if (connected) { if (connected) {
try { try {
@ -420,6 +409,10 @@ public class MagicWebSocketInstance extends WebSocketListener {
return signalingMessageReceiver; return signalingMessageReceiver;
} }
public SignalingMessageSender getSignalingMessageSender() {
return signalingMessageSender;
}
/** /**
* Temporary implementation of SignalingMessageReceiver until signaling related code is extracted to a Signaling * Temporary implementation of SignalingMessageReceiver until signaling related code is extracted to a Signaling
* class. * class.
@ -436,4 +429,11 @@ public class MagicWebSocketInstance extends WebSocketListener {
processSignalingMessage(message); processSignalingMessage(message);
} }
} }
private class ExternalSignalingMessageSender implements SignalingMessageSender {
@Override
public void send(NCSignalingMessage ncSignalingMessage) {
sendCallMessage(ncSignalingMessage);
}
}
} }

View File

@ -32,12 +32,13 @@ import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
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.WebSocketCommunicationEvent;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage; import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick; import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
import com.nextcloud.talk.models.json.signaling.NCIceCandidate; import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import com.nextcloud.talk.signaling.SignalingMessageReceiver; import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import com.nextcloud.talk.signaling.SignalingMessageSender;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.webrtc.AudioTrack; import org.webrtc.AudioTrack;
@ -78,6 +79,8 @@ public class PeerConnectionWrapper {
private final SignalingMessageReceiver signalingMessageReceiver; private final SignalingMessageReceiver signalingMessageReceiver;
private final WebRtcMessageListener webRtcMessageListener = new WebRtcMessageListener(); private final WebRtcMessageListener webRtcMessageListener = new WebRtcMessageListener();
private final SignalingMessageSender signalingMessageSender;
private List<IceCandidate> iceCandidates = new ArrayList<>(); private List<IceCandidate> iceCandidates = new ArrayList<>();
private PeerConnection peerConnection; private PeerConnection peerConnection;
private String sessionId; private String sessionId;
@ -101,7 +104,8 @@ public class PeerConnectionWrapper {
MediaConstraints mediaConstraints, MediaConstraints mediaConstraints,
String sessionId, String localSession, @Nullable MediaStream localStream, String sessionId, String localSession, @Nullable MediaStream localStream,
boolean isMCUPublisher, boolean hasMCU, String videoStreamType, boolean isMCUPublisher, boolean hasMCU, String videoStreamType,
SignalingMessageReceiver signalingMessageReceiver) { SignalingMessageReceiver signalingMessageReceiver,
SignalingMessageSender signalingMessageSender) {
Objects.requireNonNull(NextcloudTalkApplication.Companion.getSharedApplication()).getComponentApplication().inject(this); Objects.requireNonNull(NextcloudTalkApplication.Companion.getSharedApplication()).getComponentApplication().inject(this);
@ -122,6 +126,8 @@ public class PeerConnectionWrapper {
this.signalingMessageReceiver = signalingMessageReceiver; this.signalingMessageReceiver = signalingMessageReceiver;
this.signalingMessageReceiver.addListener(webRtcMessageListener, sessionId, videoStreamType); this.signalingMessageReceiver.addListener(webRtcMessageListener, sessionId, videoStreamType);
this.signalingMessageSender = signalingMessageSender;
if (peerConnection != null) { if (peerConnection != null) {
if (this.localStream != null) { if (this.localStream != null) {
List<String> localStreamIds = Collections.singletonList(this.localStream.getId()); List<String> localStreamIds = Collections.singletonList(this.localStream.getId());
@ -143,9 +149,10 @@ public class PeerConnectionWrapper {
} else if (hasMCU && this.videoStreamType.equals("video")) { } else if (hasMCU && this.videoStreamType.equals("video")) {
// If the connection type is "screen" the client sharing the screen will send an // If the connection type is "screen" the client sharing the screen will send an
// offer; offers should be requested only for videos. // offer; offers should be requested only for videos.
HashMap<String, String> hashMap = new HashMap<>(); // "to" property is not actually needed in the "requestoffer" signaling message, but it is used to
hashMap.put("sessionId", sessionId); // set the recipient session ID in the assembled call message.
EventBus.getDefault().post(new WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap)); NCSignalingMessage ncSignalingMessage = createBaseSignalingMessage("requestoffer");
signalingMessageSender.send(ncSignalingMessage);
} else if (!hasMCU && hasInitiated) { } else if (!hasMCU && hasInitiated) {
peerConnection.createOffer(magicSdpObserver, mediaConstraints); peerConnection.createOffer(magicSdpObserver, mediaConstraints);
} }
@ -269,6 +276,15 @@ public class PeerConnectionWrapper {
return false; return false;
} }
private NCSignalingMessage createBaseSignalingMessage(String type) {
NCSignalingMessage ncSignalingMessage = new NCSignalingMessage();
ncSignalingMessage.setTo(sessionId);
ncSignalingMessage.setRoomType(videoStreamType);
ncSignalingMessage.setType(type);
return ncSignalingMessage;
}
private class WebRtcMessageListener implements SignalingMessageReceiver.WebRtcMessageListener { private class WebRtcMessageListener implements SignalingMessageReceiver.WebRtcMessageListener {
public void onOffer(String sdp, String nick) { public void onOffer(String sdp, String nick) {
@ -425,12 +441,19 @@ public class PeerConnectionWrapper {
@Override @Override
public void onIceCandidate(IceCandidate iceCandidate) { public void onIceCandidate(IceCandidate iceCandidate) {
NCSignalingMessage ncSignalingMessage = createBaseSignalingMessage("candidate");
NCMessagePayload ncMessagePayload = new NCMessagePayload();
ncMessagePayload.setType("candidate");
NCIceCandidate ncIceCandidate = new NCIceCandidate(); NCIceCandidate ncIceCandidate = new NCIceCandidate();
ncIceCandidate.setSdpMid(iceCandidate.sdpMid); ncIceCandidate.setSdpMid(iceCandidate.sdpMid);
ncIceCandidate.setSdpMLineIndex(iceCandidate.sdpMLineIndex); ncIceCandidate.setSdpMLineIndex(iceCandidate.sdpMLineIndex);
ncIceCandidate.setCandidate(iceCandidate.sdp); ncIceCandidate.setCandidate(iceCandidate.sdp);
EventBus.getDefault().post(new SessionDescriptionSendEvent(null, sessionId, ncMessagePayload.setIceCandidate(ncIceCandidate);
"candidate", ncIceCandidate, videoStreamType));
ncSignalingMessage.setPayload(ncMessagePayload);
signalingMessageSender.send(ncSignalingMessage);
} }
@Override @Override
@ -484,6 +507,12 @@ public class PeerConnectionWrapper {
@Override @Override
public void onCreateSuccess(SessionDescription sessionDescription) { public void onCreateSuccess(SessionDescription sessionDescription) {
String type = sessionDescription.type.canonicalForm();
NCSignalingMessage ncSignalingMessage = createBaseSignalingMessage(type);
NCMessagePayload ncMessagePayload = new NCMessagePayload();
ncMessagePayload.setType(type);
SessionDescription sessionDescriptionWithPreferredCodec; SessionDescription sessionDescriptionWithPreferredCodec;
String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec
(sessionDescription.description, (sessionDescription.description,
@ -492,9 +521,11 @@ public class PeerConnectionWrapper {
sessionDescription.type, sessionDescription.type,
sessionDescriptionStringWithPreferredCodec); sessionDescriptionStringWithPreferredCodec);
ncMessagePayload.setSdp(sessionDescriptionWithPreferredCodec.description);
EventBus.getDefault().post(new SessionDescriptionSendEvent(sessionDescriptionWithPreferredCodec, sessionId, ncSignalingMessage.setPayload(ncMessagePayload);
sessionDescription.type.canonicalForm().toLowerCase(), null, videoStreamType));
signalingMessageSender.send(ncSignalingMessage);
if (peerConnection != null) { if (peerConnection != null) {
peerConnection.setLocalDescription(magicSdpObserver, sessionDescriptionWithPreferredCodec); peerConnection.setLocalDescription(magicSdpObserver, sessionDescriptionWithPreferredCodec);

View File

@ -25,7 +25,7 @@ import android.util.Log;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.models.json.signaling.NCMessageWrapper; import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import com.nextcloud.talk.models.json.websocket.ActorWebSocketMessage; import com.nextcloud.talk.models.json.websocket.ActorWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.AuthParametersWebSocketMessage; import com.nextcloud.talk.models.json.websocket.AuthParametersWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.AuthWebSocketMessage; import com.nextcloud.talk.models.json.websocket.AuthWebSocketMessage;
@ -33,11 +33,8 @@ import com.nextcloud.talk.models.json.websocket.CallOverallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.CallWebSocketMessage; import com.nextcloud.talk.models.json.websocket.CallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.HelloOverallWebSocketMessage; import com.nextcloud.talk.models.json.websocket.HelloOverallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.HelloWebSocketMessage; import com.nextcloud.talk.models.json.websocket.HelloWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.RequestOfferOverallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.RequestOfferSignalingMessage;
import com.nextcloud.talk.models.json.websocket.RoomOverallWebSocketMessage; import com.nextcloud.talk.models.json.websocket.RoomOverallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.RoomWebSocketMessage; import com.nextcloud.talk.models.json.websocket.RoomWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.SignalingDataWebSocketMessageForOffer;
import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.ApiUtils;
import java.util.HashMap; import java.util.HashMap;
@ -146,27 +143,7 @@ public class WebSocketConnectionHelper {
return roomOverallWebSocketMessage; return roomOverallWebSocketMessage;
} }
RequestOfferOverallWebSocketMessage getAssembledRequestOfferModel(String sessionId, String roomType) { CallOverallWebSocketMessage getAssembledCallMessageModel(NCSignalingMessage ncSignalingMessage) {
RequestOfferOverallWebSocketMessage requestOfferOverallWebSocketMessage = new RequestOfferOverallWebSocketMessage();
requestOfferOverallWebSocketMessage.setType("message");
RequestOfferSignalingMessage requestOfferSignalingMessage = new RequestOfferSignalingMessage();
ActorWebSocketMessage actorWebSocketMessage = new ActorWebSocketMessage();
actorWebSocketMessage.setType("session");
actorWebSocketMessage.setSessionId(sessionId);
requestOfferSignalingMessage.setActorWebSocketMessage(actorWebSocketMessage);
SignalingDataWebSocketMessageForOffer signalingDataWebSocketMessageForOffer = new SignalingDataWebSocketMessageForOffer();
signalingDataWebSocketMessageForOffer.setRoomType(roomType);
signalingDataWebSocketMessageForOffer.setType("requestoffer");
requestOfferSignalingMessage.setSignalingDataWebSocketMessageForOffer(signalingDataWebSocketMessageForOffer);
requestOfferOverallWebSocketMessage.setRequestOfferOverallWebSocketMessage(requestOfferSignalingMessage);
return requestOfferOverallWebSocketMessage;
}
CallOverallWebSocketMessage getAssembledCallMessageModel(NCMessageWrapper ncMessageWrapper) {
CallOverallWebSocketMessage callOverallWebSocketMessage = new CallOverallWebSocketMessage(); CallOverallWebSocketMessage callOverallWebSocketMessage = new CallOverallWebSocketMessage();
callOverallWebSocketMessage.setType("message"); callOverallWebSocketMessage.setType("message");
@ -174,9 +151,9 @@ public class WebSocketConnectionHelper {
ActorWebSocketMessage actorWebSocketMessage = new ActorWebSocketMessage(); ActorWebSocketMessage actorWebSocketMessage = new ActorWebSocketMessage();
actorWebSocketMessage.setType("session"); actorWebSocketMessage.setType("session");
actorWebSocketMessage.setSessionId(ncMessageWrapper.getSignalingMessage().getTo()); actorWebSocketMessage.setSessionId(ncSignalingMessage.getTo());
callWebSocketMessage.setRecipientWebSocketMessage(actorWebSocketMessage); callWebSocketMessage.setRecipientWebSocketMessage(actorWebSocketMessage);
callWebSocketMessage.setNcSignalingMessage(ncMessageWrapper.getSignalingMessage()); callWebSocketMessage.setNcSignalingMessage(ncSignalingMessage);
callOverallWebSocketMessage.setCallWebSocketMessage(callWebSocketMessage); callOverallWebSocketMessage.setCallWebSocketMessage(callWebSocketMessage);
return callOverallWebSocketMessage; return callOverallWebSocketMessage;

View File

@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE DO NOT TOUCH; GENERATED BY DRONE
<span class="mdl-layout-title">Lint Report: 112 warnings</span> <span class="mdl-layout-title">Lint Report: 111 warnings</span>