Keep track of raised hands by remote participants

Note the slight difference in naming between the signaling message
("raiseHand", the action) and the stored data ("RaisedHand", the record
of the action).

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This commit is contained in:
Daniel Calviño Sánchez 2023-01-26 11:56:42 +01:00 committed by Marcel Hibbe
parent de44370710
commit 4bd3cc826c
No known key found for this signature in database
GPG key ID: C793F8B59F43CE7B
7 changed files with 140 additions and 2 deletions

View file

@ -2124,7 +2124,7 @@ public class CallActivity extends CallBaseActivity {
}
private CallParticipant addCallParticipant(String sessionId) {
CallParticipant callParticipant = new CallParticipant(sessionId);
CallParticipant callParticipant = new CallParticipant(sessionId, signalingMessageReceiver);
callParticipants.put(sessionId, callParticipant);
SignalingMessageReceiver.CallParticipantMessageListener callParticipantMessageListener =

View file

@ -5,6 +5,7 @@ import android.os.Looper;
import android.text.TextUtils;
import com.nextcloud.talk.call.CallParticipantModel;
import com.nextcloud.talk.call.RaisedHand;
import com.nextcloud.talk.utils.ApiUtils;
import org.webrtc.EglBase;
@ -42,6 +43,7 @@ public class ParticipantDisplayItem {
private MediaStream mediaStream;
private boolean streamEnabled;
private boolean isAudioEnabled;
private RaisedHand raisedHand;
public ParticipantDisplayItem(String baseUrl, String defaultGuestNick, EglBase rootEglBase, String streamType,
CallParticipantModel callParticipantModel) {
@ -82,6 +84,8 @@ public class ParticipantDisplayItem {
callParticipantModel.isVideoAvailable() : false;
}
raisedHand = callParticipantModel.getRaisedHand();
participantDisplayItemNotifier.notifyChange();
}
@ -129,6 +133,10 @@ public class ParticipantDisplayItem {
return isAudioEnabled;
}
public RaisedHand getRaisedHand() {
return raisedHand;
}
public void addObserver(Observer observer) {
participantDisplayItemNotifier.addObserver(observer);
}
@ -148,6 +156,7 @@ public class ParticipantDisplayItem {
", streamType='" + streamType + '\'' +
", streamEnabled=" + streamEnabled +
", rootEglBase=" + rootEglBase +
", raisedHand=" + raisedHand +
'}';
}
}

View file

@ -19,6 +19,7 @@
*/
package com.nextcloud.talk.call;
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
import org.webrtc.MediaStream;
@ -32,6 +33,18 @@ import org.webrtc.PeerConnection;
*/
public class CallParticipant {
private final SignalingMessageReceiver.CallParticipantMessageListener callParticipantMessageListener =
new SignalingMessageReceiver.CallParticipantMessageListener() {
@Override
public void onRaiseHand(boolean state, long timestamp) {
callParticipantModel.setRaisedHand(state, timestamp);
}
@Override
public void onUnshareScreen() {
}
};
private final PeerConnectionWrapper.PeerConnectionObserver peerConnectionObserver =
new PeerConnectionWrapper.PeerConnectionObserver() {
@Override
@ -99,14 +112,21 @@ public class CallParticipant {
private final MutableCallParticipantModel callParticipantModel;
private final SignalingMessageReceiver signalingMessageReceiver;
private PeerConnectionWrapper peerConnectionWrapper;
private PeerConnectionWrapper screenPeerConnectionWrapper;
public CallParticipant(String sessionId) {
public CallParticipant(String sessionId, SignalingMessageReceiver signalingMessageReceiver) {
callParticipantModel = new MutableCallParticipantModel(sessionId);
this.signalingMessageReceiver = signalingMessageReceiver;
signalingMessageReceiver.addListener(callParticipantMessageListener, sessionId);
}
public void destroy() {
signalingMessageReceiver.removeListener(callParticipantMessageListener);
if (peerConnectionWrapper != null) {
peerConnectionWrapper.removeObserver(peerConnectionObserver);
peerConnectionWrapper.removeListener(dataChannelMessageListener);

View file

@ -29,6 +29,9 @@ import java.util.Objects;
/**
* Read-only data model for (remote) call participants.
*
* If the hand was never raised null is returned by "getRaisedHand()". Otherwise a RaisedHand object is returned with
* the current state (raised or not) and the timestamp when the raised hand state last changed.
*
* The received audio and video are available only if the participant is sending them and also has them enabled.
* Before a connection is established it is not known whether audio and video are available or not, so null is returned
* in that case (therefore it should not be autoboxed to a plain boolean without checking that).
@ -72,6 +75,8 @@ public class CallParticipantModel {
protected Data<String> userId;
protected Data<String> nick;
protected Data<RaisedHand> raisedHand;
protected Data<PeerConnection.IceConnectionState> iceConnectionState;
protected Data<MediaStream> mediaStream;
protected Data<Boolean> audioAvailable;
@ -86,6 +91,8 @@ public class CallParticipantModel {
this.userId = new Data<>();
this.nick = new Data<>();
this.raisedHand = new Data<>();
this.iceConnectionState = new Data<>();
this.mediaStream = new Data<>();
this.audioAvailable = new Data<>();
@ -107,6 +114,10 @@ public class CallParticipantModel {
return nick.getValue();
}
public RaisedHand getRaisedHand() {
return raisedHand.getValue();
}
public PeerConnection.IceConnectionState getIceConnectionState() {
return iceConnectionState.getValue();
}

View file

@ -41,6 +41,10 @@ public class MutableCallParticipantModel extends CallParticipantModel {
this.nick.setValue(nick);
}
public void setRaisedHand(boolean state, long timestamp) {
this.raisedHand.setValue(new RaisedHand(state, timestamp));
}
public void setIceConnectionState(PeerConnection.IceConnectionState iceConnectionState) {
this.iceConnectionState.setValue(iceConnectionState);
}

View file

@ -0,0 +1,23 @@
/*
* Nextcloud Talk application
*
* @author Daniel Calviño Sánchez
* Copyright (C) 2023 Daniel Calviño Sánchez <danxuliu@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.call
data class RaisedHand(val state: Boolean, val timestamp: Long) {
}

View file

@ -0,0 +1,71 @@
/*
* Nextcloud Talk application
*
* @author Daniel Calviño Sánchez
* Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.call;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class CallParticipantModelTest {
private MutableCallParticipantModel callParticipantModel;
private CallParticipantModel.Observer mockedCallParticipantModelObserver;
@Before
public void setUp() {
callParticipantModel = new MutableCallParticipantModel("theSessionId");
mockedCallParticipantModelObserver = mock(CallParticipantModel.Observer.class);
}
@Test
public void testSetRaisedHand() {
callParticipantModel.addObserver(mockedCallParticipantModelObserver);
callParticipantModel.setRaisedHand(true, 4815162342L);
verify(mockedCallParticipantModelObserver, only()).onChange();
}
@Test
public void testSetRaisedHandTwice() {
callParticipantModel.addObserver(mockedCallParticipantModelObserver);
callParticipantModel.setRaisedHand(true, 4815162342L);
callParticipantModel.setRaisedHand(false, 4815162342108L);
verify(mockedCallParticipantModelObserver, times(2)).onChange();
}
@Test
public void testSetRaisedHandTwiceWithSameValue() {
callParticipantModel.addObserver(mockedCallParticipantModelObserver);
callParticipantModel.setRaisedHand(true, 4815162342L);
callParticipantModel.setRaisedHand(true, 4815162342L);
verify(mockedCallParticipantModelObserver, only()).onChange();
}
}