mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-12-18 06:32:08 +03:00
Merge pull request #2600 from nextcloud/split-call-participants-and-peer-connections
Split call participants and peer connections
This commit is contained in:
commit
a87f2fb102
14 changed files with 2407 additions and 414 deletions
|
@ -60,6 +60,9 @@ import com.nextcloud.talk.adapters.ParticipantDisplayItem;
|
|||
import com.nextcloud.talk.adapters.ParticipantsAdapter;
|
||||
import com.nextcloud.talk.api.NcApi;
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||
import com.nextcloud.talk.call.CallParticipant;
|
||||
import com.nextcloud.talk.call.CallParticipantList;
|
||||
import com.nextcloud.talk.call.CallParticipantModel;
|
||||
import com.nextcloud.talk.data.user.model.User;
|
||||
import com.nextcloud.talk.databinding.CallActivityBinding;
|
||||
import com.nextcloud.talk.events.ConfigurationChangeEvent;
|
||||
|
@ -123,8 +126,8 @@ import org.webrtc.VideoTrack;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -231,7 +234,6 @@ public class CallActivity extends CallBaseActivity {
|
|||
private MediaStream localStream;
|
||||
private String credentials;
|
||||
private List<PeerConnectionWrapper> peerConnectionWrapperList = new ArrayList<>();
|
||||
private Map<String, String> userIdsBySessionId = new HashMap<>();
|
||||
|
||||
private boolean videoOn = false;
|
||||
private boolean microphoneOn = false;
|
||||
|
@ -263,31 +265,30 @@ public class CallActivity extends CallBaseActivity {
|
|||
private Map<String, SignalingMessageReceiver.CallParticipantMessageListener> callParticipantMessageListeners =
|
||||
new HashMap<>();
|
||||
|
||||
private Map<String, PeerConnectionWrapper.DataChannelMessageListener> dataChannelMessageListeners = new HashMap<>();
|
||||
private PeerConnectionWrapper.PeerConnectionObserver selfPeerConnectionObserver = new CallActivitySelfPeerConnectionObserver();
|
||||
|
||||
private Map<String, PeerConnectionWrapper.PeerConnectionObserver> peerConnectionObservers = new HashMap<>();
|
||||
private Map<String, CallParticipant> callParticipants = new HashMap<>();
|
||||
|
||||
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener = new SignalingMessageReceiver.ParticipantListMessageListener() {
|
||||
private Map<String, ScreenParticipantDisplayItemManager> screenParticipantDisplayItemManagers = new HashMap<>();
|
||||
|
||||
private Handler screenParticipantDisplayItemManagersHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private CallParticipantList.Observer callParticipantListObserver = new CallParticipantList.Observer() {
|
||||
@Override
|
||||
public void onUsersInRoom(List<Participant> participants) {
|
||||
processUsersInRoom(participants);
|
||||
public void onCallParticipantsChanged(Collection<Participant> joined, Collection<Participant> updated,
|
||||
Collection<Participant> left, Collection<Participant> unchanged) {
|
||||
handleCallParticipantsChanged(joined, updated, left, unchanged);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
public void onCallEndedForAll() {
|
||||
Log.d(TAG, "A moderator ended the call for all.");
|
||||
hangup(true);
|
||||
}
|
||||
};
|
||||
|
||||
private CallParticipantList callParticipantList;
|
||||
|
||||
private SignalingMessageReceiver.OfferMessageListener offerMessageListener = new SignalingMessageReceiver.OfferMessageListener() {
|
||||
@Override
|
||||
public void onOffer(String sessionId, String roomType, String sdp, String nick) {
|
||||
|
@ -382,6 +383,7 @@ public class CallActivity extends CallBaseActivity {
|
|||
requestBluetoothPermission();
|
||||
}
|
||||
basicInitialization();
|
||||
callParticipants = new HashMap<>();
|
||||
participantDisplayItems = new HashMap<>();
|
||||
initViews();
|
||||
if (!isConnectionEstablished()) {
|
||||
|
@ -740,6 +742,10 @@ public class CallActivity extends CallBaseActivity {
|
|||
}
|
||||
});
|
||||
|
||||
if (participantsAdapter != null) {
|
||||
participantsAdapter.destroy();
|
||||
}
|
||||
|
||||
participantsAdapter = new ParticipantsAdapter(
|
||||
this,
|
||||
participantDisplayItems,
|
||||
|
@ -1235,7 +1241,6 @@ public class CallActivity extends CallBaseActivity {
|
|||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
signalingMessageReceiver.removeListener(participantListMessageListener);
|
||||
signalingMessageReceiver.removeListener(offerMessageListener);
|
||||
|
||||
if (localStream != null) {
|
||||
|
@ -1369,7 +1374,6 @@ public class CallActivity extends CallBaseActivity {
|
|||
setupAndInitiateWebSocketsConnection();
|
||||
} else {
|
||||
signalingMessageReceiver = internalSignalingMessageReceiver;
|
||||
signalingMessageReceiver.addListener(participantListMessageListener);
|
||||
signalingMessageReceiver.addListener(offerMessageListener);
|
||||
signalingMessageSender = internalSignalingMessageSender;
|
||||
joinRoomAndCall();
|
||||
|
@ -1459,6 +1463,9 @@ public class CallActivity extends CallBaseActivity {
|
|||
inCallFlag += Participant.InCallFlags.WITH_VIDEO;
|
||||
}
|
||||
|
||||
callParticipantList = new CallParticipantList(signalingMessageReceiver);
|
||||
callParticipantList.addObserver(callParticipantListObserver);
|
||||
|
||||
int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
|
||||
|
||||
ncApi.joinCall(
|
||||
|
@ -1573,7 +1580,6 @@ public class CallActivity extends CallBaseActivity {
|
|||
// 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);
|
||||
signalingMessageSender = webSocketClient.getSignalingMessageSender();
|
||||
} else {
|
||||
|
@ -1715,12 +1721,21 @@ public class CallActivity extends CallBaseActivity {
|
|||
}
|
||||
}
|
||||
|
||||
List<String> sessionIdsToEnd = new ArrayList<String>(peerConnectionWrapperList.size());
|
||||
List<String> peerConnectionIdsToEnd = new ArrayList<String>(peerConnectionWrapperList.size());
|
||||
for (PeerConnectionWrapper wrapper : peerConnectionWrapperList) {
|
||||
sessionIdsToEnd.add(wrapper.getSessionId());
|
||||
peerConnectionIdsToEnd.add(wrapper.getSessionId());
|
||||
}
|
||||
for (String sessionId : sessionIdsToEnd) {
|
||||
endPeerConnection(sessionId, false);
|
||||
for (String sessionId : peerConnectionIdsToEnd) {
|
||||
endPeerConnection(sessionId, "video");
|
||||
endPeerConnection(sessionId, "screen");
|
||||
}
|
||||
|
||||
List<String> callParticipantIdsToEnd = new ArrayList<String>(peerConnectionWrapperList.size());
|
||||
for (CallParticipant callParticipant : callParticipants.values()) {
|
||||
callParticipantIdsToEnd.add(callParticipant.getCallParticipantModel().getSessionId());
|
||||
}
|
||||
for (String sessionId : callParticipantIdsToEnd) {
|
||||
removeCallParticipant(sessionId);
|
||||
}
|
||||
|
||||
hangupNetworkCalls(shutDownView);
|
||||
|
@ -1731,6 +1746,9 @@ public class CallActivity extends CallBaseActivity {
|
|||
Log.d(TAG, "hangupNetworkCalls. shutDownView=" + shutDownView);
|
||||
int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
|
||||
|
||||
callParticipantList.removeObserver(callParticipantListObserver);
|
||||
callParticipantList.destroy();
|
||||
|
||||
ncApi.leaveCall(credentials, ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
@ -1768,11 +1786,9 @@ public class CallActivity extends CallBaseActivity {
|
|||
}
|
||||
}
|
||||
|
||||
private void processUsersInRoom(List<Participant> participants) {
|
||||
Log.d(TAG, "processUsersInRoom");
|
||||
List<String> newSessions = new ArrayList<>();
|
||||
Set<String> oldSessions = new HashSet<>();
|
||||
userIdsBySessionId = new HashMap<>();
|
||||
private void handleCallParticipantsChanged(Collection<Participant> joined, Collection<Participant> updated,
|
||||
Collection<Participant> left, Collection<Participant> unchanged) {
|
||||
Log.d(TAG, "handleCallParticipantsChanged");
|
||||
|
||||
hasMCU = hasExternalSignalingServer && webSocketClient != null && webSocketClient.hasMCU();
|
||||
Log.d(TAG, " hasMCU is " + hasMCU);
|
||||
|
@ -1785,58 +1801,49 @@ public class CallActivity extends CallBaseActivity {
|
|||
|
||||
Log.d(TAG, " currentSessionId is " + currentSessionId);
|
||||
|
||||
boolean isSelfInCall = false;
|
||||
List<Participant> participantsInCall = new ArrayList<>();
|
||||
participantsInCall.addAll(joined);
|
||||
participantsInCall.addAll(updated);
|
||||
participantsInCall.addAll(unchanged);
|
||||
|
||||
for (Participant participant : participants) {
|
||||
boolean isSelfInCall = false;
|
||||
Participant selfParticipant = null;
|
||||
|
||||
for (Participant participant : participantsInCall) {
|
||||
long inCallFlag = participant.getInCall();
|
||||
if (!participant.getSessionId().equals(currentSessionId)) {
|
||||
Log.d(TAG, " inCallFlag of participant "
|
||||
+ participant.getSessionId().substring(0, 4)
|
||||
+ " : "
|
||||
+ inCallFlag);
|
||||
|
||||
boolean isInCall = inCallFlag != 0;
|
||||
if (isInCall) {
|
||||
newSessions.add(participant.getSessionId());
|
||||
}
|
||||
|
||||
userIdsBySessionId.put(participant.getSessionId(), participant.getUserId());
|
||||
} else {
|
||||
Log.d(TAG, " inCallFlag of currentSessionId: " + inCallFlag);
|
||||
isSelfInCall = inCallFlag != 0;
|
||||
if (inCallFlag == 0 && currentCallStatus != CallStatus.LEAVING && ApplicationWideCurrentRoomHolder.getInstance().isInCall()) {
|
||||
Log.d(TAG, "Most probably a moderator ended the call for all.");
|
||||
hangup(true);
|
||||
|
||||
return;
|
||||
}
|
||||
selfParticipant = participant;
|
||||
}
|
||||
}
|
||||
|
||||
for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
|
||||
if (!peerConnectionWrapper.isMCUPublisher()) {
|
||||
oldSessions.add(peerConnectionWrapper.getSessionId());
|
||||
}
|
||||
if (!isSelfInCall && currentCallStatus != CallStatus.LEAVING && ApplicationWideCurrentRoomHolder.getInstance().isInCall()) {
|
||||
Log.d(TAG, "Most probably a moderator ended the call for all.");
|
||||
hangup(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSelfInCall) {
|
||||
Log.d(TAG, "Self not in call, disconnecting from all other sessions");
|
||||
|
||||
for (String sessionId : oldSessions) {
|
||||
Log.d(TAG, " oldSession that will be removed is: " + sessionId);
|
||||
endPeerConnection(sessionId, false);
|
||||
for (Participant participant : participantsInCall) {
|
||||
String sessionId = participant.getSessionId();
|
||||
Log.d(TAG, " session that will be removed is: " + sessionId);
|
||||
endPeerConnection(sessionId, "video");
|
||||
endPeerConnection(sessionId, "screen");
|
||||
removeCallParticipant(sessionId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate sessions that left the call
|
||||
List<String> disconnectedSessions = new ArrayList<>(oldSessions);
|
||||
disconnectedSessions.removeAll(newSessions);
|
||||
|
||||
// Calculate sessions that join the call
|
||||
newSessions.removeAll(oldSessions);
|
||||
|
||||
if (currentCallStatus == CallStatus.LEAVING) {
|
||||
return;
|
||||
}
|
||||
|
@ -1846,42 +1853,72 @@ public class CallActivity extends CallBaseActivity {
|
|||
getOrCreatePeerConnectionWrapperForSessionIdAndType(webSocketClient.getSessionId(), VIDEO_STREAM_TYPE_VIDEO, true);
|
||||
}
|
||||
|
||||
for (String sessionId : newSessions) {
|
||||
Log.d(TAG, " newSession joined: " + sessionId);
|
||||
getOrCreatePeerConnectionWrapperForSessionIdAndType(sessionId, VIDEO_STREAM_TYPE_VIDEO, false);
|
||||
boolean selfJoined = false;
|
||||
boolean selfParticipantHasAudioOrVideo = participantInCallFlagsHaveAudioOrVideo(selfParticipant);
|
||||
|
||||
String userId = userIdsBySessionId.get(sessionId);
|
||||
for (Participant participant : joined) {
|
||||
String sessionId = participant.getSessionId();
|
||||
|
||||
if (sessionId == null) {
|
||||
Log.w(TAG, "Null sessionId for call participant, this should not happen: " + participant);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sessionId.equals(currentSessionId)) {
|
||||
selfJoined = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.d(TAG, " newSession joined: " + sessionId);
|
||||
|
||||
CallParticipant callParticipant = addCallParticipant(sessionId);
|
||||
|
||||
String userId = participant.getUserId();
|
||||
if (userId != null) {
|
||||
runOnUiThread(() -> {
|
||||
boolean notifyDataSetChanged = false;
|
||||
if (participantDisplayItems.get(sessionId + "-video") != null) {
|
||||
participantDisplayItems.get(sessionId + "-video").setUserId(userId);
|
||||
notifyDataSetChanged = true;
|
||||
}
|
||||
if (participantDisplayItems.get(sessionId + "-screen") != null) {
|
||||
participantDisplayItems.get(sessionId + "-screen").setUserId(userId);
|
||||
notifyDataSetChanged = true;
|
||||
}
|
||||
if (notifyDataSetChanged) {
|
||||
participantsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
callParticipants.get(sessionId).setUserId(userId);
|
||||
}
|
||||
|
||||
String nick;
|
||||
if (hasExternalSignalingServer) {
|
||||
nick = webSocketClient.getDisplayNameForSession(sessionId);
|
||||
} else {
|
||||
nick = offerAnswerNickProviders.get(sessionId) != null ? offerAnswerNickProviders.get(sessionId).getNick() : "";
|
||||
}
|
||||
callParticipants.get(sessionId).setNick(nick);
|
||||
|
||||
boolean participantHasAudioOrVideo = participantInCallFlagsHaveAudioOrVideo(participant);
|
||||
|
||||
// FIXME Without MCU, PeerConnectionWrapper only sends an offer if the local session ID is higher than the
|
||||
// remote session ID. However, if the other participant does not have audio nor video that participant
|
||||
// will not send an offer, so no connection is actually established when the remote participant has a
|
||||
// higher session ID but is not publishing media.
|
||||
if ((hasMCU && participantHasAudioOrVideo) ||
|
||||
(!hasMCU && selfParticipantHasAudioOrVideo && (!participantHasAudioOrVideo || sessionId.compareTo(currentSessionId) < 0))) {
|
||||
getOrCreatePeerConnectionWrapperForSessionIdAndType(sessionId, VIDEO_STREAM_TYPE_VIDEO, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (newSessions.size() > 0 && currentCallStatus != CallStatus.IN_CONVERSATION) {
|
||||
boolean othersInCall = selfJoined ? joined.size() > 1 : joined.size() > 0;
|
||||
if (othersInCall && currentCallStatus != CallStatus.IN_CONVERSATION) {
|
||||
setCallState(CallStatus.IN_CONVERSATION);
|
||||
}
|
||||
|
||||
for (String sessionId : disconnectedSessions) {
|
||||
for (Participant participant : left) {
|
||||
String sessionId = participant.getSessionId();
|
||||
Log.d(TAG, " oldSession that will be removed is: " + sessionId);
|
||||
endPeerConnection(sessionId, false);
|
||||
endPeerConnection(sessionId, "video");
|
||||
endPeerConnection(sessionId, "screen");
|
||||
removeCallParticipant(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
private void deletePeerConnection(PeerConnectionWrapper peerConnectionWrapper) {
|
||||
peerConnectionWrapper.removePeerConnection();
|
||||
peerConnectionWrapperList.remove(peerConnectionWrapper);
|
||||
private boolean participantInCallFlagsHaveAudioOrVideo(Participant participant) {
|
||||
if (participant == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (participant.getInCall() & Participant.InCallFlags.WITH_AUDIO) > 0 ||
|
||||
(!isVoiceOnlyCall && (participant.getInCall() & Participant.InCallFlags.WITH_VIDEO) > 0);
|
||||
}
|
||||
|
||||
private PeerConnectionWrapper getPeerConnectionWrapperForSessionIdAndType(String sessionId, String type) {
|
||||
|
@ -1965,46 +2002,22 @@ public class CallActivity extends CallBaseActivity {
|
|||
|
||||
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);
|
||||
|
||||
// DataChannel messages are sent only in video peers; (sender) screen peers do not even open them.
|
||||
PeerConnectionWrapper.DataChannelMessageListener dataChannelMessageListener =
|
||||
new CallActivityDataChannelMessageListener(sessionId);
|
||||
dataChannelMessageListeners.put(sessionId, dataChannelMessageListener);
|
||||
peerConnectionWrapper.addListener(dataChannelMessageListener);
|
||||
}
|
||||
|
||||
if (!publisher && !hasExternalSignalingServer && offerAnswerNickProviders.get(sessionId) == null) {
|
||||
OfferAnswerNickProvider offerAnswerNickProvider = new OfferAnswerNickProvider(sessionId);
|
||||
offerAnswerNickProviders.put(sessionId, offerAnswerNickProvider);
|
||||
signalingMessageReceiver.addListener(offerAnswerNickProvider.getVideoWebRtcMessageListener(), sessionId, "video");
|
||||
signalingMessageReceiver.addListener(offerAnswerNickProvider.getScreenWebRtcMessageListener(), sessionId, "screen");
|
||||
}
|
||||
|
||||
PeerConnectionWrapper.PeerConnectionObserver peerConnectionObserver =
|
||||
new CallActivityPeerConnectionObserver(sessionId, type);
|
||||
peerConnectionObservers.put(sessionId + "-" + type, peerConnectionObserver);
|
||||
peerConnectionWrapper.addObserver(peerConnectionObserver);
|
||||
|
||||
if (!publisher) {
|
||||
runOnUiThread(() -> {
|
||||
// userId is unknown here, but it will be got based on the session id, and the stream will be
|
||||
// updated once it is added to the connection.
|
||||
setupVideoStreamForLayout(
|
||||
null,
|
||||
sessionId,
|
||||
false,
|
||||
type);
|
||||
});
|
||||
CallParticipant callParticipant = callParticipants.get(sessionId);
|
||||
if (callParticipant == null) {
|
||||
callParticipant = addCallParticipant(sessionId);
|
||||
}
|
||||
|
||||
if ("screen".equals(type)) {
|
||||
callParticipant.setScreenPeerConnectionWrapper(peerConnectionWrapper);
|
||||
} else {
|
||||
callParticipant.setPeerConnectionWrapper(peerConnectionWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
if (publisher) {
|
||||
peerConnectionWrapper.addObserver(selfPeerConnectionObserver);
|
||||
|
||||
startSendingNick();
|
||||
}
|
||||
|
||||
|
@ -2012,53 +2025,91 @@ public class CallActivity extends CallBaseActivity {
|
|||
}
|
||||
}
|
||||
|
||||
private List<PeerConnectionWrapper> getPeerConnectionWrapperListForSessionId(String sessionId) {
|
||||
List<PeerConnectionWrapper> internalList = new ArrayList<>();
|
||||
for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
|
||||
if (peerConnectionWrapper.getSessionId().equals(sessionId)) {
|
||||
internalList.add(peerConnectionWrapper);
|
||||
}
|
||||
private CallParticipant addCallParticipant(String sessionId) {
|
||||
CallParticipant callParticipant = new CallParticipant(sessionId);
|
||||
callParticipants.put(sessionId, callParticipant);
|
||||
|
||||
SignalingMessageReceiver.CallParticipantMessageListener callParticipantMessageListener =
|
||||
new CallActivityCallParticipantMessageListener(sessionId);
|
||||
callParticipantMessageListeners.put(sessionId, callParticipantMessageListener);
|
||||
signalingMessageReceiver.addListener(callParticipantMessageListener, sessionId);
|
||||
|
||||
if (!hasExternalSignalingServer) {
|
||||
OfferAnswerNickProvider offerAnswerNickProvider = new OfferAnswerNickProvider(sessionId);
|
||||
offerAnswerNickProviders.put(sessionId, offerAnswerNickProvider);
|
||||
signalingMessageReceiver.addListener(offerAnswerNickProvider.getVideoWebRtcMessageListener(), sessionId, "video");
|
||||
signalingMessageReceiver.addListener(offerAnswerNickProvider.getScreenWebRtcMessageListener(), sessionId, "screen");
|
||||
}
|
||||
|
||||
return internalList;
|
||||
final CallParticipantModel callParticipantModel = callParticipant.getCallParticipantModel();
|
||||
|
||||
ScreenParticipantDisplayItemManager screenParticipantDisplayItemManager =
|
||||
new ScreenParticipantDisplayItemManager(callParticipantModel);
|
||||
screenParticipantDisplayItemManagers.put(sessionId, screenParticipantDisplayItemManager);
|
||||
callParticipantModel.addObserver(screenParticipantDisplayItemManager, screenParticipantDisplayItemManagersHandler);
|
||||
|
||||
runOnUiThread(() -> {
|
||||
addParticipantDisplayItem(callParticipantModel, "video");
|
||||
});
|
||||
|
||||
return callParticipant;
|
||||
}
|
||||
|
||||
private void endPeerConnection(String sessionId, boolean justScreen) {
|
||||
List<PeerConnectionWrapper> peerConnectionWrappers;
|
||||
if (!(peerConnectionWrappers = getPeerConnectionWrapperListForSessionId(sessionId)).isEmpty()) {
|
||||
for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrappers) {
|
||||
if (peerConnectionWrapper.getSessionId().equals(sessionId)) {
|
||||
if (!justScreen && VIDEO_STREAM_TYPE_VIDEO.equals(peerConnectionWrapper.getVideoStreamType())) {
|
||||
PeerConnectionWrapper.DataChannelMessageListener dataChannelMessageListener = dataChannelMessageListeners.remove(sessionId);
|
||||
peerConnectionWrapper.removeListener(dataChannelMessageListener);
|
||||
}
|
||||
String videoStreamType = peerConnectionWrapper.getVideoStreamType();
|
||||
if (VIDEO_STREAM_TYPE_SCREEN.equals(videoStreamType) || !justScreen) {
|
||||
PeerConnectionWrapper.PeerConnectionObserver peerConnectionObserver = peerConnectionObservers.remove(sessionId + "-" + videoStreamType);
|
||||
peerConnectionWrapper.removeObserver(peerConnectionObserver);
|
||||
private void endPeerConnection(String sessionId, String type) {
|
||||
PeerConnectionWrapper peerConnectionWrapper = getPeerConnectionWrapperForSessionIdAndType(sessionId, type);
|
||||
if (peerConnectionWrapper == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
runOnUiThread(() -> removeMediaStream(sessionId, videoStreamType));
|
||||
deletePeerConnection(peerConnectionWrapper);
|
||||
}
|
||||
}
|
||||
if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
|
||||
peerConnectionWrapper.removeObserver(selfPeerConnectionObserver);
|
||||
}
|
||||
|
||||
CallParticipant callParticipant = callParticipants.get(sessionId);
|
||||
if (callParticipant != null) {
|
||||
if ("screen".equals(type)) {
|
||||
callParticipant.setScreenPeerConnectionWrapper(null);
|
||||
} else {
|
||||
callParticipant.setPeerConnectionWrapper(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!justScreen) {
|
||||
SignalingMessageReceiver.CallParticipantMessageListener listener = callParticipantMessageListeners.remove(sessionId);
|
||||
signalingMessageReceiver.removeListener(listener);
|
||||
|
||||
OfferAnswerNickProvider offerAnswerNickProvider = offerAnswerNickProviders.remove(sessionId);
|
||||
if (offerAnswerNickProvider != null) {
|
||||
signalingMessageReceiver.removeListener(offerAnswerNickProvider.getVideoWebRtcMessageListener());
|
||||
signalingMessageReceiver.removeListener(offerAnswerNickProvider.getScreenWebRtcMessageListener());
|
||||
}
|
||||
}
|
||||
peerConnectionWrapper.removePeerConnection();
|
||||
peerConnectionWrapperList.remove(peerConnectionWrapper);
|
||||
}
|
||||
|
||||
private void removeMediaStream(String sessionId, String videoStreamType) {
|
||||
Log.d(TAG, "removeMediaStream");
|
||||
participantDisplayItems.remove(sessionId + "-" + videoStreamType);
|
||||
private void removeCallParticipant(String sessionId) {
|
||||
CallParticipant callParticipant = callParticipants.remove(sessionId);
|
||||
if (callParticipant == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ScreenParticipantDisplayItemManager screenParticipantDisplayItemManager =
|
||||
screenParticipantDisplayItemManagers.remove(sessionId);
|
||||
callParticipant.getCallParticipantModel().removeObserver(screenParticipantDisplayItemManager);
|
||||
|
||||
callParticipant.destroy();
|
||||
|
||||
SignalingMessageReceiver.CallParticipantMessageListener listener = callParticipantMessageListeners.remove(sessionId);
|
||||
signalingMessageReceiver.removeListener(listener);
|
||||
|
||||
OfferAnswerNickProvider offerAnswerNickProvider = offerAnswerNickProviders.remove(sessionId);
|
||||
if (offerAnswerNickProvider != null) {
|
||||
signalingMessageReceiver.removeListener(offerAnswerNickProvider.getVideoWebRtcMessageListener());
|
||||
signalingMessageReceiver.removeListener(offerAnswerNickProvider.getScreenWebRtcMessageListener());
|
||||
}
|
||||
|
||||
runOnUiThread(() -> removeParticipantDisplayItem(sessionId, "video"));
|
||||
}
|
||||
|
||||
private void removeParticipantDisplayItem(String sessionId, String videoStreamType) {
|
||||
Log.d(TAG, "removeParticipantDisplayItem");
|
||||
ParticipantDisplayItem participantDisplayItem = participantDisplayItems.remove(sessionId + "-" + videoStreamType);
|
||||
if (participantDisplayItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
participantDisplayItem.destroy();
|
||||
|
||||
if (!isDestroyed()) {
|
||||
initGridAdapter();
|
||||
|
@ -2072,7 +2123,10 @@ public class CallActivity extends CallBaseActivity {
|
|||
updateSelfVideoViewPosition();
|
||||
}
|
||||
|
||||
private void updateSelfVideoViewConnected(boolean connected) {
|
||||
private void updateSelfVideoViewIceConnectionState(PeerConnection.IceConnectionState iceConnectionState) {
|
||||
boolean connected = iceConnectionState == PeerConnection.IceConnectionState.CONNECTED ||
|
||||
iceConnectionState == PeerConnection.IceConnectionState.COMPLETED;
|
||||
|
||||
// FIXME In voice only calls there is no video view, so the progress bar would appear floating in the middle of
|
||||
// nowhere. However, a way to signal that the local participant is not connected to the HPB is still need in
|
||||
// that case.
|
||||
|
@ -2133,28 +2187,6 @@ public class CallActivity extends CallBaseActivity {
|
|||
}
|
||||
}
|
||||
|
||||
private void handlePeerConnected(String sessionId, String videoStreamType) {
|
||||
String participantDisplayItemId = sessionId + "-" + videoStreamType;
|
||||
|
||||
if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
|
||||
updateSelfVideoViewConnected(true);
|
||||
} else if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||
participantDisplayItems.get(participantDisplayItemId).setConnected(true);
|
||||
participantsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePeerDisconnected(String sessionId, String videoStreamType) {
|
||||
String participantDisplayItemId = sessionId + "-" + videoStreamType;
|
||||
|
||||
if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
|
||||
updateSelfVideoViewConnected(false);
|
||||
} else if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||
participantDisplayItems.get(participantDisplayItemId).setConnected(false);
|
||||
participantsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void startSendingNick() {
|
||||
DataChannelMessage dataChannelMessage = new DataChannelMessage();
|
||||
dataChannelMessage.setType("nickChanged");
|
||||
|
@ -2204,42 +2236,16 @@ public class CallActivity extends CallBaseActivity {
|
|||
this);
|
||||
}
|
||||
|
||||
private void setupVideoStreamForLayout(@Nullable MediaStream mediaStream,
|
||||
String session,
|
||||
boolean videoStreamEnabled,
|
||||
String videoStreamType) {
|
||||
PeerConnectionWrapper peerConnectionWrapper = getPeerConnectionWrapperForSessionIdAndType(session,
|
||||
videoStreamType);
|
||||
|
||||
boolean connected = false;
|
||||
if (peerConnectionWrapper != null) {
|
||||
PeerConnection.IceConnectionState iceConnectionState = peerConnectionWrapper.getPeerConnection().iceConnectionState();
|
||||
connected = iceConnectionState == PeerConnection.IceConnectionState.CONNECTED ||
|
||||
iceConnectionState == PeerConnection.IceConnectionState.COMPLETED;
|
||||
}
|
||||
|
||||
String nick;
|
||||
if (hasExternalSignalingServer) {
|
||||
nick = webSocketClient.getDisplayNameForSession(session);
|
||||
} else {
|
||||
nick = offerAnswerNickProviders.get(session) != null ? offerAnswerNickProviders.get(session).getNick() : "";
|
||||
}
|
||||
|
||||
String userId = userIdsBySessionId.get(session);
|
||||
|
||||
private void addParticipantDisplayItem(CallParticipantModel callParticipantModel, String videoStreamType) {
|
||||
String defaultGuestNick = getResources().getString(R.string.nc_nick_guest);
|
||||
|
||||
ParticipantDisplayItem participantDisplayItem = new ParticipantDisplayItem(baseUrl,
|
||||
userId,
|
||||
session,
|
||||
connected,
|
||||
nick,
|
||||
defaultGuestNick,
|
||||
mediaStream,
|
||||
rootEglBase,
|
||||
videoStreamType,
|
||||
videoStreamEnabled,
|
||||
rootEglBase);
|
||||
participantDisplayItems.put(session + "-" + videoStreamType, participantDisplayItem);
|
||||
callParticipantModel);
|
||||
String sessionId = callParticipantModel.getSessionId();
|
||||
participantDisplayItems.put(sessionId + "-" + videoStreamType, participantDisplayItem);
|
||||
|
||||
initGridAdapter();
|
||||
}
|
||||
|
@ -2548,17 +2554,8 @@ public class CallActivity extends CallBaseActivity {
|
|||
private void onOfferOrAnswer(String nick) {
|
||||
this.nick = nick;
|
||||
|
||||
boolean notifyDataSetChanged = false;
|
||||
if (participantDisplayItems.get(sessionId + "-video") != null) {
|
||||
participantDisplayItems.get(sessionId + "-video").setNick(nick);
|
||||
notifyDataSetChanged = true;
|
||||
}
|
||||
if (participantDisplayItems.get(sessionId + "-screen") != null) {
|
||||
participantDisplayItems.get(sessionId + "-screen").setNick(nick);
|
||||
notifyDataSetChanged = true;
|
||||
}
|
||||
if (notifyDataSetChanged) {
|
||||
participantsAdapter.notifyDataSetChanged();
|
||||
if (callParticipants.get(sessionId) != null) {
|
||||
callParticipants.get(sessionId).setNick(nick);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2585,149 +2582,58 @@ public class CallActivity extends CallBaseActivity {
|
|||
|
||||
@Override
|
||||
public void onUnshareScreen() {
|
||||
endPeerConnection(sessionId, true);
|
||||
endPeerConnection(sessionId, "screen");
|
||||
}
|
||||
}
|
||||
|
||||
private class CallActivityDataChannelMessageListener implements PeerConnectionWrapper.DataChannelMessageListener {
|
||||
|
||||
private final String participantDisplayItemId;
|
||||
|
||||
private CallActivityDataChannelMessageListener(String sessionId) {
|
||||
// DataChannel messages are sent only in video peers, so the listener only acts on the "video" items.
|
||||
this.participantDisplayItemId = sessionId + "-video";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioOn() {
|
||||
runOnUiThread(() -> {
|
||||
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||
participantDisplayItems.get(participantDisplayItemId).setAudioEnabled(true);
|
||||
participantsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioOff() {
|
||||
runOnUiThread(() -> {
|
||||
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||
participantDisplayItems.get(participantDisplayItemId).setAudioEnabled(false);
|
||||
participantsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoOn() {
|
||||
runOnUiThread(() -> {
|
||||
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||
participantDisplayItems.get(participantDisplayItemId).setStreamEnabled(true);
|
||||
participantsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoOff() {
|
||||
runOnUiThread(() -> {
|
||||
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||
participantDisplayItems.get(participantDisplayItemId).setStreamEnabled(false);
|
||||
participantsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNickChanged(String nick) {
|
||||
runOnUiThread(() -> {
|
||||
if (participantDisplayItems.get(participantDisplayItemId) != null) {
|
||||
participantDisplayItems.get(participantDisplayItemId).setNick(nick);
|
||||
participantsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class CallActivityPeerConnectionObserver implements PeerConnectionWrapper.PeerConnectionObserver {
|
||||
|
||||
private final String sessionId;
|
||||
private final String videoStreamType;
|
||||
private final String participantDisplayItemId;
|
||||
|
||||
private CallActivityPeerConnectionObserver(String sessionId, String videoStreamType) {
|
||||
this.sessionId = sessionId;
|
||||
this.videoStreamType = videoStreamType;
|
||||
this.participantDisplayItemId = sessionId + "-" + videoStreamType;
|
||||
}
|
||||
private class CallActivitySelfPeerConnectionObserver implements PeerConnectionWrapper.PeerConnectionObserver {
|
||||
|
||||
@Override
|
||||
public void onStreamAdded(MediaStream mediaStream) {
|
||||
handleStream(mediaStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamRemoved(MediaStream mediaStream) {
|
||||
handleStream(null);
|
||||
}
|
||||
|
||||
private void handleStream(MediaStream mediaStream) {
|
||||
runOnUiThread(() -> {
|
||||
if (participantDisplayItems.get(participantDisplayItemId) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasAtLeastOneVideoStream = false;
|
||||
if (mediaStream != null) {
|
||||
hasAtLeastOneVideoStream = mediaStream.videoTracks != null && mediaStream.videoTracks.size() > 0;
|
||||
}
|
||||
|
||||
ParticipantDisplayItem participantDisplayItem = participantDisplayItems.get(participantDisplayItemId);
|
||||
participantDisplayItem.setMediaStream(mediaStream);
|
||||
participantDisplayItem.setStreamEnabled(hasAtLeastOneVideoStream);
|
||||
participantsAdapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIceConnectionStateChanged(PeerConnection.IceConnectionState iceConnectionState) {
|
||||
runOnUiThread(() -> {
|
||||
if (iceConnectionState == PeerConnection.IceConnectionState.CONNECTED ||
|
||||
iceConnectionState == PeerConnection.IceConnectionState.COMPLETED) {
|
||||
handlePeerConnected(sessionId, videoStreamType);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED ||
|
||||
iceConnectionState == PeerConnection.IceConnectionState.NEW ||
|
||||
iceConnectionState == PeerConnection.IceConnectionState.CHECKING) {
|
||||
handlePeerDisconnected(sessionId, videoStreamType);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (iceConnectionState == PeerConnection.IceConnectionState.CLOSED) {
|
||||
endPeerConnection(sessionId, VIDEO_STREAM_TYPE_SCREEN.equals(videoStreamType));
|
||||
|
||||
return;
|
||||
}
|
||||
updateSelfVideoViewIceConnectionState(iceConnectionState);
|
||||
|
||||
if (iceConnectionState == PeerConnection.IceConnectionState.FAILED) {
|
||||
if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
|
||||
setCallState(CallStatus.PUBLISHER_FAILED);
|
||||
webSocketClient.clearResumeId();
|
||||
hangup(false);
|
||||
} else {
|
||||
handlePeerDisconnected(sessionId, videoStreamType);
|
||||
}
|
||||
|
||||
return;
|
||||
setCallState(CallStatus.PUBLISHER_FAILED);
|
||||
webSocketClient.clearResumeId();
|
||||
hangup(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class ScreenParticipantDisplayItemManager implements CallParticipantModel.Observer {
|
||||
|
||||
private final CallParticipantModel callParticipantModel;
|
||||
|
||||
private ScreenParticipantDisplayItemManager(CallParticipantModel callParticipantModel) {
|
||||
this.callParticipantModel = callParticipantModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange() {
|
||||
String sessionId = callParticipantModel.getSessionId();
|
||||
if (callParticipantModel.getScreenIceConnectionState() == null) {
|
||||
removeParticipantDisplayItem(sessionId, "screen");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasScreenParticipantDisplayItem = participantDisplayItems.get(sessionId + "-screen") != null;
|
||||
if (!hasScreenParticipantDisplayItem) {
|
||||
addParticipantDisplayItem(callParticipantModel, "screen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalSignalingMessageSender implements SignalingMessageSender {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,83 +1,88 @@
|
|||
package com.nextcloud.talk.adapters;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.nextcloud.talk.call.CallParticipantModel;
|
||||
import com.nextcloud.talk.utils.ApiUtils;
|
||||
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.PeerConnection;
|
||||
|
||||
public class ParticipantDisplayItem {
|
||||
private String baseUrl;
|
||||
private String userId;
|
||||
private String session;
|
||||
private boolean connected;
|
||||
private String nick;
|
||||
|
||||
public interface Observer {
|
||||
void onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared handler to receive change notifications from the model on the main thread.
|
||||
*/
|
||||
private static final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private final ParticipantDisplayItemNotifier participantDisplayItemNotifier = new ParticipantDisplayItemNotifier();
|
||||
|
||||
private final String baseUrl;
|
||||
private final String defaultGuestNick;
|
||||
private final EglBase rootEglBase;
|
||||
|
||||
private final String session;
|
||||
private final String streamType;
|
||||
|
||||
private final CallParticipantModel callParticipantModel;
|
||||
|
||||
private final CallParticipantModel.Observer callParticipantModelObserver = this::updateFromModel;
|
||||
|
||||
private String userId;
|
||||
private PeerConnection.IceConnectionState iceConnectionState;
|
||||
private String nick;
|
||||
private String urlForAvatar;
|
||||
private MediaStream mediaStream;
|
||||
private String streamType;
|
||||
private boolean streamEnabled;
|
||||
private EglBase rootEglBase;
|
||||
private boolean isAudioEnabled;
|
||||
|
||||
public ParticipantDisplayItem(String baseUrl, String userId, String session, boolean connected, String nick, String defaultGuestNick, MediaStream mediaStream, String streamType, boolean streamEnabled, EglBase rootEglBase) {
|
||||
public ParticipantDisplayItem(String baseUrl, String defaultGuestNick, EglBase rootEglBase, String streamType,
|
||||
CallParticipantModel callParticipantModel) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.userId = userId;
|
||||
this.session = session;
|
||||
this.connected = connected;
|
||||
this.nick = nick;
|
||||
this.defaultGuestNick = defaultGuestNick;
|
||||
this.mediaStream = mediaStream;
|
||||
this.streamType = streamType;
|
||||
this.streamEnabled = streamEnabled;
|
||||
this.rootEglBase = rootEglBase;
|
||||
|
||||
this.updateUrlForAvatar();
|
||||
this.session = callParticipantModel.getSessionId();
|
||||
this.streamType = streamType;
|
||||
|
||||
this.callParticipantModel = callParticipantModel;
|
||||
this.callParticipantModel.addObserver(callParticipantModelObserver, handler);
|
||||
|
||||
updateFromModel();
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
public void destroy() {
|
||||
this.callParticipantModel.removeObserver(callParticipantModelObserver);
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
private void updateFromModel() {
|
||||
userId = callParticipantModel.getUserId();
|
||||
nick = callParticipantModel.getNick();
|
||||
|
||||
this.updateUrlForAvatar();
|
||||
}
|
||||
|
||||
public String getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void setSession(String session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
public void setConnected(boolean connected) {
|
||||
this.connected = connected;
|
||||
}
|
||||
|
||||
public String getNick() {
|
||||
if (TextUtils.isEmpty(userId) && TextUtils.isEmpty(nick)) {
|
||||
return defaultGuestNick;
|
||||
if ("screen".equals(streamType)) {
|
||||
iceConnectionState = callParticipantModel.getScreenIceConnectionState();
|
||||
mediaStream = callParticipantModel.getScreenMediaStream();
|
||||
isAudioEnabled = true;
|
||||
streamEnabled = true;
|
||||
} else {
|
||||
iceConnectionState = callParticipantModel.getIceConnectionState();
|
||||
mediaStream = callParticipantModel.getMediaStream();
|
||||
isAudioEnabled = callParticipantModel.isAudioAvailable() != null ?
|
||||
callParticipantModel.isAudioAvailable() : false;
|
||||
streamEnabled = callParticipantModel.isVideoAvailable() != null ?
|
||||
callParticipantModel.isVideoAvailable() : false;
|
||||
}
|
||||
|
||||
return nick;
|
||||
}
|
||||
|
||||
public void setNick(String nick) {
|
||||
this.nick = nick;
|
||||
|
||||
this.updateUrlForAvatar();
|
||||
}
|
||||
|
||||
public String getUrlForAvatar() {
|
||||
return urlForAvatar;
|
||||
participantDisplayItemNotifier.notifyChange();
|
||||
}
|
||||
|
||||
private void updateUrlForAvatar() {
|
||||
|
@ -88,44 +93,48 @@ public class ParticipantDisplayItem {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return iceConnectionState == PeerConnection.IceConnectionState.CONNECTED ||
|
||||
iceConnectionState == PeerConnection.IceConnectionState.COMPLETED ||
|
||||
// If there is no connection state that means that no connection is needed, so it is a special case that is
|
||||
// also seen as "connected".
|
||||
iceConnectionState == null;
|
||||
}
|
||||
|
||||
public String getNick() {
|
||||
if (TextUtils.isEmpty(userId) && TextUtils.isEmpty(nick)) {
|
||||
return defaultGuestNick;
|
||||
}
|
||||
|
||||
return nick;
|
||||
}
|
||||
|
||||
public String getUrlForAvatar() {
|
||||
return urlForAvatar;
|
||||
}
|
||||
|
||||
public MediaStream getMediaStream() {
|
||||
return mediaStream;
|
||||
}
|
||||
|
||||
public void setMediaStream(MediaStream mediaStream) {
|
||||
this.mediaStream = mediaStream;
|
||||
}
|
||||
|
||||
public String getStreamType() {
|
||||
return streamType;
|
||||
}
|
||||
|
||||
public void setStreamType(String streamType) {
|
||||
this.streamType = streamType;
|
||||
}
|
||||
|
||||
public boolean isStreamEnabled() {
|
||||
return streamEnabled;
|
||||
}
|
||||
|
||||
public void setStreamEnabled(boolean streamEnabled) {
|
||||
this.streamEnabled = streamEnabled;
|
||||
}
|
||||
|
||||
public EglBase getRootEglBase() {
|
||||
return rootEglBase;
|
||||
}
|
||||
|
||||
public void setRootEglBase(EglBase rootEglBase) {
|
||||
this.rootEglBase = rootEglBase;
|
||||
}
|
||||
|
||||
public boolean isAudioEnabled() {
|
||||
return isAudioEnabled;
|
||||
}
|
||||
|
||||
public void setAudioEnabled(boolean audioEnabled) {
|
||||
isAudioEnabled = audioEnabled;
|
||||
public void addObserver(Observer observer) {
|
||||
participantDisplayItemNotifier.addObserver(observer);
|
||||
}
|
||||
|
||||
public void removeObserver(Observer observer) {
|
||||
participantDisplayItemNotifier.removeObserver(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.adapters;
|
||||
|
||||
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helper class to register and notify ParticipantDisplayItem.Observers.
|
||||
*
|
||||
* This class is only meant for internal use by ParticipantDisplayItem; observers must register themselves against a
|
||||
* ParticipantDisplayItem rather than against a ParticipantDisplayItemNotifier.
|
||||
*/
|
||||
class ParticipantDisplayItemNotifier {
|
||||
|
||||
private final Set<ParticipantDisplayItem.Observer> participantDisplayItemObservers = new LinkedHashSet<>();
|
||||
|
||||
public synchronized void addObserver(ParticipantDisplayItem.Observer observer) {
|
||||
if (observer == null) {
|
||||
throw new IllegalArgumentException("ParticipantDisplayItem.Observer can not be null");
|
||||
}
|
||||
|
||||
participantDisplayItemObservers.add(observer);
|
||||
}
|
||||
|
||||
public synchronized void removeObserver(ParticipantDisplayItem.Observer observer) {
|
||||
participantDisplayItemObservers.remove(observer);
|
||||
}
|
||||
|
||||
public synchronized void notifyChange() {
|
||||
for (ParticipantDisplayItem.Observer observer : new ArrayList<>(participantDisplayItemObservers)) {
|
||||
observer.onChange();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,8 @@ public class ParticipantsAdapter extends BaseAdapter {
|
|||
|
||||
private static final String TAG = "ParticipantsAdapter";
|
||||
|
||||
private final ParticipantDisplayItem.Observer participantDisplayItemObserver = this::notifyDataSetChanged;
|
||||
|
||||
private final Context mContext;
|
||||
private final ArrayList<ParticipantDisplayItem> participantDisplayItems;
|
||||
private final RelativeLayout gridViewWrapper;
|
||||
|
@ -50,8 +52,17 @@ public class ParticipantsAdapter extends BaseAdapter {
|
|||
|
||||
this.participantDisplayItems = new ArrayList<>();
|
||||
this.participantDisplayItems.addAll(participantDisplayItems.values());
|
||||
|
||||
for (ParticipantDisplayItem participantDisplayItem : this.participantDisplayItems) {
|
||||
participantDisplayItem.addObserver(participantDisplayItemObserver);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
for (ParticipantDisplayItem participantDisplayItem : participantDisplayItems) {
|
||||
participantDisplayItem.removeObserver(participantDisplayItemObserver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
|
|
198
app/src/main/java/com/nextcloud/talk/call/CallParticipant.java
Normal file
198
app/src/main/java/com/nextcloud/talk/call/CallParticipant.java
Normal file
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* 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.call;
|
||||
|
||||
import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
|
||||
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.PeerConnection;
|
||||
|
||||
/**
|
||||
* Model for (remote) call participants.
|
||||
*
|
||||
* This class keeps track of the state changes in a call participant and updates its data model as needed. View classes
|
||||
* are expected to directly use the read-only data model.
|
||||
*/
|
||||
public class CallParticipant {
|
||||
|
||||
private final PeerConnectionWrapper.PeerConnectionObserver peerConnectionObserver =
|
||||
new PeerConnectionWrapper.PeerConnectionObserver() {
|
||||
@Override
|
||||
public void onStreamAdded(MediaStream mediaStream) {
|
||||
handleStreamChange(mediaStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamRemoved(MediaStream mediaStream) {
|
||||
handleStreamChange(mediaStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIceConnectionStateChanged(PeerConnection.IceConnectionState iceConnectionState) {
|
||||
handleIceConnectionStateChange(iceConnectionState);
|
||||
}
|
||||
};
|
||||
|
||||
private final PeerConnectionWrapper.PeerConnectionObserver screenPeerConnectionObserver =
|
||||
new PeerConnectionWrapper.PeerConnectionObserver() {
|
||||
@Override
|
||||
public void onStreamAdded(MediaStream mediaStream) {
|
||||
callParticipantModel.setScreenMediaStream(mediaStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamRemoved(MediaStream mediaStream) {
|
||||
callParticipantModel.setScreenMediaStream(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIceConnectionStateChanged(PeerConnection.IceConnectionState iceConnectionState) {
|
||||
callParticipantModel.setScreenIceConnectionState(iceConnectionState);
|
||||
}
|
||||
};
|
||||
|
||||
// DataChannel messages are sent only in video peers; (sender) screen peers do not even open them.
|
||||
private final PeerConnectionWrapper.DataChannelMessageListener dataChannelMessageListener =
|
||||
new PeerConnectionWrapper.DataChannelMessageListener() {
|
||||
@Override
|
||||
public void onAudioOn() {
|
||||
callParticipantModel.setAudioAvailable(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioOff() {
|
||||
callParticipantModel.setAudioAvailable(Boolean.FALSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoOn() {
|
||||
callParticipantModel.setVideoAvailable(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoOff() {
|
||||
callParticipantModel.setVideoAvailable(Boolean.FALSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNickChanged(String nick) {
|
||||
callParticipantModel.setNick(nick);
|
||||
}
|
||||
};
|
||||
|
||||
private final MutableCallParticipantModel callParticipantModel;
|
||||
|
||||
private PeerConnectionWrapper peerConnectionWrapper;
|
||||
private PeerConnectionWrapper screenPeerConnectionWrapper;
|
||||
|
||||
public CallParticipant(String sessionId) {
|
||||
callParticipantModel = new MutableCallParticipantModel(sessionId);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (peerConnectionWrapper != null) {
|
||||
peerConnectionWrapper.removeObserver(peerConnectionObserver);
|
||||
peerConnectionWrapper.removeListener(dataChannelMessageListener);
|
||||
}
|
||||
if (screenPeerConnectionWrapper != null) {
|
||||
screenPeerConnectionWrapper.removeObserver(screenPeerConnectionObserver);
|
||||
}
|
||||
}
|
||||
|
||||
public CallParticipantModel getCallParticipantModel() {
|
||||
return callParticipantModel;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
callParticipantModel.setUserId(userId);
|
||||
}
|
||||
|
||||
public void setNick(String nick) {
|
||||
callParticipantModel.setNick(nick);
|
||||
}
|
||||
|
||||
public void setPeerConnectionWrapper(PeerConnectionWrapper peerConnectionWrapper) {
|
||||
if (this.peerConnectionWrapper != null) {
|
||||
this.peerConnectionWrapper.removeObserver(peerConnectionObserver);
|
||||
this.peerConnectionWrapper.removeListener(dataChannelMessageListener);
|
||||
}
|
||||
|
||||
this.peerConnectionWrapper = peerConnectionWrapper;
|
||||
|
||||
if (this.peerConnectionWrapper == null) {
|
||||
callParticipantModel.setIceConnectionState(null);
|
||||
callParticipantModel.setMediaStream(null);
|
||||
callParticipantModel.setAudioAvailable(null);
|
||||
callParticipantModel.setVideoAvailable(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
handleIceConnectionStateChange(this.peerConnectionWrapper.getPeerConnection().iceConnectionState());
|
||||
handleStreamChange(this.peerConnectionWrapper.getStream());
|
||||
|
||||
this.peerConnectionWrapper.addObserver(peerConnectionObserver);
|
||||
this.peerConnectionWrapper.addListener(dataChannelMessageListener);
|
||||
}
|
||||
|
||||
private void handleIceConnectionStateChange(PeerConnection.IceConnectionState iceConnectionState) {
|
||||
callParticipantModel.setIceConnectionState(iceConnectionState);
|
||||
|
||||
if (iceConnectionState == PeerConnection.IceConnectionState.NEW ||
|
||||
iceConnectionState == PeerConnection.IceConnectionState.CHECKING) {
|
||||
callParticipantModel.setAudioAvailable(null);
|
||||
callParticipantModel.setVideoAvailable(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStreamChange(MediaStream mediaStream) {
|
||||
if (mediaStream == null) {
|
||||
callParticipantModel.setMediaStream(null);
|
||||
callParticipantModel.setVideoAvailable(Boolean.FALSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasAtLeastOneVideoStream = mediaStream.videoTracks != null && !mediaStream.videoTracks.isEmpty();
|
||||
|
||||
callParticipantModel.setMediaStream(mediaStream);
|
||||
callParticipantModel.setVideoAvailable(hasAtLeastOneVideoStream);
|
||||
}
|
||||
|
||||
public void setScreenPeerConnectionWrapper(PeerConnectionWrapper screenPeerConnectionWrapper) {
|
||||
if (this.screenPeerConnectionWrapper != null) {
|
||||
this.screenPeerConnectionWrapper.removeObserver(screenPeerConnectionObserver);
|
||||
}
|
||||
|
||||
this.screenPeerConnectionWrapper = screenPeerConnectionWrapper;
|
||||
|
||||
if (this.screenPeerConnectionWrapper == null) {
|
||||
callParticipantModel.setScreenIceConnectionState(null);
|
||||
callParticipantModel.setScreenMediaStream(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
callParticipantModel.setScreenIceConnectionState(this.screenPeerConnectionWrapper.getPeerConnection().iceConnectionState());
|
||||
callParticipantModel.setScreenMediaStream(this.screenPeerConnectionWrapper.getStream());
|
||||
|
||||
this.screenPeerConnectionWrapper.addObserver(screenPeerConnectionObserver);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* 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.call;
|
||||
|
||||
import com.nextcloud.talk.models.json.participants.Participant;
|
||||
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class to keep track of the participants in a call based on the signaling messages.
|
||||
*
|
||||
* The CallParticipantList adds a listener for participant list messages as soon as it is created and starts tracking
|
||||
* the call participants until destroyed. Notifications about the changes can be received by adding an observer to the
|
||||
* CallParticipantList; note that no sorting is guaranteed on the participants.
|
||||
*/
|
||||
public class CallParticipantList {
|
||||
|
||||
public interface Observer {
|
||||
void onCallParticipantsChanged(Collection<Participant> joined, Collection<Participant> updated,
|
||||
Collection<Participant> left, Collection<Participant> unchanged);
|
||||
void onCallEndedForAll();
|
||||
}
|
||||
|
||||
private final SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener =
|
||||
new SignalingMessageReceiver.ParticipantListMessageListener() {
|
||||
|
||||
private final Map<String, Participant> callParticipants = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void onUsersInRoom(List<Participant> participants) {
|
||||
processParticipantList(participants);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onParticipantsUpdate(List<Participant> participants) {
|
||||
processParticipantList(participants);
|
||||
}
|
||||
|
||||
private void processParticipantList(List<Participant> participants) {
|
||||
Collection<Participant> joined = new ArrayList<>();
|
||||
Collection<Participant> updated = new ArrayList<>();
|
||||
Collection<Participant> left = new ArrayList<>();
|
||||
Collection<Participant> unchanged = new ArrayList<>();
|
||||
|
||||
Collection<Participant> knownCallParticipantsNotFound = new ArrayList<>(callParticipants.values());
|
||||
|
||||
for (Participant participant : participants) {
|
||||
String sessionId = participant.getSessionId();
|
||||
Participant callParticipant = callParticipants.get(sessionId);
|
||||
|
||||
boolean knownCallParticipant = callParticipant != null;
|
||||
if (!knownCallParticipant && participant.getInCall() != Participant.InCallFlags.DISCONNECTED) {
|
||||
callParticipants.put(sessionId, copyParticipant(participant));
|
||||
joined.add(copyParticipant(participant));
|
||||
} else if (knownCallParticipant && participant.getInCall() == Participant.InCallFlags.DISCONNECTED) {
|
||||
callParticipants.remove(sessionId);
|
||||
// No need to copy it, as it will be no longer used.
|
||||
callParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
|
||||
left.add(callParticipant);
|
||||
} else if (knownCallParticipant && callParticipant.getInCall() != participant.getInCall()) {
|
||||
callParticipant.setInCall(participant.getInCall());
|
||||
updated.add(copyParticipant(participant));
|
||||
} else if (knownCallParticipant) {
|
||||
unchanged.add(copyParticipant(participant));
|
||||
}
|
||||
|
||||
if (knownCallParticipant) {
|
||||
knownCallParticipantsNotFound.remove(callParticipant);
|
||||
}
|
||||
}
|
||||
|
||||
for (Participant callParticipant : knownCallParticipantsNotFound) {
|
||||
callParticipants.remove(callParticipant.getSessionId());
|
||||
// No need to copy it, as it will be no longer used.
|
||||
callParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
|
||||
left.add(callParticipant);
|
||||
}
|
||||
|
||||
if (!joined.isEmpty() || !updated.isEmpty() || !left.isEmpty()) {
|
||||
callParticipantListNotifier.notifyChanged(joined, updated, left, unchanged);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllParticipantsUpdate(long inCall) {
|
||||
if (inCall != Participant.InCallFlags.DISCONNECTED) {
|
||||
// Updating all participants is expected to happen only to disconnect them.
|
||||
return;
|
||||
}
|
||||
|
||||
callParticipantListNotifier.notifyCallEndedForAll();
|
||||
|
||||
Collection<Participant> joined = new ArrayList<>();
|
||||
Collection<Participant> updated = new ArrayList<>();
|
||||
Collection<Participant> left = new ArrayList<>(callParticipants.size());
|
||||
Collection<Participant> unchanged = new ArrayList<>();
|
||||
|
||||
for (Participant callParticipant : callParticipants.values()) {
|
||||
// No need to copy it, as it will be no longer used.
|
||||
callParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
|
||||
left.add(callParticipant);
|
||||
}
|
||||
callParticipants.clear();
|
||||
|
||||
if (!left.isEmpty()) {
|
||||
callParticipantListNotifier.notifyChanged(joined, updated, left, unchanged);
|
||||
}
|
||||
}
|
||||
|
||||
private Participant copyParticipant(Participant participant) {
|
||||
Participant copiedParticipant = new Participant();
|
||||
copiedParticipant.setInCall(participant.getInCall());
|
||||
copiedParticipant.setLastPing(participant.getLastPing());
|
||||
copiedParticipant.setSessionId(participant.getSessionId());
|
||||
copiedParticipant.setType(participant.getType());
|
||||
copiedParticipant.setUserId(participant.getUserId());
|
||||
|
||||
return copiedParticipant;
|
||||
}
|
||||
};
|
||||
|
||||
private final CallParticipantListNotifier callParticipantListNotifier = new CallParticipantListNotifier();
|
||||
|
||||
private final SignalingMessageReceiver signalingMessageReceiver;
|
||||
|
||||
public CallParticipantList(SignalingMessageReceiver signalingMessageReceiver) {
|
||||
this.signalingMessageReceiver = signalingMessageReceiver;
|
||||
this.signalingMessageReceiver.addListener(participantListMessageListener);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
signalingMessageReceiver.removeListener(participantListMessageListener);
|
||||
}
|
||||
|
||||
public void addObserver(Observer observer) {
|
||||
callParticipantListNotifier.addObserver(observer);
|
||||
}
|
||||
|
||||
public void removeObserver(Observer observer) {
|
||||
callParticipantListNotifier.removeObserver(observer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.call;
|
||||
|
||||
import com.nextcloud.talk.models.json.participants.Participant;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helper class to register and notify CallParticipantList.Observers.
|
||||
*
|
||||
* This class is only meant for internal use by CallParticipantList; listeners must register themselves against
|
||||
* a CallParticipantList rather than against a CallParticipantListNotifier.
|
||||
*/
|
||||
class CallParticipantListNotifier {
|
||||
|
||||
private final Set<CallParticipantList.Observer> callParticipantListObservers = new LinkedHashSet<>();
|
||||
|
||||
public synchronized void addObserver(CallParticipantList.Observer observer) {
|
||||
if (observer == null) {
|
||||
throw new IllegalArgumentException("CallParticipantList.Observer can not be null");
|
||||
}
|
||||
|
||||
callParticipantListObservers.add(observer);
|
||||
}
|
||||
|
||||
public synchronized void removeObserver(CallParticipantList.Observer observer) {
|
||||
callParticipantListObservers.remove(observer);
|
||||
}
|
||||
|
||||
public synchronized void notifyChanged(Collection<Participant> joined, Collection<Participant> updated,
|
||||
Collection<Participant> left, Collection<Participant> unchanged) {
|
||||
for (CallParticipantList.Observer observer : new ArrayList<>(callParticipantListObservers)) {
|
||||
observer.onCallParticipantsChanged(joined, updated, left, unchanged);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void notifyCallEndedForAll() {
|
||||
for (CallParticipantList.Observer observer : new ArrayList<>(callParticipantListObservers)) {
|
||||
observer.onCallEndedForAll();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* 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.call;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.PeerConnection;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Read-only data model for (remote) call participants.
|
||||
*
|
||||
* The received audio and video are available only if the participant is sending them and also has them enabled.
|
||||
* Before a connection is established it is not known whether audio and video are available or not, so null is returned
|
||||
* in that case (therefore it should not be autoboxed to a plain boolean without checking that).
|
||||
*
|
||||
* Audio and video in screen shares, on the other hand, are always seen as available.
|
||||
*
|
||||
* Clients of the model can observe it with CallParticipantModel.Observer to be notified when any value changes.
|
||||
* Getters called after receiving a notification are guaranteed to provide at least the value that triggered the
|
||||
* notification, but it may return even a more up to date one (so getting the value again on the following
|
||||
* notification may return the same value as before).
|
||||
*/
|
||||
public class CallParticipantModel {
|
||||
|
||||
public interface Observer {
|
||||
void onChange();
|
||||
}
|
||||
|
||||
protected class Data<T> {
|
||||
|
||||
private T value;
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(T value) {
|
||||
if (Objects.equals(this.value, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
|
||||
callParticipantModelNotifier.notifyChange();
|
||||
}
|
||||
}
|
||||
|
||||
private final CallParticipantModelNotifier callParticipantModelNotifier = new CallParticipantModelNotifier();
|
||||
|
||||
protected final String sessionId;
|
||||
|
||||
protected Data<String> userId;
|
||||
protected Data<String> nick;
|
||||
|
||||
protected Data<PeerConnection.IceConnectionState> iceConnectionState;
|
||||
protected Data<MediaStream> mediaStream;
|
||||
protected Data<Boolean> audioAvailable;
|
||||
protected Data<Boolean> videoAvailable;
|
||||
|
||||
protected Data<PeerConnection.IceConnectionState> screenIceConnectionState;
|
||||
protected Data<MediaStream> screenMediaStream;
|
||||
|
||||
public CallParticipantModel(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
|
||||
this.userId = new Data<>();
|
||||
this.nick = new Data<>();
|
||||
|
||||
this.iceConnectionState = new Data<>();
|
||||
this.mediaStream = new Data<>();
|
||||
this.audioAvailable = new Data<>();
|
||||
this.videoAvailable = new Data<>();
|
||||
|
||||
this.screenIceConnectionState = new Data<>();
|
||||
this.screenMediaStream = new Data<>();
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId.getValue();
|
||||
}
|
||||
|
||||
public String getNick() {
|
||||
return nick.getValue();
|
||||
}
|
||||
|
||||
public PeerConnection.IceConnectionState getIceConnectionState() {
|
||||
return iceConnectionState.getValue();
|
||||
}
|
||||
|
||||
public MediaStream getMediaStream() {
|
||||
return mediaStream.getValue();
|
||||
}
|
||||
|
||||
public Boolean isAudioAvailable() {
|
||||
return audioAvailable.getValue();
|
||||
}
|
||||
|
||||
public Boolean isVideoAvailable() {
|
||||
return videoAvailable.getValue();
|
||||
}
|
||||
|
||||
public PeerConnection.IceConnectionState getScreenIceConnectionState() {
|
||||
return screenIceConnectionState.getValue();
|
||||
}
|
||||
|
||||
public MediaStream getScreenMediaStream() {
|
||||
return screenMediaStream.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Observer to be notified when any value changes.
|
||||
*
|
||||
* @param observer the Observer
|
||||
* @see CallParticipantModel#addObserver(Observer, Handler)
|
||||
*/
|
||||
public void addObserver(Observer observer) {
|
||||
addObserver(observer, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an observer to be notified when any value changes.
|
||||
*
|
||||
* The observer will be notified on the thread associated to the given handler. If no handler is given the
|
||||
* observer will be immediately notified on the same thread that changed the value; the observer will be
|
||||
* immediately notified too if the thread of the handler is the same thread that changed the value.
|
||||
*
|
||||
* An observer is expected to be added only once. If the same observer is added again it will be notified just
|
||||
* once on the thread of the last handler.
|
||||
*
|
||||
* @param observer the Observer
|
||||
* @param handler a Handler for the thread to be notified on
|
||||
*/
|
||||
public void addObserver(Observer observer, Handler handler) {
|
||||
callParticipantModelNotifier.addObserver(observer, handler);
|
||||
}
|
||||
|
||||
public void removeObserver(Observer observer) {
|
||||
callParticipantModelNotifier.removeObserver(observer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.call;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper class to register and notify CallParticipantModel.Observers.
|
||||
*
|
||||
* This class is only meant for internal use by CallParticipantModel; observers must register themselves against a
|
||||
* CallParticipantModel rather than against a CallParticipantModelNotifier.
|
||||
*/
|
||||
class CallParticipantModelNotifier {
|
||||
|
||||
/**
|
||||
* Helper class to associate a CallParticipantModel.Observer with a Handler.
|
||||
*/
|
||||
private static class CallParticipantModelObserverOn {
|
||||
public final CallParticipantModel.Observer observer;
|
||||
public final Handler handler;
|
||||
|
||||
private CallParticipantModelObserverOn(CallParticipantModel.Observer observer, Handler handler) {
|
||||
this.observer = observer;
|
||||
this.handler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<CallParticipantModelObserverOn> callParticipantModelObserversOn = new ArrayList<>();
|
||||
|
||||
public synchronized void addObserver(CallParticipantModel.Observer observer, Handler handler) {
|
||||
if (observer == null) {
|
||||
throw new IllegalArgumentException("CallParticipantModel.Observer can not be null");
|
||||
}
|
||||
|
||||
removeObserver(observer);
|
||||
|
||||
callParticipantModelObserversOn.add(new CallParticipantModelObserverOn(observer, handler));
|
||||
}
|
||||
|
||||
public synchronized void removeObserver(CallParticipantModel.Observer observer) {
|
||||
Iterator<CallParticipantModelObserverOn> it = callParticipantModelObserversOn.iterator();
|
||||
while (it.hasNext()) {
|
||||
CallParticipantModelObserverOn observerOn = it.next();
|
||||
|
||||
if (observerOn.observer == observer) {
|
||||
it.remove();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void notifyChange() {
|
||||
for (CallParticipantModelObserverOn observerOn : new ArrayList<>(callParticipantModelObserversOn)) {
|
||||
if (observerOn.handler == null || observerOn.handler.getLooper() == Looper.myLooper()) {
|
||||
observerOn.observer.onChange();
|
||||
} else {
|
||||
observerOn.handler.post(() -> {
|
||||
observerOn.observer.onChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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.call;
|
||||
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.PeerConnection;
|
||||
|
||||
/**
|
||||
* Mutable data model for (remote) call participants.
|
||||
*
|
||||
* There is no synchronization when setting the values; if needed, it should be handled by the clients of the model.
|
||||
*/
|
||||
public class MutableCallParticipantModel extends CallParticipantModel {
|
||||
|
||||
public MutableCallParticipantModel(String sessionId) {
|
||||
super(sessionId);
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId.setValue(userId);
|
||||
}
|
||||
|
||||
public void setNick(String nick) {
|
||||
this.nick.setValue(nick);
|
||||
}
|
||||
|
||||
public void setIceConnectionState(PeerConnection.IceConnectionState iceConnectionState) {
|
||||
this.iceConnectionState.setValue(iceConnectionState);
|
||||
}
|
||||
|
||||
public void setMediaStream(MediaStream mediaStream) {
|
||||
this.mediaStream.setValue(mediaStream);
|
||||
}
|
||||
|
||||
public void setAudioAvailable(Boolean audioAvailable) {
|
||||
this.audioAvailable.setValue(audioAvailable);
|
||||
}
|
||||
|
||||
public void setVideoAvailable(Boolean videoAvailable) {
|
||||
this.videoAvailable.setValue(videoAvailable);
|
||||
}
|
||||
|
||||
public void setScreenIceConnectionState(PeerConnection.IceConnectionState screenIceConnectionState) {
|
||||
this.screenIceConnectionState.setValue(screenIceConnectionState);
|
||||
}
|
||||
|
||||
public void setScreenMediaStream(MediaStream screenMediaStream) {
|
||||
this.screenMediaStream.setValue(screenMediaStream);
|
||||
}
|
||||
}
|
|
@ -121,6 +121,9 @@ public class PeerConnectionWrapper {
|
|||
private final boolean isMCUPublisher;
|
||||
private final String videoStreamType;
|
||||
|
||||
// It is assumed that there will be at most one remote stream at each time.
|
||||
private MediaStream stream;
|
||||
|
||||
@Inject
|
||||
Context context;
|
||||
|
||||
|
@ -219,6 +222,10 @@ public class PeerConnectionWrapper {
|
|||
return videoStreamType;
|
||||
}
|
||||
|
||||
public MediaStream getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void removePeerConnection() {
|
||||
signalingMessageReceiver.removeListener(webRtcMessageListener);
|
||||
|
||||
|
@ -484,11 +491,15 @@ public class PeerConnectionWrapper {
|
|||
|
||||
@Override
|
||||
public void onAddStream(MediaStream mediaStream) {
|
||||
stream = mediaStream;
|
||||
|
||||
peerConnectionNotifier.notifyStreamAdded(mediaStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveStream(MediaStream mediaStream) {
|
||||
stream = null;
|
||||
|
||||
peerConnectionNotifier.notifyStreamRemoved(mediaStream);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,663 @@
|
|||
/*
|
||||
* 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.call;
|
||||
|
||||
import com.nextcloud.talk.models.json.participants.Participant;
|
||||
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.DISCONNECTED;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.IN_CALL;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_AUDIO;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_VIDEO;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.GUEST;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.GUEST_MODERATOR;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.MODERATOR;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.OWNER;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.USER;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
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;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
public class CallParticipantListExternalSignalingTest {
|
||||
|
||||
private static class ParticipantsUpdateParticipantBuilder {
|
||||
private Participant newUser(long inCall, long lastPing, String sessionId, Participant.ParticipantType type,
|
||||
String userId) {
|
||||
Participant participant = new Participant();
|
||||
participant.setInCall(inCall);
|
||||
participant.setLastPing(lastPing);
|
||||
participant.setSessionId(sessionId);
|
||||
participant.setType(type);
|
||||
participant.setUserId(userId);
|
||||
|
||||
return participant;
|
||||
}
|
||||
|
||||
private Participant newGuest(long inCall, long lastPing, String sessionId, Participant.ParticipantType type) {
|
||||
Participant participant = new Participant();
|
||||
participant.setInCall(inCall);
|
||||
participant.setLastPing(lastPing);
|
||||
participant.setSessionId(sessionId);
|
||||
participant.setType(type);
|
||||
|
||||
return participant;
|
||||
}
|
||||
}
|
||||
|
||||
private final ParticipantsUpdateParticipantBuilder builder = new ParticipantsUpdateParticipantBuilder();
|
||||
|
||||
private CallParticipantList callParticipantList;
|
||||
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener;
|
||||
|
||||
private CallParticipantList.Observer mockedCallParticipantListObserver;
|
||||
|
||||
private Collection<Participant> expectedJoined;
|
||||
private Collection<Participant> expectedUpdated;
|
||||
private Collection<Participant> expectedLeft;
|
||||
private Collection<Participant> expectedUnchanged;
|
||||
|
||||
// The order of the left participants in some tests depends on how they are internally sorted by the map, so the
|
||||
// list of left participants needs to be checked ignoring the sorting (or, rather, sorting by session ID as in
|
||||
// expectedLeft).
|
||||
// Other tests can just relay on the not guaranteed, but known internal sorting of the elements.
|
||||
private final ArgumentMatcher<List<Participant>> matchesExpectedLeftIgnoringOrder = left -> {
|
||||
Collections.sort(left, Comparator.comparing(Participant::getSessionId));
|
||||
return expectedLeft.equals(left);
|
||||
};
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
SignalingMessageReceiver mockedSignalingMessageReceiver = mock(SignalingMessageReceiver.class);
|
||||
|
||||
callParticipantList = new CallParticipantList(mockedSignalingMessageReceiver);
|
||||
|
||||
mockedCallParticipantListObserver = mock(CallParticipantList.Observer.class);
|
||||
|
||||
// Get internal ParticipantListMessageListener from callParticipantList set in the
|
||||
// mockedSignalingMessageReceiver.
|
||||
ArgumentCaptor<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListenerArgumentCaptor =
|
||||
ArgumentCaptor.forClass(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
verify(mockedSignalingMessageReceiver).addListener(participantListMessageListenerArgumentCaptor.capture());
|
||||
|
||||
participantListMessageListener = participantListMessageListenerArgumentCaptor.getValue();
|
||||
|
||||
expectedJoined = new ArrayList<>();
|
||||
expectedUpdated = new ArrayList<>();
|
||||
expectedLeft = new ArrayList<>();
|
||||
expectedUnchanged = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateJoinRoom() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateJoinRoomSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", USER, "theUserId4"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 5, "theSessionId5", OWNER, "theUserId5"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateJoinRoomThenJoinCall() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateJoinRoomThenJoinCallSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateJoinRoomAndCall() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateJoinRoomAndCallSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateJoinRoomAndCallRepeated() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateChangeCallFlags() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateChangeCallFlagsSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedUpdated.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
expectedUpdated.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", USER, "theUserId4"));
|
||||
expectedUnchanged.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2", GUEST));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateChangeLastPing() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateChangeLastPingSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", USER, "theUserId3"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 108, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 815, "theSessionId3", USER, "theUserId3"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateChangeParticipantType() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", USER, "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateChangeParticipantTypeeSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", USER, "theUserId3"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", USER, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2", GUEST_MODERATOR));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", MODERATOR, "theUserId3"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateLeaveCall() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateLeaveCallSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateLeaveCallThenLeaveRoom() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateLeaveCallThenLeaveRoomSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateLeaveCallAndRoom() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateLeaveCallAndRoomSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
|
||||
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
|
||||
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParticipantsUpdateSeveralEventsSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
participants.add(builder.newUser(IN_CALL, 5, "theSessionId5", OWNER, "theUserId5"));
|
||||
// theSessionId6 has not joined yet.
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_VIDEO, 7, "theSessionId7", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 8, "theSessionId8", USER, "theUserId8"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 9, "theSessionId9", MODERATOR, "theUserId9"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
// theSessionId1 is gone.
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", OWNER, "theUserId5"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6", GUEST));
|
||||
participants.add(builder.newGuest(IN_CALL, 7, "theSessionId7", GUEST));
|
||||
participants.add(builder.newUser(IN_CALL, 8, "theSessionId8", USER, "theUserId8"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", USER, "theUserId9"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedJoined.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6", GUEST));
|
||||
expectedJoined.add(builder.newUser(IN_CALL, 8, "theSessionId8", USER, "theUserId8"));
|
||||
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", OWNER, "theUserId5"));
|
||||
expectedUpdated.add(builder.newGuest(IN_CALL, 7, "theSessionId7", GUEST));
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
|
||||
// Last ping and participant type are not seen as changed, even if they did.
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", USER, "theUserId9"));
|
||||
|
||||
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
|
||||
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllParticipantsUpdateDisconnected() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
|
||||
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
InOrder inOrder = inOrder(mockedCallParticipantListObserver);
|
||||
|
||||
inOrder.verify(mockedCallParticipantListObserver).onCallEndedForAll();
|
||||
inOrder.verify(mockedCallParticipantListObserver).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllParticipantsUpdateDisconnectedWithSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 2, "theSessionId2", USER, "theUserId2"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", USER, "theUserId3"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 4, "theSessionId4", GUEST));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
|
||||
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
|
||||
expectedLeft.add(builder.newGuest(DISCONNECTED, 4, "theSessionId4", GUEST));
|
||||
|
||||
InOrder inOrder = inOrder(mockedCallParticipantListObserver);
|
||||
|
||||
inOrder.verify(mockedCallParticipantListObserver).onCallEndedForAll();
|
||||
inOrder.verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
|
||||
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllParticipantsUpdateDisconnectedNoOneInCall() {
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
|
||||
|
||||
InOrder inOrder = inOrder(mockedCallParticipantListObserver);
|
||||
|
||||
inOrder.verify(mockedCallParticipantListObserver).onCallEndedForAll();
|
||||
verifyNoMoreInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllParticipantsUpdateDisconnectedThenJoinCallAgain() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
participantListMessageListener.onParticipantsUpdate(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,535 @@
|
|||
/*
|
||||
* 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.call;
|
||||
|
||||
import com.nextcloud.talk.models.json.participants.Participant;
|
||||
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.DISCONNECTED;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.IN_CALL;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_AUDIO;
|
||||
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_VIDEO;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
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 CallParticipantListInternalSignalingTest {
|
||||
|
||||
private static class UsersInRoomParticipantBuilder {
|
||||
private Participant newUser(long inCall, long lastPing, String sessionId, String userId) {
|
||||
Participant participant = new Participant();
|
||||
participant.setInCall(inCall);
|
||||
participant.setLastPing(lastPing);
|
||||
participant.setSessionId(sessionId);
|
||||
participant.setUserId(userId);
|
||||
|
||||
return participant;
|
||||
}
|
||||
|
||||
private Participant newGuest(long inCall, long lastPing, String sessionId) {
|
||||
Participant participant = new Participant();
|
||||
participant.setInCall(inCall);
|
||||
participant.setLastPing(lastPing);
|
||||
participant.setSessionId(sessionId);
|
||||
|
||||
return participant;
|
||||
}
|
||||
}
|
||||
|
||||
private final UsersInRoomParticipantBuilder builder = new UsersInRoomParticipantBuilder();
|
||||
|
||||
private CallParticipantList callParticipantList;
|
||||
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener;
|
||||
|
||||
private CallParticipantList.Observer mockedCallParticipantListObserver;
|
||||
|
||||
private Collection<Participant> expectedJoined;
|
||||
private Collection<Participant> expectedUpdated;
|
||||
private Collection<Participant> expectedLeft;
|
||||
private Collection<Participant> expectedUnchanged;
|
||||
|
||||
// The order of the left participants in some tests depends on how they are internally sorted by the map, so the
|
||||
// list of left participants needs to be checked ignoring the sorting (or, rather, sorting by session ID as in
|
||||
// expectedLeft).
|
||||
// Other tests can just relay on the not guaranteed, but known internal sorting of the elements.
|
||||
private final ArgumentMatcher<List<Participant>> matchesExpectedLeftIgnoringOrder = left -> {
|
||||
Collections.sort(left, Comparator.comparing(Participant::getSessionId));
|
||||
return expectedLeft.equals(left);
|
||||
};
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
SignalingMessageReceiver mockedSignalingMessageReceiver = mock(SignalingMessageReceiver.class);
|
||||
|
||||
callParticipantList = new CallParticipantList(mockedSignalingMessageReceiver);
|
||||
|
||||
mockedCallParticipantListObserver = mock(CallParticipantList.Observer.class);
|
||||
|
||||
// Get internal ParticipantListMessageListener from callParticipantList set in the
|
||||
// mockedSignalingMessageReceiver.
|
||||
ArgumentCaptor<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListenerArgumentCaptor =
|
||||
ArgumentCaptor.forClass(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
verify(mockedSignalingMessageReceiver).addListener(participantListMessageListenerArgumentCaptor.capture());
|
||||
|
||||
participantListMessageListener = participantListMessageListenerArgumentCaptor.getValue();
|
||||
|
||||
expectedJoined = new ArrayList<>();
|
||||
expectedUpdated = new ArrayList<>();
|
||||
expectedLeft = new ArrayList<>();
|
||||
expectedUnchanged = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomJoinRoom() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomJoinRoomSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", "theUserId4"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 5, "theSessionId5", "theUserId5"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomJoinRoomThenJoinCall() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomJoinRoomThenJoinCallSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomJoinRoomAndCall() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomJoinRoomAndCallSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomJoinRoomAndCallRepeated() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomChangeCallFlags() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomChangeCallFlagsSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedUpdated.add(builder.newUser(IN_CALL, 1, "theSessionId1", "theUserId1"));
|
||||
expectedUpdated.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", "theUserId4"));
|
||||
expectedUnchanged.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomChangeLastPing() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", "theUserId1"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomChangeLastPingSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", "theUserId3"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 108, "theSessionId2"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 815, "theSessionId3", "theUserId3"));
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomLeaveCall() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomLeaveCallSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomLeaveCallThenLeaveRoom() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomLeaveCallThenLeaveRoomSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
verifyNoInteractions(mockedCallParticipantListObserver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomLeaveCallAndRoom() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
|
||||
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
|
||||
expectedLeft, expectedUnchanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomLeaveCallAndRoomSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
|
||||
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
|
||||
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsersInRoomSeveralEventsSeveralParticipants() {
|
||||
List<Participant> participants = new ArrayList<>();
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
|
||||
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
participants.add(builder.newUser(IN_CALL, 5, "theSessionId5", "theUserId5"));
|
||||
// theSessionId6 has not joined yet.
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_VIDEO, 7, "theSessionId7"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 8, "theSessionId8", "theUserId8"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 9, "theSessionId9", "theUserId9"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
callParticipantList.addObserver(mockedCallParticipantListObserver);
|
||||
|
||||
participants = new ArrayList<>();
|
||||
// theSessionId1 is gone.
|
||||
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
|
||||
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
|
||||
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", "theUserId5"));
|
||||
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6"));
|
||||
participants.add(builder.newGuest(IN_CALL, 7, "theSessionId7"));
|
||||
participants.add(builder.newUser(IN_CALL, 8, "theSessionId8", "theUserId8"));
|
||||
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", "theUserId9"));
|
||||
|
||||
participantListMessageListener.onUsersInRoom(participants);
|
||||
|
||||
expectedJoined.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6"));
|
||||
expectedJoined.add(builder.newUser(IN_CALL, 8, "theSessionId8", "theUserId8"));
|
||||
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", "theUserId5"));
|
||||
expectedUpdated.add(builder.newGuest(IN_CALL, 7, "theSessionId7"));
|
||||
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
|
||||
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
|
||||
// Last ping is not seen as changed, even if it did.
|
||||
expectedUnchanged.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", "theUserId9"));
|
||||
|
||||
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
|
||||
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.call;
|
||||
|
||||
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
public class CallParticipantListTest {
|
||||
|
||||
private SignalingMessageReceiver mockedSignalingMessageReceiver;
|
||||
|
||||
private CallParticipantList callParticipantList;
|
||||
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mockedSignalingMessageReceiver = mock(SignalingMessageReceiver.class);
|
||||
|
||||
callParticipantList = new CallParticipantList(mockedSignalingMessageReceiver);
|
||||
|
||||
// Get internal ParticipantListMessageListener from callParticipantList set in the
|
||||
// mockedSignalingMessageReceiver.
|
||||
ArgumentCaptor<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListenerArgumentCaptor =
|
||||
ArgumentCaptor.forClass(SignalingMessageReceiver.ParticipantListMessageListener.class);
|
||||
|
||||
verify(mockedSignalingMessageReceiver).addListener(participantListMessageListenerArgumentCaptor.capture());
|
||||
|
||||
participantListMessageListener = participantListMessageListenerArgumentCaptor.getValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDestroy() {
|
||||
callParticipantList.destroy();
|
||||
|
||||
verify(mockedSignalingMessageReceiver).removeListener(participantListMessageListener);
|
||||
verifyNoMoreInteractions(mockedSignalingMessageReceiver);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue