Add support for sending signaling messages in the MessageSender

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This commit is contained in:
Daniel Calviño Sánchez 2024-12-19 12:17:41 +01:00 committed by Marcel Hibbe
parent 8644d05636
commit ea2bebe3b0
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
7 changed files with 218 additions and 8 deletions

View File

@ -1590,6 +1590,8 @@ class CallActivity : CallBaseActivity() {
hasMCU = false hasMCU = false
messageSender = MessageSenderNoMcu( messageSender = MessageSenderNoMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrapperList peerConnectionWrapperList
) )
@ -1895,11 +1897,15 @@ class CallActivity : CallBaseActivity() {
if (hasMCU) { if (hasMCU) {
messageSender = MessageSenderMcu( messageSender = MessageSenderMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrapperList, peerConnectionWrapperList,
webSocketClient!!.sessionId webSocketClient!!.sessionId
) )
} else { } else {
messageSender = MessageSenderNoMcu( messageSender = MessageSenderNoMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrapperList peerConnectionWrapperList
) )
} }
@ -1934,11 +1940,15 @@ class CallActivity : CallBaseActivity() {
if (hasMCU) { if (hasMCU) {
messageSender = MessageSenderMcu( messageSender = MessageSenderMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrapperList, peerConnectionWrapperList,
webSocketClient!!.sessionId webSocketClient!!.sessionId
) )
} else { } else {
messageSender = MessageSenderNoMcu( messageSender = MessageSenderNoMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrapperList peerConnectionWrapperList
) )
} }

View File

