Browse Source

Merge pull request #2590 from nextcloud/add-observer-for-peer-connections

Add observer for peer connections
Tim Krüger 2 years ago
parent
commit
1b70006b5b

+ 178 - 132
app/src/main/java/com/nextcloud/talk/activities/CallActivity.java

@@ -63,9 +63,8 @@ import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.data.user.model.User;
 import com.nextcloud.talk.databinding.CallActivityBinding;
 import com.nextcloud.talk.events.ConfigurationChangeEvent;
-import com.nextcloud.talk.events.MediaStreamEvent;
 import com.nextcloud.talk.events.NetworkEvent;
-import com.nextcloud.talk.events.PeerConnectionEvent;
+import com.nextcloud.talk.events.ProximitySensorEvent;
 import com.nextcloud.talk.events.WebSocketCommunicationEvent;
 import com.nextcloud.talk.models.ExternalSignalingServer;
 import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
@@ -74,7 +73,6 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall;
 import com.nextcloud.talk.models.json.conversations.RoomsOverall;
 import com.nextcloud.talk.models.json.generic.GenericOverall;
 import com.nextcloud.talk.models.json.participants.Participant;
-import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
 import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
 import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
 import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
@@ -236,7 +234,7 @@ public class CallActivity extends CallBaseActivity {
     private MediaStream localStream;
     private String credentials;
     private List<PeerConnectionWrapper> peerConnectionWrapperList = new ArrayList<>();
-    private Map<String, Participant> participantMap = new HashMap<>();
+    private Map<String, String> userIdsBySessionId = new HashMap<>();
 
     private boolean videoOn = false;
     private boolean microphoneOn = false;
@@ -270,6 +268,8 @@ public class CallActivity extends CallBaseActivity {
 
     private Map<String, PeerConnectionWrapper.DataChannelMessageListener> dataChannelMessageListeners = new HashMap<>();
 
+    private Map<String, PeerConnectionWrapper.PeerConnectionObserver> peerConnectionObservers = new HashMap<>();
+
     private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener = new SignalingMessageReceiver.ParticipantListMessageListener() {
 
         @Override
@@ -1775,7 +1775,7 @@ public class CallActivity extends CallBaseActivity {
         Log.d(TAG, "processUsersInRoom");
         List<String> newSessions = new ArrayList<>();
         Set<String> oldSessions = new HashSet<>();
-        Map<String, String> userIdsBySessionId = new HashMap<>();
+        userIdsBySessionId = new HashMap<>();
 
         hasMCU = hasExternalSignalingServer && webSocketClient != null && webSocketClient.hasMCU();
         Log.d(TAG, "   hasMCU is " + hasMCU);
@@ -1844,10 +1844,6 @@ public class CallActivity extends CallBaseActivity {
             return;
         }
 
-        if (newSessions.size() > 0 && !hasMCU) {
-            getPeersForCall();
-        }
-
         if (hasMCU) {
             // Ensure that own publishing peer is set up.
             getOrCreatePeerConnectionWrapperForSessionIdAndType(webSocketClient.getSessionId(), VIDEO_STREAM_TYPE_VIDEO, true);
@@ -1858,15 +1854,22 @@ public class CallActivity extends CallBaseActivity {
             getOrCreatePeerConnectionWrapperForSessionIdAndType(sessionId, VIDEO_STREAM_TYPE_VIDEO, false);
 
             String userId = userIdsBySessionId.get(sessionId);
-
-            runOnUiThread(() -> {
-                setupVideoStreamForLayout(
-                    null,
-                    sessionId,
-                    userId,
-                    false,
-                    VIDEO_STREAM_TYPE_VIDEO);
-            });
+            if (userId != null) {
+                runOnUiThread(() -> {
+                    boolean notifyDataSetChanged = false;
+                    if (participantDisplayItems.get(sessionId + "-video") != null) {
+                        participantDisplayItems.get(sessionId + "-video").setUserId(userId);
+                        notifyDataSetChanged = true;
+                    }
+                    if (participantDisplayItems.get(sessionId + "-screen") != null) {
+                        participantDisplayItems.get(sessionId + "-screen").setUserId(userId);
+                        notifyDataSetChanged = true;
+                    }
+                    if (notifyDataSetChanged) {
+                        participantsAdapter.notifyDataSetChanged();
+                    }
+                });
+            }
         }
 
         if (newSessions.size() > 0 && currentCallStatus != CallStatus.IN_CONVERSATION) {
@@ -1879,43 +1882,6 @@ public class CallActivity extends CallBaseActivity {
         }
     }
 
-    private void getPeersForCall() {
-        Log.d(TAG, "getPeersForCall");
-        int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
-
-        ncApi.getPeersForCall(
-                credentials,
-                ApiUtils.getUrlForCall(
-                    apiVersion,
-                    baseUrl,
-                    roomToken))
-            .subscribeOn(Schedulers.io())
-            .subscribe(new Observer<ParticipantsOverall>() {
-                @Override
-                public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-                    // unused atm
-                }
-
-                @Override
-                public void onNext(@io.reactivex.annotations.NonNull ParticipantsOverall participantsOverall) {
-                    participantMap = new HashMap<>();
-                    for (Participant participant : participantsOverall.getOcs().getData()) {
-                        participantMap.put(participant.getSessionId(), participant);
-                    }
-                }
-
-                @Override
-                public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-                    Log.e(TAG, "error while executing getPeersForCall", e);
-                }
-
-                @Override
-                public void onComplete() {
-                    // unused atm
-                }
-            });
-    }
-
     private void deletePeerConnection(PeerConnectionWrapper peerConnectionWrapper) {
         peerConnectionWrapper.removePeerConnection();
         peerConnectionWrapperList.remove(peerConnectionWrapper);
@@ -2018,12 +1984,29 @@ public class CallActivity extends CallBaseActivity {
             }
 
             if (!publisher && !hasExternalSignalingServer && offerAnswerNickProviders.get(sessionId) == null) {
-                OfferAnswerNickProvider offerAnswerNickProvider = new OfferAnswerNickProvider();
+                OfferAnswerNickProvider offerAnswerNickProvider = new OfferAnswerNickProvider(sessionId);
                 offerAnswerNickProviders.put(sessionId, offerAnswerNickProvider);
                 signalingMessageReceiver.addListener(offerAnswerNickProvider.getVideoWebRtcMessageListener(), sessionId, "video");
                 signalingMessageReceiver.addListener(offerAnswerNickProvider.getScreenWebRtcMessageListener(), sessionId, "screen");
             }
 
+            PeerConnectionWrapper.PeerConnectionObserver peerConnectionObserver =
+                new CallActivityPeerConnectionObserver(sessionId, type);
+            peerConnectionObservers.put(sessionId + "-" + type, peerConnectionObserver);
+            peerConnectionWrapper.addObserver(peerConnectionObserver);
+
+            if (!publisher) {
+                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);
+                });
+            }
+
             if (publisher) {
                 startSendingNick();
             }
@@ -2054,6 +2037,9 @@ public class CallActivity extends CallBaseActivity {
                     }
                     String videoStreamType = peerConnectionWrapper.getVideoStreamType();
                     if (VIDEO_STREAM_TYPE_SCREEN.equals(videoStreamType) || !justScreen) {
+                        PeerConnectionWrapper.PeerConnectionObserver peerConnectionObserver = peerConnectionObservers.remove(sessionId + "-" + videoStreamType);
+                        peerConnectionWrapper.removeObserver(peerConnectionObserver);
+
                         runOnUiThread(() -> removeMediaStream(sessionId, videoStreamType));
                         deletePeerConnection(peerConnectionWrapper);
                     }
@@ -2138,48 +2124,37 @@ public class CallActivity extends CallBaseActivity {
     }
 
     @Subscribe(threadMode = ThreadMode.MAIN)
-    public void onMessageEvent(PeerConnectionEvent peerConnectionEvent) {
-        String sessionId = peerConnectionEvent.getSessionId();
-        String participantDisplayItemId = sessionId + "-" + peerConnectionEvent.getVideoStreamType();
-
-        if (peerConnectionEvent.getPeerConnectionEventType() ==
-            PeerConnectionEvent.PeerConnectionEventType.PEER_CONNECTED) {
-            if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
-                updateSelfVideoViewConnected(true);
-            } else if (participantDisplayItems.get(participantDisplayItemId) != null) {
-                participantDisplayItems.get(participantDisplayItemId).setConnected(true);
-                participantsAdapter.notifyDataSetChanged();
-            }
-        } else if (peerConnectionEvent.getPeerConnectionEventType() ==
-            PeerConnectionEvent.PeerConnectionEventType.PEER_DISCONNECTED) {
-            if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
-                updateSelfVideoViewConnected(false);
-            } else if (participantDisplayItems.get(participantDisplayItemId) != null) {
-                participantDisplayItems.get(participantDisplayItemId).setConnected(false);
-                participantsAdapter.notifyDataSetChanged();
+    public void onMessageEvent(ProximitySensorEvent proximitySensorEvent) {
+        if (!isVoiceOnlyCall) {
+            boolean enableVideo = proximitySensorEvent.getProximitySensorEventType() ==
+                ProximitySensorEvent.ProximitySensorEventType.SENSOR_FAR && videoOn;
+            if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA) &&
+                (currentCallStatus == CallStatus.CONNECTING || isConnectionEstablished()) && videoOn
+                && enableVideo != localVideoTrack.enabled()) {
+                toggleMedia(enableVideo, true);
             }
-        } else if (peerConnectionEvent.getPeerConnectionEventType() ==
-            PeerConnectionEvent.PeerConnectionEventType.PEER_CLOSED) {
-            endPeerConnection(sessionId, VIDEO_STREAM_TYPE_SCREEN.equals(peerConnectionEvent.getVideoStreamType()));
-        } else if (peerConnectionEvent.getPeerConnectionEventType() ==
-            PeerConnectionEvent.PeerConnectionEventType.SENSOR_FAR ||
-            peerConnectionEvent.getPeerConnectionEventType() ==
-                PeerConnectionEvent.PeerConnectionEventType.SENSOR_NEAR) {
+        }
+    }
 
