Update ParticipantDisplayItem from CallParticipantModel

Instead of explicitly setting the values on the ParticipantDisplayItems
now the values are set on the CallParticipantModels, and the items are
automatically updated from their model when they change.

Different items are still used for the audio/video and screen shares of
the same participant, so the type is used to select from which
properties of the model is the item updated.

As the model may be updated from background threads it is explicitly
observed by the items from the main thread using a Handler shared by all
the items.

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This commit is contained in:
Daniel Calviño Sánchez 2022-11-25 22:07:05 +01:00 committed by Marcel Hibbe (Rebase PR Action)
parent d72648379e
commit 18f21c4f48
2 changed files with 150 additions and 154 deletions

View file

@ -60,6 +60,8 @@ import com.nextcloud.talk.adapters.ParticipantDisplayItem;
import com.nextcloud.talk.adapters.ParticipantsAdapter;
import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.call.CallParticipantModel;
import com.nextcloud.talk.call.MutableCallParticipantModel;
import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.databinding.CallActivityBinding;
import com.nextcloud.talk.events.ConfigurationChangeEvent;
@ -231,7 +233,6 @@ public class CallActivity extends CallBaseActivity {
private MediaStream localStream;
private String credentials;
private List<PeerConnectionWrapper> peerConnectionWrapperList = new ArrayList<>();
private Map<String, String> userIdsBySessionId = new HashMap<>();
private boolean videoOn = false;
private boolean microphoneOn = false;
@ -267,6 +268,8 @@ public class CallActivity extends CallBaseActivity {
private Map<String, PeerConnectionWrapper.PeerConnectionObserver> peerConnectionObservers = new HashMap<>();
private Map<String, MutableCallParticipantModel> callParticipantModels = new HashMap<>();
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener = new SignalingMessageReceiver.ParticipantListMessageListener() {
@Override
@ -382,6 +385,7 @@ public class CallActivity extends CallBaseActivity {
requestBluetoothPermission();
}
basicInitialization();
callParticipantModels = new HashMap<>();
participantDisplayItems = new HashMap<>();
initViews();
if (!isConnectionEstablished()) {
@ -1776,7 +1780,7 @@ public class CallActivity extends CallBaseActivity {
Log.d(TAG, "processUsersInRoom");
List<String> newSessions = new ArrayList<>();
Set<String> oldSessions = new HashSet<>();
userIdsBySessionId = new HashMap<>();
Map<String, String> userIdsBySessionId = new HashMap<>();
hasMCU = hasExternalSignalingServer && webSocketClient != null && webSocketClient.hasMCU();
Log.d(TAG, " hasMCU is " + hasMCU);
@ -1856,15 +1860,16 @@ public class CallActivity extends CallBaseActivity {
String userId = userIdsBySessionId.get(sessionId);
if (userId != null) {
runOnUiThread(() -> {
if (participantDisplayItems.get(sessionId + "-video") != null) {
participantDisplayItems.get(sessionId + "-video").setUserId(userId);
}
if (participantDisplayItems.get(sessionId + "-screen") != null) {
participantDisplayItems.get(sessionId + "-screen").setUserId(userId);
}
});
callParticipantModels.get(sessionId).setUserId(userId);
}
String nick;
if (hasExternalSignalingServer) {
nick = webSocketClient.getDisplayNameForSession(sessionId);
} else {
nick = offerAnswerNickProviders.get(sessionId) != null ? offerAnswerNickProviders.get(sessionId).getNick() : "";
}
callParticipantModels.get(sessionId).setNick(nick);
}
if (newSessions.size() > 0 && currentCallStatus != CallStatus.IN_CONVERSATION) {
@ -1991,14 +1996,16 @@ public class CallActivity extends CallBaseActivity {
peerConnectionWrapper.addObserver(peerConnectionObserver);
if (!publisher) {
MutableCallParticipantModel mutableCallParticipantModel = callParticipantModels.get(sessionId);
if (mutableCallParticipantModel == null) {
mutableCallParticipantModel = new MutableCallParticipantModel(sessionId);
callParticipantModels.put(sessionId, mutableCallParticipantModel);
}
final CallParticipantModel callParticipantModel = mutableCallParticipantModel;
runOnUiThread(() -> {
// userId is unknown here, but it will be got based on the session id, and the stream will be
// updated once it is added to the connection.
setupVideoStreamForLayout(
null,
sessionId,
false,
type);
setupVideoStreamForLayout(callParticipantModel, type);
});
}
@ -2036,6 +2043,20 @@ public class CallActivity extends CallBaseActivity {
peerConnectionWrapper.removeObserver(peerConnectionObserver);
runOnUiThread(() -> removeMediaStream(sessionId, videoStreamType));
MutableCallParticipantModel mutableCallParticipantModel = callParticipantModels.get(sessionId);
if (mutableCallParticipantModel != null) {
if ("screen".equals(videoStreamType)) {
mutableCallParticipantModel.setScreenMediaStream(null);
mutableCallParticipantModel.setScreenIceConnectionState(null);
} else {
mutableCallParticipantModel.setMediaStream(null);
mutableCallParticipantModel.setIceConnectionState(null);
mutableCallParticipantModel.setAudioAvailable(null);
mutableCallParticipantModel.setVideoAvailable(null);
}
}
deletePeerConnection(peerConnectionWrapper);
}
}
@ -2051,12 +2072,19 @@ public class CallActivity extends CallBaseActivity {
signalingMessageReceiver.removeListener(offerAnswerNickProvider.getVideoWebRtcMessageListener());
signalingMessageReceiver.removeListener(offerAnswerNickProvider.getScreenWebRtcMessageListener());
}
callParticipantModels.remove(sessionId);
}
}
private void removeMediaStream(String sessionId, String videoStreamType) {
Log.d(TAG, "removeMediaStream");
participantDisplayItems.remove(sessionId + "-" + videoStreamType);
ParticipantDisplayItem participantDisplayItem = participantDisplayItems.remove(sessionId + "-" + videoStreamType);
if (participantDisplayItem == null) {
return;
}
participantDisplayItem.destroy();
if (!isDestroyed()) {
initGridAdapter();
@ -2183,40 +2211,16 @@ public class CallActivity extends CallBaseActivity {
this);
}
private void setupVideoStreamForLayout(@Nullable MediaStream mediaStream,
String session,
boolean videoStreamEnabled,
String videoStreamType) {
PeerConnectionWrapper peerConnectionWrapper = getPeerConnectionWrapperForSessionIdAndType(session,
videoStreamType);
PeerConnection.IceConnectionState iceConnectionState = null;
if (peerConnectionWrapper != null) {
iceConnectionState = peerConnectionWrapper.getPeerConnection().iceConnectionState();
}
String nick;
if (hasExternalSignalingServer) {
nick = webSocketClient.getDisplayNameForSession(session);
} else {
nick = offerAnswerNickProviders.get(session) != null ? offerAnswerNickProviders.get(session).getNick() : "";
}
String userId = userIdsBySessionId.get(session);
private void setupVideoStreamForLayout(CallParticipantModel callParticipantModel, String videoStreamType) {
String defaultGuestNick = getResources().getString(R.string.nc_nick_guest);
ParticipantDisplayItem participantDisplayItem = new ParticipantDisplayItem(baseUrl,
userId,
session,
iceConnectionState,
nick,
defaultGuestNick,
mediaStream,
rootEglBase,
videoStreamType,
videoStreamEnabled,
rootEglBase);
participantDisplayItems.put(session + "-" + videoStreamType, participantDisplayItem);
callParticipantModel);
String sessionId = callParticipantModel.getSessionId();
participantDisplayItems.put(sessionId + "-" + videoStreamType, participantDisplayItem);
initGridAdapter();
}
@ -2525,11 +2529,8 @@ public class CallActivity extends CallBaseActivity {
private void onOfferOrAnswer(String nick) {
this.nick = nick;
if (participantDisplayItems.get(sessionId + "-video") != null) {
participantDisplayItems.get(sessionId + "-video").setNick(nick);
}
if (participantDisplayItems.get(sessionId + "-screen") != null) {
participantDisplayItems.get(sessionId + "-screen").setNick(nick);
if (callParticipantModels.get(sessionId) != null) {
callParticipantModels.get(sessionId).setNick(nick);
}
}
@ -2562,56 +2563,45 @@ public class CallActivity extends CallBaseActivity {
private class CallActivityDataChannelMessageListener implements PeerConnectionWrapper.DataChannelMessageListener {
private final String participantDisplayItemId;
private final String sessionId;
private CallActivityDataChannelMessageListener(String sessionId) {
// DataChannel messages are sent only in video peers, so the listener only acts on the "video" items.
this.participantDisplayItemId = sessionId + "-video";
this.sessionId = sessionId;
}
@Override
public void onAudioOn() {
runOnUiThread(() -> {
if (participantDisplayItems.get(participantDisplayItemId) != null) {
participantDisplayItems.get(participantDisplayItemId).setAudioEnabled(true);
}
});
if (callParticipantModels.get(sessionId) != null) {
callParticipantModels.get(sessionId).setAudioAvailable(true);
}
}
@Override
public void onAudioOff() {
runOnUiThread(() -> {
if (participantDisplayItems.get(participantDisplayItemId) != null) {
participantDisplayItems.get(participantDisplayItemId).setAudioEnabled(false);
}
});
if (callParticipantModels.get(sessionId) != null) {
callParticipantModels.get(sessionId).setAudioAvailable(false);
}
}
@Override
public void onVideoOn() {
runOnUiThread(() -> {
if (participantDisplayItems.get(participantDisplayItemId) != null) {
participantDisplayItems.get(participantDisplayItemId).setStreamEnabled(true);
}
});
if (callParticipantModels.get(sessionId) != null) {
callParticipantModels.get(sessionId).setVideoAvailable(true);
}
}
@Override
public void onVideoOff() {
runOnUiThread(() -> {
if (participantDisplayItems.get(participantDisplayItemId) != null) {
participantDisplayItems.get(participantDisplayItemId).setStreamEnabled(false);
}
});
if (callParticipantModels.get(sessionId) != null) {
callParticipantModels.get(sessionId).setVideoAvailable(false);
}
}
@Override
public void onNickChanged(String nick) {
runOnUiThread(() -> {
if (participantDisplayItems.get(participantDisplayItemId) != null) {
participantDisplayItems.get(participantDisplayItemId).setNick(nick);
}
});
if (callParticipantModels.get(sessionId) != null) {
callParticipantModels.get(sessionId).setNick(nick);
}
}
}
@ -2619,12 +2609,10 @@ public class CallActivity extends CallBaseActivity {
private final String sessionId;
private final String videoStreamType;
private final String participantDisplayItemId;
private CallActivityPeerConnectionObserver(String sessionId, String videoStreamType) {
this.sessionId = sessionId;
this.videoStreamType = videoStreamType;
this.participantDisplayItemId = sessionId + "-" + videoStreamType;
}
@Override
@ -2638,29 +2626,38 @@ public class CallActivity extends CallBaseActivity {
}
private void handleStream(MediaStream mediaStream) {
runOnUiThread(() -> {
if (participantDisplayItems.get(participantDisplayItemId) == null) {
return;
}
if (callParticipantModels.get(sessionId) == null) {
return;
}
boolean hasAtLeastOneVideoStream = false;
if (mediaStream != null) {
hasAtLeastOneVideoStream = mediaStream.videoTracks != null && mediaStream.videoTracks.size() > 0;
}
if ("screen".equals(videoStreamType)) {
callParticipantModels.get(sessionId).setScreenMediaStream(mediaStream);
ParticipantDisplayItem participantDisplayItem = participantDisplayItems.get(participantDisplayItemId);
participantDisplayItem.setMediaStream(mediaStream);
participantDisplayItem.setStreamEnabled(hasAtLeastOneVideoStream);
});
return;
}
boolean hasAtLeastOneVideoStream = false;
if (mediaStream != null) {
hasAtLeastOneVideoStream = mediaStream.videoTracks != null && mediaStream.videoTracks.size() > 0;
}
callParticipantModels.get(sessionId).setMediaStream(mediaStream);
callParticipantModels.get(sessionId).setVideoAvailable(hasAtLeastOneVideoStream);
}
@Override
public void onIceConnectionStateChanged(PeerConnection.IceConnectionState iceConnectionState) {
if (callParticipantModels.get(sessionId) != null) {
if ("screen".equals(videoStreamType)) {
callParticipantModels.get(sessionId).setScreenIceConnectionState(iceConnectionState);
} else {
callParticipantModels.get(sessionId).setIceConnectionState(iceConnectionState);
}
}
runOnUiThread(() -> {
if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
updateSelfVideoViewIceConnectionState(iceConnectionState);
} else if (participantDisplayItems.get(participantDisplayItemId) != null) {
participantDisplayItems.get(participantDisplayItemId).setIceConnectionState(iceConnectionState);
}
if (iceConnectionState == PeerConnection.IceConnectionState.CLOSED) {

View file

@ -1,7 +1,10 @@
package com.nextcloud.talk.adapters;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import com.nextcloud.talk.call.CallParticipantModel;
import com.nextcloud.talk.utils.ApiUtils;
import org.webrtc.EglBase;
@ -14,6 +17,11 @@ public class ParticipantDisplayItem {
void onChange();
}
/**
* Shared handler to receive change notifications from the model on the main thread.
*/
private static final Handler handler = new Handler(Looper.getMainLooper());
private final ParticipantDisplayItemNotifier participantDisplayItemNotifier = new ParticipantDisplayItemNotifier();
private final String baseUrl;
@ -23,6 +31,10 @@ public class ParticipantDisplayItem {
private final String session;
private final String streamType;
private final CallParticipantModel callParticipantModel;
private final CallParticipantModel.Observer callParticipantModelObserver = this::updateFromModel;
private String userId;
private PeerConnection.IceConnectionState iceConnectionState;
private String nick;
@ -31,60 +43,48 @@ public class ParticipantDisplayItem {
private boolean streamEnabled;
private boolean isAudioEnabled;
public ParticipantDisplayItem(String baseUrl, String userId, String session, PeerConnection.IceConnectionState iceConnectionState, String nick, String defaultGuestNick, MediaStream mediaStream, String streamType, boolean streamEnabled, EglBase rootEglBase) {
public ParticipantDisplayItem(String baseUrl, String defaultGuestNick, EglBase rootEglBase, String streamType,
CallParticipantModel callParticipantModel) {
this.baseUrl = baseUrl;
this.userId = userId;
this.session = session;
this.iceConnectionState = iceConnectionState;
this.nick = nick;
this.defaultGuestNick = defaultGuestNick;
this.mediaStream = mediaStream;
this.streamType = streamType;
this.streamEnabled = streamEnabled;
this.rootEglBase = rootEglBase;
this.updateUrlForAvatar();
this.session = callParticipantModel.getSessionId();
this.streamType = streamType;
this.callParticipantModel = callParticipantModel;
this.callParticipantModel.addObserver(callParticipantModelObserver, handler);
updateFromModel();
}
public void setUserId(String userId) {
this.userId = userId;
public void destroy() {
this.callParticipantModel.removeObserver(callParticipantModelObserver);
}
private void updateFromModel() {
userId = callParticipantModel.getUserId();
nick = callParticipantModel.getNick();
this.updateUrlForAvatar();
participantDisplayItemNotifier.notifyChange();
}
public boolean isConnected() {
return iceConnectionState == PeerConnection.IceConnectionState.CONNECTED ||
iceConnectionState == PeerConnection.IceConnectionState.COMPLETED;
}
public void setIceConnectionState(PeerConnection.IceConnectionState iceConnectionState) {
this.iceConnectionState = iceConnectionState;
participantDisplayItemNotifier.notifyChange();
}
public String getNick() {
if (TextUtils.isEmpty(userId) && TextUtils.isEmpty(nick)) {
return defaultGuestNick;
if ("screen".equals(streamType)) {
iceConnectionState = callParticipantModel.getScreenIceConnectionState();
mediaStream = callParticipantModel.getScreenMediaStream();
isAudioEnabled = true;
streamEnabled = true;
} else {
iceConnectionState = callParticipantModel.getIceConnectionState();
mediaStream = callParticipantModel.getMediaStream();
isAudioEnabled = callParticipantModel.isAudioAvailable() != null ?
callParticipantModel.isAudioAvailable() : false;
streamEnabled = callParticipantModel.isVideoAvailable() != null ?
callParticipantModel.isVideoAvailable() : false;
}
return nick;
}
public void setNick(String nick) {
this.nick = nick;
this.updateUrlForAvatar();
participantDisplayItemNotifier.notifyChange();
}
public String getUrlForAvatar() {
return urlForAvatar;
}
private void updateUrlForAvatar() {
if (!TextUtils.isEmpty(userId)) {
urlForAvatar = ApiUtils.getUrlForAvatar(baseUrl, userId, true);
@ -93,26 +93,31 @@ public class ParticipantDisplayItem {
}
}
public boolean isConnected() {
return iceConnectionState == PeerConnection.IceConnectionState.CONNECTED ||
iceConnectionState == PeerConnection.IceConnectionState.COMPLETED;
}
public String getNick() {
if (TextUtils.isEmpty(userId) && TextUtils.isEmpty(nick)) {
return defaultGuestNick;
}
return nick;
}
public String getUrlForAvatar() {
return urlForAvatar;
}
public MediaStream getMediaStream() {
return mediaStream;
}
public void setMediaStream(MediaStream mediaStream) {
this.mediaStream = mediaStream;
participantDisplayItemNotifier.notifyChange();
}
public boolean isStreamEnabled() {
return streamEnabled;
}
public void setStreamEnabled(boolean streamEnabled) {
this.streamEnabled = streamEnabled;
participantDisplayItemNotifier.notifyChange();
}
public EglBase getRootEglBase() {
return rootEglBase;
}
@ -121,12 +126,6 @@ public class ParticipantDisplayItem {
return isAudioEnabled;
}
public void setAudioEnabled(boolean audioEnabled) {
isAudioEnabled = audioEnabled;
participantDisplayItemNotifier.notifyChange();
}
public void addObserver(Observer observer) {
participantDisplayItemNotifier.addObserver(observer);
}