@ -7,16 +7,22 @@
package com.nextcloud.talk.call; package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage; import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import com.nextcloud.talk.signaling.SignalingMessageSender;
import com.nextcloud.talk.webrtc.PeerConnectionWrapper; import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* Helper class to send messages to participants in a call. * Helper class to send messages to participants in a call.
* <p> * <p>
* A specific subclass has to be created depending on whether an MCU is being used or not. * A specific subclass has to be created depending on whether an MCU is being used or not.
* <p> * <p>
* Note that, unlike signaling messages, data channel messages require a peer connection. Therefore data channel * Note that recipients of signaling messages are not validated, so no error will be triggered if trying to send a
* message to a participant with a session ID that does not exist or is not in the call.
* <p>
* Also note that, unlike signaling messages, data channel messages require a peer connection. Therefore data channel
* messages may not be received by a participant if there is no peer connection with that participant (for example, if * messages may not be received by a participant if there is no peer connection with that participant (for example, if
* neither the local and remote participants have publishing rights). Moreover, data channel messages are expected to * neither the local and remote participants have publishing rights). Moreover, data channel messages are expected to
* be received only on peer connections with type "video", so data channel messages will not be sent on other peer * be received only on peer connections with type "video", so data channel messages will not be sent on other peer
@ -24,9 +30,17 @@ import java.util.List;
*/ */
public abstract class MessageSender { public abstract class MessageSender {
private final SignalingMessageSender signalingMessageSender;
private final Set<String> callParticipantSessionIds;
protected final List<PeerConnectionWrapper> peerConnectionWrappers; protected final List<PeerConnectionWrapper> peerConnectionWrappers;
public MessageSender(List<PeerConnectionWrapper> peerConnectionWrappers) { public MessageSender(SignalingMessageSender signalingMessageSender,
Set<String> callParticipantSessionIds,
List<PeerConnectionWrapper> peerConnectionWrappers) {
this.signalingMessageSender = signalingMessageSender;
this.callParticipantSessionIds = callParticipantSessionIds;
this.peerConnectionWrappers = peerConnectionWrappers; this.peerConnectionWrappers = peerConnectionWrappers;
} }
@ -37,6 +51,35 @@ public abstract class MessageSender {
*/ */
public abstract void sendToAll(DataChannelMessage dataChannelMessage); public abstract void sendToAll(DataChannelMessage dataChannelMessage);
/**
* Sends the given signaling message to the given session ID.
* <p>
* Note that the signaling message will be modified to set the recipient in the "to" field.
*
* @param ncSignalingMessage the message to send
* @param sessionId the signaling session ID of the participant to send the message to
*/
public void send(NCSignalingMessage ncSignalingMessage, String sessionId) {
ncSignalingMessage.setTo(sessionId);
signalingMessageSender.send(ncSignalingMessage);
}
/**
* Sends the given signaling message to all the participants in the call.
* <p>
* Note that the signaling message will be modified to set each of the recipients in the "to" field.
*
* @param ncSignalingMessage the message to send
*/
public void sendToAll(NCSignalingMessage ncSignalingMessage) {
for (String sessionId: callParticipantSessionIds) {
ncSignalingMessage.setTo(sessionId);
signalingMessageSender.send(ncSignalingMessage);
}
}
protected PeerConnectionWrapper getPeerConnectionWrapper(String sessionId) { protected PeerConnectionWrapper getPeerConnectionWrapper(String sessionId) {
for (PeerConnectionWrapper peerConnectionWrapper: peerConnectionWrappers) { for (PeerConnectionWrapper peerConnectionWrapper: peerConnectionWrappers) {
if (peerConnectionWrapper.getSessionId().equals(sessionId) if (peerConnectionWrapper.getSessionId().equals(sessionId)

View File

@ -7,9 +7,11 @@
package com.nextcloud.talk.call; package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage; import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
import com.nextcloud.talk.signaling.SignalingMessageSender;
import com.nextcloud.talk.webrtc.PeerConnectionWrapper; import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* Helper class to send messages to participants in a call when an MCU is used. * Helper class to send messages to participants in a call when an MCU is used.
@ -21,9 +23,11 @@ public class MessageSenderMcu extends MessageSender {
private final String ownSessionId; private final String ownSessionId;
public MessageSenderMcu(List<PeerConnectionWrapper> peerConnectionWrappers, public MessageSenderMcu(SignalingMessageSender signalingMessageSender,
Set<String> callParticipantSessionIds,
List<PeerConnectionWrapper> peerConnectionWrappers,
String ownSessionId) { String ownSessionId) {
super(peerConnectionWrappers); super(signalingMessageSender, callParticipantSessionIds, peerConnectionWrappers);
this.ownSessionId = ownSessionId; this.ownSessionId = ownSessionId;
} }

View File

@ -7,17 +7,21 @@
package com.nextcloud.talk.call; package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage; import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
import com.nextcloud.talk.signaling.SignalingMessageSender;
import com.nextcloud.talk.webrtc.PeerConnectionWrapper; import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* Helper class to send messages to participants in a call when an MCU is not used. * Helper class to send messages to participants in a call when an MCU is not used.
*/ */
public class MessageSenderNoMcu extends MessageSender { public class MessageSenderNoMcu extends MessageSender {
public MessageSenderNoMcu(List<PeerConnectionWrapper> peerConnectionWrappers) { public MessageSenderNoMcu(SignalingMessageSender signalingMessageSender,
super(peerConnectionWrappers); Set<String> callParticipantSessionIds,
List<PeerConnectionWrapper> peerConnectionWrappers) {
super(signalingMessageSender, callParticipantSessionIds, peerConnectionWrappers);
} }
/** /**

View File

@ -7,6 +7,7 @@
package com.nextcloud.talk.call package com.nextcloud.talk.call
import com.nextcloud.talk.models.json.signaling.DataChannelMessage import com.nextcloud.talk.models.json.signaling.DataChannelMessage
import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.webrtc.PeerConnectionWrapper import com.nextcloud.talk.webrtc.PeerConnectionWrapper
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -27,6 +28,10 @@ class MessageSenderMcuTest {
@Before @Before
fun setUp() { fun setUp() {
val signalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
val callParticipants = HashMap<String, CallParticipant>()
peerConnectionWrappers = ArrayList() peerConnectionWrappers = ArrayList()
peerConnectionWrapper1 = Mockito.mock(PeerConnectionWrapper::class.java) peerConnectionWrapper1 = Mockito.mock(PeerConnectionWrapper::class.java)
@ -59,7 +64,12 @@ class MessageSenderMcuTest {
Mockito.`when`(ownPeerConnectionWrapperScreen!!.videoStreamType).thenReturn("screen") Mockito.`when`(ownPeerConnectionWrapperScreen!!.videoStreamType).thenReturn("screen")
peerConnectionWrappers!!.add(ownPeerConnectionWrapperScreen) peerConnectionWrappers!!.add(ownPeerConnectionWrapperScreen)
messageSender = MessageSenderMcu(peerConnectionWrappers, "ownSessionId") messageSender = MessageSenderMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrappers,
"ownSessionId"
)
} }
@Test @Test

View File

@ -7,6 +7,7 @@
package com.nextcloud.talk.call package com.nextcloud.talk.call
import com.nextcloud.talk.models.json.signaling.DataChannelMessage import com.nextcloud.talk.models.json.signaling.DataChannelMessage
import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.webrtc.PeerConnectionWrapper import com.nextcloud.talk.webrtc.PeerConnectionWrapper
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -25,6 +26,10 @@ class MessageSenderNoMcuTest {
@Before @Before
fun setUp() { fun setUp() {
val signalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
val callParticipants = HashMap<String, CallParticipant>()
peerConnectionWrappers = ArrayList() peerConnectionWrappers = ArrayList()
peerConnectionWrapper1 = Mockito.mock(PeerConnectionWrapper::class.java) peerConnectionWrapper1 = Mockito.mock(PeerConnectionWrapper::class.java)
@ -47,7 +52,7 @@ class MessageSenderNoMcuTest {
Mockito.`when`(peerConnectionWrapper4Screen!!.videoStreamType).thenReturn("screen") Mockito.`when`(peerConnectionWrapper4Screen!!.videoStreamType).thenReturn("screen")
peerConnectionWrappers!!.add(peerConnectionWrapper4Screen) peerConnectionWrappers!!.add(peerConnectionWrapper4Screen)
messageSender = MessageSenderNoMcu(peerConnectionWrappers) messageSender = MessageSenderNoMcu(signalingMessageSender, callParticipants.keys, peerConnectionWrappers)
} }
@Test @Test

View File

@ -0,0 +1,134 @@
/*
* 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 com.nextcloud.talk.models.json.signaling.NCSignalingMessage
import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.webrtc.PeerConnectionWrapper
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.times
import org.mockito.invocation.InvocationOnMock
class MessageSenderTest {
private class MessageSender(
signalingMessageSender: SignalingMessageSender?,
callParticipantSessionIds: Set<String>?,
peerConnectionWrappers: List<PeerConnectionWrapper>?
) : com.nextcloud.talk.call.MessageSender(
signalingMessageSender,
callParticipantSessionIds,
peerConnectionWrappers
) {
override fun sendToAll(dataChannelMessage: DataChannelMessage?) {
// Not used in base class tests
}
}
private var signalingMessageSender: SignalingMessageSender? = null
private var callParticipants: MutableMap<String, CallParticipant>? = null
private var messageSender: MessageSender? = null
@Before
fun setUp() {
signalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
callParticipants = HashMap()
val callParticipant1: CallParticipant = Mockito.mock(CallParticipant::class.java)
callParticipants!!["theSessionId1"] = callParticipant1
val callParticipant2: CallParticipant = Mockito.mock(CallParticipant::class.java)
callParticipants!!["theSessionId2"] = callParticipant2
val callParticipant3: CallParticipant = Mockito.mock(CallParticipant::class.java)
callParticipants!!["theSessionId3"] = callParticipant3
val callParticipant4: CallParticipant = Mockito.mock(CallParticipant::class.java)
callParticipants!!["theSessionId4"] = callParticipant4
val peerConnectionWrappers = ArrayList<PeerConnectionWrapper>()
messageSender = MessageSender(signalingMessageSender, callParticipants!!.keys, peerConnectionWrappers)
}
@Test
fun testSendSignalingMessage() {
val message: NCSignalingMessage = Mockito.mock(NCSignalingMessage::class.java)
messageSender!!.send(message, "theSessionId2")
Mockito.verify(message).to = "theSessionId2"
Mockito.verify(signalingMessageSender!!).send(message)
}
@Test
fun testSendSignalingMessageIfUnknownSessionId() {
val message: NCSignalingMessage = Mockito.mock(NCSignalingMessage::class.java)
messageSender!!.send(message, "unknownSessionId")
Mockito.verify(message).to = "unknownSessionId"
Mockito.verify(signalingMessageSender!!).send(message)
}
@Test
fun testSendSignalingMessageToAll() {
val sentTo: MutableList<String?> = ArrayList()
doAnswer { invocation: InvocationOnMock ->
val arguments = invocation.arguments
val message = (arguments[0] as NCSignalingMessage)
sentTo.add(message.to)
null
}.`when`(signalingMessageSender!!).send(any())
val message = NCSignalingMessage()
messageSender!!.sendToAll(message)
assertTrue(sentTo.contains("theSessionId1"))
assertTrue(sentTo.contains("theSessionId2"))
assertTrue(sentTo.contains("theSessionId3"))
assertTrue(sentTo.contains("theSessionId4"))
Mockito.verify(signalingMessageSender!!, times(4)).send(message)
Mockito.verifyNoMoreInteractions(signalingMessageSender)
}
@Test
fun testSendSignalingMessageToAllWhenParticipantsWereUpdated() {
val callParticipant5: CallParticipant = Mockito.mock(CallParticipant::class.java)
callParticipants!!["theSessionId5"] = callParticipant5
callParticipants!!.remove("theSessionId2")
callParticipants!!.remove("theSessionId3")
val sentTo: MutableList<String?> = ArrayList()
doAnswer { invocation: InvocationOnMock ->
val arguments = invocation.arguments
val message = (arguments[0] as NCSignalingMessage)
sentTo.add(message.to)
null
}.`when`(signalingMessageSender!!).send(any())
val message = NCSignalingMessage()
messageSender!!.sendToAll(message)
assertTrue(sentTo.contains("theSessionId1"))
assertTrue(sentTo.contains("theSessionId4"))
assertTrue(sentTo.contains("theSessionId5"))
Mockito.verify(signalingMessageSender!!, times(3)).send(message)
Mockito.verifyNoMoreInteractions(signalingMessageSender)
}
}