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

Add observer for peer connections
This commit is contained in:
Tim Krüger 2022-12-29 11:32:13 +01:00 committed by GitHub
commit 1b70006b5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 358 additions and 401 deletions

View File

@ -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();
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

View File

@ -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() + ")";
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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()) {

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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));
}
}
}