mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-14 16:25:05 +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.participants.ParticipantsOverall;
|
||||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
|
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
|
||||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
|
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
|
||||||
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
|
|
||||||
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
|
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
|
||||||
import com.nextcloud.talk.models.json.signaling.NCMessageWrapper;
|
import com.nextcloud.talk.models.json.signaling.NCMessageWrapper;
|
||||||
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
|
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.SignalingOverall;
|
||||||
import com.nextcloud.talk.models.json.signaling.settings.IceServer;
|
import com.nextcloud.talk.models.json.signaling.settings.IceServer;
|
||||||
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
|
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
|
||||||
|
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||||
import com.nextcloud.talk.ui.dialog.AudioOutputDialog;
|
import com.nextcloud.talk.ui.dialog.AudioOutputDialog;
|
||||||
import com.nextcloud.talk.users.UserManager;
|
import com.nextcloud.talk.users.UserManager;
|
||||||
import com.nextcloud.talk.utils.ApiUtils;
|
import com.nextcloud.talk.utils.ApiUtils;
|
||||||
@ -116,14 +116,12 @@ import org.webrtc.CameraVideoCapturer;
|
|||||||
import org.webrtc.DefaultVideoDecoderFactory;
|
import org.webrtc.DefaultVideoDecoderFactory;
|
||||||
import org.webrtc.DefaultVideoEncoderFactory;
|
import org.webrtc.DefaultVideoEncoderFactory;
|
||||||
import org.webrtc.EglBase;
|
import org.webrtc.EglBase;
|
||||||
import org.webrtc.IceCandidate;
|
|
||||||
import org.webrtc.Logging;
|
import org.webrtc.Logging;
|
||||||
import org.webrtc.MediaConstraints;
|
import org.webrtc.MediaConstraints;
|
||||||
import org.webrtc.MediaStream;
|
import org.webrtc.MediaStream;
|
||||||
import org.webrtc.PeerConnection;
|
import org.webrtc.PeerConnection;
|
||||||
import org.webrtc.PeerConnectionFactory;
|
import org.webrtc.PeerConnectionFactory;
|
||||||
import org.webrtc.RendererCommon;
|
import org.webrtc.RendererCommon;
|
||||||
import org.webrtc.SessionDescription;
|
|
||||||
import org.webrtc.SurfaceTextureHelper;
|
import org.webrtc.SurfaceTextureHelper;
|
||||||
import org.webrtc.VideoCapturer;
|
import org.webrtc.VideoCapturer;
|
||||||
import org.webrtc.VideoSource;
|
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_ID;
|
||||||
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN;
|
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.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)
|
@AutoInjector(NextcloudTalkApplication.class)
|
||||||
public class CallActivity extends CallBaseActivity {
|
public class CallActivity extends CallBaseActivity {
|
||||||
@ -266,6 +259,40 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
|
|
||||||
private SpotlightView spotlightView;
|
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 ExternalSignalingServer externalSignalingServer;
|
||||||
private MagicWebSocketInstance webSocketClient;
|
private MagicWebSocketInstance webSocketClient;
|
||||||
private WebSocketConnectionHelper webSocketConnectionHelper;
|
private WebSocketConnectionHelper webSocketConnectionHelper;
|
||||||
@ -1206,6 +1233,9 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
signalingMessageReceiver.removeListener(participantListMessageListener);
|
||||||
|
signalingMessageReceiver.removeListener(offerMessageListener);
|
||||||
|
|
||||||
if (localStream != null) {
|
if (localStream != null) {
|
||||||
localStream.dispose();
|
localStream.dispose();
|
||||||
localStream = null;
|
localStream = null;
|
||||||
@ -1336,6 +1366,9 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
if (hasExternalSignalingServer) {
|
if (hasExternalSignalingServer) {
|
||||||
setupAndInitiateWebSocketsConnection();
|
setupAndInitiateWebSocketsConnection();
|
||||||
} else {
|
} else {
|
||||||
|
signalingMessageReceiver = internalSignalingMessageReceiver;
|
||||||
|
signalingMessageReceiver.addListener(participantListMessageListener);
|
||||||
|
signalingMessageReceiver.addListener(offerMessageListener);
|
||||||
joinRoomAndCall();
|
joinRoomAndCall();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1534,6 +1567,11 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
externalSignalingServer.getExternalSignalingServer(),
|
externalSignalingServer.getExternalSignalingServer(),
|
||||||
conversationUser, externalSignalingServer.getExternalSignalingTicket(),
|
conversationUser, externalSignalingServer.getExternalSignalingTicket(),
|
||||||
TextUtils.isEmpty(credentials));
|
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 {
|
} else {
|
||||||
if (webSocketClient.isConnected() && currentCallStatus == CallStatus.PUBLISHER_FAILED) {
|
if (webSocketClient.isConnected() && currentCallStatus == CallStatus.PUBLISHER_FAILED) {
|
||||||
webSocketClient.restartWebSocket();
|
webSocketClient.restartWebSocket();
|
||||||
@ -1577,42 +1615,6 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
performCall();
|
performCall();
|
||||||
}
|
}
|
||||||
break;
|
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":
|
case "peerReadyForRequestingOffer":
|
||||||
Log.d(TAG, "onMessageEvent 'peerReadyForRequestingOffer'");
|
Log.d(TAG, "onMessageEvent 'peerReadyForRequestingOffer'");
|
||||||
webSocketClient.requestOfferForSessionIdWithType(
|
webSocketClient.requestOfferForSessionIdWithType(
|
||||||
@ -1652,85 +1654,16 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("usersInRoom".equals(messageType)) {
|
if ("usersInRoom".equals(messageType)) {
|
||||||
processUsersInRoom((List<HashMap<String, Object>>) signaling.getMessageWrapper());
|
internalSignalingMessageReceiver.process((List<Map<String, Object>>) signaling.getMessageWrapper());
|
||||||
} else if ("message".equals(messageType)) {
|
} else if ("message".equals(messageType)) {
|
||||||
NCSignalingMessage ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(),
|
NCSignalingMessage ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(),
|
||||||
NCSignalingMessage.class);
|
NCSignalingMessage.class);
|
||||||
processMessage(ncSignalingMessage);
|
internalSignalingMessageReceiver.process(ncSignalingMessage);
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "unexpected message type when receiving signaling message");
|
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) {
|
private void hangup(boolean shutDownView) {
|
||||||
Log.d(TAG, "hangup! shutDownView=" + shutDownView);
|
Log.d(TAG, "hangup! shutDownView=" + shutDownView);
|
||||||
if (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");
|
Log.d(TAG, "processUsersInRoom");
|
||||||
List<String> newSessions = new ArrayList<>();
|
List<String> newSessions = new ArrayList<>();
|
||||||
Set<String> oldSessions = new HashSet<>();
|
Set<String> oldSessions = new HashSet<>();
|
||||||
@ -1855,27 +1788,20 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
|
|
||||||
boolean isSelfInCall = false;
|
boolean isSelfInCall = false;
|
||||||
|
|
||||||
for (HashMap<String, Object> participant : users) {
|
for (Participant participant : participants) {
|
||||||
long inCallFlag = (long) participant.get("inCall");
|
long inCallFlag = participant.getInCall();
|
||||||
if (!participant.get("sessionId").equals(currentSessionId)) {
|
if (!participant.getSessionId().equals(currentSessionId)) {
|
||||||
Log.d(TAG, " inCallFlag of participant "
|
Log.d(TAG, " inCallFlag of participant "
|
||||||
+ participant.get("sessionId").toString().substring(0, 4)
|
+ participant.getSessionId().substring(0, 4)
|
||||||
+ " : "
|
+ " : "
|
||||||
+ inCallFlag);
|
+ inCallFlag);
|
||||||
|
|
||||||
boolean isInCall = inCallFlag != 0;
|
boolean isInCall = inCallFlag != 0;
|
||||||
if (isInCall) {
|
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.
|
userIdsBySessionId.put(participant.getSessionId(), participant.getUserId());
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, " inCallFlag of currentSessionId: " + inCallFlag);
|
Log.d(TAG, " inCallFlag of currentSessionId: " + inCallFlag);
|
||||||
isSelfInCall = inCallFlag != 0;
|
isSelfInCall = inCallFlag != 0;
|
||||||
@ -2028,7 +1954,8 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
localStream,
|
localStream,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
type);
|
type,
|
||||||
|
signalingMessageReceiver);
|
||||||
|
|
||||||
} else if (hasMCU) {
|
} else if (hasMCU) {
|
||||||
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
|
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
|
||||||
@ -2039,7 +1966,8 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
type);
|
type,
|
||||||
|
signalingMessageReceiver);
|
||||||
} else {
|
} else {
|
||||||
if (!"screen".equals(type)) {
|
if (!"screen".equals(type)) {
|
||||||
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
|
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
|
||||||
@ -2050,7 +1978,8 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
localStream,
|
localStream,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
type);
|
type,
|
||||||
|
signalingMessageReceiver);
|
||||||
} else {
|
} else {
|
||||||
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
|
peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
|
||||||
iceServers,
|
iceServers,
|
||||||
@ -2060,12 +1989,22 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
type);
|
type,
|
||||||
|
signalingMessageReceiver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
peerConnectionWrapperList.add(peerConnectionWrapper);
|
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) {
|
if (publisher) {
|
||||||
startSendingNick();
|
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) {
|
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 {
|
private class MicrophoneButtonTouchListener implements View.OnTouchListener {
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@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 class Globals {
|
||||||
public static final String ROOM_TOKEN = "roomToken";
|
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 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.EventOverallWebSocketMessage;
|
||||||
import com.nextcloud.talk.models.json.websocket.HelloResponseOverallWebSocketMessage;
|
import com.nextcloud.talk.models.json.websocket.HelloResponseOverallWebSocketMessage;
|
||||||
import com.nextcloud.talk.models.json.websocket.JoinedRoomOverallWebSocketMessage;
|
import com.nextcloud.talk.models.json.websocket.JoinedRoomOverallWebSocketMessage;
|
||||||
import com.nextcloud.talk.utils.MagicMap;
|
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
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.GUESTS;
|
||||||
import static com.nextcloud.talk.models.json.participants.Participant.ActorType.USERS;
|
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.ROOM_TOKEN;
|
||||||
import static com.nextcloud.talk.webrtc.Globals.TARGET_PARTICIPANTS;
|
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)
|
@AutoInjector(NextcloudTalkApplication.class)
|
||||||
public class MagicWebSocketInstance extends WebSocketListener {
|
public class MagicWebSocketInstance extends WebSocketListener {
|
||||||
@ -98,7 +90,6 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
|||||||
private boolean connected;
|
private boolean connected;
|
||||||
private WebSocketConnectionHelper webSocketConnectionHelper;
|
private WebSocketConnectionHelper webSocketConnectionHelper;
|
||||||
private WebSocket internalWebSocket;
|
private WebSocket internalWebSocket;
|
||||||
private MagicMap magicMap;
|
|
||||||
private String connectionUrl;
|
private String connectionUrl;
|
||||||
|
|
||||||
private String currentRoomToken;
|
private String currentRoomToken;
|
||||||
@ -109,6 +100,8 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
|||||||
|
|
||||||
private List<String> messagesQueue = new ArrayList<>();
|
private List<String> messagesQueue = new ArrayList<>();
|
||||||
|
|
||||||
|
private final ExternalSignalingMessageReceiver signalingMessageReceiver = new ExternalSignalingMessageReceiver();
|
||||||
|
|
||||||
MagicWebSocketInstance(User conversationUser, String connectionUrl, String webSocketTicket) {
|
MagicWebSocketInstance(User conversationUser, String connectionUrl, String webSocketTicket) {
|
||||||
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
|
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
|
||||||
|
|
||||||
@ -117,7 +110,6 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
|||||||
this.webSocketTicket = webSocketTicket;
|
this.webSocketTicket = webSocketTicket;
|
||||||
this.webSocketConnectionHelper = new WebSocketConnectionHelper();
|
this.webSocketConnectionHelper = new WebSocketConnectionHelper();
|
||||||
this.usersHashMap = new HashMap<>();
|
this.usersHashMap = new HashMap<>();
|
||||||
magicMap = new MagicMap();
|
|
||||||
|
|
||||||
connected = false;
|
connected = false;
|
||||||
eventBus.register(this);
|
eventBus.register(this);
|
||||||
@ -276,45 +268,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TARGET_PARTICIPANTS:
|
case TARGET_PARTICIPANTS:
|
||||||
if (EVENT_TYPE_UPDATE.equals(eventOverallWebSocketMessage.getEventMap().get(EVENT_TYPE))) {
|
signalingMessageReceiver.process(eventOverallWebSocketMessage.getEventMap());
|
||||||
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));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -326,11 +280,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
|||||||
ncSignalingMessage.setFrom(callOverallWebSocketMessage.getCallWebSocketMessage().getSenderWebSocketMessage().getSessionId());
|
ncSignalingMessage.setFrom(callOverallWebSocketMessage.getCallWebSocketMessage().getSenderWebSocketMessage().getSessionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(ncSignalingMessage.getFrom())) {
|
signalingMessageReceiver.process(ncSignalingMessage);
|
||||||
HashMap<String, String> messageHashMap = new HashMap<>();
|
|
||||||
messageHashMap.put(JOB_ID, Integer.toString(magicMap.add(ncSignalingMessage)));
|
|
||||||
eventBus.post(new WebSocketCommunicationEvent("signalingMessage", messageHashMap));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "bye":
|
case "bye":
|
||||||
connected = false;
|
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) {
|
public void requestOfferForSessionIdWithType(String sessionIdParam, String roomType) {
|
||||||
try {
|
try {
|
||||||
String message = LoganSquare.serialize(webSocketConnectionHelper.getAssembledRequestOfferModel(sessionIdParam, roomType));
|
String message = LoganSquare.serialize(webSocketConnectionHelper.getAssembledRequestOfferModel(sessionIdParam, roomType));
|
||||||
@ -471,4 +415,25 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
|||||||
restartWebSocket();
|
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.DataChannelMessage;
|
||||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
|
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
|
||||||
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
|
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
|
||||||
|
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.webrtc.AudioTrack;
|
import org.webrtc.AudioTrack;
|
||||||
@ -74,6 +75,9 @@ public class PeerConnectionWrapper {
|
|||||||
|
|
||||||
private static final String TAG = PeerConnectionWrapper.class.getCanonicalName();
|
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 List<IceCandidate> iceCandidates = new ArrayList<>();
|
||||||
private PeerConnection peerConnection;
|
private PeerConnection peerConnection;
|
||||||
private String sessionId;
|
private String sessionId;
|
||||||
@ -96,7 +100,8 @@ public class PeerConnectionWrapper {
|
|||||||
List<PeerConnection.IceServer> iceServerList,
|
List<PeerConnection.IceServer> iceServerList,
|
||||||
MediaConstraints mediaConstraints,
|
MediaConstraints mediaConstraints,
|
||||||
String sessionId, String localSession, @Nullable MediaStream localStream,
|
String sessionId, String localSession, @Nullable MediaStream localStream,
|
||||||
boolean isMCUPublisher, boolean hasMCU, String videoStreamType) {
|
boolean isMCUPublisher, boolean hasMCU, String videoStreamType,
|
||||||
|
SignalingMessageReceiver signalingMessageReceiver) {
|
||||||
|
|
||||||
Objects.requireNonNull(NextcloudTalkApplication.Companion.getSharedApplication()).getComponentApplication().inject(this);
|
Objects.requireNonNull(NextcloudTalkApplication.Companion.getSharedApplication()).getComponentApplication().inject(this);
|
||||||
|
|
||||||
@ -114,6 +119,9 @@ public class PeerConnectionWrapper {
|
|||||||
configuration.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
|
configuration.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
|
||||||
peerConnection = peerConnectionFactory.createPeerConnection(configuration, new MagicPeerConnectionObserver());
|
peerConnection = peerConnectionFactory.createPeerConnection(configuration, new MagicPeerConnectionObserver());
|
||||||
|
|
||||||
|
this.signalingMessageReceiver = signalingMessageReceiver;
|
||||||
|
this.signalingMessageReceiver.addListener(webRtcMessageListener, sessionId, videoStreamType);
|
||||||
|
|
||||||
if (peerConnection != null) {
|
if (peerConnection != null) {
|
||||||
if (this.localStream != null) {
|
if (this.localStream != null) {
|
||||||
List<String> localStreamIds = Collections.singletonList(this.localStream.getId());
|
List<String> localStreamIds = Collections.singletonList(this.localStream.getId());
|
||||||
@ -150,6 +158,8 @@ public class PeerConnectionWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void removePeerConnection() {
|
public void removePeerConnection() {
|
||||||
|
signalingMessageReceiver.removeListener(webRtcMessageListener);
|
||||||
|
|
||||||
if (dataChannel != null) {
|
if (dataChannel != null) {
|
||||||
dataChannel.dispose();
|
dataChannel.dispose();
|
||||||
dataChannel = null;
|
dataChannel = null;
|
||||||
@ -167,7 +177,7 @@ public class PeerConnectionWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drainIceCandidates() {
|
private void drainIceCandidates() {
|
||||||
|
|
||||||
if (peerConnection != null) {
|
if (peerConnection != null) {
|
||||||
for (IceCandidate iceCandidate : iceCandidates) {
|
for (IceCandidate iceCandidate : iceCandidates) {
|
||||||
@ -178,11 +188,7 @@ public class PeerConnectionWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MagicSdpObserver getMagicSdpObserver() {
|
private void addCandidate(IceCandidate iceCandidate) {
|
||||||
return magicSdpObserver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addCandidate(IceCandidate iceCandidate) {
|
|
||||||
if (peerConnection != null && peerConnection.getRemoteDescription() != null) {
|
if (peerConnection != null && peerConnection.getRemoteDescription() != null) {
|
||||||
peerConnection.addIceCandidate(iceCandidate);
|
peerConnection.addIceCandidate(iceCandidate);
|
||||||
} else {
|
} else {
|
||||||
@ -222,10 +228,6 @@ public class PeerConnectionWrapper {
|
|||||||
return sessionId;
|
return sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSessionId(String sessionId) {
|
|
||||||
this.sessionId = sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNick() {
|
public String getNick() {
|
||||||
if (!TextUtils.isEmpty(nick)) {
|
if (!TextUtils.isEmpty(nick)) {
|
||||||
return nick;
|
return nick;
|
||||||
@ -234,7 +236,7 @@ public class PeerConnectionWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNick(String nick) {
|
private void setNick(String nick) {
|
||||||
this.nick = nick;
|
this.nick = nick;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,6 +269,43 @@ public class PeerConnectionWrapper {
|
|||||||
return false;
|
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 {
|
private class MagicDataChannelObserver implements DataChannel.Observer {
|
||||||
|
|
||||||
@Override
|
@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