mirror of
https://github.com/nextcloud/talk-android
synced 2025-01-18 21:18:15 +00:00
Send current state to remote participants when they join
Note that this implicitly send the current state to remote participants when the local participant joins, as in that case all the remote participants already in the call join from the point of view of the local participant Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This commit is contained in:
parent
ea4bccdaf7
commit
0ec5175c61
@ -64,6 +64,8 @@ import com.nextcloud.talk.call.CallParticipant
|
||||
import com.nextcloud.talk.call.CallParticipantList
|
||||
import com.nextcloud.talk.call.CallParticipantModel
|
||||
import com.nextcloud.talk.call.LocalStateBroadcaster
|
||||
import com.nextcloud.talk.call.LocalStateBroadcasterMcu
|
||||
import com.nextcloud.talk.call.LocalStateBroadcasterNoMcu
|
||||
import com.nextcloud.talk.call.MessageSender
|
||||
import com.nextcloud.talk.call.MessageSenderMcu
|
||||
import com.nextcloud.talk.call.MessageSenderNoMcu
|
||||
@ -1728,7 +1730,14 @@ class CallActivity : CallBaseActivity() {
|
||||
callParticipantList = CallParticipantList(signalingMessageReceiver)
|
||||
callParticipantList!!.addObserver(callParticipantListObserver)
|
||||
|
||||
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, messageSender)
|
||||
if (hasMCU) {
|
||||
localStateBroadcaster = LocalStateBroadcasterMcu(localCallParticipantModel, messageSender)
|
||||
} else {
|
||||
localStateBroadcaster = LocalStateBroadcasterNoMcu(
|
||||
localCallParticipantModel,
|
||||
messageSender as MessageSenderNoMcu
|
||||
)
|
||||
}
|
||||
|
||||
val apiVersion = ApiUtils.getCallApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
|
||||
ncApi!!.joinCall(
|
||||
@ -2429,6 +2438,9 @@ class CallActivity : CallBaseActivity() {
|
||||
callParticipantEventDisplayers[sessionId] = callParticipantEventDisplayer
|
||||
callParticipantModel.addObserver(callParticipantEventDisplayer, callParticipantEventDisplayersHandler)
|
||||
runOnUiThread { addParticipantDisplayItem(callParticipantModel, "video") }
|
||||
|
||||
localStateBroadcaster!!.handleCallParticipantAdded(callParticipant.callParticipantModel)
|
||||
|
||||
return callParticipant
|
||||
}
|
||||
|
||||
@ -2454,6 +2466,9 @@ class CallActivity : CallBaseActivity() {
|
||||
|
||||
private fun removeCallParticipant(sessionId: String?) {
|
||||
val callParticipant = callParticipants.remove(sessionId) ?: return
|
||||
|
||||
localStateBroadcaster!!.handleCallParticipantRemoved(callParticipant.callParticipantModel)
|
||||
|
||||
val screenParticipantDisplayItemManager = screenParticipantDisplayItemManagers.remove(sessionId)
|
||||
callParticipant.callParticipantModel.removeObserver(screenParticipantDisplayItemManager)
|
||||
val callParticipantEventDisplayer = callParticipantEventDisplayers.remove(sessionId)
|
||||
|
@ -17,8 +17,12 @@ import java.util.Objects;
|
||||
* all the participants in the call. Note that the LocalStateBroadcaster does not check whether the local participant
|
||||
* is actually in the call or not; it is expected that the LocalStateBroadcaster will be created and destroyed when the
|
||||
* local participant joins and leaves the call.
|
||||
* <p>
|
||||
* The LocalStateBroadcaster also sends the current state to remote participants when they join (which implicitly
|
||||
* sends it to all remote participants when the local participant joins the call) so they can set an initial state
|
||||
* for the local participant.
|
||||
*/
|
||||
public class LocalStateBroadcaster {
|
||||
public abstract class LocalStateBroadcaster {
|
||||
|
||||
private final LocalCallParticipantModel localCallParticipantModel;
|
||||
|
||||
@ -73,7 +77,10 @@ public class LocalStateBroadcaster {
|
||||
this.localCallParticipantModel.removeObserver(localCallParticipantModelObserver);
|
||||
}
|
||||
|
||||
private DataChannelMessage getDataChannelMessageForAudioState() {
|
||||
public abstract void handleCallParticipantAdded(CallParticipantModel callParticipantModel);
|
||||
public abstract void handleCallParticipantRemoved(CallParticipantModel callParticipantModel);
|
||||
|
||||
protected DataChannelMessage getDataChannelMessageForAudioState() {
|
||||
String type = "audioOff";
|
||||
if (localCallParticipantModel.isAudioEnabled() != null && localCallParticipantModel.isAudioEnabled()) {
|
||||
type = "audioOn";
|
||||
@ -82,7 +89,7 @@ public class LocalStateBroadcaster {
|
||||
return new DataChannelMessage(type);
|
||||
}
|
||||
|
||||
private DataChannelMessage getDataChannelMessageForSpeakingState() {
|
||||
protected DataChannelMessage getDataChannelMessageForSpeakingState() {
|
||||
String type = "stoppedSpeaking";
|
||||
if (localCallParticipantModel.isSpeaking() != null && localCallParticipantModel.isSpeaking()) {
|
||||
type = "speaking";
|
||||
@ -91,7 +98,7 @@ public class LocalStateBroadcaster {
|
||||
return new DataChannelMessage(type);
|
||||
}
|
||||
|
||||
private DataChannelMessage getDataChannelMessageForVideoState() {
|
||||
protected DataChannelMessage getDataChannelMessageForVideoState() {
|
||||
String type = "videoOff";
|
||||
if (localCallParticipantModel.isVideoEnabled() != null && localCallParticipantModel.isVideoEnabled()) {
|
||||
type = "videoOn";
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.call;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* Helper class to send the local participant state to the other participants in the call when an MCU is used.
|
||||
* <p>
|
||||
* Sending the state when it changes is handled by the base class; this subclass only handles sending the initial
|
||||
* state when a remote participant is added.
|
||||
* <p>
|
||||
* When Janus is used data channel messages are sent to all remote participants (with a peer connection to receive from
|
||||
* the local participant). Moreover, it is not possible to know when the remote participants open the data channel to
|
||||
* receive the messages, or even when they establish the receiver connection; it is only possible to know when the
|
||||
* data channel is open for the publisher connection of the local participant. Due to all that the state is sent
|
||||
* several times with an increasing delay whenever a participant joins the call (which implicitly broadcasts the
|
||||
* initial state when the local participant joins the call, as all the remote participants joined from the point of
|
||||
* view of the local participant). If the state was already being sent the sending is restarted with each new
|
||||
* participant that joins.
|
||||
*/
|
||||
public class LocalStateBroadcasterMcu extends LocalStateBroadcaster {
|
||||
|
||||
private final MessageSender messageSender;
|
||||
|
||||
private Disposable sendStateWithRepetition;
|
||||
|
||||
public LocalStateBroadcasterMcu(LocalCallParticipantModel localCallParticipantModel,
|
||||
MessageSender messageSender) {
|
||||
super(localCallParticipantModel, messageSender);
|
||||
|
||||
this.messageSender = messageSender;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
|
||||
if (sendStateWithRepetition != null) {
|
||||
sendStateWithRepetition.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallParticipantAdded(CallParticipantModel callParticipantModel) {
|
||||
if (sendStateWithRepetition != null) {
|
||||
sendStateWithRepetition.dispose();
|
||||
}
|
||||
|
||||
sendStateWithRepetition = Observable
|
||||
.fromArray(new Integer[]{0, 1, 2, 4, 8, 16})
|
||||
.concatMap(i -> Observable.just(i).delay(i, TimeUnit.SECONDS, Schedulers.io()))
|
||||
.subscribe(value -> sendState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallParticipantRemoved(CallParticipantModel callParticipantModel) {
|
||||
}
|
||||
|
||||
private void sendState() {
|
||||
messageSender.sendToAll(getDataChannelMessageForAudioState());
|
||||
messageSender.sendToAll(getDataChannelMessageForSpeakingState());
|
||||
messageSender.sendToAll(getDataChannelMessageForVideoState());
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.call;
|
||||
|
||||
import org.webrtc.PeerConnection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Helper class to send the local participant state to the other participants in the call when an MCU is not used.
|
||||
* <p>
|
||||
* Sending the state when it changes is handled by the base class; this subclass only handles sending the initial
|
||||
* state when a remote participant is added.
|
||||
* <p>
|
||||
* The state is sent when a connection with another participant is first established (which implicitly broadcasts the
|
||||
* initial state when the local participant joins the call, as a connection is established with all the remote
|
||||
* participants). Note that, as long as that participant stays in the call, the initial state is not sent again, even
|
||||
* after a temporary disconnection; data channels use a reliable transport by default, so even if the state changes
|
||||
* while the connection is temporarily interrupted the normal state update messages should be received by the other
|
||||
* participant once the connection is restored.
|
||||
*/
|
||||
public class LocalStateBroadcasterNoMcu extends LocalStateBroadcaster {
|
||||
|
||||
private final MessageSenderNoMcu messageSender;
|
||||
|
||||
private final Map<String, IceConnectionStateObserver> iceConnectionStateObservers = new HashMap<>();
|
||||
|
||||
private class IceConnectionStateObserver implements CallParticipantModel.Observer {
|
||||
|
||||
private final CallParticipantModel callParticipantModel;
|
||||
|
||||
private PeerConnection.IceConnectionState iceConnectionState;
|
||||
|
||||
public IceConnectionStateObserver(CallParticipantModel callParticipantModel) {
|
||||
this.callParticipantModel = callParticipantModel;
|
||||
|
||||
callParticipantModel.addObserver(this);
|
||||
iceConnectionStateObservers.put(callParticipantModel.getSessionId(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange() {
|
||||
if (Objects.equals(iceConnectionState, callParticipantModel.getIceConnectionState())) {
|
||||
return;
|
||||
}
|
||||
|
||||
iceConnectionState = callParticipantModel.getIceConnectionState();
|
||||
|
||||
if (iceConnectionState == PeerConnection.IceConnectionState.CONNECTED ||
|
||||
iceConnectionState == PeerConnection.IceConnectionState.COMPLETED) {
|
||||
remove();
|
||||
|
||||
sendState(callParticipantModel.getSessionId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReaction(String reaction) {
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
callParticipantModel.removeObserver(this);
|
||||
iceConnectionStateObservers.remove(callParticipantModel.getSessionId());
|
||||
}
|
||||
}
|
||||
|
||||
public LocalStateBroadcasterNoMcu(LocalCallParticipantModel localCallParticipantModel,
|
||||
MessageSenderNoMcu messageSender) {
|
||||
super(localCallParticipantModel, messageSender);
|
||||
|
||||
this.messageSender = messageSender;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
|
||||
// The observers remove themselves from the map, so a copy is needed to remove them while iterating.
|
||||
List<IceConnectionStateObserver> iceConnectionStateObserversCopy =
|
||||
new ArrayList<>(iceConnectionStateObservers.values());
|
||||
for (IceConnectionStateObserver iceConnectionStateObserver : iceConnectionStateObserversCopy) {
|
||||
iceConnectionStateObserver.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallParticipantAdded(CallParticipantModel callParticipantModel) {
|
||||
IceConnectionStateObserver iceConnectionStateObserver =
|
||||
iceConnectionStateObservers.get(callParticipantModel.getSessionId());
|
||||
if (iceConnectionStateObserver != null) {
|
||||
iceConnectionStateObserver.remove();
|
||||
}
|
||||
|
||||
iceConnectionStateObserver = new IceConnectionStateObserver(callParticipantModel);
|
||||
iceConnectionStateObservers.put(callParticipantModel.getSessionId(), iceConnectionStateObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallParticipantRemoved(CallParticipantModel callParticipantModel) {
|
||||
IceConnectionStateObserver iceConnectionStateObserver =
|
||||
iceConnectionStateObservers.get(callParticipantModel.getSessionId());
|
||||
if (iceConnectionStateObserver != null) {
|
||||
iceConnectionStateObserver.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendState(String sessionId) {
|
||||
messageSender.send(getDataChannelMessageForAudioState(), sessionId);
|
||||
messageSender.send(getDataChannelMessageForSpeakingState(), sessionId);
|
||||
messageSender.send(getDataChannelMessageForVideoState(), sessionId);
|
||||
}
|
||||
}
|
@ -329,22 +329,6 @@ public class PeerConnectionWrapper {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
private void sendInitialMediaStatus() {
|
||||
if (localStream != null) {
|
||||
if (localStream.videoTracks.size() == 1 && localStream.videoTracks.get(0).enabled()) {
|
||||
send(new DataChannelMessage("videoOn"));
|
||||
} else {
|
||||
send(new DataChannelMessage("videoOff"));
|
||||
}
|
||||
|
||||
if (localStream.audioTracks.size() == 1 && localStream.audioTracks.get(0).enabled()) {
|
||||
send(new DataChannelMessage("audioOn"));
|
||||
} else {
|
||||
send(new DataChannelMessage("audioOff"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMCUPublisher() {
|
||||
return isMCUPublisher;
|
||||
}
|
||||
@ -432,10 +416,6 @@ public class PeerConnectionWrapper {
|
||||
}
|
||||
pendingDataChannelMessages.clear();
|
||||
}
|
||||
|
||||
if (dataChannel.state() == DataChannel.State.OPEN) {
|
||||
sendInitialMediaStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -523,11 +503,6 @@ public class PeerConnectionWrapper {
|
||||
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
|
||||
|
||||
Log.d("iceConnectionChangeTo: ", iceConnectionState.name() + " over " + peerConnection.hashCode() + " " + sessionId);
|
||||
if (iceConnectionState == PeerConnection.IceConnectionState.CONNECTED) {
|
||||
if (hasInitiated) {
|
||||
sendInitialMediaStatus();
|
||||
}
|
||||
}
|
||||
|
||||
peerConnectionNotifier.notifyIceConnectionStateChanged(iceConnectionState);
|
||||
}
|
||||
|
@ -0,0 +1,439 @@
|
||||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.call
|
||||
|
||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
|
||||
import io.reactivex.plugins.RxJavaPlugins
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.times
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Suppress("LongMethod")
|
||||
class LocalStateBroadcasterMcuTest {
|
||||
|
||||
private var localCallParticipantModel: MutableLocalCallParticipantModel? = null
|
||||
private var mockedMessageSender: MessageSender? = null
|
||||
|
||||
private var localStateBroadcasterMcu: LocalStateBroadcasterMcu? = null
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
localCallParticipantModel = MutableLocalCallParticipantModel()
|
||||
localCallParticipantModel!!.isAudioEnabled = true
|
||||
localCallParticipantModel!!.isSpeaking = true
|
||||
localCallParticipantModel!!.isVideoEnabled = true
|
||||
mockedMessageSender = Mockito.mock(MessageSender::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateSentWithExponentialBackoffWhenParticipantAdded() {
|
||||
val testScheduler = TestScheduler()
|
||||
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
|
||||
|
||||
localStateBroadcasterMcu = LocalStateBroadcasterMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSender
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
// Sending will be done in another thread, so just adding the participant does not send anything until that
|
||||
// other thread could run.
|
||||
Mockito.verifyNoInteractions(mockedMessageSender)
|
||||
|
||||
val expectedAudioOn = DataChannelMessage("audioOn")
|
||||
val expectedSpeaking = DataChannelMessage("speaking")
|
||||
val expectedVideoOn = DataChannelMessage("videoOn")
|
||||
|
||||
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
|
||||
|
||||
var messageCount = 1
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 2
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 3
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(4, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 4
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(8, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 5
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(16, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 6
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(100, TimeUnit.SECONDS)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateSentWithExponentialBackoffIsTheCurrentState() {
|
||||
// This test could have been included in "testStateSentWithExponentialBackoffWhenParticipantAdded", but was
|
||||
// kept separate for clarity.
|
||||
|
||||
val testScheduler = TestScheduler()
|
||||
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
|
||||
|
||||
localStateBroadcasterMcu = LocalStateBroadcasterMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSender
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
// Sending will be done in another thread, so just adding the participant does not send anything until that
|
||||
// other thread could run.
|
||||
Mockito.verifyNoInteractions(mockedMessageSender)
|
||||
|
||||
val expectedAudioOn = DataChannelMessage("audioOn")
|
||||
val expectedSpeaking = DataChannelMessage("speaking")
|
||||
val expectedVideoOn = DataChannelMessage("videoOn")
|
||||
|
||||
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
|
||||
|
||||
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
localCallParticipantModel!!.isSpeaking = false
|
||||
|
||||
val expectedStoppedSpeaking = DataChannelMessage("stoppedSpeaking")
|
||||
|
||||
// Changing the state causes the normal state update to be sent, independently of the initial state
|
||||
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedStoppedSpeaking)
|
||||
|
||||
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
|
||||
|
||||
Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedStoppedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
localCallParticipantModel!!.isAudioEnabled = false
|
||||
|
||||
val expectedAudioOff = DataChannelMessage("audioOff")
|
||||
|
||||
// Changing the state causes the normal state update to be sent, independently of the initial state
|
||||
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedAudioOff)
|
||||
|
||||
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
|
||||
|
||||
Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedAudioOff)
|
||||
Mockito.verify(mockedMessageSender!!, times(3)).sendToAll(expectedStoppedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(3)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
localCallParticipantModel!!.isVideoEnabled = false
|
||||
|
||||
val expectedVideoOff = DataChannelMessage("videoOff")
|
||||
|
||||
// Changing the state causes the normal state update to be sent, independently of the initial state
|
||||
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedVideoOff)
|
||||
|
||||
testScheduler.advanceTimeBy(4, TimeUnit.SECONDS)
|
||||
|
||||
Mockito.verify(mockedMessageSender!!, times(3)).sendToAll(expectedAudioOff)
|
||||
Mockito.verify(mockedMessageSender!!, times(4)).sendToAll(expectedStoppedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedVideoOff)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
localCallParticipantModel!!.isVideoEnabled = true
|
||||
|
||||
// Changing the state causes the normal state update to be sent, independently of the initial state
|
||||
Mockito.verify(mockedMessageSender!!, times(4)).sendToAll(expectedVideoOn)
|
||||
|
||||
testScheduler.advanceTimeBy(8, TimeUnit.SECONDS)
|
||||
|
||||
Mockito.verify(mockedMessageSender!!, times(4)).sendToAll(expectedAudioOff)
|
||||
Mockito.verify(mockedMessageSender!!, times(5)).sendToAll(expectedStoppedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(5)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateSentWithExponentialBackoffRestartedWhenAnotherParticipantAdded() {
|
||||
val testScheduler = TestScheduler()
|
||||
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
|
||||
|
||||
localStateBroadcasterMcu = LocalStateBroadcasterMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSender
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
// Sending will be done in another thread, so just adding the participant does not send anything until that
|
||||
// other thread could run.
|
||||
Mockito.verifyNoInteractions(mockedMessageSender)
|
||||
|
||||
val expectedAudioOn = DataChannelMessage("audioOn")
|
||||
val expectedSpeaking = DataChannelMessage("speaking")
|
||||
val expectedVideoOn = DataChannelMessage("videoOn")
|
||||
|
||||
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
|
||||
|
||||
var messageCount = 1
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 2
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 3
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(4, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 4
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
val callParticipantModel2 = MutableCallParticipantModel("theSessionId2")
|
||||
|
||||
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel2)
|
||||
|
||||
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 5
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 6
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 7
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(4, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 8
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(8, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 9
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(16, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 10
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(100, TimeUnit.SECONDS)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateStillSentWithExponentialBackoffWhenParticipantRemoved() {
|
||||
// For simplicity the exponential backoff is not aborted when the participant that triggered it is removed.
|
||||
|
||||
val testScheduler = TestScheduler()
|
||||
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
|
||||
|
||||
localStateBroadcasterMcu = LocalStateBroadcasterMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSender
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
// Sending will be done in another thread, so just adding the participant does not send anything until that
|
||||
// other thread could run.
|
||||
Mockito.verifyNoInteractions(mockedMessageSender)
|
||||
|
||||
val expectedAudioOn = DataChannelMessage("audioOn")
|
||||
val expectedSpeaking = DataChannelMessage("speaking")
|
||||
val expectedVideoOn = DataChannelMessage("videoOn")
|
||||
|
||||
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
|
||||
|
||||
var messageCount = 1
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 2
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 3
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(4, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 4
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
localStateBroadcasterMcu!!.handleCallParticipantRemoved(callParticipantModel)
|
||||
|
||||
testScheduler.advanceTimeBy(8, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 5
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(16, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 6
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(100, TimeUnit.SECONDS)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateNoLongerSentOnceDestroyed() {
|
||||
val testScheduler = TestScheduler()
|
||||
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
|
||||
|
||||
localStateBroadcasterMcu = LocalStateBroadcasterMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSender
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
// Sending will be done in another thread, so just adding the participant does not send anything until that
|
||||
// other thread could run.
|
||||
Mockito.verifyNoInteractions(mockedMessageSender)
|
||||
|
||||
val expectedAudioOn = DataChannelMessage("audioOn")
|
||||
val expectedSpeaking = DataChannelMessage("speaking")
|
||||
val expectedVideoOn = DataChannelMessage("videoOn")
|
||||
|
||||
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
|
||||
|
||||
var messageCount = 1
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 2
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
|
||||
|
||||
messageCount = 3
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
|
||||
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
|
||||
localStateBroadcasterMcu!!.destroy()
|
||||
|
||||
testScheduler.advanceTimeBy(100, TimeUnit.SECONDS)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSender)
|
||||
}
|
||||
}
|
@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.call
|
||||
|
||||
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito
|
||||
import org.webrtc.PeerConnection
|
||||
|
||||
class LocalStateBroadcasterNoMcuTest {
|
||||
|
||||
private var localCallParticipantModel: MutableLocalCallParticipantModel? = null
|
||||
private var mockedMessageSenderNoMcu: MessageSenderNoMcu? = null
|
||||
|
||||
private var localStateBroadcasterNoMcu: LocalStateBroadcasterNoMcu? = null
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
localCallParticipantModel = MutableLocalCallParticipantModel()
|
||||
localCallParticipantModel!!.isAudioEnabled = true
|
||||
localCallParticipantModel!!.isSpeaking = true
|
||||
localCallParticipantModel!!.isVideoEnabled = true
|
||||
mockedMessageSenderNoMcu = Mockito.mock(MessageSenderNoMcu::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateSentWhenIceConnected() {
|
||||
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSenderNoMcu
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
|
||||
val expectedAudioOn = DataChannelMessage("audioOn")
|
||||
val expectedSpeaking = DataChannelMessage("speaking")
|
||||
val expectedVideoOn = DataChannelMessage("videoOn")
|
||||
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId")
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateSentWhenIceCompleted() {
|
||||
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSenderNoMcu
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED)
|
||||
|
||||
val expectedAudioOn = DataChannelMessage("audioOn")
|
||||
val expectedSpeaking = DataChannelMessage("speaking")
|
||||
val expectedVideoOn = DataChannelMessage("videoOn")
|
||||
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId")
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateNotSentWhenIceCompletedAfterConnected() {
|
||||
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSenderNoMcu
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
|
||||
val expectedAudioOn = DataChannelMessage("audioOn")
|
||||
val expectedSpeaking = DataChannelMessage("speaking")
|
||||
val expectedVideoOn = DataChannelMessage("videoOn")
|
||||
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId")
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateNotSentWhenIceConnectedAgain() {
|
||||
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSenderNoMcu
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
|
||||
val expectedAudioOn = DataChannelMessage("audioOn")
|
||||
val expectedSpeaking = DataChannelMessage("speaking")
|
||||
val expectedVideoOn = DataChannelMessage("videoOn")
|
||||
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId")
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
// Completed -> Connected could happen with an ICE restart
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.DISCONNECTED)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
// Failed -> Checking could happen with an ICE restart
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.FAILED)
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateNotSentToOtherParticipantsWhenIceConnected() {
|
||||
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSenderNoMcu
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
val callParticipantModel2 = MutableCallParticipantModel("theSessionId2")
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel2)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
callParticipantModel2.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
|
||||
val expectedAudioOn = DataChannelMessage("audioOn")
|
||||
val expectedSpeaking = DataChannelMessage("speaking")
|
||||
val expectedVideoOn = DataChannelMessage("videoOn")
|
||||
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId")
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
callParticipantModel2.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId2")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId2")
|
||||
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId2")
|
||||
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateNotSentWhenIceConnectedAfterParticipantIsRemoved() {
|
||||
// This should not happen, as peer connections are expected to be ended when a call participant is removed, but
|
||||
// just in case.
|
||||
|
||||
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSenderNoMcu
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantRemoved(callParticipantModel)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateNotSentWhenIceCompletedAfterParticipantIsRemoved() {
|
||||
// This should not happen, as peer connections are expected to be ended when a call participant is removed, but
|
||||
// just in case.
|
||||
|
||||
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSenderNoMcu
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantRemoved(callParticipantModel)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateNotSentWhenIceConnectedAfterDestroyed() {
|
||||
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSenderNoMcu
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
val callParticipantModel2 = MutableCallParticipantModel("theSessionId2")
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel2)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
callParticipantModel2.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
localStateBroadcasterNoMcu!!.destroy()
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
callParticipantModel2.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStateNotSentWhenIceCompletedAfterDestroyed() {
|
||||
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
|
||||
localCallParticipantModel,
|
||||
mockedMessageSenderNoMcu
|
||||
)
|
||||
|
||||
val callParticipantModel = MutableCallParticipantModel("theSessionId")
|
||||
|
||||
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
|
||||
localStateBroadcasterNoMcu!!.destroy()
|
||||
|
||||
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED)
|
||||
|
||||
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
|
||||
}
|
||||
}
|
@ -14,6 +14,20 @@ import org.mockito.Mockito
|
||||
@Suppress("TooManyFunctions")
|
||||
class LocalStateBroadcasterTest {
|
||||
|
||||
private class LocalStateBroadcaster(
|
||||
localCallParticipantModel: LocalCallParticipantModel?,
|
||||
messageSender: MessageSender?
|
||||
) : com.nextcloud.talk.call.LocalStateBroadcaster(localCallParticipantModel, messageSender) {
|
||||
|
||||
override fun handleCallParticipantAdded(callParticipantModel: CallParticipantModel) {
|
||||
// Not used in base class tests
|
||||
}
|
||||
|
||||
override fun handleCallParticipantRemoved(callParticipantModel: CallParticipantModel) {
|
||||
// Not used in base class tests
|
||||
}
|
||||
}
|
||||
|
||||
private var localCallParticipantModel: MutableLocalCallParticipantModel? = null
|
||||
private var mockedMessageSender: MessageSender? = null
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user