-            if (!isVoiceOnlyCall) {
-                boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType() ==
-                    PeerConnectionEvent.PeerConnectionEventType.SENSOR_FAR && videoOn;
-                if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA) &&
-                    (currentCallStatus == CallStatus.CONNECTING || isConnectionEstablished()) && videoOn
-                    && enableVideo != localVideoTrack.enabled()) {
-                    toggleMedia(enableVideo, true);
-                }
-            }
-        } else if (peerConnectionEvent.getPeerConnectionEventType() ==
-            PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED) {
-            setCallState(CallStatus.PUBLISHER_FAILED);
-            webSocketClient.clearResumeId();
-            hangup(false);
+    private void handlePeerConnected(String sessionId, String videoStreamType) {
+        String participantDisplayItemId = sessionId + "-" + videoStreamType;
+
+        if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
+            updateSelfVideoViewConnected(true);
+        } else if (participantDisplayItems.get(participantDisplayItemId) != null) {
+            participantDisplayItems.get(participantDisplayItemId).setConnected(true);
+            participantsAdapter.notifyDataSetChanged();
+        }
+    }
+
+    private void handlePeerDisconnected(String sessionId, String videoStreamType) {
+        String participantDisplayItemId = sessionId + "-" + videoStreamType;
+
+        if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
+            updateSelfVideoViewConnected(false);
+        } else if (participantDisplayItems.get(participantDisplayItemId) != null) {
+            participantDisplayItems.get(participantDisplayItemId).setConnected(false);
+            participantsAdapter.notifyDataSetChanged();
         }
     }
 
