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
messageSender = MessageSenderNoMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrapperList
)
@ -1895,11 +1897,15 @@ class CallActivity : CallBaseActivity() {
if (hasMCU) {
messageSender = MessageSenderMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrapperList,
webSocketClient!!.sessionId
)
} else {
messageSender = MessageSenderNoMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrapperList
)
}
@ -1934,11 +1940,15 @@ class CallActivity : CallBaseActivity() {
if (hasMCU) {
messageSender = MessageSenderMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrapperList,
webSocketClient!!.sessionId
)
} else {
messageSender = MessageSenderNoMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrapperList
)
}

View File

@ -7,16 +7,22 @@
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 java.util.List;
import java.util.Set;
/**
* Helper class to send messages to participants in a call.
* <p>
* A specific subclass has to be created depending on whether an MCU is being used or not.
* <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
* 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
@ -24,9 +30,17 @@ import java.util.List;
*/
public abstract class MessageSender {
private final SignalingMessageSender signalingMessageSender;
private final Set<String> callParticipantSessionIds;
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;
}
@ -37,6 +51,35 @@ public abstract class MessageSender {
*/
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) {
for (PeerConnectionWrapper peerConnectionWrapper: peerConnectionWrappers) {
if (peerConnectionWrapper.getSessionId().equals(sessionId)

View File

@ -7,9 +7,11 @@
package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
import com.nextcloud.talk.signaling.SignalingMessageSender;
import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
import java.util.List;
import java.util.Set;
/**
* 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;
public MessageSenderMcu(List<PeerConnectionWrapper> peerConnectionWrappers,
public MessageSenderMcu(SignalingMessageSender signalingMessageSender,
Set<String> callParticipantSessionIds,
List<PeerConnectionWrapper> peerConnectionWrappers,
String ownSessionId) {
super(peerConnectionWrappers);
super(signalingMessageSender, callParticipantSessionIds, peerConnectionWrappers);
this.ownSessionId = ownSessionId;
}

View File

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

View File

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

View File

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