mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-11-23 21:45:42 +03:00
Merge pull request #2995 from nextcloud/handle-received-signaling-messages-for-call-reactions
Handle received signaling messages for call reactions
This commit is contained in:
commit
51bedbba9c
11 changed files with 180 additions and 14 deletions
|
@ -296,6 +296,10 @@ public class CallActivity extends CallBaseActivity {
|
||||||
|
|
||||||
private Handler screenParticipantDisplayItemManagersHandler = new Handler(Looper.getMainLooper());
|
private Handler screenParticipantDisplayItemManagersHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
private Map<String, CallParticipantEventDisplayer> callParticipantEventDisplayers = new HashMap<>();
|
||||||
|
|
||||||
|
private Handler callParticipantEventDisplayersHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
private CallParticipantList.Observer callParticipantListObserver = new CallParticipantList.Observer() {
|
private CallParticipantList.Observer callParticipantListObserver = new CallParticipantList.Observer() {
|
||||||
@Override
|
@Override
|
||||||
public void onCallParticipantsChanged(Collection<Participant> joined, Collection<Participant> updated,
|
public void onCallParticipantsChanged(Collection<Participant> joined, Collection<Participant> updated,
|
||||||
|
@ -2248,6 +2252,11 @@ public class CallActivity extends CallBaseActivity {
|
||||||
screenParticipantDisplayItemManagers.put(sessionId, screenParticipantDisplayItemManager);
|
screenParticipantDisplayItemManagers.put(sessionId, screenParticipantDisplayItemManager);
|
||||||
callParticipantModel.addObserver(screenParticipantDisplayItemManager, screenParticipantDisplayItemManagersHandler);
|
callParticipantModel.addObserver(screenParticipantDisplayItemManager, screenParticipantDisplayItemManagersHandler);
|
||||||
|
|
||||||
|
CallParticipantEventDisplayer callParticipantEventDisplayer =
|
||||||
|
new CallParticipantEventDisplayer(callParticipantModel);
|
||||||
|
callParticipantEventDisplayers.put(sessionId, callParticipantEventDisplayer);
|
||||||
|
callParticipantModel.addObserver(callParticipantEventDisplayer, callParticipantEventDisplayersHandler);
|
||||||
|
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
addParticipantDisplayItem(callParticipantModel, "video");
|
addParticipantDisplayItem(callParticipantModel, "video");
|
||||||
});
|
});
|
||||||
|
@ -2288,6 +2297,10 @@ public class CallActivity extends CallBaseActivity {
|
||||||
screenParticipantDisplayItemManagers.remove(sessionId);
|
screenParticipantDisplayItemManagers.remove(sessionId);
|
||||||
callParticipant.getCallParticipantModel().removeObserver(screenParticipantDisplayItemManager);
|
callParticipant.getCallParticipantModel().removeObserver(screenParticipantDisplayItemManager);
|
||||||
|
|
||||||
|
CallParticipantEventDisplayer callParticipantEventDisplayer =
|
||||||
|
callParticipantEventDisplayers.remove(sessionId);
|
||||||
|
callParticipant.getCallParticipantModel().removeObserver(callParticipantEventDisplayer);
|
||||||
|
|
||||||
callParticipant.destroy();
|
callParticipant.destroy();
|
||||||
|
|
||||||
SignalingMessageReceiver.CallParticipantMessageListener listener = callParticipantMessageListeners.remove(sessionId);
|
SignalingMessageReceiver.CallParticipantMessageListener listener = callParticipantMessageListeners.remove(sessionId);
|
||||||
|
@ -2793,16 +2806,10 @@ public class CallActivity extends CallBaseActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRaiseHand(boolean state, long timestamp) {
|
public void onRaiseHand(boolean state, long timestamp) {
|
||||||
if (state) {
|
|
||||||
CallParticipant participant = callParticipants.get(sessionId);
|
|
||||||
if (participant != null) {
|
|
||||||
String nick = participant.getCallParticipantModel().getNick();
|
|
||||||
runOnUiThread(() -> Toast.makeText(
|
|
||||||
context,
|
|
||||||
String.format(context.getResources().getString(R.string.nc_call_raised_hand), nick),
|
|
||||||
Toast.LENGTH_LONG).show());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReaction(String reaction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2857,6 +2864,44 @@ public class CallActivity extends CallBaseActivity {
|
||||||
addParticipantDisplayItem(callParticipantModel, "screen");
|
addParticipantDisplayItem(callParticipantModel, "screen");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReaction(String reaction) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CallParticipantEventDisplayer implements CallParticipantModel.Observer {
|
||||||
|
|
||||||
|
private final CallParticipantModel callParticipantModel;
|
||||||
|
|
||||||
|
private boolean raisedHand;
|
||||||
|
|
||||||
|
private CallParticipantEventDisplayer(CallParticipantModel callParticipantModel) {
|
||||||
|
this.callParticipantModel = callParticipantModel;
|
||||||
|
this.raisedHand = callParticipantModel.getRaisedHand() != null ?
|
||||||
|
callParticipantModel.getRaisedHand().getState() : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange() {
|
||||||
|
if (callParticipantModel.getRaisedHand() == null || !callParticipantModel.getRaisedHand().getState()) {
|
||||||
|
raisedHand = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raisedHand) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
raisedHand = true;
|
||||||
|
|
||||||
|
String nick = callParticipantModel.getNick();
|
||||||
|
Toast.makeText(context, String.format(context.getResources().getString(R.string.nc_call_raised_hand), nick), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReaction(String reaction) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class InternalSignalingMessageSender implements SignalingMessageSender {
|
private class InternalSignalingMessageSender implements SignalingMessageSender {
|
||||||
|
|
|
@ -34,7 +34,16 @@ public class ParticipantDisplayItem {
|
||||||
|
|
||||||
private final CallParticipantModel callParticipantModel;
|
private final CallParticipantModel callParticipantModel;
|
||||||
|
|
||||||
private final CallParticipantModel.Observer callParticipantModelObserver = this::updateFromModel;
|
private final CallParticipantModel.Observer callParticipantModelObserver = new CallParticipantModel.Observer() {
|
||||||
|
@Override
|
||||||
|
public void onChange() {
|
||||||
|
updateFromModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReaction(String reaction) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private String userId;
|
private String userId;
|
||||||
private PeerConnection.IceConnectionState iceConnectionState;
|
private PeerConnection.IceConnectionState iceConnectionState;
|
||||||
|
|
|
@ -40,6 +40,11 @@ public class CallParticipant {
|
||||||
callParticipantModel.setRaisedHand(state, timestamp);
|
callParticipantModel.setRaisedHand(state, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReaction(String reaction) {
|
||||||
|
callParticipantModel.emitReaction(reaction);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUnshareScreen() {
|
public void onUnshareScreen() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,11 +42,15 @@ import java.util.Objects;
|
||||||
* Getters called after receiving a notification are guaranteed to provide at least the value that triggered the
|
* Getters called after receiving a notification are guaranteed to provide at least the value that triggered the
|
||||||
* notification, but it may return even a more up to date one (so getting the value again on the following
|
* notification, but it may return even a more up to date one (so getting the value again on the following
|
||||||
* notification may return the same value as before).
|
* notification may return the same value as before).
|
||||||
|
*
|
||||||
|
* Besides onChange(), which notifies about changes in the model values, CallParticipantModel.Observer provides
|
||||||
|
* additional methods to be notified about one-time events that are not reflected in the model values, like reactions.
|
||||||
*/
|
*/
|
||||||
public class CallParticipantModel {
|
public class CallParticipantModel {
|
||||||
|
|
||||||
public interface Observer {
|
public interface Observer {
|
||||||
void onChange();
|
void onChange();
|
||||||
|
void onReaction(String reaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class Data<T> {
|
protected class Data<T> {
|
||||||
|
@ -68,7 +72,7 @@ public class CallParticipantModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final CallParticipantModelNotifier callParticipantModelNotifier = new CallParticipantModelNotifier();
|
protected final CallParticipantModelNotifier callParticipantModelNotifier = new CallParticipantModelNotifier();
|
||||||
|
|
||||||
protected final String sessionId;
|
protected final String sessionId;
|
||||||
|
|
||||||
|
|
|
@ -83,4 +83,16 @@ class CallParticipantModelNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void notifyReaction(String reaction) {
|
||||||
|
for (CallParticipantModelObserverOn observerOn : new ArrayList<>(callParticipantModelObserversOn)) {
|
||||||
|
if (observerOn.handler == null || observerOn.handler.getLooper() == Looper.myLooper()) {
|
||||||
|
observerOn.observer.onReaction(reaction);
|
||||||
|
} else {
|
||||||
|
observerOn.handler.post(() -> {
|
||||||
|
observerOn.observer.onReaction(reaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,4 +72,8 @@ public class MutableCallParticipantModel extends CallParticipantModel {
|
||||||
public void setScreenMediaStream(MediaStream screenMediaStream) {
|
public void setScreenMediaStream(MediaStream screenMediaStream) {
|
||||||
this.screenMediaStream.setValue(screenMediaStream);
|
this.screenMediaStream.setValue(screenMediaStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void emitReaction(String reaction) {
|
||||||
|
this.callParticipantModelNotifier.notifyReaction(reaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,10 @@ data class NCMessagePayload(
|
||||||
@JsonField(name = ["state"])
|
@JsonField(name = ["state"])
|
||||||
var state: Boolean? = null,
|
var state: Boolean? = null,
|
||||||
@JsonField(name = ["timestamp"])
|
@JsonField(name = ["timestamp"])
|
||||||
var timestamp: Long? = null
|
var timestamp: Long? = null,
|
||||||
|
@JsonField(name = ["reaction"])
|
||||||
|
var reaction: String? = null
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
constructor() : this(null, null, null, null, null, null, null)
|
constructor() : this(null, null, null, null, null, null, null, null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,12 @@ class CallParticipantMessageNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void notifyReaction(String sessionId, String reaction) {
|
||||||
|
for (SignalingMessageReceiver.CallParticipantMessageListener listener : getListenersFor(sessionId)) {
|
||||||
|
listener.onReaction(reaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void notifyUnshareScreen(String sessionId) {
|
public synchronized void notifyUnshareScreen(String sessionId) {
|
||||||
for (SignalingMessageReceiver.CallParticipantMessageListener listener : getListenersFor(sessionId)) {
|
for (SignalingMessageReceiver.CallParticipantMessageListener listener : getListenersFor(sessionId)) {
|
||||||
listener.onUnshareScreen();
|
listener.onUnshareScreen();
|
||||||
|
|
|
@ -149,6 +149,7 @@ public abstract class SignalingMessageReceiver {
|
||||||
*/
|
*/
|
||||||
public interface CallParticipantMessageListener {
|
public interface CallParticipantMessageListener {
|
||||||
void onRaiseHand(boolean state, long timestamp);
|
void onRaiseHand(boolean state, long timestamp);
|
||||||
|
void onReaction(String reaction);
|
||||||
void onUnshareScreen();
|
void onUnshareScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,6 +563,57 @@ public abstract class SignalingMessageReceiver {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("reaction".equals(type)) {
|
||||||
|
// Message schema (external signaling server):
|
||||||
|
// {
|
||||||
|
// "type": "message",
|
||||||
|
// "message": {
|
||||||
|
// "sender": {
|
||||||
|
// ...
|
||||||
|
// },
|
||||||
|
// "data": {
|
||||||
|
// "to": #STRING#,
|
||||||
|
// "roomType": "video",
|
||||||
|
// "type": "reaction",
|
||||||
|
// "payload": {
|
||||||
|
// "reaction": #STRING#,
|
||||||
|
// },
|
||||||
|
// "from": #STRING#,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Message schema (internal signaling server):
|
||||||
|
// {
|
||||||
|
// "type": "message",
|
||||||
|
// "data": {
|
||||||
|
// "to": #STRING#,
|
||||||
|
// "roomType": "video",
|
||||||
|
// "type": "reaction",
|
||||||
|
// "payload": {
|
||||||
|
// "reaction": #STRING#,
|
||||||
|
// },
|
||||||
|
// "from": #STRING#,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
NCMessagePayload payload = signalingMessage.getPayload();
|
||||||
|
if (payload == null) {
|
||||||
|
// Broken message, this should not happen.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String reaction = payload.getReaction();
|
||||||
|
if (reaction == null) {
|
||||||
|
// Broken message, this should not happen.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callParticipantMessageNotifier.notifyReaction(sessionId, reaction);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// "unshareScreen" messages are directly sent to the screen peer connection when the internal signaling
|
// "unshareScreen" messages are directly sent to the screen peer connection when the internal signaling
|
||||||
// server is used, and to the room when the external signaling server is used. However, the (relevant) data
|
// server is used, and to the room when the external signaling server is used. However, the (relevant) data
|
||||||
// of the received message ("from" and "type") is the same in both cases.
|
// of the received message ("from" and "type") is the same in both cases.
|
||||||
|
|
|
@ -55,4 +55,11 @@ class CallParticipantModelTest {
|
||||||
callParticipantModel!!.setRaisedHand(true, 4815162342L)
|
callParticipantModel!!.setRaisedHand(true, 4815162342L)
|
||||||
Mockito.verify(mockedCallParticipantModelObserver, Mockito.only())?.onChange()
|
Mockito.verify(mockedCallParticipantModelObserver, Mockito.only())?.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEmitReaction() {
|
||||||
|
callParticipantModel!!.addObserver(mockedCallParticipantModelObserver)
|
||||||
|
callParticipantModel!!.emitReaction("theReaction")
|
||||||
|
Mockito.verify(mockedCallParticipantModelObserver, Mockito.only())?.onReaction("theReaction")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,26 @@ public class SignalingMessageReceiverCallParticipantTest {
|
||||||
verify(mockedCallParticipantMessageListener, only()).onRaiseHand(true, 4815162342L);
|
verify(mockedCallParticipantMessageListener, only()).onRaiseHand(true, 4815162342L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCallParticipantMessageReaction() {
|
||||||
|
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
|
||||||
|
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
|
||||||
|
|
||||||
|
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
|
||||||
|
|
||||||
|
NCSignalingMessage signalingMessage = new NCSignalingMessage();
|
||||||
|
signalingMessage.setFrom("theSessionId");
|
||||||
|
signalingMessage.setType("reaction");
|
||||||
|
signalingMessage.setRoomType("theRoomType");
|
||||||
|
NCMessagePayload messagePayload = new NCMessagePayload();
|
||||||
|
messagePayload.setType("reaction");
|
||||||
|
messagePayload.setReaction("theReaction");
|
||||||
|
signalingMessage.setPayload(messagePayload);
|
||||||
|
signalingMessageReceiver.processSignalingMessage(signalingMessage);
|
||||||
|
|
||||||
|
verify(mockedCallParticipantMessageListener, only()).onReaction("theReaction");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCallParticipantMessageUnshareScreen() {
|
public void testCallParticipantMessageUnshareScreen() {
|
||||||
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
|
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
|
||||||
|
|
Loading…
Reference in a new issue