From d094dc55c957e7be540354e3e443175cf60e8a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 19 Dec 2024 12:41:07 +0100 Subject: [PATCH] Send state also through signaling messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The speaking state is still sent only through data channels, as it is not currently handled by other clients when sent through signaling messages. Signed-off-by: Daniel Calviño Sánchez --- .../talk/call/LocalStateBroadcaster.java | 61 +++++ .../talk/call/LocalStateBroadcasterMcu.java | 46 ++++ .../talk/call/LocalStateBroadcasterNoMcu.java | 9 + .../talk/call/LocalStateBroadcasterMcuTest.kt | 210 +++++++++++++++++- .../call/LocalStateBroadcasterNoMcuTest.kt | 53 +++++ .../talk/call/LocalStateBroadcasterTest.kt | 50 +++++ 6 files changed, 425 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcaster.java b/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcaster.java index 0376312970..1022d39e12 100644 --- a/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcaster.java +++ b/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcaster.java @@ -7,6 +7,8 @@ package com.nextcloud.talk.call; import com.nextcloud.talk.models.json.signaling.DataChannelMessage; +import com.nextcloud.talk.models.json.signaling.NCMessagePayload; +import com.nextcloud.talk.models.json.signaling.NCSignalingMessage; import java.util.Objects; @@ -48,6 +50,7 @@ public void onChange() { audioEnabled = localCallParticipantModel.isAudioEnabled(); messageSender.sendToAll(getDataChannelMessageForAudioState()); + messageSender.sendToAll(getSignalingMessageForAudioState()); } if (!Objects.equals(speaking, localCallParticipantModel.isSpeaking())) { @@ -60,6 +63,7 @@ public void onChange() { videoEnabled = localCallParticipantModel.isVideoEnabled(); messageSender.sendToAll(getDataChannelMessageForVideoState()); + messageSender.sendToAll(getSignalingMessageForVideoState()); } } } @@ -106,4 +110,61 @@ protected DataChannelMessage getDataChannelMessageForVideoState() { return new DataChannelMessage(type); } + + /** + * Returns a signaling message with the common fields set (type and room type). + * + * @param type the type of the signaling message + * @return the signaling message + */ + private NCSignalingMessage createBaseSignalingMessage(String type) { + NCSignalingMessage ncSignalingMessage = new NCSignalingMessage(); + // "roomType" is not really relevant without a peer or when referring to the whole participant, but it is + // nevertheless expected in the message. As most of the signaling messages currently sent to all participants + // are related to audio/video state "video" is used as the room type. + ncSignalingMessage.setRoomType("video"); + ncSignalingMessage.setType(type); + + return ncSignalingMessage; + } + + /** + * Returns a signaling message to notify current audio state. + * + * @return the signaling message + */ + protected NCSignalingMessage getSignalingMessageForAudioState() { + String type = "mute"; + if (localCallParticipantModel.isAudioEnabled() != null && localCallParticipantModel.isAudioEnabled()) { + type = "unmute"; + } + + NCSignalingMessage ncSignalingMessage = createBaseSignalingMessage(type); + + NCMessagePayload ncMessagePayload = new NCMessagePayload(); + ncMessagePayload.setName("audio"); + ncSignalingMessage.setPayload(ncMessagePayload); + + return ncSignalingMessage; + } + + /** + * Returns a signaling message to notify current video state. + * + * @return the signaling message + */ + protected NCSignalingMessage getSignalingMessageForVideoState() { + String type = "mute"; + if (localCallParticipantModel.isVideoEnabled() != null && localCallParticipantModel.isVideoEnabled()) { + type = "unmute"; + } + + NCSignalingMessage ncSignalingMessage = createBaseSignalingMessage(type); + + NCMessagePayload ncMessagePayload = new NCMessagePayload(); + ncMessagePayload.setName("video"); + ncSignalingMessage.setPayload(ncMessagePayload); + + return ncSignalingMessage; + } } diff --git a/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcasterMcu.java b/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcasterMcu.java index 628b615375..911bf1bf39 100644 --- a/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcasterMcu.java +++ b/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcasterMcu.java @@ -6,6 +6,8 @@ */ package com.nextcloud.talk.call; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.TimeUnit; import io.reactivex.Observable; @@ -26,11 +28,30 @@ * 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. + *

+ * Similarly, in the case of signaling messages it is not possible either to know when the remote participants have + * "seen" the local participant and thus are ready to handle signaling messages about the state. However, in the case + * of signaling messages it is possible to send them to a specific participant, so the initial state is sent several + * times with an increasing delay directly to the participant that was added. Moreover, if the participant is removed + * the state is no longer directly sent. + *

+ * In any case, note that the state is sent only when the remote participant joins the call. Even in case of + * temporary disconnections the normal state updates sent when the state changes are expected to be received by the + * other participant, as signaling messages are sent through a WebSocket and are therefore reliable. Moreover, even + * if the WebSocket is restarted and the connection resumed (rather than joining with a new session ID) the messages + * would be also received, as in that case they would be queued until the WebSocket is connected again. + *

+ * Data channel messages, on the other hand, could be lost if the remote participant restarts the peer receiver + * connection (although they would be received in case of temporary disconnections, as data channels use a reliable + * transport by default). Therefore, as the speaking state is sent only through data channels, updates of the speaking + * state could be not received by remote participants. */ public class LocalStateBroadcasterMcu extends LocalStateBroadcaster { private final MessageSender messageSender; + private final Map sendStateWithRepetitionByParticipant = new HashMap<>(); + private Disposable sendStateWithRepetition; public LocalStateBroadcasterMcu(LocalCallParticipantModel localCallParticipantModel, @@ -46,6 +67,10 @@ public void destroy() { if (sendStateWithRepetition != null) { sendStateWithRepetition.dispose(); } + + for (Disposable sendStateWithRepetitionForParticipant: sendStateWithRepetitionByParticipant.values()) { + sendStateWithRepetitionForParticipant.dispose(); + } } @Override @@ -58,10 +83,26 @@ public void handleCallParticipantAdded(CallParticipantModel callParticipantModel .fromArray(new Integer[]{0, 1, 2, 4, 8, 16}) .concatMap(i -> Observable.just(i).delay(i, TimeUnit.SECONDS, Schedulers.io())) .subscribe(value -> sendState()); + + String sessionId = callParticipantModel.getSessionId(); + Disposable sendStateWithRepetitionForParticipant = sendStateWithRepetitionByParticipant.get(sessionId); + if (sendStateWithRepetitionForParticipant != null) { + sendStateWithRepetitionForParticipant.dispose(); + } + + sendStateWithRepetitionByParticipant.put(sessionId, Observable + .fromArray(new Integer[]{0, 1, 2, 4, 8, 16}) + .concatMap(i -> Observable.just(i).delay(i, TimeUnit.SECONDS, Schedulers.io())) + .subscribe(value -> sendState(sessionId))); } @Override public void handleCallParticipantRemoved(CallParticipantModel callParticipantModel) { + String sessionId = callParticipantModel.getSessionId(); + Disposable sendStateWithRepetitionForParticipant = sendStateWithRepetitionByParticipant.get(sessionId); + if (sendStateWithRepetitionForParticipant != null) { + sendStateWithRepetitionForParticipant.dispose(); + } } private void sendState() { @@ -69,4 +110,9 @@ private void sendState() { messageSender.sendToAll(getDataChannelMessageForSpeakingState()); messageSender.sendToAll(getDataChannelMessageForVideoState()); } + + private void sendState(String sessionId) { + messageSender.send(getSignalingMessageForAudioState(), sessionId); + messageSender.send(getSignalingMessageForVideoState(), sessionId); + } } diff --git a/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcasterNoMcu.java b/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcasterNoMcu.java index 2a1bf04ea0..1377e626b4 100644 --- a/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcasterNoMcu.java +++ b/app/src/main/java/com/nextcloud/talk/call/LocalStateBroadcasterNoMcu.java @@ -26,6 +26,12 @@ * 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. + *

+ * Nevertheless, in case of a failed connection and an ICE restart it is unclear whether the data channel messages + * would be received or not (as the data channel transport may be the one that failed and needs to be restarted). + * However, the state (except the speaking state) is also sent through signaling messages, which need to be + * explicitly fetched from the internal signaling server, so even in case of a failed connection they will be + * eventually received once the remote participant connects again. */ public class LocalStateBroadcasterNoMcu extends LocalStateBroadcaster { @@ -115,5 +121,8 @@ private void sendState(String sessionId) { messageSender.send(getDataChannelMessageForAudioState(), sessionId); messageSender.send(getDataChannelMessageForSpeakingState(), sessionId); messageSender.send(getDataChannelMessageForVideoState(), sessionId); + + messageSender.send(getSignalingMessageForAudioState(), sessionId); + messageSender.send(getSignalingMessageForVideoState(), sessionId); } } diff --git a/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterMcuTest.kt b/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterMcuTest.kt index a20fea8437..03e32457e9 100644 --- a/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterMcuTest.kt +++ b/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterMcuTest.kt @@ -7,6 +7,8 @@ package com.nextcloud.talk.call import com.nextcloud.talk.models.json.signaling.DataChannelMessage +import com.nextcloud.talk.models.json.signaling.NCMessagePayload +import com.nextcloud.talk.models.json.signaling.NCSignalingMessage import io.reactivex.plugins.RxJavaPlugins import io.reactivex.schedulers.TestScheduler import org.junit.Before @@ -32,6 +34,54 @@ class LocalStateBroadcasterMcuTest { mockedMessageSender = Mockito.mock(MessageSender::class.java) } + private fun getExpectedUnmuteAudio(): NCSignalingMessage { + val expectedUnmuteAudio = NCSignalingMessage() + expectedUnmuteAudio.roomType = "video" + expectedUnmuteAudio.type = "unmute" + + val payload = NCMessagePayload() + payload.name = "audio" + expectedUnmuteAudio.payload = payload + + return expectedUnmuteAudio + } + + private fun getExpectedMuteAudio(): NCSignalingMessage { + val expectedMuteAudio = NCSignalingMessage() + expectedMuteAudio.roomType = "video" + expectedMuteAudio.type = "mute" + + val payload = NCMessagePayload() + payload.name = "audio" + expectedMuteAudio.payload = payload + + return expectedMuteAudio + } + + private fun getExpectedUnmuteVideo(): NCSignalingMessage { + val expectedUnmuteVideo = NCSignalingMessage() + expectedUnmuteVideo.roomType = "video" + expectedUnmuteVideo.type = "unmute" + + val payload = NCMessagePayload() + payload.name = "video" + expectedUnmuteVideo.payload = payload + + return expectedUnmuteVideo + } + + private fun getExpectedMuteVideo(): NCSignalingMessage { + val expectedMuteVideo = NCSignalingMessage() + expectedMuteVideo.roomType = "video" + expectedMuteVideo.type = "mute" + + val payload = NCMessagePayload() + payload.name = "video" + expectedMuteVideo.payload = payload + + return expectedMuteVideo + } + @Test fun testStateSentWithExponentialBackoffWhenParticipantAdded() { val testScheduler = TestScheduler() @@ -54,12 +104,17 @@ class LocalStateBroadcasterMcuTest { val expectedSpeaking = DataChannelMessage("speaking") val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteAudio = getExpectedUnmuteAudio() + val expectedUnmuteVideo = getExpectedUnmuteVideo() + 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.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(1, TimeUnit.SECONDS) @@ -68,6 +123,8 @@ class LocalStateBroadcasterMcuTest { Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(2, TimeUnit.SECONDS) @@ -76,6 +133,8 @@ class LocalStateBroadcasterMcuTest { Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(4, TimeUnit.SECONDS) @@ -84,6 +143,8 @@ class LocalStateBroadcasterMcuTest { Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(8, TimeUnit.SECONDS) @@ -92,6 +153,8 @@ class LocalStateBroadcasterMcuTest { Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(16, TimeUnit.SECONDS) @@ -100,6 +163,8 @@ class LocalStateBroadcasterMcuTest { Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(100, TimeUnit.SECONDS) @@ -132,11 +197,16 @@ class LocalStateBroadcasterMcuTest { val expectedSpeaking = DataChannelMessage("speaking") val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteAudio = getExpectedUnmuteAudio() + val expectedUnmuteVideo = getExpectedUnmuteVideo() + 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.verify(mockedMessageSender!!, times(1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(1)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) localCallParticipantModel!!.isSpeaking = false @@ -151,51 +221,73 @@ class LocalStateBroadcasterMcuTest { Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedStoppedSpeaking) Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(2)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(2)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) localCallParticipantModel!!.isAudioEnabled = false val expectedAudioOff = DataChannelMessage("audioOff") + val expectedMuteAudio = getExpectedMuteAudio() // Changing the state causes the normal state update to be sent, independently of the initial state Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedAudioOff) + Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteAudio) 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.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteAudio) + Mockito.verify(mockedMessageSender!!, times(1)).send(expectedMuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(3)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) localCallParticipantModel!!.isVideoEnabled = false val expectedVideoOff = DataChannelMessage("videoOff") + val expectedMuteVideo = getExpectedMuteVideo() // Changing the state causes the normal state update to be sent, independently of the initial state Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedVideoOff) + Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteVideo) 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.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteAudio) + Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteVideo) + Mockito.verify(mockedMessageSender!!, times(2)).send(expectedMuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(1)).send(expectedMuteVideo, "theSessionId") 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) + Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedUnmuteVideo) 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.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteAudio) + Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteVideo) + Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedUnmuteVideo) + Mockito.verify(mockedMessageSender!!, times(3)).send(expectedMuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(4)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) } @Test - fun testStateSentWithExponentialBackoffRestartedWhenAnotherParticipantAdded() { + fun testStateSentWithExponentialBackoffWhenAnotherParticipantAdded() { + // The state sent through data channels should be restarted, although the state sent through signaling + // messages should be independent for each participant. + val testScheduler = TestScheduler() RxJavaPlugins.setIoSchedulerHandler { testScheduler } @@ -216,36 +308,51 @@ class LocalStateBroadcasterMcuTest { val expectedSpeaking = DataChannelMessage("speaking") val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteAudio = getExpectedUnmuteAudio() + val expectedUnmuteVideo = getExpectedUnmuteVideo() + testScheduler.advanceTimeBy(0, TimeUnit.SECONDS) var dataChannelMessageCount = 1 + var signalingMessageCount1 = 1 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(1, TimeUnit.SECONDS) dataChannelMessageCount = 2 + signalingMessageCount1 = 2 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(2, TimeUnit.SECONDS) dataChannelMessageCount = 3 + signalingMessageCount1 = 3 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(4, TimeUnit.SECONDS) dataChannelMessageCount = 4 + signalingMessageCount1 = 4 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) val callParticipantModel2 = MutableCallParticipantModel("theSessionId2") @@ -255,49 +362,107 @@ class LocalStateBroadcasterMcuTest { testScheduler.advanceTimeBy(0, TimeUnit.SECONDS) dataChannelMessageCount = 5 + var signalingMessageCount2 = 1 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(1, TimeUnit.SECONDS) dataChannelMessageCount = 6 + signalingMessageCount2 = 2 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(2, TimeUnit.SECONDS) dataChannelMessageCount = 7 + signalingMessageCount2 = 3 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(4, TimeUnit.SECONDS) dataChannelMessageCount = 8 + signalingMessageCount2 = 4 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2") Mockito.verifyNoMoreInteractions(mockedMessageSender) - testScheduler.advanceTimeBy(8, TimeUnit.SECONDS) + // 0+1+2+4+1=8 seconds since last signaling messages for participant 1 + testScheduler.advanceTimeBy(1, TimeUnit.SECONDS) + + signalingMessageCount1 = 5 + Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) + Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) + Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2") + Mockito.verifyNoMoreInteractions(mockedMessageSender) + + // 1+7=8 seconds since last data channel messages and signaling messages for participant 2 + testScheduler.advanceTimeBy(7, TimeUnit.SECONDS) dataChannelMessageCount = 9 + signalingMessageCount2 = 5 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2") Mockito.verifyNoMoreInteractions(mockedMessageSender) - testScheduler.advanceTimeBy(16, TimeUnit.SECONDS) + // 7+9=16 seconds since last signaling messages for participant 1 + testScheduler.advanceTimeBy(9, TimeUnit.SECONDS) + + signalingMessageCount1 = 6 + Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) + Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) + Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2") + Mockito.verifyNoMoreInteractions(mockedMessageSender) + + // 9+7=16 seconds since last data channel messages and signaling messages for participant 2 + testScheduler.advanceTimeBy(7, TimeUnit.SECONDS) dataChannelMessageCount = 10 + signalingMessageCount2 = 6 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(100, TimeUnit.SECONDS) @@ -306,8 +471,9 @@ class LocalStateBroadcasterMcuTest { } @Test - fun testStateStillSentWithExponentialBackoffWhenParticipantRemoved() { + fun testStateSentWithExponentialBackoffWhenParticipantRemoved() { // For simplicity the exponential backoff is not aborted when the participant that triggered it is removed. + // However, the signaling messages are stopped when the participant is removed. val testScheduler = TestScheduler() RxJavaPlugins.setIoSchedulerHandler { testScheduler } @@ -329,36 +495,51 @@ class LocalStateBroadcasterMcuTest { val expectedSpeaking = DataChannelMessage("speaking") val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteAudio = getExpectedUnmuteAudio() + val expectedUnmuteVideo = getExpectedUnmuteVideo() + testScheduler.advanceTimeBy(0, TimeUnit.SECONDS) var dataChannelMessageCount = 1 + var signalingMessageCount = 1 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(1, TimeUnit.SECONDS) dataChannelMessageCount = 2 + signalingMessageCount = 2 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(2, TimeUnit.SECONDS) dataChannelMessageCount = 3 + signalingMessageCount = 3 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(4, TimeUnit.SECONDS) dataChannelMessageCount = 4 + signalingMessageCount = 4 Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) localStateBroadcasterMcu!!.handleCallParticipantRemoved(callParticipantModel) @@ -369,6 +550,8 @@ class LocalStateBroadcasterMcuTest { Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(16, TimeUnit.SECONDS) @@ -377,6 +560,8 @@ class LocalStateBroadcasterMcuTest { Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(100, TimeUnit.SECONDS) @@ -395,8 +580,10 @@ class LocalStateBroadcasterMcuTest { ) val callParticipantModel = MutableCallParticipantModel("theSessionId") + val callParticipantModel2 = MutableCallParticipantModel("theSessionId2") localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel) + localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel2) // Sending will be done in another thread, so just adding the participant does not send anything until that // other thread could run. @@ -406,12 +593,19 @@ class LocalStateBroadcasterMcuTest { val expectedSpeaking = DataChannelMessage("speaking") val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteAudio = getExpectedUnmuteAudio() + val expectedUnmuteVideo = getExpectedUnmuteVideo() + 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.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId2") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(1, TimeUnit.SECONDS) @@ -420,6 +614,10 @@ class LocalStateBroadcasterMcuTest { Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId2") Mockito.verifyNoMoreInteractions(mockedMessageSender) testScheduler.advanceTimeBy(2, TimeUnit.SECONDS) @@ -428,6 +626,10 @@ class LocalStateBroadcasterMcuTest { Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking) Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId2") Mockito.verifyNoMoreInteractions(mockedMessageSender) localStateBroadcasterMcu!!.destroy() diff --git a/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterNoMcuTest.kt b/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterNoMcuTest.kt index 2075785fdd..f225ff7394 100644 --- a/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterNoMcuTest.kt +++ b/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterNoMcuTest.kt @@ -7,6 +7,8 @@ package com.nextcloud.talk.call import com.nextcloud.talk.models.json.signaling.DataChannelMessage +import com.nextcloud.talk.models.json.signaling.NCMessagePayload +import com.nextcloud.talk.models.json.signaling.NCSignalingMessage import org.junit.Before import org.junit.Test import org.mockito.Mockito @@ -28,6 +30,30 @@ class LocalStateBroadcasterNoMcuTest { mockedMessageSenderNoMcu = Mockito.mock(MessageSenderNoMcu::class.java) } + private fun getExpectedUnmuteAudio(): NCSignalingMessage { + val expectedUnmuteAudio = NCSignalingMessage() + expectedUnmuteAudio.roomType = "video" + expectedUnmuteAudio.type = "unmute" + + val payload = NCMessagePayload() + payload.name = "audio" + expectedUnmuteAudio.payload = payload + + return expectedUnmuteAudio + } + + private fun getExpectedUnmuteVideo(): NCSignalingMessage { + val expectedUnmuteVideo = NCSignalingMessage() + expectedUnmuteVideo.roomType = "video" + expectedUnmuteVideo.type = "unmute" + + val payload = NCMessagePayload() + payload.name = "video" + expectedUnmuteVideo.payload = payload + + return expectedUnmuteVideo + } + @Test fun testStateSentWhenIceConnected() { localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu( @@ -49,9 +75,14 @@ class LocalStateBroadcasterNoMcuTest { val expectedSpeaking = DataChannelMessage("speaking") val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteAudio = getExpectedUnmuteAudio() + val expectedUnmuteVideo = getExpectedUnmuteVideo() + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu) } @@ -76,9 +107,14 @@ class LocalStateBroadcasterNoMcuTest { val expectedSpeaking = DataChannelMessage("speaking") val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteAudio = getExpectedUnmuteAudio() + val expectedUnmuteVideo = getExpectedUnmuteVideo() + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu) } @@ -103,9 +139,14 @@ class LocalStateBroadcasterNoMcuTest { val expectedSpeaking = DataChannelMessage("speaking") val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteAudio = getExpectedUnmuteAudio() + val expectedUnmuteVideo = getExpectedUnmuteVideo() + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu) callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED) @@ -134,9 +175,14 @@ class LocalStateBroadcasterNoMcuTest { val expectedSpeaking = DataChannelMessage("speaking") val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteAudio = getExpectedUnmuteAudio() + val expectedUnmuteVideo = getExpectedUnmuteVideo() + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu) callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED) @@ -191,9 +237,14 @@ class LocalStateBroadcasterNoMcuTest { val expectedSpeaking = DataChannelMessage("speaking") val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteAudio = getExpectedUnmuteAudio() + val expectedUnmuteVideo = getExpectedUnmuteVideo() + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId") Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu) callParticipantModel2.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED) @@ -201,6 +252,8 @@ class LocalStateBroadcasterNoMcuTest { Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId2") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId2") Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId2") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId2") + Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId2") Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu) } diff --git a/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterTest.kt b/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterTest.kt index 29b205cda5..34ca59e7e7 100644 --- a/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterTest.kt +++ b/app/src/test/java/com/nextcloud/talk/call/LocalStateBroadcasterTest.kt @@ -7,6 +7,8 @@ package com.nextcloud.talk.call import com.nextcloud.talk.models.json.signaling.DataChannelMessage +import com.nextcloud.talk.models.json.signaling.NCMessagePayload +import com.nextcloud.talk.models.json.signaling.NCSignalingMessage import org.junit.Before import org.junit.Test import org.mockito.Mockito @@ -49,7 +51,15 @@ class LocalStateBroadcasterTest { val expectedAudioOn = DataChannelMessage("audioOn") + val expectedUnmuteAudio = NCSignalingMessage() + expectedUnmuteAudio.roomType = "video" + expectedUnmuteAudio.type = "unmute" + val payload = NCMessagePayload() + payload.name = "audio" + expectedUnmuteAudio.payload = payload + Mockito.verify(mockedMessageSender!!).sendToAll(expectedAudioOn) + Mockito.verify(mockedMessageSender!!).sendToAll(expectedUnmuteAudio) Mockito.verifyNoMoreInteractions(mockedMessageSender) } @@ -74,7 +84,15 @@ class LocalStateBroadcasterTest { val expectedAudioOff = DataChannelMessage("audioOff") + val expectedMuteAudio = NCSignalingMessage() + expectedMuteAudio.roomType = "video" + expectedMuteAudio.type = "mute" + val payload = NCMessagePayload() + payload.name = "audio" + expectedMuteAudio.payload = payload + Mockito.verify(mockedMessageSender!!).sendToAll(expectedAudioOff) + Mockito.verify(mockedMessageSender!!).sendToAll(expectedMuteAudio) Mockito.verifyNoMoreInteractions(mockedMessageSender) } @@ -141,10 +159,18 @@ class LocalStateBroadcasterTest { val expectedAudioOn = DataChannelMessage("audioOn") val expectedSpeaking = DataChannelMessage("speaking") + val expectedUnmuteAudio = NCSignalingMessage() + expectedUnmuteAudio.roomType = "video" + expectedUnmuteAudio.type = "unmute" + val payload = NCMessagePayload() + payload.name = "audio" + expectedUnmuteAudio.payload = payload + val inOrder = Mockito.inOrder(mockedMessageSender) inOrder.verify(mockedMessageSender!!).sendToAll(expectedAudioOn) inOrder.verify(mockedMessageSender!!).sendToAll(expectedSpeaking) + Mockito.verify(mockedMessageSender!!).sendToAll(expectedUnmuteAudio) Mockito.verifyNoMoreInteractions(mockedMessageSender) } @@ -187,10 +213,18 @@ class LocalStateBroadcasterTest { val expectedStoppedSpeaking = DataChannelMessage("stoppedSpeaking") val expectedAudioOff = DataChannelMessage("audioOff") + val expectedMuteAudio = NCSignalingMessage() + expectedMuteAudio.roomType = "video" + expectedMuteAudio.type = "mute" + val payload = NCMessagePayload() + payload.name = "audio" + expectedMuteAudio.payload = payload + val inOrder = Mockito.inOrder(mockedMessageSender) inOrder.verify(mockedMessageSender!!).sendToAll(expectedStoppedSpeaking) inOrder.verify(mockedMessageSender!!).sendToAll(expectedAudioOff) + Mockito.verify(mockedMessageSender!!).sendToAll(expectedMuteAudio) Mockito.verifyNoMoreInteractions(mockedMessageSender) } @@ -216,7 +250,15 @@ class LocalStateBroadcasterTest { val expectedVideoOn = DataChannelMessage("videoOn") + val expectedUnmuteVideo = NCSignalingMessage() + expectedUnmuteVideo.roomType = "video" + expectedUnmuteVideo.type = "unmute" + val payload = NCMessagePayload() + payload.name = "video" + expectedUnmuteVideo.payload = payload + Mockito.verify(mockedMessageSender!!).sendToAll(expectedVideoOn) + Mockito.verify(mockedMessageSender!!).sendToAll(expectedUnmuteVideo) Mockito.verifyNoMoreInteractions(mockedMessageSender) } @@ -241,7 +283,15 @@ class LocalStateBroadcasterTest { val expectedVideoOff = DataChannelMessage("videoOff") + val expectedMuteVideo = NCSignalingMessage() + expectedMuteVideo.roomType = "video" + expectedMuteVideo.type = "mute" + val payload = NCMessagePayload() + payload.name = "video" + expectedMuteVideo.payload = payload + Mockito.verify(mockedMessageSender!!).sendToAll(expectedVideoOff) + Mockito.verify(mockedMessageSender!!).sendToAll(expectedMuteVideo) Mockito.verifyNoMoreInteractions(mockedMessageSender) }