diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java index bfcf198d0..2402579e6 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java +++ b/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 peerConnectionWrapperList = new ArrayList<>(); - private Map participantMap = new HashMap<>(); + private Map userIdsBySessionId = new HashMap<>(); private boolean videoOn = false; private boolean microphoneOn = false; @@ -270,6 +268,8 @@ public class CallActivity extends CallBaseActivity { private Map dataChannelMessageListeners = new HashMap<>(); + private Map 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 newSessions = new ArrayList<>(); Set oldSessions = new HashSet<>(); - Map 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() { - @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(); + 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); + } + } + } - 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(); - } - } 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) { + private void handlePeerConnected(String sessionId, String videoStreamType) { + String participantDisplayItemId = sessionId + "-" + videoStreamType; - 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); + 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 diff --git a/app/src/main/java/com/nextcloud/talk/events/MediaStreamEvent.java b/app/src/main/java/com/nextcloud/talk/events/MediaStreamEvent.java deleted file mode 100644 index 9db02e2b4..000000000 --- a/app/src/main/java/com/nextcloud/talk/events/MediaStreamEvent.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic - * - * 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 . - */ - -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() + ")"; - } -} diff --git a/app/src/main/java/com/nextcloud/talk/events/PeerConnectionEvent.java b/app/src/main/java/com/nextcloud/talk/events/PeerConnectionEvent.java deleted file mode 100644 index fd10c30ce..000000000 --- a/app/src/main/java/com/nextcloud/talk/events/PeerConnectionEvent.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic - * - * 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 . - */ - -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 - } -} diff --git a/app/src/main/java/com/nextcloud/talk/events/ProximitySensorEvent.java b/app/src/main/java/com/nextcloud/talk/events/ProximitySensorEvent.java new file mode 100644 index 000000000..9864db731 --- /dev/null +++ b/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 + * + * 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 . + */ + +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 + } +} diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.java b/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.java index 49df4b00e..da98e27f3 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.java +++ b/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()) { diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionNotifier.java b/app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionNotifier.java new file mode 100644 index 000000000..d2a9db207 --- /dev/null +++ b/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 + * + * 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 . + */ +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 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); + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionWrapper.java b/app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionWrapper.java index c5b2f66b2..f4a7d6b7e 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionWrapper.java +++ b/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 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 diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManager.java b/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManager.java index dd0818a4f..f7e36034e 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManager.java +++ b/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)); } } }