@@ -2223,28 +2198,6 @@ public class CallActivity extends CallBaseActivity {
         }
     }
 
-    @Subscribe(threadMode = ThreadMode.MAIN)
-    public void onMessageEvent(MediaStreamEvent mediaStreamEvent) {
-        if (mediaStreamEvent.getMediaStream() != null) {
-            boolean hasAtLeastOneVideoStream = mediaStreamEvent.getMediaStream().videoTracks != null
-                && mediaStreamEvent.getMediaStream().videoTracks.size() > 0;
-
-            setupVideoStreamForLayout(
-                mediaStreamEvent.getMediaStream(),
-                mediaStreamEvent.getSession(),
-                null,
-                hasAtLeastOneVideoStream,
-                mediaStreamEvent.getVideoStreamType());
-        } else {
-            setupVideoStreamForLayout(
-                null,
-                mediaStreamEvent.getSession(),
-                null,
-                false,
-                mediaStreamEvent.getVideoStreamType());
-        }
-    }
-
     @Override
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                            @NonNull int[] grantResults) {
@@ -2256,7 +2209,6 @@ public class CallActivity extends CallBaseActivity {
 
     private void setupVideoStreamForLayout(@Nullable MediaStream mediaStream,
                                            String session,
-                                           String userId,
                                            boolean videoStreamEnabled,
                                            String videoStreamType) {
         PeerConnectionWrapper peerConnectionWrapper = getPeerConnectionWrapperForSessionIdAndType(session,
@@ -2276,20 +2228,12 @@ public class CallActivity extends CallBaseActivity {
             nick = offerAnswerNickProviders.get(session) != null ? offerAnswerNickProviders.get(session).getNick() : "";
         }
 
-        String userId4Usage = userId;
-
-        if (userId4Usage == null) {
-            if (hasMCU) {
-                userId4Usage = webSocketClient.getUserIdForSession(session);
-            } else if (participantMap.get(session) != null && participantMap.get(session).getCalculatedActorType() == Participant.ActorType.USERS) {
-                userId4Usage = participantMap.get(session).getCalculatedActorId();
-            }
-        }
+        String userId = userIdsBySessionId.get(session);
 
         String defaultGuestNick = getResources().getString(R.string.nc_nick_guest);
 
         ParticipantDisplayItem participantDisplayItem = new ParticipantDisplayItem(baseUrl,
-                                                                                   userId4Usage,
+                                                                                   userId,
                                                                                    session,
                                                                                    connected,
                                                                                    nick,
@@ -2570,18 +2514,18 @@ public class CallActivity extends CallBaseActivity {
         }
     }
 
-    private static class OfferAnswerNickProvider {
+    private class OfferAnswerNickProvider {
 
         private class WebRtcMessageListener implements SignalingMessageReceiver.WebRtcMessageListener {
 
             @Override
             public void onOffer(String sdp, String nick) {
-                (OfferAnswerNickProvider.this).nick = nick;
+                onOfferOrAnswer(nick);
             }
 
             @Override
             public void onAnswer(String sdp, String nick) {
-                (OfferAnswerNickProvider.this).nick = nick;
+                onOfferOrAnswer(nick);
             }
 
             @Override
@@ -2596,8 +2540,31 @@ public class CallActivity extends CallBaseActivity {
         private final WebRtcMessageListener videoWebRtcMessageListener = new WebRtcMessageListener();
         private final WebRtcMessageListener screenWebRtcMessageListener = new WebRtcMessageListener();
 
+        private final String sessionId;
+
         private String nick;
 
+        private OfferAnswerNickProvider(String sessionId) {
+            this.sessionId = sessionId;
+        }
+
+        private void onOfferOrAnswer(String nick) {
+            this.nick = nick;
+
+            boolean notifyDataSetChanged = false;
+            if (participantDisplayItems.get(sessionId + "-video") != null) {
+                participantDisplayItems.get(sessionId + "-video").setNick(nick);
+                notifyDataSetChanged = true;
+            }
+            if (participantDisplayItems.get(sessionId + "-screen") != null) {
+                participantDisplayItems.get(sessionId + "-screen").setNick(nick);
+                notifyDataSetChanged = true;
+            }
+            if (notifyDataSetChanged) {
+                participantsAdapter.notifyDataSetChanged();
+            }
+        }
+
         public WebRtcMessageListener getVideoWebRtcMessageListener() {
             return videoWebRtcMessageListener;
         }
@@ -2685,6 +2652,85 @@ public class CallActivity extends CallBaseActivity {
         }
     }
 
+    private class CallActivityPeerConnectionObserver implements PeerConnectionWrapper.PeerConnectionObserver {
+
+        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
+        public void onStreamAdded(MediaStream mediaStream) {
+            handleStream(mediaStream);
+        }
+
+        @Override
+        public void onStreamRemoved(MediaStream mediaStream) {
+            handleStream(null);
+        }
+
+        private void handleStream(MediaStream mediaStream) {
+            runOnUiThread(() -> {
+                if (participantDisplayItems.get(participantDisplayItemId) == null) {
+                    return;
+                }
+
+                boolean hasAtLeastOneVideoStream = false;
+                if (mediaStream != null) {
+                    hasAtLeastOneVideoStream = mediaStream.videoTracks != null && mediaStream.videoTracks.size() > 0;
+                }
+
+                ParticipantDisplayItem participantDisplayItem = participantDisplayItems.get(participantDisplayItemId);
+                participantDisplayItem.setMediaStream(mediaStream);
+                participantDisplayItem.setStreamEnabled(hasAtLeastOneVideoStream);
+                participantsAdapter.notifyDataSetChanged();
+            });
+        }
+
+        @Override
+        public void onIceConnectionStateChanged(PeerConnection.IceConnectionState iceConnectionState) {
+            runOnUiThread(() -> {
+                if (iceConnectionState == PeerConnection.IceConnectionState.CONNECTED ||
+                        iceConnectionState == PeerConnection.IceConnectionState.COMPLETED) {
+                    handlePeerConnected(sessionId, videoStreamType);
+
+                    return;
+                }
+
+                if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED ||
+                        iceConnectionState == PeerConnection.IceConnectionState.NEW ||
+                        iceConnectionState == PeerConnection.IceConnectionState.CHECKING) {
+                    handlePeerDisconnected(sessionId, videoStreamType);
+
+                    return;
+                }
+
+                if (iceConnectionState == PeerConnection.IceConnectionState.CLOSED) {
+                    endPeerConnection(sessionId, VIDEO_STREAM_TYPE_SCREEN.equals(videoStreamType));
+
+                    return;
+                }
+
+                if (iceConnectionState == PeerConnection.IceConnectionState.FAILED) {
+                    if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
+                        setCallState(CallStatus.PUBLISHER_FAILED);
+                        webSocketClient.clearResumeId();
+                        hangup(false);
+                    } else {
+                        handlePeerDisconnected(sessionId, videoStreamType);
+                    }
+
+                    return;
+                }
+            });
+        }
+    }
+
     private class InternalSignalingMessageSender implements SignalingMessageSender {
 
         @Override

+ 0 - 96
app/src/main/java/com/nextcloud/talk/events/MediaStreamEvent.java

@@ -1,96 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * Copyright (C) 2017 Mario Danic <mario@lovelyhq.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.events;
-
-import org.webrtc.MediaStream;
-
-import androidx.annotation.Nullable;
-
-public class MediaStreamEvent {
-    private final MediaStream mediaStream;
-    private final String session;
-    private final String videoStreamType;
-
-    public MediaStreamEvent(@Nullable MediaStream mediaStream, String session, String videoStreamType) {
-        this.mediaStream = mediaStream;
-        this.session = session;
-        this.videoStreamType = videoStreamType;
-    }
-
-    public MediaStream getMediaStream() {
-        return this.mediaStream;
-    }
-
-    public String getSession() {
-        return this.session;
-    }
-
-    public String getVideoStreamType() {
-        return this.videoStreamType;
-    }
-
-    public boolean equals(final Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (!(o instanceof MediaStreamEvent)) {
-            return false;
-        }
-        final MediaStreamEvent other = (MediaStreamEvent) o;
-        if (!other.canEqual((Object) this)) {
-            return false;
-        }
-        final Object this$mediaStream = this.getMediaStream();
-        final Object other$mediaStream = other.getMediaStream();
-        if (this$mediaStream == null ? other$mediaStream != null : !this$mediaStream.equals(other$mediaStream)) {
-            return false;
-        }
-        final Object this$session = this.getSession();
-        final Object other$session = other.getSession();
-        if (this$session == null ? other$session != null : !this$session.equals(other$session)) {
-            return false;
-        }
-        final Object this$videoStreamType = this.getVideoStreamType();
-        final Object other$videoStreamType = other.getVideoStreamType();
-
-        return this$videoStreamType == null ? other$videoStreamType == null : this$videoStreamType.equals(other$videoStreamType);
-    }
-
-    protected boolean canEqual(final Object other) {
-        return other instanceof MediaStreamEvent;
-    }
-
-    public int hashCode() {
-        final int PRIME = 59;
-        int result = 1;
-        final Object $mediaStream = this.getMediaStream();
-        result = result * PRIME + ($mediaStream == null ? 43 : $mediaStream.hashCode());
-        final Object $session = this.getSession();
-        result = result * PRIME + ($session == null ? 43 : $session.hashCode());
-        final Object $videoStreamType = this.getVideoStreamType();
-        result = result * PRIME + ($videoStreamType == null ? 43 : $videoStreamType.hashCode());
-        return result;
-    }
-
-    public String toString() {
-        return "MediaStreamEvent(mediaStream=" + this.getMediaStream() + ", session=" + this.getSession() + ", videoStreamType=" + this.getVideoStreamType() + ")";
-    }
-}

+ 0 - 125
app/src/main/java/com/nextcloud/talk/events/PeerConnectionEvent.java

@@ -1,125 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * Copyright (C) 2017 Mario Danic <mario@lovelyhq.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.events;
-
-import androidx.annotation.Nullable;
-
-public class PeerConnectionEvent {
-    private final PeerConnectionEventType peerConnectionEventType;
-    private final String sessionId;
-    private final String nick;
-    private final Boolean changeValue;
-    private final String videoStreamType;
-
-    public PeerConnectionEvent(PeerConnectionEventType peerConnectionEventType, @Nullable String sessionId,
-                               @Nullable String nick, Boolean changeValue, @Nullable String videoStreamType) {
-        this.peerConnectionEventType = peerConnectionEventType;
-        this.nick = nick;
-        this.changeValue = changeValue;
-        this.sessionId = sessionId;
-        this.videoStreamType = videoStreamType;
-    }
-
-    public PeerConnectionEventType getPeerConnectionEventType() {
-        return this.peerConnectionEventType;
-    }
-
-    public String getSessionId() {
-        return this.sessionId;
-    }
-
-    public String getNick() {
-        return this.nick;
-    }
-
-    public Boolean getChangeValue() {
-        return this.changeValue;
-    }
-
-    public String getVideoStreamType() {
-        return this.videoStreamType;
-    }
-
-    public boolean equals(final Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (!(o instanceof PeerConnectionEvent)) {
-            return false;
-        }
-        final PeerConnectionEvent other = (PeerConnectionEvent) o;
-        if (!other.canEqual((Object) this)) {
-            return false;
-        }
-        final Object this$peerConnectionEventType = this.getPeerConnectionEventType();
-        final Object other$peerConnectionEventType = other.getPeerConnectionEventType();
-        if (this$peerConnectionEventType == null ? other$peerConnectionEventType != null : !this$peerConnectionEventType.equals(other$peerConnectionEventType)) {
-            return false;
-        }
-        final Object this$sessionId = this.getSessionId();
-        final Object other$sessionId = other.getSessionId();
-        if (this$sessionId == null ? other$sessionId != null : !this$sessionId.equals(other$sessionId)) {
-            return false;
-        }
-        final Object this$nick = this.getNick();
-        final Object other$nick = other.getNick();
-        if (this$nick == null ? other$nick != null : !this$nick.equals(other$nick)) {
-            return false;
-        }
-        final Object this$changeValue = this.getChangeValue();
-        final Object other$changeValue = other.getChangeValue();
-        if (this$changeValue == null ? other$changeValue != null : !this$changeValue.equals(other$changeValue)) {
-            return false;
-        }
-        final Object this$videoStreamType = this.getVideoStreamType();
-        final Object other$videoStreamType = other.getVideoStreamType();
-
-        return this$videoStreamType == null ? other$videoStreamType == null : this$videoStreamType.equals(other$videoStreamType);
-    }
-
-    protected boolean canEqual(final Object other) {
-        return other instanceof PeerConnectionEvent;
-    }
-
-    public int hashCode() {
-        final int PRIME = 59;
-        int result = 1;
-        final Object $peerConnectionEventType = this.getPeerConnectionEventType();
-        result = result * PRIME + ($peerConnectionEventType == null ? 43 : $peerConnectionEventType.hashCode());
-        final Object $sessionId = this.getSessionId();
-        result = result * PRIME + ($sessionId == null ? 43 : $sessionId.hashCode());
-        final Object $nick = this.getNick();
-        result = result * PRIME + ($nick == null ? 43 : $nick.hashCode());
-        final Object $changeValue = this.getChangeValue();
-        result = result * PRIME + ($changeValue == null ? 43 : $changeValue.hashCode());
-        final Object $videoStreamType = this.getVideoStreamType();
-        result = result * PRIME + ($videoStreamType == null ? 43 : $videoStreamType.hashCode());
-        return result;
-    }
-
-    public String toString() {
-        return "PeerConnectionEvent(peerConnectionEventType=" + this.getPeerConnectionEventType() + ", sessionId=" + this.getSessionId() + ", nick=" + this.getNick() + ", changeValue=" + this.getChangeValue() + ", videoStreamType=" + this.getVideoStreamType() + ")";
-    }
-
-    public enum PeerConnectionEventType {
-        PEER_CONNECTED, PEER_DISCONNECTED, PEER_CLOSED, SENSOR_FAR, SENSOR_NEAR, PUBLISHER_FAILED
-    }
-}

+ 73 - 0
app/src/main/java/com/nextcloud/talk/events/ProximitySensorEvent.java

@@ -0,0 +1,73 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic <mario@lovelyhq.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.events;
+
+public class ProximitySensorEvent {
+    private final ProximitySensorEventType proximitySensorEventType;
+
+    public ProximitySensorEvent(ProximitySensorEventType proximitySensorEventType) {
+        this.proximitySensorEventType = proximitySensorEventType;
+    }
+
+    public ProximitySensorEventType getProximitySensorEventType() {
+        return this.proximitySensorEventType;
+    }
+
+    public boolean equals(final Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (!(o instanceof ProximitySensorEvent)) {
+            return false;
+        }
+        final ProximitySensorEvent other = (ProximitySensorEvent) o;
+        if (!other.canEqual((Object) this)) {
+            return false;
+        }
+        final Object this$proximitySensorEventType = this.getProximitySensorEventType();
+        final Object other$proximitySensorEventType = other.getProximitySensorEventType();
+        if (this$proximitySensorEventType == null ? other$proximitySensorEventType != null : !this$proximitySensorEventType.equals(other$proximitySensorEventType)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    protected boolean canEqual(final Object other) {
+        return other instanceof ProximitySensorEvent;
+    }
+
+    public int hashCode() {
+        final int PRIME = 59;
+        int result = 1;
+        final Object $proximitySensorEventType = this.getProximitySensorEventType();
+        result = result * PRIME + ($proximitySensorEventType == null ? 43 : $proximitySensorEventType.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "ProximitySensorEvent(proximitySensorEventType=" + this.getProximitySensorEventType() + ")";
+    }
+
+    public enum ProximitySensorEventType {
+        SENSOR_FAR, SENSOR_NEAR
+    }
+}

+ 0 - 11
app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.java

@@ -386,17 +386,6 @@ public class MagicWebSocketInstance extends WebSocketListener {
         return "";
     }
 
-    public String getUserIdForSession(String session) {
-        Participant participant = usersHashMap.get(session);
-        if (participant != null) {
-            if (participant.getCalculatedActorType() == USERS) {
-                return participant.getCalculatedActorId();
-            }
-        }
-
-        return "";
-    }
-
     @Subscribe(threadMode = ThreadMode.BACKGROUND)
     public void onMessageEvent(NetworkEvent networkEvent) {
         if (networkEvent.getNetworkConnectionEvent() == NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED && !isConnected()) {

+ 68 - 0
app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionNotifier.java

@@ -0,0 +1,68 @@
+/*
+ * 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.webrtc;
+
+import org.webrtc.MediaStream;
+import org.webrtc.PeerConnection;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Helper class to register and notify PeerConnectionObserver.
+ *
+ * This class is only meant for internal use by PeerConnectionWrapper; observers must register themselves against
+ * a PeerConnectionWrapper rather than against a PeerConnectionNotifier.
+ */
+public class PeerConnectionNotifier {
+
+    private final Set<PeerConnectionWrapper.PeerConnectionObserver> peerConnectionObservers = new LinkedHashSet<>();
+
+    public synchronized void addObserver(PeerConnectionWrapper.PeerConnectionObserver observer) {
+        if (observer == null) {
+            throw new IllegalArgumentException("PeerConnectionObserver can not be null");
+        }
+
+        peerConnectionObservers.add(observer);
+    }
+
+    public synchronized void removeObserver(PeerConnectionWrapper.PeerConnectionObserver observer) {
+        peerConnectionObservers.remove(observer);
+    }
+
+    public synchronized void notifyStreamAdded(MediaStream stream) {
+        for (PeerConnectionWrapper.PeerConnectionObserver observer : new ArrayList<>(peerConnectionObservers)) {
+            observer.onStreamAdded(stream);
+        }
+    }
+
+    public synchronized void notifyStreamRemoved(MediaStream stream) {
+        for (PeerConnectionWrapper.PeerConnectionObserver observer : new ArrayList<>(peerConnectionObservers)) {
+            observer.onStreamRemoved(stream);
+        }
+    }
+
+    public synchronized void notifyIceConnectionStateChanged(PeerConnection.IceConnectionState state) {
+        for (PeerConnectionWrapper.PeerConnectionObserver observer : new ArrayList<>(peerConnectionObservers)) {
+            observer.onIceConnectionStateChanged(state);
+        }
+    }
+}

+ 36 - 32
app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionWrapper.java

@@ -28,8 +28,6 @@ import android.util.Log;
 
 import com.bluelinelabs.logansquare.LoganSquare;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
-import com.nextcloud.talk.events.MediaStreamEvent;
-import com.nextcloud.talk.events.PeerConnectionEvent;
 import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
 import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
 import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
@@ -37,7 +35,6 @@ import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
 import com.nextcloud.talk.signaling.SignalingMessageReceiver;
 import com.nextcloud.talk.signaling.SignalingMessageSender;
 
-import org.greenrobot.eventbus.EventBus;
 import org.webrtc.AudioTrack;
 import org.webrtc.DataChannel;
 import org.webrtc.IceCandidate;
@@ -85,6 +82,21 @@ public class PeerConnectionWrapper {
         void onNickChanged(String nick);
     }
 
+    /**
+     * Observer for changes on the peer connection.
+     *
+     * The changes are bound to a specific peer connection, so each observer is expected to handle messages only for
+     * a single peer connection.
+     *
+     * All methods are called on the so called "signaling" thread of WebRTC, which is an internal thread created by the
+     * WebRTC library and NOT the same thread where signaling messages are received.
+     */
+    public interface PeerConnectionObserver {
+        void onStreamAdded(MediaStream mediaStream);
+        void onStreamRemoved(MediaStream mediaStream);
+        void onIceConnectionStateChanged(PeerConnection.IceConnectionState iceConnectionState);
+    }
+
     private static final String TAG = PeerConnectionWrapper.class.getCanonicalName();
 
     private final SignalingMessageReceiver signalingMessageReceiver;
@@ -94,13 +106,14 @@ public class PeerConnectionWrapper {
 
     private final DataChannelMessageNotifier dataChannelMessageNotifier = new DataChannelMessageNotifier();
 
+    private final PeerConnectionNotifier peerConnectionNotifier = new PeerConnectionNotifier();
+
     private List<IceCandidate> iceCandidates = new ArrayList<>();
     private PeerConnection peerConnection;
     private String sessionId;
     private final MediaConstraints mediaConstraints;
     private DataChannel dataChannel;
     private final MagicSdpObserver magicSdpObserver;
-    private MediaStream remoteStream;
 
     private final boolean hasInitiated;
 
@@ -187,6 +200,21 @@ public class PeerConnectionWrapper {
         dataChannelMessageNotifier.removeListener(listener);
     }
 
+    /**
+     * Adds an observer for peer connection changes.
+     *
+     * An observer is expected to be added only once. If the same observer is added again it will be notified just once.
+     *
+     * @param observer the PeerConnectionObserver
+     */
+    public void addObserver(PeerConnectionObserver observer) {
+        peerConnectionNotifier.addObserver(observer);
+    }
+
+    public void removeObserver(PeerConnectionObserver observer) {
+        peerConnectionNotifier.removeObserver(observer);
+    }
+
     public String getVideoStreamType() {
         return videoStreamType;
     }
@@ -413,34 +441,12 @@ public class PeerConnectionWrapper {
 
             Log.d("iceConnectionChangeTo: ", iceConnectionState.name() + " over " + peerConnection.hashCode() + " " + sessionId);
             if (iceConnectionState == PeerConnection.IceConnectionState.CONNECTED) {
-                EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.PEER_CONNECTED,
-                                                                   sessionId, null, null, videoStreamType));
-
-                if (!isMCUPublisher) {
-                    EventBus.getDefault().post(new MediaStreamEvent(remoteStream, sessionId, videoStreamType));
-                }
-
                 if (hasInitiated) {
                     sendInitialMediaStatus();
                 }
-            } else if (iceConnectionState == PeerConnection.IceConnectionState.COMPLETED) {
-                EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.PEER_CONNECTED,
-                                                                   sessionId, null, null, videoStreamType));
-            } else if (iceConnectionState == PeerConnection.IceConnectionState.CLOSED) {
-                EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
-                        .PEER_CLOSED, sessionId, null, null, videoStreamType));
-            } else if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED ||
-                    iceConnectionState == PeerConnection.IceConnectionState.NEW ||
-                    iceConnectionState == PeerConnection.IceConnectionState.CHECKING) {
-                EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.PEER_DISCONNECTED,
-                                                                   sessionId, null, null, videoStreamType));
-            } else if (iceConnectionState == PeerConnection.IceConnectionState.FAILED) {
-                EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.PEER_DISCONNECTED,
-                                                                   sessionId, null, null, videoStreamType));
-                if (isMCUPublisher) {
-                    EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED, sessionId, null, null, videoStreamType));
-                }
             }
