mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-13 15:54:59 +01:00
Merge pull request #2536 from nextcloud/add-listeners-for-signaling-messages
Add listeners for signaling messages
This commit is contained in:
commit
93137e1eeb
@ -78,7 +78,6 @@ import com.nextcloud.talk.models.json.participants.Participant;
|
||||
import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
|
||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
|
||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
|
||||
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
|
||||
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
|
||||
import com.nextcloud.talk.models.json.signaling.NCMessageWrapper;
|
||||
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
|
||||
@ -86,6 +85,7 @@ import com.nextcloud.talk.models.json.signaling.Signaling;
|
||||
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.SignalingSettingsOverall;
|
||||
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||
import com.nextcloud.talk.ui.dialog.AudioOutputDialog;
|
||||
import com.nextcloud.talk.users.UserManager;
|
||||
import com.nextcloud.talk.utils.ApiUtils;
|
||||
@ -116,14 +116,12 @@ import org.webrtc.CameraVideoCapturer;
|
||||
import org.webrtc.DefaultVideoDecoderFactory;
|
||||
import org.webrtc.DefaultVideoEncoderFactory;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.IceCandidate;
|
||||
import org.webrtc.Logging;
|
||||
import org.webrtc.MediaConstraints;
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.PeerConnection;
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
import org.webrtc.RendererCommon;
|
||||
import org.webrtc.SessionDescription;
|
||||
import org.webrtc.SurfaceTextureHelper;
|
||||
import org.webrtc.VideoCapturer;
|
||||
import org.webrtc.VideoSource;
|
||||
@ -175,11 +173,6 @@ import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_PARTICIPANT_PERMISS
|
||||
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID;
|
||||
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN;
|
||||
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY;
|
||||
import static com.nextcloud.talk.webrtc.Globals.JOB_ID;
|
||||
import static com.nextcloud.talk.webrtc.Globals.PARTICIPANTS_UPDATE;
|
||||
import static com.nextcloud.talk.webrtc.Globals.ROOM_TOKEN;
|
||||
import static com.nextcloud.talk.webrtc.Globals.UPDATE_ALL;
|
||||
import static com.nextcloud.talk.webrtc.Globals.UPDATE_IN_CALL;
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication.class)
|
||||
public class CallActivity extends CallBaseActivity {
|
||||
@ -266,6 +259,40 @@ public class CallActivity extends CallBaseActivity {
|
||||
|
||||
private SpotlightView spotlightView;
|
||||
|
||||
private InternalSignalingMessageReceiver internalSignalingMessageReceiver = new InternalSignalingMessageReceiver();
|
||||
private SignalingMessageReceiver signalingMessageReceiver;
|
||||
|
||||
private Map<String, SignalingMessageReceiver.CallParticipantMessageListener> callParticipantMessageListeners =
|
||||
new HashMap<>();
|
||||
|
||||
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener = new SignalingMessageReceiver.ParticipantListMessageListener() {
|
||||
|
||||
@Override
|
||||
public void onUsersInRoom(List<Participant> participants) {
|
||||
processUsersInRoom(participants);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onParticipantsUpdate(List<Participant> participants) {
|
||||
processUsersInRoom(participants);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllParticipantsUpdate(long inCall) {
|
||||
if (inCall == Participant.InCallFlags.DISCONNECTED) {
|
||||
Log.d(TAG, "A moderator ended the call for all.");
|
||||
hangup(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private SignalingMessageReceiver.OfferMessageListener offerMessageListener = new SignalingMessageReceiver.OfferMessageListener() {
|
||||
@Override
|
||||
public void onOffer(String sessionId, String roomType, String sdp, String nick) {
|
||||
getOrCreatePeerConnectionWrapperForSessionIdAndType(sessionId, roomType, false);
|
||||
}
|
||||
};
|
||||
|
||||
private ExternalSignalingServer externalSignalingServer;
|
||||
private MagicWebSocketInstance webSocketClient;
|
||||
private WebSocketConnectionHelper webSocketConnectionHelper;
|
||||
@ -1206,6 +1233,9 @@ public class CallActivity extends CallBaseActivity {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
signalingMessageReceiver.removeListener(participantListMessageListener);
|
||||
signalingMessageReceiver.removeListener(offerMessageListener);
|
||||
|
||||
if (localStream != null) {
|
||||
localStream.dispose();
|
||||
localStream = null;
|
||||
@ -1336,6 +1366,9 @@ public class CallActivity extends CallBaseActivity {
|
||||
if (hasExternalSignalingServer) {
|
||||
setupAndInitiateWebSocketsConnection();
|
||||
} else {
|
||||
signalingMessageReceiver = internalSignalingMessageReceiver;
|
||||
signalingMessageReceiver.addListener(participantListMessageListener);
|
||||
signalingMessageReceiver.addListener(offerMessageListener);
|
||||
joinRoomAndCall();
|
||||
}
|
||||
}
|
||||
@ -1534,6 +1567,11 @@ public class CallActivity extends CallBaseActivity {
|
||||
externalSignalingServer.getExternalSignalingServer(),
|
||||
conversationUser, externalSignalingServer.getExternalSignalingTicket(),
|
||||
TextUtils.isEmpty(credentials));
|
||||
// Although setupAndInitiateWebSocketsConnection could be called several times the web socket is
|
||||
// initialized just once, so the message receiver is also initialized just once.
|
||||
signalingMessageReceiver = webSocketClient.getSignalingMessageReceiver();
|
||||
signalingMessageReceiver.addListener(participantListMessageListener);
|
||||
signalingMessageReceiver.addListener(offerMessageListener);
|
||||
} else {
|
||||
if (webSocketClient.isConnected() && currentCallStatus == CallStatus.PUBLISHER_FAILED) {
|
||||
webSocketClient.restartWebSocket();
|
||||
@ -1577,42 +1615,6 @@ public class CallActivity extends CallBaseActivity {
|
||||
performCall();
|
||||
}
|
||||
break;
|
||||
case PARTICIPANTS_UPDATE:
|
||||
Log.d(TAG, "onMessageEvent 'participantsUpdate'");
|
||||
|
||||
// See MagicWebSocketInstance#onMessage in case "participants" how the 'updateParameters' are created
|
||||
Map<String, String> updateParameters = webSocketCommunicationEvent.getHashMap();
|
||||
|
||||
if (updateParameters == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
String updateRoomToken = updateParameters.get(ROOM_TOKEN);
|
||||
String updateAll = updateParameters.get(UPDATE_ALL);
|
||||
String updateInCall = updateParameters.get(UPDATE_IN_CALL);
|
||||
String jobId = updateParameters.get(JOB_ID);
|
||||
|
||||
if (roomToken.equals(updateRoomToken)) {
|
||||
if (updateAll != null && Boolean.parseBoolean(updateAll)) {
|
||||
if ("0".equals(updateInCall)) {
|
||||
Log.d(TAG, "Most probably a moderator ended the call for all.");
|
||||
hangup(true);
|
||||
}
|
||||
} else if (jobId != null) {
|
||||
// In that case a list of users for the room is passed.
|
||||
processUsersInRoom(
|
||||
(List<HashMap<String, Object>>) webSocketClient
|
||||
.getJobWithId(
|
||||
Integer.valueOf(jobId)));
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case "signalingMessage":
|
||||
Log.d(TAG, "onMessageEvent 'signalingMessage'");
|
||||
processMessage((NCSignalingMessage) webSocketClient.getJobWithId(
|
||||
Integer.valueOf(webSocketCommunicationEvent.getHashMap().get("jobId"))));
|
||||
break;
|
||||
case "peerReadyForRequestingOffer":
|
||||
Log.d(TAG, "onMessageEvent 'peerReadyForRequestingOffer'");
|
||||
webSocketClient.requestOfferForSessionIdWithType(
|
||||
@ -1652,85 +1654,16 @@ public class CallActivity extends CallBaseActivity {
|
||||
}
|
||||
|
||||
if ("usersInRoom".equals(messageType)) {
|
||||
processUsersInRoom((List<HashMap<String, Object>>) signaling.getMessageWrapper());
|
||||
internalSignalingMessageReceiver.process((List<Map<String, Object>>) signaling.getMessageWrapper());
|
||||
} else if ("message".equals(messageType)) {
|
||||
NCSignalingMessage ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(),
|
||||
NCSignalingMessage.class);
|
||||
processMessage(ncSignalingMessage);
|
||||
internalSignalingMessageReceiver.process(ncSignalingMessage);
|
||||
} else {
|
||||
Log.e(TAG, "unexpected message type when receiving signaling message");
|
||||
}
|
||||
}
|
||||
|
||||
private void processMessage(NCSignalingMessage ncSignalingMessage) {
|
||||
if ("video".equals(ncSignalingMessage.getRoomType()) || "screen".equals(ncSignalingMessage.getRoomType())) {
|
||||
String type = null;
|
||||
if (ncSignalingMessage.getPayload() != null && ncSignalingMessage.getPayload().getType() != null) {
|
||||
type = ncSignalingMessage.getPayload().getType();
|
||||
} else if (ncSignalingMessage.getType() != null) {
|
||||
type = ncSignalingMessage.getType();
|
||||
}
|
||||
|
||||
PeerConnectionWrapper peerConnectionWrapper = null;
|
||||
|
||||
if ("offer".equals(type)) {
|
||||
peerConnectionWrapper =
|
||||
getOrCreatePeerConnectionWrapperForSessionIdAndType(ncSignalingMessage.getFrom(),
|
||||
ncSignalingMessage.getRoomType(), false);
|
||||
} else {
|
||||
peerConnectionWrapper =
|
||||
getPeerConnectionWrapperForSessionIdAndType(ncSignalingMessage.getFrom(),
|
||||
ncSignalingMessage.getRoomType());
|
||||
}
|
||||
|
||||
if ("unshareScreen".equals(type) ||
|
||||
(("offer".equals(type) ||
|
||||
"answer".equals(type) ||
|
||||
"candidate".equals(type) ||
|
||||
"endOfCandidates".equals(type)) &&
|
||||
peerConnectionWrapper != null)) {
|
||||
switch (type) {
|
||||
case "unshareScreen":
|
||||
endPeerConnection(ncSignalingMessage.getFrom(), true);
|
||||
break;
|
||||
case "offer":
|
||||
case "answer":
|
||||
peerConnectionWrapper.setNick(ncSignalingMessage.getPayload().getNick());
|
||||
SessionDescription sessionDescriptionWithPreferredCodec;
|
||||
|
||||
String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec
|
||||
(ncSignalingMessage.getPayload().getSdp(),
|
||||
"H264", false);
|
||||
|
||||
sessionDescriptionWithPreferredCodec = new SessionDescription(
|
||||
SessionDescription.Type.fromCanonicalForm(type),
|
||||
sessionDescriptionStringWithPreferredCodec);
|
||||
|
||||
if (peerConnectionWrapper.getPeerConnection() != null) {
|
||||
peerConnectionWrapper.getPeerConnection().setRemoteDescription(
|
||||
peerConnectionWrapper.getMagicSdpObserver(),
|
||||
sessionDescriptionWithPreferredCodec);
|
||||
}
|
||||
break;
|
||||
case "candidate":
|
||||
NCIceCandidate ncIceCandidate = ncSignalingMessage.getPayload().getIceCandidate();
|
||||
IceCandidate iceCandidate = new IceCandidate(ncIceCandidate.getSdpMid(),
|
||||
ncIceCandidate.getSdpMLineIndex(),
|
||||
ncIceCandidate.getCandidate());
|
||||
peerConnectionWrapper.addCandidate(iceCandidate);
|
||||
break;
|
||||
case "endOfCandidates":
|
||||
peerConnectionWrapper.drainIceCandidates();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "unexpected RoomType while processing NCSignalingMessage");
|
||||
}
|
||||
}
|
||||
|
||||
private void hangup(boolean shutDownView) {
|
||||
Log.d(TAG, "hangup! shutDownView=" + shutDownView);
|
||||
if (shutDownView) {
|
||||
@ -1836,7 +1769,7 @@ public class CallActivity extends CallBaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void processUsersInRoom(List<HashMap<String, Object>> users) {
|
||||
private void processUsersInRoom(List<Participant> participants) {
|
||||
Log.d(TAG, "processUsersInRoom");
|
||||
List<String> newSessions = new ArrayList<>();
|
||||
Set<String> oldSessions = new HashSet<>();
|
||||
@ -1855,27 +1788,20 @@ public class CallActivity extends CallBaseActivity {
|
||||
|
||||
boolean isSelfInCall = false;
|
||||
|
||||
for (HashMap<String, Object> participant : users) {
|
||||
long inCallFlag = (long) participant.get("inCall");
|
||||
if (!participant.get("sessionId").equals(currentSessionId)) {
|
||||
for (Participant participant : participants) {
|
||||
long inCallFlag = participant.getInCall();
|
||||
if (!participant.getSessionId().equals(currentSessionId)) {
|
||||
Log.d(TAG, " inCallFlag of participant "
|
||||
+ participant.get("sessionId").toString().substring(0, 4)
|
||||
+ participant.getSessionId().substring(0, 4)
|
||||
+ " : "
|
||||
+ inCallFlag);
|
||||
|
||||
boolean isInCall = inCallFlag != 0;
|
||||
if (isInCall) {
|
||||
newSessions.add(participant.get("sessionId").toString());
|
||||
newSessions.add(participant.getSessionId());
|
||||
}
|
||||
|
||||
// The property is "userId" when not using the external signaling server and "userid" when using it.
|
||||
String userId = null;
|
||||
if (participant.get("userId") != null) {
|
||||
userId = participant.get("userId").toString();
|
||||
} else if (participant.get("userid") != null) {
|
||||
userId = participant.get("userid").toString();
|
||||
}
|
||||
userIdsBySessionId.put(participant.get("sessionId").toString(), userId);
|
||||
userIdsBySessionId.put(participant.getSessionId(), participant.getUserId());
|
||||
} else {
|
||||
Log.d(TAG, " inCallFlag of currentSessionId: " + inCallFlag);
|
||||
isSelfInCall = inCallFlag != 0;
|
||||
@ -2028,7 +1954,8 @@ public class CallActivity extends CallBaseActivity {
|
||||
localStream,
|
||||
true,
|
||||
true,
|
||||
type);
|
||||
type,
|
||||
signalingMessageReceiver);
|
||||
|
||||
} else if (hasMCU) {
|
||||
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
|
||||
@ -2039,7 +1966,8 @@ public class CallActivity extends CallBaseActivity {
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
type);
|
||||
type,
|
||||
signalingMessageReceiver);
|
||||
} else {
|
||||
if (!"screen".equals(type)) {
|
||||
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
|
||||
@ -2050,7 +1978,8 @@ public class CallActivity extends CallBaseActivity {
|
||||
localStream,
|
||||
false,
|
||||
false,
|
||||
type);
|
||||
type,
|
||||
signalingMessageReceiver);
|
||||
} else {
|
||||
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
|
||||
iceServers,
|
||||
@ -2060,12 +1989,22 @@ public class CallActivity extends CallBaseActivity {
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
type);
|
||||
type,
|
||||
signalingMessageReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
peerConnectionWrapperList.add(peerConnectionWrapper);
|
||||
|
||||
// Currently there is no separation between call participants and peer connections, so any video peer
|
||||
// connection (except the own publisher connection) is treated as a call participant.
|
||||
if (!publisher && "video".equals(type)) {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener callParticipantMessageListener =
|
||||
new CallActivityCallParticipantMessageListener(sessionId);
|
||||
callParticipantMessageListeners.put(sessionId, callParticipantMessageListener);
|
||||
signalingMessageReceiver.addListener(callParticipantMessageListener, sessionId);
|
||||
}
|
||||
|
||||
if (publisher) {
|
||||
startSendingNick();
|
||||
}
|
||||
@ -2098,6 +2037,11 @@ public class CallActivity extends CallBaseActivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!justScreen) {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener listener = callParticipantMessageListeners.remove(sessionId);
|
||||
signalingMessageReceiver.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeMediaStream(String sessionId, String videoStreamType) {
|
||||
@ -2671,6 +2615,35 @@ public class CallActivity extends CallBaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary implementation of SignalingMessageReceiver until signaling related code is extracted from CallActivity.
|
||||
*
|
||||
* All listeners are called in the main thread.
|
||||
*/
|
||||
private static class InternalSignalingMessageReceiver extends SignalingMessageReceiver {
|
||||
public void process(List<Map<String, Object>> users) {
|
||||
processUsersInRoom(users);
|
||||
}
|
||||
|
||||
public void process(NCSignalingMessage message) {
|
||||
processSignalingMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private class CallActivityCallParticipantMessageListener implements SignalingMessageReceiver.CallParticipantMessageListener {
|
||||
|
||||
private final String sessionId;
|
||||
|
||||
public CallActivityCallParticipantMessageListener(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnshareScreen() {
|
||||
endPeerConnection(sessionId, true);
|
||||
}
|
||||
}
|
||||
|
||||
private class MicrophoneButtonTouchListener implements View.OnTouchListener {
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper class to register and notify CallParticipantMessageListeners.
|
||||
*
|
||||
* This class is only meant for internal use by SignalingMessageReceiver; listeners must register themselves against
|
||||
* a SignalingMessageReceiver rather than against a CallParticipantMessageNotifier.
|
||||
*/
|
||||
class CallParticipantMessageNotifier {
|
||||
|
||||
/**
|
||||
* Helper class to associate a CallParticipantMessageListener with a session ID.
|
||||
*/
|
||||
private static class CallParticipantMessageListenerFrom {
|
||||
public final SignalingMessageReceiver.CallParticipantMessageListener listener;
|
||||
public final String sessionId;
|
||||
|
||||
private CallParticipantMessageListenerFrom(SignalingMessageReceiver.CallParticipantMessageListener listener,
|
||||
String sessionId) {
|
||||
this.listener = listener;
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<CallParticipantMessageListenerFrom> callParticipantMessageListenersFrom = new ArrayList<>();
|
||||
|
||||
public synchronized void addListener(SignalingMessageReceiver.CallParticipantMessageListener listener, String sessionId) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("CallParticipantMessageListener can not be null");
|
||||
}
|
||||
|
||||
if (sessionId == null) {
|
||||
throw new IllegalArgumentException("sessionId can not be null");
|
||||
}
|
||||
|
||||
removeListener(listener);
|
||||
|
||||
callParticipantMessageListenersFrom.add(new CallParticipantMessageListenerFrom(listener, sessionId));
|
||||
}
|
||||
|
||||
public synchronized void removeListener(SignalingMessageReceiver.CallParticipantMessageListener listener) {
|
||||
Iterator<CallParticipantMessageListenerFrom> it = callParticipantMessageListenersFrom.iterator();
|
||||
while (it.hasNext()) {
|
||||
CallParticipantMessageListenerFrom listenerFrom = it.next();
|
||||
|
||||
if (listenerFrom.listener == listener) {
|
||||
it.remove();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SignalingMessageReceiver.CallParticipantMessageListener> getListenersFor(String sessionId) {
|
||||
List<SignalingMessageReceiver.CallParticipantMessageListener> callParticipantMessageListeners =
|
||||
new ArrayList<>(callParticipantMessageListenersFrom.size());
|
||||
|
||||
for (CallParticipantMessageListenerFrom listenerFrom : callParticipantMessageListenersFrom) {
|
||||
if (listenerFrom.sessionId.equals(sessionId)) {
|
||||
callParticipantMessageListeners.add(listenerFrom.listener);
|
||||
}
|
||||
}
|
||||
|
||||
return callParticipantMessageListeners;
|
||||
}
|
||||
|
||||
public synchronized void notifyUnshareScreen(String sessionId) {
|
||||
for (SignalingMessageReceiver.CallParticipantMessageListener listener : getListenersFor(sessionId)) {
|
||||
listener.onUnshareScreen();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helper class to register and notify OfferMessageListeners.
|
||||
*
|
||||
* This class is only meant for internal use by SignalingMessageReceiver; listeners must register themselves against
|
||||
* a SignalingMessageReceiver rather than against an OfferMessageNotifier.
|
||||
*/
|
||||
class OfferMessageNotifier {
|
||||
|
||||
private final Set<SignalingMessageReceiver.OfferMessageListener> offerMessageListeners = new LinkedHashSet<>();
|
||||
|
||||
public synchronized void addListener(SignalingMessageReceiver.OfferMessageListener listener) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("OfferMessageListener can not be null");
|
||||
}
|
||||
|
||||
offerMessageListeners.add(listener);
|
||||
}
|
||||
|
||||
public synchronized void removeListener(SignalingMessageReceiver.OfferMessageListener listener) {
|
||||
offerMessageListeners.remove(listener);
|
||||
}
|
||||
|
||||
public synchronized void notifyOffer(String sessionId, String roomType, String sdp, String nick) {
|
||||
for (SignalingMessageReceiver.OfferMessageListener listener : new ArrayList<>(offerMessageListeners)) {
|
||||
listener.onOffer(sessionId, roomType, sdp, nick);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.participants.Participant;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helper class to register and notify ParticipantListMessageListeners.
|
||||
*
|
||||
* This class is only meant for internal use by SignalingMessageReceiver; listeners must register themselves against
|
||||
* a SignalingMessageReceiver rather than against a ParticipantListMessageNotifier.
|
||||
*/
|
||||
class ParticipantListMessageNotifier {
|
||||
|
||||
private final Set<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListeners = new LinkedHashSet<>();
|
||||
|
||||
public synchronized void addListener(SignalingMessageReceiver.ParticipantListMessageListener listener) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("participantListMessageListeners can not be null");
|
||||
}
|
||||
|
||||
participantListMessageListeners.add(listener);
|
||||
}
|
||||
|
||||
public synchronized void removeListener(SignalingMessageReceiver.ParticipantListMessageListener listener) {
|
||||
participantListMessageListeners.remove(listener);
|
||||
}
|
||||
|
||||
public synchronized void notifyUsersInRoom(List<Participant> participants) {
|
||||
for (SignalingMessageReceiver.ParticipantListMessageListener listener : new ArrayList<>(participantListMessageListeners)) {
|
||||
listener.onUsersInRoom(participants);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void notifyParticipantsUpdate(List<Participant> participants) {
|
||||
for (SignalingMessageReceiver.ParticipantListMessageListener listener : new ArrayList<>(participantListMessageListeners)) {
|
||||
listener.onParticipantsUpdate(participants);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void notifyAllParticipantsUpdate(long inCall) {
|
||||
for (SignalingMessageReceiver.ParticipantListMessageListener listener : new ArrayList<>(participantListMessageListeners)) {
|
||||
listener.onAllParticipantsUpdate(inCall);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,601 @@
|
||||
/*
|
||||
* 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.converters.EnumParticipantTypeConverter;
|
||||
import com.nextcloud.talk.models.json.participants.Participant;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Hub to register listeners for signaling messages of different kinds.
|
||||
*
|
||||
* In general, if a listener is added while an event is being handled the new listener will not receive that event.
|
||||
* An exception to that is adding a WebRtcMessageListener when handling an offer in an OfferMessageListener; in that
|
||||
* case the "onOffer()" method of the WebRtcMessageListener will be called for that same offer.
|
||||
*
|
||||
* Similarly, if a listener is removed while an event is being handled the removed listener will still receive that
|
||||
* event. Again the exception is removing a WebRtcMessageListener when handling an offer in an OfferMessageListener; in
|
||||
* that case the "onOffer()" method of the WebRtcMessageListener will not be called for that offer.
|
||||
*
|
||||
* Adding and removing listeners, as well as notifying them is internally synchronized. This should be kept in mind
|
||||
* if listeners are added or removed when handling an event to prevent deadlocks (nevertheless, just adding or
|
||||
* removing a listener in the same thread handling the event is fine, and in most cases it will be fine too if done
|
||||
* in a different thread, as long as the notifier thread is not forced to wait until the listener is added or removed).
|
||||
*
|
||||
* SignalingMessageReceiver does not fetch the signaling messages itself; subclasses must fetch them and then call
|
||||
* the appropriate protected methods to process the messages and notify the listeners.
|
||||
*/
|
||||
public abstract class SignalingMessageReceiver {
|
||||
|
||||
/**
|
||||
* Listener for participant list messages.
|
||||
*
|
||||
* The messages are implicitly bound to the room currently joined in the signaling server; listeners are expected
|
||||
* to know the current room.
|
||||
*/
|
||||
public interface ParticipantListMessageListener {
|
||||
|
||||
/**
|
||||
* List of all the participants in the room.
|
||||
*
|
||||
* This message is received only when the internal signaling server is used.
|
||||
*
|
||||
* The message is received periodically, and the participants may not have been modified since the last message.
|
||||
*
|
||||
* Only the following participant properties are set:
|
||||
* - inCall
|
||||
* - lastPing
|
||||
* - sessionId
|
||||
* - userId (if the participant is not a guest)
|
||||
*
|
||||
* "participantPermissions" is provided in the message (since Talk 13), but not currently set in the
|
||||
* participant. "publishingPermissions" was provided instead in Talk 12, but it was not used anywhere, so it is
|
||||
* ignored.
|
||||
*
|
||||
* @param participants all the participants (users and guests) in the room
|
||||
*/
|
||||
void onUsersInRoom(List<Participant> participants);
|
||||
|
||||
/**
|
||||
* List of all the participants in the call or the room (depending on what triggered the event).
|
||||
*
|
||||
* This message is received only when the external signaling server is used.
|
||||
*
|
||||
* The message is received when any participant changed, although what changed is not provided and should be
|
||||
* derived from the difference with previous messages. The list of participants may include only the
|
||||
* participants in the call (including those that just left it and thus triggered the event) or all the
|
||||
* participants currently in the room (participants in the room but not currently active, that is, without a
|
||||
* session, are not included).
|
||||
*
|
||||
* Only the following participant properties are set:
|
||||
* - inCall
|
||||
* - lastPing
|
||||
* - sessionId
|
||||
* - type
|
||||
* - userId (if the participant is not a guest)
|
||||
*
|
||||
* "nextcloudSessionId" is provided in the message (when the "inCall" property of any participant changed), but
|
||||
* not currently set in the participant.
|
||||
*
|
||||
* "participantPermissions" is provided in the message (since Talk 13), but not currently set in the
|
||||
* participant. "publishingPermissions" was provided instead in Talk 12, but it was not used anywhere, so it is
|
||||
* ignored.
|
||||
*
|
||||
* @param participants all the participants (users and guests) in the room
|
||||
*/
|
||||
void onParticipantsUpdate(List<Participant> participants);
|
||||
|
||||
/**
|
||||
* Update of the properties of all the participants in the room.
|
||||
*
|
||||
* This message is received only when the external signaling server is used.
|
||||
*
|
||||
* @param inCall the new value of the inCall property
|
||||
*/
|
||||
void onAllParticipantsUpdate(long inCall);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for call participant messages.
|
||||
*
|
||||
* The messages are bound to a specific call participant (or, rather, session), so each listener is expected to
|
||||
* handle messages only for a single call participant.
|
||||
*
|
||||
* Although "unshareScreen" is technically bound to a specific peer connection it is instead treated as a general
|
||||
* message on the call participant.
|
||||
*/
|
||||
public interface CallParticipantMessageListener {
|
||||
void onUnshareScreen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for WebRTC offers.
|
||||
*
|
||||
* Unlike the WebRtcMessageListener, which is bound to a specific peer connection, an OfferMessageListener listens
|
||||
* to all offer messages, no matter which peer connection they are bound to. This can be used, for example, to
|
||||
* create a new peer connection when a remote offer for which there is no previous connection is received.
|
||||
*
|
||||
* When an offer is received all OfferMessageListeners are notified before any WebRtcMessageListener is notified.
|
||||
*/
|
||||
public interface OfferMessageListener {
|
||||
void onOffer(String sessionId, String roomType, String sdp, String nick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for WebRTC messages.
|
||||
*
|
||||
* The messages are bound to a specific peer connection, so each listener is expected to handle messages only for
|
||||
* a single peer connection.
|
||||
*/
|
||||
public interface WebRtcMessageListener {
|
||||
void onOffer(String sdp, String nick);
|
||||
void onAnswer(String sdp, String nick);
|
||||
void onCandidate(String sdpMid, int sdpMLineIndex, String sdp);
|
||||
void onEndOfCandidates();
|
||||
}
|
||||
|
||||
private final ParticipantListMessageNotifier participantListMessageNotifier = new ParticipantListMessageNotifier();
|
||||
|
||||
private final CallParticipantMessageNotifier callParticipantMessageNotifier = new CallParticipantMessageNotifier();
|
||||
|
||||
private final OfferMessageNotifier offerMessageNotifier = new OfferMessageNotifier();
|
||||
|
||||
private final WebRtcMessageNotifier webRtcMessageNotifier = new WebRtcMessageNotifier();
|
||||
|
||||
/**
|
||||
* Adds a listener for participant list messages.
|
||||
*
|
||||
* A listener is expected to be added only once. If the same listener is added again it will be notified just once.
|
||||
*
|
||||
* @param listener the ParticipantListMessageListener
|
||||
*/
|
||||
public void addListener(ParticipantListMessageListener listener) {
|
||||
participantListMessageNotifier.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(ParticipantListMessageListener listener) {
|
||||
participantListMessageNotifier.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for call participant messages.
|
||||
*
|
||||
* A listener is expected to be added only once. If the same listener is added again it will no longer be notified
|
||||
* for the messages from the previous session ID.
|
||||
*
|
||||
* @param listener the CallParticipantMessageListener
|
||||
* @param sessionId the ID of the session that messages come from
|
||||
*/
|
||||
public void addListener(CallParticipantMessageListener listener, String sessionId) {
|
||||
callParticipantMessageNotifier.addListener(listener, sessionId);
|
||||
}
|
||||
|
||||
public void removeListener(CallParticipantMessageListener listener) {
|
||||
callParticipantMessageNotifier.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for all offer messages.
|
||||
*
|
||||
* A listener is expected to be added only once. If the same listener is added again it will be notified just once.
|
||||
*
|
||||
* @param listener the OfferMessageListener
|
||||
*/
|
||||
public void addListener(OfferMessageListener listener) {
|
||||
offerMessageNotifier.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(OfferMessageListener listener) {
|
||||
offerMessageNotifier.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for WebRTC messages from the given session ID and room type.
|
||||
*
|
||||
* A listener is expected to be added only once. If the same listener is added again it will no longer be notified
|
||||
* for the messages from the previous session ID or room type.
|
||||
*
|
||||
* @param listener the WebRtcMessageListener
|
||||
* @param sessionId the ID of the session that messages come from
|
||||
* @param roomType the room type that messages come from
|
||||
*/
|
||||
public void addListener(WebRtcMessageListener listener, String sessionId, String roomType) {
|
||||
webRtcMessageNotifier.addListener(listener, sessionId, roomType);
|
||||
}
|
||||
|
||||
public void removeListener(WebRtcMessageListener listener) {
|
||||
webRtcMessageNotifier.removeListener(listener);
|
||||
}
|
||||
|
||||
protected void processEvent(Map<String, Object> eventMap) {
|
||||
if (!"update".equals(eventMap.get("type")) || !"participants".equals(eventMap.get("target"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> updateMap;
|
||||
try {
|
||||
updateMap = (Map<String, Object>) eventMap.get("update");
|
||||
} catch (RuntimeException e) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateMap == null) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateMap.get("all") != null && Boolean.parseBoolean(updateMap.get("all").toString())) {
|
||||
processAllParticipantsUpdate(updateMap);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateMap.get("users") != null) {
|
||||
processParticipantsUpdate(updateMap);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void processAllParticipantsUpdate(Map<String, Object> updateMap) {
|
||||
// Message schema:
|
||||
// {
|
||||
// "type": "event",
|
||||
// "event": {
|
||||
// "target": "participants",
|
||||
// "type": "update",
|
||||
// "update": {
|
||||
// "roomid": #STRING#,
|
||||
// "incall": 0,
|
||||
// "all": true,
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
|
||||
long inCall;
|
||||
try {
|
||||
inCall = Long.parseLong(updateMap.get("inCall").toString());
|
||||
} catch (RuntimeException e) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
participantListMessageNotifier.notifyAllParticipantsUpdate(inCall);
|
||||
}
|
||||
|
||||
private void processParticipantsUpdate(Map<String, Object> updateMap) {
|
||||
// Message schema:
|
||||
// {
|
||||
// "type": "event",
|
||||
// "event": {
|
||||
// "target": "participants",
|
||||
// "type": "update",
|
||||
// "update": {
|
||||
// "roomid": #INTEGER#,
|
||||
// "users": [
|
||||
// {
|
||||
// "inCall": #INTEGER#,
|
||||
// "lastPing": #INTEGER#,
|
||||
// "sessionId": #STRING#,
|
||||
// "participantType": #INTEGER#,
|
||||
// "userId": #STRING#, // Optional
|
||||
// "nextcloudSessionId": #STRING#, // Optional
|
||||
// "participantPermissions": #INTEGER#, // Talk >= 13
|
||||
// },
|
||||
// ...
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// Note that "userId" in participants->update comes from the Nextcloud server, so it is "userId"; in other
|
||||
// messages, like room->join, it comes directly from the external signaling server, so it is "userid" instead.
|
||||
|
||||
List<Map<String, Object>> users;
|
||||
try {
|
||||
users = (List<Map<String, Object>>) updateMap.get("users");
|
||||
} catch (RuntimeException e) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
if (users == null) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
List<Participant> participants = new ArrayList<>(users.size());
|
||||
|
||||
for (Map<String, Object> user: users) {
|
||||
try {
|
||||
participants.add(getParticipantFromMessageMap(user));
|
||||
} catch (RuntimeException e) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
participantListMessageNotifier.notifyParticipantsUpdate(participants);
|
||||
}
|
||||
|
||||
protected void processUsersInRoom(List<Map<String, Object>> users) {
|
||||
// Message schema:
|
||||
// {
|
||||
// "type": "usersInRoom",
|
||||
// "data": [
|
||||
// {
|
||||
// "inCall": #INTEGER#,
|
||||
// "lastPing": #INTEGER#,
|
||||
// "roomId": #INTEGER#,
|
||||
// "sessionId": #STRING#,
|
||||
// "userId": #STRING#, // Always included, although it can be empty
|
||||
// "participantPermissions": #INTEGER#, // Talk >= 13
|
||||
// },
|
||||
// ...
|
||||
// ],
|
||||
// }
|
||||
|
||||
List<Participant> participants = new ArrayList<>(users.size());
|
||||
|
||||
for (Map<String, Object> user: users) {
|
||||
try {
|
||||
participants.add(getParticipantFromMessageMap(user));
|
||||
} catch (RuntimeException e) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
participantListMessageNotifier.notifyUsersInRoom(participants);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and initializes a Participant from the data in the given map.
|
||||
*
|
||||
* Maps from internal and external signaling server messages can be used. Nevertheless, besides the differences
|
||||
* between the messages and the optional properties, it is expected that the message is correct and the given data
|
||||
* is parseable. Broken messages (for example, a string instead of an integer for "inCall" or a missing
|
||||
* "sessionId") may cause a RuntimeException to be thrown.
|
||||
*
|
||||
* @param participantMap the map with the participant data
|
||||
* @return the Participant
|
||||
*/
|
||||
private Participant getParticipantFromMessageMap(Map<String, Object> participantMap) {
|
||||
Participant participant = new Participant();
|
||||
|
||||
participant.setInCall(Long.parseLong(participantMap.get("inCall").toString()));
|
||||
participant.setLastPing(Long.parseLong(participantMap.get("lastPing").toString()));
|
||||
participant.setSessionId(participantMap.get("sessionId").toString());
|
||||
|
||||
if (participantMap.get("userId") != null && !participantMap.get("userId").toString().isEmpty()) {
|
||||
participant.setUserId(participantMap.get("userId").toString());
|
||||
}
|
||||
|
||||
// Only in external signaling messages
|
||||
if (participantMap.get("participantType") != null) {
|
||||
int participantTypeInt = Integer.parseInt(participantMap.get("participantType").toString());
|
||||
|
||||
EnumParticipantTypeConverter converter = new EnumParticipantTypeConverter();
|
||||
participant.setType(converter.getFromInt(participantTypeInt));
|
||||
}
|
||||
|
||||
return participant;
|
||||
}
|
||||
|
||||
protected void processSignalingMessage(NCSignalingMessage signalingMessage) {
|
||||
// Note that in the internal signaling server message "data" is the String representation of a JSON
|
||||
// object, although it is already decoded when used here.
|
||||
|
||||
String type = signalingMessage.getType();
|
||||
|
||||
String sessionId = signalingMessage.getFrom();
|
||||
String roomType = signalingMessage.getRoomType();
|
||||
|
||||
// "unshareScreen" messages are directly sent to the screen peer connection when the internal signaling
|
||||
// server is used, and to the room when the external signaling server is used. However, the (relevant) data
|
||||
// of the received message ("from" and "type") is the same in both cases.
|
||||
if ("unshareScreen".equals(type)) {
|
||||
// Message schema (external signaling server):
|
||||
// {
|
||||
// "type": "message",
|
||||
// "message": {
|
||||
// "sender": {
|
||||
// ...
|
||||
// },
|
||||
// "data": {
|
||||
// "roomType": "screen",
|
||||
// "type": "unshareScreen",
|
||||
// "from": #STRING#,
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// Message schema (internal signaling server):
|
||||
// {
|
||||
// "type": "message",
|
||||
// "data": {
|
||||
// "to": #STRING#,
|
||||
// "sid": #STRING#,
|
||||
// "broadcaster": #STRING#,
|
||||
// "roomType": "screen",
|
||||
// "type": "unshareScreen",
|
||||
// "from": #STRING#,
|
||||
// },
|
||||
// }
|
||||
|
||||
callParticipantMessageNotifier.notifyUnshareScreen(sessionId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ("offer".equals(type)) {
|
||||
// Message schema (external signaling server):
|
||||
// {
|
||||
// "type": "message",
|
||||
// "message": {
|
||||
// "sender": {
|
||||
// ...
|
||||
// },
|
||||
// "data": {
|
||||
// "to": #STRING#,
|
||||
// "from": #STRING#,
|
||||
// "type": "offer",
|
||||
// "roomType": #STRING#, // "video" or "screen"
|
||||
// "payload": {
|
||||
// "type": "offer",
|
||||
// "sdp": #STRING#,
|
||||
// },
|
||||
// "sid": #STRING#, // external signaling server >= 0.5.0
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// Message schema (internal signaling server):
|
||||
// {
|
||||
// "type": "message",
|
||||
// "data": {
|
||||
// "to": #STRING#,
|
||||
// "sid": #STRING#,
|
||||
// "roomType": #STRING#, // "video" or "screen"
|
||||
// "type": "offer",
|
||||
// "payload": {
|
||||
// "type": "offer",
|
||||
// "sdp": #STRING#,
|
||||
// "nick": #STRING#, // Optional
|
||||
// },
|
||||
// "from": #STRING#,
|
||||
// },
|
||||
// }
|
||||
|
||||
NCMessagePayload payload = signalingMessage.getPayload();
|
||||
if (payload == null) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
String sdp = payload.getSdp();
|
||||
String nick = payload.getNick();
|
||||
|
||||
// If "processSignalingMessage" is called with two offers from two different threads it is possible,
|
||||
// although extremely unlikely, that the WebRtcMessageListeners for the second offer are notified before the
|
||||
// WebRtcMessageListeners for the first offer. This should not be a problem, though, so for simplicity
|
||||
// the statements are not synchronized.
|
||||
offerMessageNotifier.notifyOffer(sessionId, roomType, sdp, nick);
|
||||
webRtcMessageNotifier.notifyOffer(sessionId, roomType, sdp, nick);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ("answer".equals(type)) {
|
||||
// Message schema: same as offers, but with type "answer".
|
||||
|
||||
NCMessagePayload payload = signalingMessage.getPayload();
|
||||
if (payload == null) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
String sdp = payload.getSdp();
|
||||
String nick = payload.getNick();
|
||||
|
||||
webRtcMessageNotifier.notifyAnswer(sessionId, roomType, sdp, nick);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ("candidate".equals(type)) {
|
||||
// Message schema (external signaling server):
|
||||
// {
|
||||
// "type": "message",
|
||||
// "message": {
|
||||
// "sender": {
|
||||
// ...
|
||||
// },
|
||||
// "data": {
|
||||
// "to": #STRING#,
|
||||
// "from": #STRING#,
|
||||
// "type": "candidate",
|
||||
// "roomType": #STRING#, // "video" or "screen"
|
||||
// "payload": {
|
||||
// "candidate": {
|
||||
// "candidate": #STRING#,
|
||||
// "sdpMid": #STRING#,
|
||||
// "sdpMLineIndex": #INTEGER#,
|
||||
// },
|
||||
// },
|
||||
// "sid": #STRING#, // external signaling server >= 0.5.0
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// Message schema (internal signaling server):
|
||||
// {
|
||||
// "type": "message",
|
||||
// "data": {
|
||||
// "to": #STRING#,
|
||||
// "sid": #STRING#,
|
||||
// "roomType": #STRING#, // "video" or "screen"
|
||||
// "type": "candidate",
|
||||
// "payload": {
|
||||
// "candidate": {
|
||||
// "candidate": #STRING#,
|
||||
// "sdpMid": #STRING#,
|
||||
// "sdpMLineIndex": #INTEGER#,
|
||||
// },
|
||||
// },
|
||||
// "from": #STRING#,
|
||||
// },
|
||||
// }
|
||||
|
||||
NCMessagePayload payload = signalingMessage.getPayload();
|
||||
if (payload == null) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
NCIceCandidate ncIceCandidate = payload.getIceCandidate();
|
||||
if (ncIceCandidate == null) {
|
||||
// Broken message, this should not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
webRtcMessageNotifier.notifyCandidate(sessionId,
|
||||
roomType,
|
||||
ncIceCandidate.getSdpMid(),
|
||||
ncIceCandidate.getSdpMLineIndex(),
|
||||
ncIceCandidate.getCandidate());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ("endOfCandidates".equals(type)) {
|
||||
webRtcMessageNotifier.notifyEndOfCandidates(sessionId, roomType);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper class to register and notify WebRtcMessageListeners.
|
||||
*
|
||||
* This class is only meant for internal use by SignalingMessageReceiver; listeners must register themselves against
|
||||
* a SignalingMessageReceiver rather than against a WebRtcMessageNotifier.
|
||||
*/
|
||||
class WebRtcMessageNotifier {
|
||||
|
||||
/**
|
||||
* Helper class to associate a WebRtcMessageListener with a session ID and room type.
|
||||
*/
|
||||
private static class WebRtcMessageListenerFrom {
|
||||
public final SignalingMessageReceiver.WebRtcMessageListener listener;
|
||||
public final String sessionId;
|
||||
public final String roomType;
|
||||
|
||||
private WebRtcMessageListenerFrom(SignalingMessageReceiver.WebRtcMessageListener listener,
|
||||
String sessionId,
|
||||
String roomType) {
|
||||
this.listener = listener;
|
||||
this.sessionId = sessionId;
|
||||
this.roomType = roomType;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<WebRtcMessageListenerFrom> webRtcMessageListenersFrom = new ArrayList<>();
|
||||
|
||||
public synchronized void addListener(SignalingMessageReceiver.WebRtcMessageListener listener, String sessionId, String roomType) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("WebRtcMessageListener can not be null");
|
||||
}
|
||||
|
||||
if (sessionId == null) {
|
||||
throw new IllegalArgumentException("sessionId can not be null");
|
||||
}
|
||||
|
||||
if (roomType == null) {
|
||||
throw new IllegalArgumentException("roomType can not be null");
|
||||
}
|
||||
|
||||
removeListener(listener);
|
||||
|
||||
webRtcMessageListenersFrom.add(new WebRtcMessageListenerFrom(listener, sessionId, roomType));
|
||||
}
|
||||
|
||||
public synchronized void removeListener(SignalingMessageReceiver.WebRtcMessageListener listener) {
|
||||
Iterator<WebRtcMessageListenerFrom> it = webRtcMessageListenersFrom.iterator();
|
||||
while (it.hasNext()) {
|
||||
WebRtcMessageListenerFrom listenerFrom = it.next();
|
||||
|
||||
if (listenerFrom.listener == listener) {
|
||||
it.remove();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SignalingMessageReceiver.WebRtcMessageListener> getListenersFor(String sessionId, String roomType) {
|
||||
List<SignalingMessageReceiver.WebRtcMessageListener> webRtcMessageListeners =
|
||||
new ArrayList<>(webRtcMessageListenersFrom.size());
|
||||
|
||||
for (WebRtcMessageListenerFrom listenerFrom : webRtcMessageListenersFrom) {
|
||||
if (listenerFrom.sessionId.equals(sessionId) && listenerFrom.roomType.equals(roomType)) {
|
||||
webRtcMessageListeners.add(listenerFrom.listener);
|
||||
}
|
||||
}
|
||||
|
||||
return webRtcMessageListeners;
|
||||
}
|
||||
|
||||
public synchronized void notifyOffer(String sessionId, String roomType, String sdp, String nick) {
|
||||
for (SignalingMessageReceiver.WebRtcMessageListener listener : getListenersFor(sessionId, roomType)) {
|
||||
listener.onOffer(sdp, nick);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void notifyAnswer(String sessionId, String roomType, String sdp, String nick) {
|
||||
for (SignalingMessageReceiver.WebRtcMessageListener listener : getListenersFor(sessionId, roomType)) {
|
||||
listener.onAnswer(sdp, nick);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void notifyCandidate(String sessionId, String roomType, String sdpMid, int sdpMLineIndex, String sdp) {
|
||||
for (SignalingMessageReceiver.WebRtcMessageListener listener : getListenersFor(sessionId, roomType)) {
|
||||
listener.onCandidate(sdpMid, sdpMLineIndex, sdp);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void notifyEndOfCandidates(String sessionId, String roomType) {
|
||||
for (SignalingMessageReceiver.WebRtcMessageListener listener : getListenersFor(sessionId, roomType)) {
|
||||
listener.onEndOfCandidates();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
* 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.utils
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class MagicMap : ConcurrentHashMap<Int, Any>() {
|
||||
fun add(element: Any): Int {
|
||||
val key = System.identityHashCode(element)
|
||||
super.put(key, element)
|
||||
return key
|
||||
}
|
||||
}
|
@ -2,17 +2,6 @@ package com.nextcloud.talk.webrtc;
|
||||
|
||||
public class Globals {
|
||||
public static final String ROOM_TOKEN = "roomToken";
|
||||
public static final String JOB_ID = "jobId";
|
||||
|
||||
public static final String PARTICIPANTS_UPDATE = "participantsUpdate";
|
||||
|
||||
public static final String TARGET_PARTICIPANTS = "participants";
|
||||
|
||||
public static final String EVENT_TYPE = "type";
|
||||
public static final String EVENT_TYPE_UPDATE = "update";
|
||||
|
||||
public static final String UPDATE_ALL = "all";
|
||||
public static final String UPDATE_IN_CALL = "incall";
|
||||
public static final String UPDATE_ROOM_ID = "roomid";
|
||||
public static final String UPDATE_USERS = "users";
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ import com.nextcloud.talk.models.json.websocket.ErrorOverallWebSocketMessage;
|
||||
import com.nextcloud.talk.models.json.websocket.EventOverallWebSocketMessage;
|
||||
import com.nextcloud.talk.models.json.websocket.HelloResponseOverallWebSocketMessage;
|
||||
import com.nextcloud.talk.models.json.websocket.JoinedRoomOverallWebSocketMessage;
|
||||
import com.nextcloud.talk.utils.MagicMap;
|
||||
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
@ -65,16 +65,8 @@ import okio.ByteString;
|
||||
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.ActorType.GUESTS;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.ActorType.USERS;
|
||||
import static com.nextcloud.talk.webrtc.Globals.EVENT_TYPE;
|
||||
import static com.nextcloud.talk.webrtc.Globals.EVENT_TYPE_UPDATE;
|
||||
import static com.nextcloud.talk.webrtc.Globals.JOB_ID;
|
||||
import static com.nextcloud.talk.webrtc.Globals.PARTICIPANTS_UPDATE;
|
||||
import static com.nextcloud.talk.webrtc.Globals.ROOM_TOKEN;
|
||||
import static com.nextcloud.talk.webrtc.Globals.TARGET_PARTICIPANTS;
|
||||
import static com.nextcloud.talk.webrtc.Globals.UPDATE_ALL;
|
||||
import static com.nextcloud.talk.webrtc.Globals.UPDATE_IN_CALL;
|
||||
import static com.nextcloud.talk.webrtc.Globals.UPDATE_ROOM_ID;
|
||||
import static com.nextcloud.talk.webrtc.Globals.UPDATE_USERS;
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication.class)
|
||||
public class MagicWebSocketInstance extends WebSocketListener {
|
||||
@ -98,7 +90,6 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
||||
private boolean connected;
|
||||
private WebSocketConnectionHelper webSocketConnectionHelper;
|
||||
private WebSocket internalWebSocket;
|
||||
private MagicMap magicMap;
|
||||
private String connectionUrl;
|
||||
|
||||
private String currentRoomToken;
|
||||
@ -109,6 +100,8 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
||||
|
||||
private List<String> messagesQueue = new ArrayList<>();
|
||||
|
||||
private final ExternalSignalingMessageReceiver signalingMessageReceiver = new ExternalSignalingMessageReceiver();
|
||||
|
||||
MagicWebSocketInstance(User conversationUser, String connectionUrl, String webSocketTicket) {
|
||||
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
|
||||
|
||||
@ -117,7 +110,6 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
||||
this.webSocketTicket = webSocketTicket;
|
||||
this.webSocketConnectionHelper = new WebSocketConnectionHelper();
|
||||
this.usersHashMap = new HashMap<>();
|
||||
magicMap = new MagicMap();
|
||||
|
||||
connected = false;
|
||||
eventBus.register(this);
|
||||
@ -276,45 +268,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
||||
}
|
||||
break;
|
||||
case TARGET_PARTICIPANTS:
|
||||
if (EVENT_TYPE_UPDATE.equals(eventOverallWebSocketMessage.getEventMap().get(EVENT_TYPE))) {
|
||||
HashMap<String, String> refreshChatHashMap = new HashMap<>();
|
||||
HashMap<String, Object> updateEventMap = (HashMap<String, Object>) eventOverallWebSocketMessage.getEventMap().get(EVENT_TYPE_UPDATE);
|
||||
|
||||
if (updateEventMap == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (updateEventMap.containsKey(UPDATE_ROOM_ID)) {
|
||||
Object updateRoomId = updateEventMap.get(UPDATE_ROOM_ID);
|
||||
if (updateRoomId != null) {
|
||||
refreshChatHashMap.put(ROOM_TOKEN,
|
||||
(String) updateEventMap.get(UPDATE_ROOM_ID));
|
||||
}
|
||||
}
|
||||
|
||||
if (updateEventMap.containsKey(UPDATE_USERS)) {
|
||||
Object updateUsers = updateEventMap.get(UPDATE_USERS);
|
||||
if (updateUsers != null) {
|
||||
refreshChatHashMap.put(JOB_ID, Integer.toString(magicMap.add(updateUsers)));
|
||||
}
|
||||
}
|
||||
|
||||
if (updateEventMap.containsKey(UPDATE_IN_CALL)) {
|
||||
Object inCall = updateEventMap.get(UPDATE_IN_CALL);
|
||||
if (inCall != null) {
|
||||
refreshChatHashMap.put(UPDATE_IN_CALL, Long.toString((Long) inCall));
|
||||
}
|
||||
}
|
||||
|
||||
if (updateEventMap.containsKey(UPDATE_ALL)) {
|
||||
Object updateAll = updateEventMap.get(UPDATE_ALL);
|
||||
if (updateAll != null) {
|
||||
refreshChatHashMap.put(UPDATE_ALL, Boolean.toString((Boolean) updateAll));
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.post(new WebSocketCommunicationEvent(PARTICIPANTS_UPDATE, refreshChatHashMap));
|
||||
}
|
||||
signalingMessageReceiver.process(eventOverallWebSocketMessage.getEventMap());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -326,11 +280,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
||||
ncSignalingMessage.setFrom(callOverallWebSocketMessage.getCallWebSocketMessage().getSenderWebSocketMessage().getSessionId());
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(ncSignalingMessage.getFrom())) {
|
||||
HashMap<String, String> messageHashMap = new HashMap<>();
|
||||
messageHashMap.put(JOB_ID, Integer.toString(magicMap.add(ncSignalingMessage)));
|
||||
eventBus.post(new WebSocketCommunicationEvent("signalingMessage", messageHashMap));
|
||||
}
|
||||
signalingMessageReceiver.process(ncSignalingMessage);
|
||||
break;
|
||||
case "bye":
|
||||
connected = false;
|
||||
@ -407,12 +357,6 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
||||
}
|
||||
}
|
||||
|
||||
public Object getJobWithId(Integer id) {
|
||||
Object copyJob = magicMap.get(id);
|
||||
magicMap.remove(id);
|
||||
return copyJob;
|
||||
}
|
||||
|
||||
public void requestOfferForSessionIdWithType(String sessionIdParam, String roomType) {
|
||||
try {
|
||||
String message = LoganSquare.serialize(webSocketConnectionHelper.getAssembledRequestOfferModel(sessionIdParam, roomType));
|
||||
@ -471,4 +415,25 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
||||
restartWebSocket();
|
||||
}
|
||||
}
|
||||
|
||||
public SignalingMessageReceiver getSignalingMessageReceiver() {
|
||||
return signalingMessageReceiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary implementation of SignalingMessageReceiver until signaling related code is extracted to a Signaling
|
||||
* class.
|
||||
*
|
||||
* All listeners are called in the WebSocket reader thread. This thread should be the same as long as the
|
||||
* WebSocket stays connected, but it may change whenever it is connected again.
|
||||
*/
|
||||
private static class ExternalSignalingMessageReceiver extends SignalingMessageReceiver {
|
||||
public void process(Map<String, Object> eventMap) {
|
||||
processEvent(eventMap);
|
||||
}
|
||||
|
||||
public void process(NCSignalingMessage message) {
|
||||
processSignalingMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import com.nextcloud.talk.events.WebSocketCommunicationEvent;
|
||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
|
||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
|
||||
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
|
||||
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.webrtc.AudioTrack;
|
||||
@ -74,6 +75,9 @@ public class PeerConnectionWrapper {
|
||||
|
||||
private static final String TAG = PeerConnectionWrapper.class.getCanonicalName();
|
||||
|
||||
private final SignalingMessageReceiver signalingMessageReceiver;
|
||||
private final WebRtcMessageListener webRtcMessageListener = new WebRtcMessageListener();
|
||||
|
||||
private List<IceCandidate> iceCandidates = new ArrayList<>();
|
||||
private PeerConnection peerConnection;
|
||||
private String sessionId;
|
||||
@ -96,7 +100,8 @@ public class PeerConnectionWrapper {
|
||||
List<PeerConnection.IceServer> iceServerList,
|
||||
MediaConstraints mediaConstraints,
|
||||
String sessionId, String localSession, @Nullable MediaStream localStream,
|
||||
boolean isMCUPublisher, boolean hasMCU, String videoStreamType) {
|
||||
boolean isMCUPublisher, boolean hasMCU, String videoStreamType,
|
||||
SignalingMessageReceiver signalingMessageReceiver) {
|
||||
|
||||
Objects.requireNonNull(NextcloudTalkApplication.Companion.getSharedApplication()).getComponentApplication().inject(this);
|
||||
|
||||
@ -114,6 +119,9 @@ public class PeerConnectionWrapper {
|
||||
configuration.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
|
||||
peerConnection = peerConnectionFactory.createPeerConnection(configuration, new MagicPeerConnectionObserver());
|
||||
|
||||
this.signalingMessageReceiver = signalingMessageReceiver;
|
||||
this.signalingMessageReceiver.addListener(webRtcMessageListener, sessionId, videoStreamType);
|
||||
|
||||
if (peerConnection != null) {
|
||||
if (this.localStream != null) {
|
||||
List<String> localStreamIds = Collections.singletonList(this.localStream.getId());
|
||||
@ -150,6 +158,8 @@ public class PeerConnectionWrapper {
|
||||
}
|
||||
|
||||
public void removePeerConnection() {
|
||||
signalingMessageReceiver.removeListener(webRtcMessageListener);
|
||||
|
||||
if (dataChannel != null) {
|
||||
dataChannel.dispose();
|
||||
dataChannel = null;
|
||||
@ -167,7 +177,7 @@ public class PeerConnectionWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
public void drainIceCandidates() {
|
||||
private void drainIceCandidates() {
|
||||
|
||||
if (peerConnection != null) {
|
||||
for (IceCandidate iceCandidate : iceCandidates) {
|
||||
@ -178,11 +188,7 @@ public class PeerConnectionWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
public MagicSdpObserver getMagicSdpObserver() {
|
||||
return magicSdpObserver;
|
||||
}
|
||||
|
||||
public void addCandidate(IceCandidate iceCandidate) {
|
||||
private void addCandidate(IceCandidate iceCandidate) {
|
||||
if (peerConnection != null && peerConnection.getRemoteDescription() != null) {
|
||||
peerConnection.addIceCandidate(iceCandidate);
|
||||
} else {
|
||||
@ -222,10 +228,6 @@ public class PeerConnectionWrapper {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getNick() {
|
||||
if (!TextUtils.isEmpty(nick)) {
|
||||
return nick;
|
||||
@ -234,7 +236,7 @@ public class PeerConnectionWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
public void setNick(String nick) {
|
||||
private void setNick(String nick) {
|
||||
this.nick = nick;
|
||||
}
|
||||
|
||||
@ -267,6 +269,43 @@ public class PeerConnectionWrapper {
|
||||
return false;
|
||||
}
|
||||
|
||||
private class WebRtcMessageListener implements SignalingMessageReceiver.WebRtcMessageListener {
|
||||
|
||||
public void onOffer(String sdp, String nick) {
|
||||
onOfferOrAnswer("offer", sdp, nick);
|
||||
}
|
||||
|
||||
public void onAnswer(String sdp, String nick) {
|
||||
onOfferOrAnswer("answer", sdp, nick);
|
||||
}
|
||||
|
||||
private void onOfferOrAnswer(String type, String sdp, String nick) {
|
||||
setNick(nick);
|
||||
|
||||
SessionDescription sessionDescriptionWithPreferredCodec;
|
||||
|
||||
boolean isAudio = false;
|
||||
String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec(sdp, "H264", isAudio);
|
||||
|
||||
sessionDescriptionWithPreferredCodec = new SessionDescription(
|
||||
SessionDescription.Type.fromCanonicalForm(type),
|
||||
sessionDescriptionStringWithPreferredCodec);
|
||||
|
||||
if (getPeerConnection() != null) {
|
||||
getPeerConnection().setRemoteDescription(magicSdpObserver, sessionDescriptionWithPreferredCodec);
|
||||
}
|
||||
}
|
||||
|
||||
public void onCandidate(String sdpMid, int sdpMLineIndex, String sdp) {
|
||||
IceCandidate iceCandidate = new IceCandidate(sdpMid, sdpMLineIndex, sdp);
|
||||
addCandidate(iceCandidate);
|
||||
}
|
||||
|
||||
public void onEndOfCandidates() {
|
||||
drainIceCandidates();
|
||||
}
|
||||
}
|
||||
|
||||
private class MagicDataChannelObserver implements DataChannel.Observer {
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
public class SignalingMessageReceiverCallParticipantTest {
|
||||
|
||||
private SignalingMessageReceiver signalingMessageReceiver;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
|
||||
// protected methods.
|
||||
signalingMessageReceiver = new SignalingMessageReceiver() {
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddCallParticipantMessageListenerWithNullListener() {
|
||||
Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
signalingMessageReceiver.addListener(null, "theSessionId");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddCallParticipantMessageListenerWithNullSessionId() {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
|
||||
Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, null);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallParticipantMessageUnshareScreen() {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("unshareScreen");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedCallParticipantMessageListener, only()).onUnshareScreen();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallParticipantMessageSeveralListenersSameFrom() {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener1 =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener2 =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener1, "theSessionId");
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener2, "theSessionId");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("unshareScreen");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedCallParticipantMessageListener1, only()).onUnshareScreen();
|
||||
verify(mockedCallParticipantMessageListener2, only()).onUnshareScreen();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallParticipantMessageNotMatchingSessionId() {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("notMatchingSessionId");
|
||||
signalingMessage.setType("unshareScreen");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantMessageListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallParticipantMessageAfterRemovingListener() {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
|
||||
signalingMessageReceiver.removeListener(mockedCallParticipantMessageListener);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("unshareScreen");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantMessageListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallParticipantMessageAfterRemovingSingleListenerOfSeveral() {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener1 =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener2 =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener3 =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener1, "theSessionId");
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener2, "theSessionId");
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener3, "theSessionId");
|
||||
signalingMessageReceiver.removeListener(mockedCallParticipantMessageListener2);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("unshareScreen");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedCallParticipantMessageListener1, only()).onUnshareScreen();
|
||||
verify(mockedCallParticipantMessageListener3, only()).onUnshareScreen();
|
||||
verifyNoInteractions(mockedCallParticipantMessageListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallParticipantMessageAfterAddingListenerAgainForDifferentFrom() {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId2");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("unshareScreen");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantMessageListener);
|
||||
|
||||
signalingMessage.setFrom("theSessionId2");
|
||||
signalingMessage.setType("unshareScreen");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedCallParticipantMessageListener, only()).onUnshareScreen();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddCallParticipantMessageListenerWhenHandlingCallParticipantMessage() {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener1 =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener2 =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener2, "theSessionId");
|
||||
return null;
|
||||
}).when(mockedCallParticipantMessageListener1).onUnshareScreen();
|
||||
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener1, "theSessionId");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("unshareScreen");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedCallParticipantMessageListener1, only()).onUnshareScreen();
|
||||
verifyNoInteractions(mockedCallParticipantMessageListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveCallParticipantMessageListenerWhenHandlingCallParticipantMessage() {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener1 =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener2 =
|
||||
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.removeListener(mockedCallParticipantMessageListener2);
|
||||
return null;
|
||||
}).when(mockedCallParticipantMessageListener1).onUnshareScreen();
|
||||
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener1, "theSessionId");
|
||||
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener2, "theSessionId");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("unshareScreen");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
InOrder inOrder = inOrder(mockedCallParticipantMessageListener1, mockedCallParticipantMessageListener2);
|
||||
|
||||
inOrder.verify(mockedCallParticipantMessageListener1).onUnshareScreen();
|
||||
inOrder.verify(mockedCallParticipantMessageListener2).onUnshareScreen();
|
||||
}
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
/*
|
||||
* 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.NCMessagePayload;
|
||||
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
public class SignalingMessageReceiverOfferTest {
|
||||
|
||||
private SignalingMessageReceiver signalingMessageReceiver;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
|
||||
// protected methods.
|
||||
signalingMessageReceiver = new SignalingMessageReceiver() {
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddOfferMessageListenerWithNullListener() {
|
||||
Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
signalingMessageReceiver.addListener((SignalingMessageReceiver.OfferMessageListener) null);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOfferMessage() {
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedOfferMessageListener, only()).onOffer("theSessionId", "theRoomType", "theSdp", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOfferMessageWithNick() {
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedOfferMessageListener, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOfferMessageAfterRemovingListener() {
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener);
|
||||
signalingMessageReceiver.removeListener(mockedOfferMessageListener);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verifyNoInteractions(mockedOfferMessageListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOfferMessageAfterRemovingSingleListenerOfSeveral() {
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener1 =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener2 =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener3 =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener1);
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener2);
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener3);
|
||||
signalingMessageReceiver.removeListener(mockedOfferMessageListener2);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedOfferMessageListener1, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
verify(mockedOfferMessageListener3, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
verifyNoInteractions(mockedOfferMessageListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOfferMessageAfterAddingListenerAgain() {
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener);
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedOfferMessageListener, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddOfferMessageListenerWhenHandlingOffer() {
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener1 =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener2 =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener2);
|
||||
return null;
|
||||
}).when(mockedOfferMessageListener1).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener1);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedOfferMessageListener1, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
verifyNoInteractions(mockedOfferMessageListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveOfferMessageListenerWhenHandlingOffer() {
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener1 =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener2 =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.removeListener(mockedOfferMessageListener2);
|
||||
return null;
|
||||
}).when(mockedOfferMessageListener1).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener1);
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener2);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
InOrder inOrder = inOrder(mockedOfferMessageListener1, mockedOfferMessageListener2);
|
||||
|
||||
inOrder.verify(mockedOfferMessageListener1).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
inOrder.verify(mockedOfferMessageListener2).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
}
|
||||
}
|
@ -0,0 +1,466 @@
|
||||
/*
|
||||
* 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.participants.Participant;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
public class SignalingMessageReceiverParticipantListTest {
|
||||
|
||||
private SignalingMessageReceiver signalingMessageReceiver;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
|
||||
// protected methods.
|
||||
signalingMessageReceiver = new SignalingMessageReceiver() {
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddParticipantListMessageListenerWithNullListener() {
|
||||
Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
signalingMessageReceiver.addListener((SignalingMessageReceiver.ParticipantListMessageListener) null);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternalSignalingParticipantListMessageUsersInRoom() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
|
||||
|
||||
List<Map<String, Object>> users = new ArrayList<>(2);
|
||||
Map<String, Object> user1 = new HashMap<>();
|
||||
user1.put("inCall", 7);
|
||||
user1.put("lastPing", 4815);
|
||||
user1.put("roomId", 108);
|
||||
user1.put("sessionId", "theSessionId1");
|
||||
user1.put("userId", "theUserId");
|
||||
// If "participantPermissions" is set in any of the participants all the other participants in the message
|
||||
// would have it too. But for test simplicity, and as it is not relevant for the processing, in this test it
|
||||
// is included only in one of the participants.
|
||||
user1.put("participantPermissions", 42);
|
||||
users.add(user1);
|
||||
Map<String, Object> user2 = new HashMap<>();
|
||||
user2.put("inCall", 0);
|
||||
user2.put("lastPing", 162342);
|
||||
user2.put("roomId", 108);
|
||||
user2.put("sessionId", "theSessionId2");
|
||||
user2.put("userId", "");
|
||||
users.add(user2);
|
||||
signalingMessageReceiver.processUsersInRoom(users);
|
||||
|
||||
List<Participant> expectedParticipantList = new ArrayList<>();
|
||||
Participant expectedParticipant1 = new Participant();
|
||||
expectedParticipant1.setInCall(Participant.InCallFlags.IN_CALL | Participant.InCallFlags.WITH_AUDIO | Participant.InCallFlags.WITH_VIDEO);
|
||||
expectedParticipant1.setLastPing(4815);
|
||||
expectedParticipant1.setSessionId("theSessionId1");
|
||||
expectedParticipant1.setUserId("theUserId");
|
||||
expectedParticipantList.add(expectedParticipant1);
|
||||
|
||||
Participant expectedParticipant2 = new Participant();
|
||||
expectedParticipant2.setInCall(Participant.InCallFlags.DISCONNECTED);
|
||||
expectedParticipant2.setLastPing(162342);
|
||||
expectedParticipant2.setSessionId("theSessionId2");
|
||||
expectedParticipantList.add(expectedParticipant2);
|
||||
|
||||
verify(mockedParticipantListMessageListener, only()).onUsersInRoom(expectedParticipantList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternalSignalingParticipantListMessageAfterRemovingListener() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
|
||||
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener);
|
||||
|
||||
List<Map<String, Object>> users = new ArrayList<>(1);
|
||||
Map<String, Object> user = new HashMap<>();
|
||||
user.put("inCall", 0);
|
||||
user.put("lastPing", 4815);
|
||||
user.put("roomId", 108);
|
||||
user.put("sessionId", "theSessionId");
|
||||
user.put("userId", "");
|
||||
users.add(user);
|
||||
signalingMessageReceiver.processUsersInRoom(users);
|
||||
|
||||
verifyNoInteractions(mockedParticipantListMessageListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternalSignalingParticipantListMessageAfterRemovingSingleListenerOfSeveral() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener3 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener3);
|
||||
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
|
||||
|
||||
List<Map<String, Object>> users = new ArrayList<>(1);
|
||||
Map<String, Object> user = new HashMap<>();
|
||||
user.put("inCall", 0);
|
||||
user.put("lastPing", 4815);
|
||||
user.put("roomId", 108);
|
||||
user.put("sessionId", "theSessionId");
|
||||
user.put("userId", "");
|
||||
users.add(user);
|
||||
signalingMessageReceiver.processUsersInRoom(users);
|
||||
|
||||
List<Participant> expectedParticipantList = new ArrayList<>();
|
||||
Participant expectedParticipant = new Participant();
|
||||
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
|
||||
expectedParticipant.setLastPing(4815);
|
||||
expectedParticipant.setSessionId("theSessionId");
|
||||
expectedParticipantList.add(expectedParticipant);
|
||||
|
||||
verify(mockedParticipantListMessageListener1, only()).onUsersInRoom(expectedParticipantList);
|
||||
verify(mockedParticipantListMessageListener3, only()).onUsersInRoom(expectedParticipantList);
|
||||
verifyNoInteractions(mockedParticipantListMessageListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternalSignalingParticipantListMessageAfterAddingListenerAgain() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
|
||||
|
||||
List<Map<String, Object>> users = new ArrayList<>(1);
|
||||
Map<String, Object> user = new HashMap<>();
|
||||
user.put("inCall", 0);
|
||||
user.put("lastPing", 4815);
|
||||
user.put("roomId", 108);
|
||||
user.put("sessionId", "theSessionId");
|
||||
user.put("userId", "");
|
||||
users.add(user);
|
||||
signalingMessageReceiver.processUsersInRoom(users);
|
||||
|
||||
List<Participant> expectedParticipantList = new ArrayList<>();
|
||||
Participant expectedParticipant = new Participant();
|
||||
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
|
||||
expectedParticipant.setLastPing(4815);
|
||||
expectedParticipant.setSessionId("theSessionId");
|
||||
expectedParticipantList.add(expectedParticipant);
|
||||
|
||||
verify(mockedParticipantListMessageListener, only()).onUsersInRoom(expectedParticipantList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddParticipantListMessageListenerWhenHandlingInternalSignalingParticipantListMessage() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
List<Participant> expectedParticipantList = new ArrayList<>();
|
||||
Participant expectedParticipant = new Participant();
|
||||
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
|
||||
expectedParticipant.setLastPing(4815);
|
||||
expectedParticipant.setSessionId("theSessionId");
|
||||
expectedParticipantList.add(expectedParticipant);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
|
||||
return null;
|
||||
}).when(mockedParticipantListMessageListener1).onUsersInRoom(expectedParticipantList);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
|
||||
|
||||
List<Map<String, Object>> users = new ArrayList<>(1);
|
||||
Map<String, Object> user = new HashMap<>();
|
||||
user.put("inCall", 0);
|
||||
user.put("lastPing", 4815);
|
||||
user.put("roomId", 108);
|
||||
user.put("sessionId", "theSessionId");
|
||||
user.put("userId", "");
|
||||
users.add(user);
|
||||
signalingMessageReceiver.processUsersInRoom(users);
|
||||
|
||||
verify(mockedParticipantListMessageListener1, only()).onUsersInRoom(expectedParticipantList);
|
||||
verifyNoInteractions(mockedParticipantListMessageListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveParticipantListMessageListenerWhenHandlingInternalSignalingParticipantListMessage() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
List<Participant> expectedParticipantList = new ArrayList<>();
|
||||
Participant expectedParticipant = new Participant();
|
||||
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
|
||||
expectedParticipant.setLastPing(4815);
|
||||
expectedParticipant.setSessionId("theSessionId");
|
||||
expectedParticipantList.add(expectedParticipant);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
|
||||
return null;
|
||||
}).when(mockedParticipantListMessageListener1).onUsersInRoom(expectedParticipantList);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
|
||||
|
||||
List<Map<String, Object>> users = new ArrayList<>(1);
|
||||
Map<String, Object> user = new HashMap<>();
|
||||
user.put("inCall", 0);
|
||||
user.put("lastPing", 4815);
|
||||
user.put("roomId", 108);
|
||||
user.put("sessionId", "theSessionId");
|
||||
user.put("userId", "");
|
||||
users.add(user);
|
||||
signalingMessageReceiver.processUsersInRoom(users);
|
||||
|
||||
InOrder inOrder = inOrder(mockedParticipantListMessageListener1, mockedParticipantListMessageListener2);
|
||||
|
||||
inOrder.verify(mockedParticipantListMessageListener1).onUsersInRoom(expectedParticipantList);
|
||||
inOrder.verify(mockedParticipantListMessageListener2).onUsersInRoom(expectedParticipantList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalSignalingParticipantListMessageParticipantsUpdate() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
|
||||
|
||||
Map<String, Object> eventMap = new HashMap<>();
|
||||
eventMap.put("type", "update");
|
||||
eventMap.put("target", "participants");
|
||||
Map<String, Object> updateMap = new HashMap<>();
|
||||
updateMap.put("roomId", 108);
|
||||
List<Map<String, Object>> users = new ArrayList<>(2);
|
||||
Map<String, Object> user1 = new HashMap<>();
|
||||
user1.put("inCall", 7);
|
||||
user1.put("lastPing", 4815);
|
||||
user1.put("sessionId", "theSessionId1");
|
||||
user1.put("participantType", 3);
|
||||
user1.put("userId", "theUserId");
|
||||
// If "nextcloudSessionId" or "participantPermissions" is set in any of the participants all the other
|
||||
// participants in the message would have them too. But for test simplicity, and as it is not relevant for
|
||||
// the processing, in this test they are included only in one of the participants.
|
||||
user1.put("nextcloudSessionId", "theNextcloudSessionId");
|
||||
user1.put("participantPermissions", 42);
|
||||
users.add(user1);
|
||||
Map<String, Object> user2 = new HashMap<>();
|
||||
user2.put("inCall", 0);
|
||||
user2.put("lastPing", 162342);
|
||||
user2.put("sessionId", "theSessionId2");
|
||||
user2.put("participantType", 4);
|
||||
users.add(user2);
|
||||
updateMap.put("users", users);
|
||||
eventMap.put("update", updateMap);
|
||||
signalingMessageReceiver.processEvent(eventMap);
|
||||
|
||||
List<Participant> expectedParticipantList = new ArrayList<>(2);
|
||||
Participant expectedParticipant1 = new Participant();
|
||||
expectedParticipant1.setInCall(Participant.InCallFlags.IN_CALL | Participant.InCallFlags.WITH_AUDIO | Participant.InCallFlags.WITH_VIDEO);
|
||||
expectedParticipant1.setLastPing(4815);
|
||||
expectedParticipant1.setSessionId("theSessionId1");
|
||||
expectedParticipant1.setType(Participant.ParticipantType.USER);
|
||||
expectedParticipant1.setUserId("theUserId");
|
||||
expectedParticipantList.add(expectedParticipant1);
|
||||
|
||||
Participant expectedParticipant2 = new Participant();
|
||||
expectedParticipant2.setInCall(Participant.InCallFlags.DISCONNECTED);
|
||||
expectedParticipant2.setLastPing(162342);
|
||||
expectedParticipant2.setSessionId("theSessionId2");
|
||||
expectedParticipant2.setType(Participant.ParticipantType.GUEST);
|
||||
expectedParticipantList.add(expectedParticipant2);
|
||||
|
||||
verify(mockedParticipantListMessageListener, only()).onParticipantsUpdate(expectedParticipantList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalSignalingParticipantListMessageAllParticipantsUpdate() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
|
||||
|
||||
Map<String, Object> eventMap = new HashMap<>();
|
||||
eventMap.put("type", "update");
|
||||
eventMap.put("target", "participants");
|
||||
Map<String, Object> updateMap = new HashMap<>();
|
||||
updateMap.put("roomId", 108);
|
||||
updateMap.put("all", true);
|
||||
updateMap.put("inCall", 0);
|
||||
eventMap.put("update", updateMap);
|
||||
signalingMessageReceiver.processEvent(eventMap);
|
||||
|
||||
verify(mockedParticipantListMessageListener, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalSignalingParticipantListMessageAfterRemovingListener() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
|
||||
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener);
|
||||
|
||||
Map<String, Object> eventMap = new HashMap<>();
|
||||
eventMap.put("type", "update");
|
||||
eventMap.put("target", "participants");
|
||||
HashMap<String, Object> updateMap = new HashMap<>();
|
||||
updateMap.put("roomId", 108);
|
||||
updateMap.put("all", true);
|
||||
updateMap.put("inCall", 0);
|
||||
eventMap.put("update", updateMap);
|
||||
signalingMessageReceiver.processEvent(eventMap);
|
||||
|
||||
verifyNoInteractions(mockedParticipantListMessageListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalSignalingParticipantListMessageAfterRemovingSingleListenerOfSeveral() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener3 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener3);
|
||||
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
|
||||
|
||||
Map<String, Object> eventMap = new HashMap<>();
|
||||
eventMap.put("type", "update");
|
||||
eventMap.put("target", "participants");
|
||||
HashMap<String, Object> updateMap = new HashMap<>();
|
||||
updateMap.put("roomId", 108);
|
||||
updateMap.put("all", true);
|
||||
updateMap.put("inCall", 0);
|
||||
eventMap.put("update", updateMap);
|
||||
signalingMessageReceiver.processEvent(eventMap);
|
||||
|
||||
verify(mockedParticipantListMessageListener1, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
|
||||
verify(mockedParticipantListMessageListener3, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
|
||||
verifyNoInteractions(mockedParticipantListMessageListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalSignalingParticipantListMessageAfterAddingListenerAgain() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
|
||||
|
||||
Map<String, Object> eventMap = new HashMap<>();
|
||||
eventMap.put("type", "update");
|
||||
eventMap.put("target", "participants");
|
||||
HashMap<String, Object> updateMap = new HashMap<>();
|
||||
updateMap.put("roomId", 108);
|
||||
updateMap.put("all", true);
|
||||
updateMap.put("inCall", 0);
|
||||
eventMap.put("update", updateMap);
|
||||
signalingMessageReceiver.processEvent(eventMap);
|
||||
|
||||
verify(mockedParticipantListMessageListener, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddParticipantListMessageListenerWhenHandlingExternalSignalingParticipantListMessage() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
|
||||
return null;
|
||||
}).when(mockedParticipantListMessageListener1).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
|
||||
|
||||
Map<String, Object> eventMap = new HashMap<>();
|
||||
eventMap.put("type", "update");
|
||||
eventMap.put("target", "participants");
|
||||
HashMap<String, Object> updateMap = new HashMap<>();
|
||||
updateMap.put("roomId", 108);
|
||||
updateMap.put("all", true);
|
||||
updateMap.put("inCall", 0);
|
||||
eventMap.put("update", updateMap);
|
||||
signalingMessageReceiver.processEvent(eventMap);
|
||||
|
||||
verify(mockedParticipantListMessageListener1, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
|
||||
verifyNoInteractions(mockedParticipantListMessageListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveParticipantListMessageListenerWhenHandlingExternalSignalingParticipantListMessage() {
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
|
||||
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
|
||||
return null;
|
||||
}).when(mockedParticipantListMessageListener1).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
|
||||
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
|
||||
|
||||
Map<String, Object> eventMap = new HashMap<>();
|
||||
eventMap.put("type", "update");
|
||||
eventMap.put("target", "participants");
|
||||
HashMap<String, Object> updateMap = new HashMap<>();
|
||||
updateMap.put("roomId", 108);
|
||||
updateMap.put("all", true);
|
||||
updateMap.put("inCall", 0);
|
||||
eventMap.put("update", updateMap);
|
||||
signalingMessageReceiver.processEvent(eventMap);
|
||||
|
||||
InOrder inOrder = inOrder(mockedParticipantListMessageListener1, mockedParticipantListMessageListener2);
|
||||
|
||||
inOrder.verify(mockedParticipantListMessageListener1).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
|
||||
inOrder.verify(mockedParticipantListMessageListener2).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.NCMessagePayload;
|
||||
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
public class SignalingMessageReceiverTest {
|
||||
|
||||
private SignalingMessageReceiver signalingMessageReceiver;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
|
||||
// protected methods.
|
||||
signalingMessageReceiver = new SignalingMessageReceiver() {
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOfferWithOfferAndWebRtcMessageListeners() {
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener);
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
InOrder inOrder = inOrder(mockedOfferMessageListener, mockedWebRtcMessageListener);
|
||||
|
||||
inOrder.verify(mockedOfferMessageListener).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
inOrder.verify(mockedWebRtcMessageListener).onOffer("theSdp", "theNick");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddWebRtcMessageListenerWhenHandlingOffer() {
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
return null;
|
||||
}).when(mockedOfferMessageListener).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
InOrder inOrder = inOrder(mockedOfferMessageListener, mockedWebRtcMessageListener);
|
||||
|
||||
inOrder.verify(mockedOfferMessageListener).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
inOrder.verify(mockedWebRtcMessageListener).onOffer("theSdp", "theNick");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveWebRtcMessageListenerWhenHandlingOffer() {
|
||||
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
|
||||
mock(SignalingMessageReceiver.OfferMessageListener.class);
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.removeListener(mockedWebRtcMessageListener);
|
||||
return null;
|
||||
}).when(mockedOfferMessageListener).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
|
||||
signalingMessageReceiver.addListener(mockedOfferMessageListener);
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedOfferMessageListener, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
|
||||
verifyNoInteractions(mockedWebRtcMessageListener);
|
||||
}
|
||||
}
|
@ -0,0 +1,366 @@
|
||||
/*
|
||||
* 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.NCIceCandidate;
|
||||
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
|
||||
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
public class SignalingMessageReceiverWebRtcTest {
|
||||
|
||||
private SignalingMessageReceiver signalingMessageReceiver;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
|
||||
// protected methods.
|
||||
signalingMessageReceiver = new SignalingMessageReceiver() {
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddWebRtcMessageListenerWithNullListener() {
|
||||
Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
signalingMessageReceiver.addListener(null, "theSessionId", "theRoomType");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddWebRtcMessageListenerWithNullSessionId() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, null, "theRoomType");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddWebRtcMessageListenerWithNullRoomType() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", null);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageOffer() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedWebRtcMessageListener, only()).onOffer("theSdp", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageOfferWithNick() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("offer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("offer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedWebRtcMessageListener, only()).onOffer("theSdp", "theNick");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageAnswer() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("answer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("answer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedWebRtcMessageListener, only()).onAnswer("theSdp", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageAnswerWithNick() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("answer");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
messagePayload.setType("answer");
|
||||
messagePayload.setSdp("theSdp");
|
||||
messagePayload.setNick("theNick");
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedWebRtcMessageListener, only()).onAnswer("theSdp", "theNick");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageCandidate() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("candidate");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||
NCIceCandidate iceCandidate = new NCIceCandidate();
|
||||
iceCandidate.setSdpMid("theSdpMid");
|
||||
iceCandidate.setSdpMLineIndex(42);
|
||||
iceCandidate.setCandidate("theSdp");
|
||||
messagePayload.setIceCandidate(iceCandidate);
|
||||
signalingMessage.setPayload(messagePayload);
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedWebRtcMessageListener, only()).onCandidate("theSdpMid", 42, "theSdp");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageEndOfCandidates() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("endOfCandidates");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedWebRtcMessageListener, only()).onEndOfCandidates();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageSeveralListenersSameFrom() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener1 =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener2 =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener1, "theSessionId", "theRoomType");
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener2, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("endOfCandidates");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedWebRtcMessageListener1, only()).onEndOfCandidates();
|
||||
verify(mockedWebRtcMessageListener2, only()).onEndOfCandidates();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageNotMatchingSessionId() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("notMatchingSessionId");
|
||||
signalingMessage.setType("endOfCandidates");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verifyNoInteractions(mockedWebRtcMessageListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageNotMatchingRoomType() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("endOfCandidates");
|
||||
signalingMessage.setRoomType("notMatchingRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verifyNoInteractions(mockedWebRtcMessageListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageAfterRemovingListener() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
signalingMessageReceiver.removeListener(mockedWebRtcMessageListener);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("endOfCandidates");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verifyNoInteractions(mockedWebRtcMessageListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageAfterRemovingSingleListenerOfSeveral() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener1 =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener2 =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener3 =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener1, "theSessionId", "theRoomType");
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener2, "theSessionId", "theRoomType");
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener3, "theSessionId", "theRoomType");
|
||||
signalingMessageReceiver.removeListener(mockedWebRtcMessageListener2);
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("endOfCandidates");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedWebRtcMessageListener1, only()).onEndOfCandidates();
|
||||
verify(mockedWebRtcMessageListener3, only()).onEndOfCandidates();
|
||||
verifyNoInteractions(mockedWebRtcMessageListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebRtcMessageAfterAddingListenerAgainForDifferentFrom() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId2", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("endOfCandidates");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verifyNoInteractions(mockedWebRtcMessageListener);
|
||||
|
||||
signalingMessage.setFrom("theSessionId2");
|
||||
signalingMessage.setType("endOfCandidates");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedWebRtcMessageListener, only()).onEndOfCandidates();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddWebRtcMessageListenerWhenHandlingWebRtcMessage() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener1 =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener2 =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener2, "theSessionId", "theRoomType");
|
||||
return null;
|
||||
}).when(mockedWebRtcMessageListener1).onEndOfCandidates();
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener1, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("endOfCandidates");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
verify(mockedWebRtcMessageListener1, only()).onEndOfCandidates();
|
||||
verifyNoInteractions(mockedWebRtcMessageListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveWebRtcMessageListenerWhenHandlingWebRtcMessage() {
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener1 =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener2 =
|
||||
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
|
||||
|
||||
doAnswer((invocation) -> {
|
||||
signalingMessageReceiver.removeListener(mockedWebRtcMessageListener2);
|
||||
return null;
|
||||
}).when(mockedWebRtcMessageListener1).onEndOfCandidates();
|
||||
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener1, "theSessionId", "theRoomType");
|
||||
signalingMessageReceiver.addListener(mockedWebRtcMessageListener2, "theSessionId", "theRoomType");
|
||||
|
||||
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||
signalingMessage.setFrom("theSessionId");
|
||||
signalingMessage.setType("endOfCandidates");
|
||||
signalingMessage.setRoomType("theRoomType");
|
||||
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||
|
||||
InOrder inOrder = inOrder(mockedWebRtcMessageListener1, mockedWebRtcMessageListener2);
|
||||
|
||||
inOrder.verify(mockedWebRtcMessageListener1).onEndOfCandidates();
|
||||
inOrder.verify(mockedWebRtcMessageListener2).onEndOfCandidates();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user