+
+            peerConnectionNotifier.notifyIceConnectionStateChanged(iceConnectionState);
         }
 
         @Override
@@ -477,14 +483,12 @@ public class PeerConnectionWrapper {
 
         @Override
         public void onAddStream(MediaStream mediaStream) {
-            remoteStream = mediaStream;
+            peerConnectionNotifier.notifyStreamAdded(mediaStream);
         }
 
         @Override
         public void onRemoveStream(MediaStream mediaStream) {
-            if (!isMCUPublisher) {
-                EventBus.getDefault().post(new MediaStreamEvent(null, sessionId, videoStreamType));
-            }
+            peerConnectionNotifier.notifyStreamRemoved(mediaStream);
         }
 
         @Override

+ 3 - 5
app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManager.java

@@ -43,7 +43,7 @@ import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.util.Log;
 
-import com.nextcloud.talk.events.PeerConnectionEvent;
+import com.nextcloud.talk.events.ProximitySensorEvent;
 import com.nextcloud.talk.utils.power.PowerManagerUtils;
 
 import org.greenrobot.eventbus.EventBus;
@@ -136,15 +136,13 @@ public class WebRtcAudioManager {
                 setAudioDeviceInternal(AudioDevice.EARPIECE);
                 Log.d(TAG, "switched to EARPIECE because userSelectedAudioDevice was SPEAKER_PHONE and proximity=near");
 
-                EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
-                                                                       .SENSOR_NEAR, null, null, null, null));
+                EventBus.getDefault().post(new ProximitySensorEvent(ProximitySensorEvent.ProximitySensorEventType.SENSOR_NEAR));
 
             } else {
                 setAudioDeviceInternal(WebRtcAudioManager.AudioDevice.SPEAKER_PHONE);
                 Log.d(TAG, "switched to SPEAKER_PHONE because userSelectedAudioDevice was SPEAKER_PHONE and proximity=far");
 
-                EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
-                                                                       .SENSOR_FAR, null, null, null, null));
+                EventBus.getDefault().post(new ProximitySensorEvent(ProximitySensorEvent.ProximitySensorEventType.SENSOR_FAR));
             }
         }
     }