Improve reconnections & Avatar handling

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-06-06 12:33:12 +02:00
parent 9fff869d9a
commit 6e68f41d1f
5 changed files with 241 additions and 141 deletions

View File

@ -73,6 +73,7 @@ import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.power.PowerManagerUtils; import com.nextcloud.talk.utils.power.PowerManagerUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences; import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder; import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder;
import com.nextcloud.talk.utils.singletons.MerlinTheWizard;
import com.nextcloud.talk.webrtc.*; import com.nextcloud.talk.webrtc.*;
import com.wooplr.spotlight.SpotlightView; import com.wooplr.spotlight.SpotlightView;
import io.reactivex.Observable; import io.reactivex.Observable;
@ -88,6 +89,7 @@ import org.apache.commons.lang3.StringEscapeUtils;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
import org.parceler.Parcel;
import org.webrtc.*; import org.webrtc.*;
import pub.devrel.easypermissions.AfterPermissionGranted; import pub.devrel.easypermissions.AfterPermissionGranted;
@ -165,7 +167,7 @@ public class CallController extends BaseController {
private VideoCapturer videoCapturer; private VideoCapturer videoCapturer;
private EglBase rootEglBase; private EglBase rootEglBase;
private boolean leavingCall = false; private boolean leavingCall = false;
private boolean inCall = false; private boolean connectedToCall = false;
private Disposable signalingDisposable; private Disposable signalingDisposable;
private Disposable pingDisposable; private Disposable pingDisposable;
private List<PeerConnection.IceServer> iceServers; private List<PeerConnection.IceServer> iceServers;
@ -199,7 +201,7 @@ public class CallController extends BaseController {
private SpotlightView spotlightView; private SpotlightView spotlightView;
private ExternalSignalingServer externalSignalingServer; private ExternalSignalingServer externalSignalingServer;
private MagicWebSocketInstance webSocketClient ; private MagicWebSocketInstance webSocketClient;
private WebSocketConnectionHelper webSocketConnectionHelper; private WebSocketConnectionHelper webSocketConnectionHelper;
private boolean hasMCU; private boolean hasMCU;
private boolean hasExternalSignalingServer; private boolean hasExternalSignalingServer;
@ -207,6 +209,15 @@ public class CallController extends BaseController {
private PowerManagerUtils powerManagerUtils; private PowerManagerUtils powerManagerUtils;
private Handler handler;
private CallStatus currentCallStatus;
@Parcel
public enum CallStatus {
CALLING, CALLING_TIMEOUT, ESTABLISHED, RECONNECTING, OFFLINE
}
public CallController(Bundle args) { public CallController(Bundle args) {
super(args); super(args);
NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
@ -227,6 +238,7 @@ public class CallController extends BaseController {
isFromNotification = TextUtils.isEmpty(roomToken); isFromNotification = TextUtils.isEmpty(roomToken);
powerManagerUtils = new PowerManagerUtils(); powerManagerUtils = new PowerManagerUtils();
currentCallStatus = CallStatus.CALLING;
} }
@Override @Override
@ -273,13 +285,9 @@ public class CallController extends BaseController {
callControls.setZ(100.0f); callControls.setZ(100.0f);
basicInitialization(); basicInitialization();
initViews();
if (isFromNotification) { initiateCall();
handleFromNotification();
} else {
initViews();
checkPermissions();
}
} }
private void basicInitialization() { private void basicInitialization() {
@ -357,7 +365,6 @@ public class CallController extends BaseController {
} }
} }
initViews();
checkPermissions(); checkPermissions();
} }
@ -427,7 +434,7 @@ public class CallController extends BaseController {
} }
} }
if (!inCall) { if (!connectedToCall) {
fetchSignalingSettings(); fetchSignalingSettings();
} }
} else if (getActivity() != null && EffortlessPermissions.somePermissionPermanentlyDenied(getActivity(), } else if (getActivity() != null && EffortlessPermissions.somePermissionPermanentlyDenied(getActivity(),
@ -466,7 +473,7 @@ public class CallController extends BaseController {
microphoneControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); microphoneControlButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px);
} }
if (!inCall) { if (!connectedToCall) {
fetchSignalingSettings(); fetchSignalingSettings();
} }
} }
@ -484,7 +491,7 @@ public class CallController extends BaseController {
if (getActivity() != null && (EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA) || if (getActivity() != null && (EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA) ||
EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_MICROPHONE))) { EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_MICROPHONE))) {
checkIfSomeAreApproved(); checkIfSomeAreApproved();
} else if (!inCall) { } else if (!connectedToCall) {
fetchSignalingSettings(); fetchSignalingSettings();
} }
} }
@ -634,7 +641,7 @@ public class CallController extends BaseController {
toggleMedia(true, false); toggleMedia(true, false);
} }
if (isVoiceOnlyCall && !inCall) { if (isVoiceOnlyCall && !connectedToCall) {
fetchSignalingSettings(); fetchSignalingSettings();
} }
@ -656,7 +663,7 @@ public class CallController extends BaseController {
@OnClick(R.id.callControlHangupView) @OnClick(R.id.callControlHangupView)
void onHangupClick() { void onHangupClick() {
hangup(false); hangup(true);
} }
@OnClick(R.id.call_control_camera) @OnClick(R.id.call_control_camera)
@ -751,7 +758,7 @@ public class CallController extends BaseController {
} }
} }
if (inCall) { if (connectedToCall) {
if (!hasMCU) { if (!hasMCU) {
for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) { for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) {
magicPeerConnectionWrapperList.get(i).sendChannelData(new DataChannelMessage(message)); magicPeerConnectionWrapperList.get(i).sendChannelData(new DataChannelMessage(message));
@ -1056,7 +1063,8 @@ public class CallController extends BaseController {
@Override @Override
public void onNext(GenericOverall genericOverall) { public void onNext(GenericOverall genericOverall) {
inCall = true; connectedToCall = true;
currentCallStatus = CallStatus.CALLING;
if (connectingView != null) { if (connectingView != null) {
connectingView.setVisibility(View.GONE); connectingView.setVisibility(View.GONE);
@ -1077,8 +1085,8 @@ public class CallController extends BaseController {
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.repeatWhen(observable -> observable.delay(5000, TimeUnit.MILLISECONDS)) .repeatWhen(observable -> observable.delay(5000, TimeUnit.MILLISECONDS))
.takeWhile(observable -> inCall) .takeWhile(observable -> connectedToCall)
.retry(3, observable -> inCall) .retry(3, observable -> connectedToCall)
.subscribe(new Observer<GenericOverall>() { .subscribe(new Observer<GenericOverall>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -1110,7 +1118,7 @@ public class CallController extends BaseController {
if (!conversationUser.hasSpreedCapabilityWithName("no-ping") && !TextUtils.isEmpty(roomId)) { if (!conversationUser.hasSpreedCapabilityWithName("no-ping") && !TextUtils.isEmpty(roomId)) {
NotificationUtils.cancelExistingNotifications(getApplicationContext(), conversationUser, roomId); NotificationUtils.cancelExistingNotifications(getApplicationContext(), conversationUser, roomId);
} else if (!TextUtils.isEmpty(roomToken)){ } else if (!TextUtils.isEmpty(roomToken)) {
NotificationUtils.cancelExistingNotifications(getApplicationContext(), conversationUser, roomToken); NotificationUtils.cancelExistingNotifications(getApplicationContext(), conversationUser, roomToken);
} }
@ -1119,8 +1127,8 @@ public class CallController extends BaseController {
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.repeatWhen(observable -> observable) .repeatWhen(observable -> observable)
.takeWhile(observable -> inCall) .takeWhile(observable -> connectedToCall)
.retry(3, observable -> inCall) .retry(3, observable -> connectedToCall)
.subscribe(new Observer<SignalingOverall>() { .subscribe(new Observer<SignalingOverall>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -1177,11 +1185,24 @@ public class CallController extends BaseController {
joinRoomAndCall(); joinRoomAndCall();
} }
private void initiateCall() {
if (!TextUtils.isEmpty(roomToken)) {
checkPermissions();
} else {
handleFromNotification();
}
}
@Subscribe(threadMode = ThreadMode.BACKGROUND) @Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEvent(WebSocketCommunicationEvent webSocketCommunicationEvent) { public void onMessageEvent(WebSocketCommunicationEvent webSocketCommunicationEvent) {
switch (webSocketCommunicationEvent.getType()) { switch (webSocketCommunicationEvent.getType()) {
case "hello": case "hello":
joinRoomAndCall(); if (!currentCallStatus.equals(CallStatus.RECONNECTING)) {
if (!webSocketCommunicationEvent.getHashMap().containsKey("oldResumeId")) {
initiateCall();
} else {
// do nothing, let's just continue
}
}
break; break;
case "roomJoined": case "roomJoined":
if (hasExternalSignalingServer) { if (hasExternalSignalingServer) {
@ -1300,104 +1321,105 @@ public class CallController extends BaseController {
} }
} }
private void hangup(boolean dueToNetworkChange) { private void hangup(boolean shutDownView) {
leavingCall = true; leavingCall = true;
inCall = false; connectedToCall = false;
if (videoCapturer != null) { if (shutDownView) {
try { if (videoCapturer != null) {
videoCapturer.stopCapture(); try {
} catch (InterruptedException e) { videoCapturer.stopCapture();
Log.e(TAG, "Failed to stop capturing while hanging up"); } catch (InterruptedException e) {
Log.e(TAG, "Failed to stop capturing while hanging up");
}
videoCapturer.dispose();
videoCapturer = null;
}
if (pipVideoView != null) {
pipVideoView.release();
}
if (audioSource != null) {
audioSource.dispose();
audioSource = null;
}
if (audioManager != null) {
audioManager.stop();
audioManager = null;
}
if (videoSource != null) {
videoSource = null;
}
if (peerConnectionFactory != null) {
peerConnectionFactory = null;
}
localMediaStream = null;
localAudioTrack = null;
localVideoTrack = null;
if (TextUtils.isEmpty(credentials) && hasExternalSignalingServer) {
WebSocketConnectionHelper.deleteExternalSignalingInstanceForUserEntity(-1);
} }
videoCapturer.dispose();
videoCapturer = null;
} }
for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) { for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) {
endPeerConnection(magicPeerConnectionWrapperList.get(i).getSessionId(), false); endPeerConnection(magicPeerConnectionWrapperList.get(i).getSessionId(), false);
} }
if (pipVideoView != null) { hangupNetworkCalls(shutDownView);
pipVideoView.release();
}
if (audioSource != null) {
audioSource.dispose();
audioSource = null;
}
if (audioManager != null) {
audioManager.stop();
audioManager = null;
}
if (videoSource != null) {
videoSource = null;
}
if (peerConnectionFactory != null) {
peerConnectionFactory = null;
}
localMediaStream = null;
localAudioTrack = null;
localVideoTrack = null;
if (TextUtils.isEmpty(credentials) && hasExternalSignalingServer) {
WebSocketConnectionHelper.deleteExternalSignalingInstanceForUserEntity(-1);
}
if (!dueToNetworkChange) {
hangupNetworkCalls();
} else {
if (getActivity() != null) {
getActivity().finish();
}
}
} }
private void hangupNetworkCalls() { private void hangupNetworkCalls(boolean shutDownView) {
ncApi.leaveCall(credentials, ApiUtils.getUrlForCall(baseUrl, roomToken)) if (MerlinTheWizard.isConnectedToInternet()) {
.subscribeOn(Schedulers.io()) ncApi.leaveCall(credentials, ApiUtils.getUrlForCall(baseUrl, roomToken))
.observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io())
.subscribe(new Observer<GenericOverall>() { .observeOn(AndroidSchedulers.mainThread())
@Override .subscribe(new Observer<GenericOverall>() {
public void onSubscribe(Disposable d) { @Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(GenericOverall genericOverall) {
if (hasExternalSignalingServer) {
webSocketClient.joinRoomWithRoomTokenAndSession("", callSession);
} }
if (isMultiSession) { @Override
if (getActivity() != null) { public void onNext(GenericOverall genericOverall) {
getActivity().finish(); if (!TextUtils.isEmpty(credentials) && hasExternalSignalingServer) {
webSocketClient.joinRoomWithRoomTokenAndSession("", callSession);
}
if (isMultiSession) {
if (shutDownView && getActivity() != null) {
getActivity().finish();
} else if (!shutDownView && currentCallStatus.equals(CallStatus.RECONNECTING)) {
initiateCall();
}
} else {
leaveRoom(shutDownView);
} }
} else {
leaveRoom();
} }
}
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
} }
@Override @Override
public void onComplete() { public void onComplete() {
} }
}); });
} else if (shutDownView && getActivity() != null) {
getActivity().finish();
}
} }
private void leaveRoom() { private void leaveRoom(boolean shutDownView) {
ncApi.leaveRoom(credentials, ApiUtils.getUrlForSettingMyselfAsActiveParticipant(baseUrl, roomToken)) ncApi.leaveRoom(credentials, ApiUtils.getUrlForSettingMyselfAsActiveParticipant(baseUrl, roomToken))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -1409,7 +1431,7 @@ public class CallController extends BaseController {
@Override @Override
public void onNext(GenericOverall genericOverall) { public void onNext(GenericOverall genericOverall) {
if (getActivity() != null) { if (shutDownView && getActivity() != null) {
getActivity().finish(); getActivity().finish();
} }
} }
@ -1431,7 +1453,7 @@ public class CallController extends BaseController {
videoCapturer.startCapture(1280, 720, 30); videoCapturer.startCapture(1280, 720, 30);
} }
} }
private void processUsersInRoom(List<HashMap<String, Object>> users) { private void processUsersInRoom(List<HashMap<String, Object>> users) {
List<String> newSessions = new ArrayList<>(); List<String> newSessions = new ArrayList<>();
Set<String> oldSesssions = new HashSet<>(); Set<String> oldSesssions = new HashSet<>();
@ -1633,6 +1655,7 @@ public class CallController extends BaseController {
pipVideoView.setLayoutParams(layoutParams); pipVideoView.setLayoutParams(layoutParams);
} }
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(PeerConnectionEvent peerConnectionEvent) { public void onMessageEvent(PeerConnectionEvent peerConnectionEvent) {
if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent.PeerConnectionEventType if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent.PeerConnectionEventType
@ -1647,7 +1670,7 @@ public class CallController extends BaseController {
boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
.PeerConnectionEventType.SENSOR_FAR) && videoOn; .PeerConnectionEventType.SENSOR_FAR) && videoOn;
if (getActivity() != null && EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA) && if (getActivity() != null && EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA) &&
inCall && videoOn connectedToCall && videoOn
&& enableVideo != localVideoTrack.enabled()) { && enableVideo != localVideoTrack.enabled()) {
toggleMedia(enableVideo, true); toggleMedia(enableVideo, true);
} }
@ -1678,7 +1701,7 @@ public class CallController extends BaseController {
int finalI = i; int finalI = i;
Observable Observable
.interval(1, TimeUnit.SECONDS) .interval(1, TimeUnit.SECONDS)
.takeWhile(observer -> inCall) .takeWhile(observer -> connectedToCall)
.observeOn(Schedulers.io()) .observeOn(Schedulers.io())
.doOnNext(n -> magicPeerConnectionWrapperList.get(finalI).sendChannelData(dataChannelMessage)); .doOnNext(n -> magicPeerConnectionWrapperList.get(finalI).sendChannelData(dataChannelMessage));
break; break;
@ -1796,14 +1819,23 @@ public class CallController extends BaseController {
if (relativeLayout != null) { if (relativeLayout != null) {
SimpleDraweeView avatarImageView = relativeLayout.findViewById(R.id.avatarImageView); SimpleDraweeView avatarImageView = relativeLayout.findViewById(R.id.avatarImageView);
if (participantMap.containsKey(session) && avatarImageView.getDrawable() == null) { String userId;
if (hasMCU) {
userId = webSocketClient.getUserIdForSession(session);
} else {
userId = participantMap.get(session).getUserId();
}
if (!TextUtils.isEmpty(userId)) {
if (getActivity() != null) { if (getActivity() != null) {
avatarImageView.setController(null);
DraweeController draweeController = Fresco.newDraweeControllerBuilder() DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setOldController(avatarImageView.getController()) .setOldController(avatarImageView.getController())
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(baseUrl, .setImageRequest(DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(baseUrl,
participantMap.get(session).getUserId(), userId,
R.dimen.avatar_size_big), null)) R.dimen.avatar_size_big), null))
.build(); .build();
avatarImageView.setController(draweeController); avatarImageView.setController(draweeController);
@ -1958,4 +1990,24 @@ public class CallController extends BaseController {
showCallControls(); showCallControls();
} }
} }
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEvent(NetworkEvent networkEvent) {
if (networkEvent.getNetworkConnectionEvent().equals(NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED)) {
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
currentCallStatus = CallStatus.RECONNECTING;
hangupNetworkCalls(false);
} else if (networkEvent.getNetworkConnectionEvent().equals(NetworkEvent.NetworkConnectionEvent.NETWORK_DISCONNECTED)) {
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
currentCallStatus = CallStatus.OFFLINE;
hangup(false);
}
}
} }

View File

@ -34,7 +34,6 @@ import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class) @AutoInjector(NextcloudTalkApplication.class)
public class MerlinTheWizard { public class MerlinTheWizard {
private static Merlin merlin; private static Merlin merlin;
private static MerlinsBeard merlinsBeard;
private UserEntity currentUserEntity; private UserEntity currentUserEntity;
@ -47,10 +46,16 @@ public class MerlinTheWizard {
@Inject @Inject
UserUtils userUtils; UserUtils userUtils;
private static boolean isConnectedToInternet;
public MerlinTheWizard() { public MerlinTheWizard() {
NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
} }
public static boolean isConnectedToInternet() {
return isConnectedToInternet;
}
public void initMerlin() { public void initMerlin() {
if (userUtils.anyUserExists() && (currentUserEntity == null || if (userUtils.anyUserExists() && (currentUserEntity == null ||
(userUtils.getCurrentUser().getId() != currentUserEntity.getId()))) { (userUtils.getCurrentUser().getId() != currentUserEntity.getId()))) {
@ -60,14 +65,13 @@ public class MerlinTheWizard {
} }
public Merlin getMerlin() { public Merlin getMerlin() {
if (merlin == null) {
initMerlin();
}
return merlin; return merlin;
} }
public MerlinsBeard getMerlinsBeard() {
return merlinsBeard;
}
private void setupMerlinForCurrentUserEntity() { private void setupMerlinForCurrentUserEntity() {
Endpoint endpoint = Endpoint.from(currentUserEntity.getBaseUrl() + "/index.php/204"); Endpoint endpoint = Endpoint.from(currentUserEntity.getBaseUrl() + "/index.php/204");
ResponseCodeValidator responseCodeValidator = ResponseCodeValidator responseCodeValidator =
@ -81,12 +85,10 @@ public class MerlinTheWizard {
merlin.bind(); merlin.bind();
merlinsBeard = new MerlinsBeard.Builder().withEndpoint(Endpoint.from(currentUserEntity.getBaseUrl() +
"/index.php/204")).withResponseCodeValidator(new ResponseCodeValidator.CaptivePortalResponseCodeValidator()).build(context);
merlin.registerConnectable(new Connectable() { merlin.registerConnectable(new Connectable() {
@Override @Override
public void onConnect() { public void onConnect() {
isConnectedToInternet = true;
eventBus.post(new NetworkEvent(NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED)); eventBus.post(new NetworkEvent(NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED));
} }
}); });
@ -94,6 +96,7 @@ public class MerlinTheWizard {
merlin.registerDisconnectable(new Disconnectable() { merlin.registerDisconnectable(new Disconnectable() {
@Override @Override
public void onDisconnect() { public void onDisconnect() {
isConnectedToInternet = false;
eventBus.post(new NetworkEvent(NetworkEvent.NetworkConnectionEvent.NETWORK_DISCONNECTED)); eventBus.post(new NetworkEvent(NetworkEvent.NetworkConnectionEvent.NETWORK_DISCONNECTED));
} }
}); });

View File

@ -36,6 +36,7 @@ import com.nextcloud.talk.events.WebSocketCommunicationEvent;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage; import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
import com.nextcloud.talk.models.json.signaling.NCIceCandidate; import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
import com.nextcloud.talk.utils.LoggingUtils; import com.nextcloud.talk.utils.LoggingUtils;
import com.nextcloud.talk.utils.singletons.MerlinTheWizard;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.webrtc.*; import org.webrtc.*;
@ -53,7 +54,7 @@ public class MagicPeerConnectionWrapper {
private PeerConnection peerConnection; private PeerConnection peerConnection;
private String sessionId; private String sessionId;
private String nick; private String nick;
private MediaConstraints mediaConstraints; private MediaConstraints sdpConstraints;
private DataChannel magicDataChannel; private DataChannel magicDataChannel;
private MagicSdpObserver magicSdpObserver; private MagicSdpObserver magicSdpObserver;
private MediaStream remoteMediaStream; private MediaStream remoteMediaStream;
@ -65,14 +66,17 @@ public class MagicPeerConnectionWrapper {
private MediaStream localMediaStream; private MediaStream localMediaStream;
private boolean isMCUPublisher; private boolean isMCUPublisher;
private boolean hasMCU;
private String videoStreamType; private String videoStreamType;
private int connectionAttempts = 0;
@Inject @Inject
Context context; Context context;
public MagicPeerConnectionWrapper(PeerConnectionFactory peerConnectionFactory, public MagicPeerConnectionWrapper(PeerConnectionFactory peerConnectionFactory,
List<PeerConnection.IceServer> iceServerList, List<PeerConnection.IceServer> iceServerList,
MediaConstraints mediaConstraints, MediaConstraints sdpConstraints,
String sessionId, String localSession, @Nullable MediaStream mediaStream, String sessionId, String localSession, @Nullable MediaStream mediaStream,
boolean isMCUPublisher, boolean hasMCU, String videoStreamType) { boolean isMCUPublisher, boolean hasMCU, String videoStreamType) {
@ -80,15 +84,16 @@ public class MagicPeerConnectionWrapper {
this.localMediaStream = mediaStream; this.localMediaStream = mediaStream;
this.videoStreamType = videoStreamType; this.videoStreamType = videoStreamType;
this.hasMCU = hasMCU;
this.sessionId = sessionId; this.sessionId = sessionId;
this.mediaConstraints = mediaConstraints; this.sdpConstraints = sdpConstraints;
magicSdpObserver = new MagicSdpObserver(); magicSdpObserver = new MagicSdpObserver();
hasInitiated = sessionId.compareTo(localSession) < 0; hasInitiated = sessionId.compareTo(localSession) < 0;
this.isMCUPublisher = isMCUPublisher; this.isMCUPublisher = isMCUPublisher;
peerConnection = peerConnectionFactory.createPeerConnection(iceServerList, mediaConstraints, peerConnection = peerConnectionFactory.createPeerConnection(iceServerList, sdpConstraints,
new MagicPeerConnectionObserver()); new MagicPeerConnectionObserver());
if (peerConnection != null) { if (peerConnection != null) {
@ -102,13 +107,13 @@ public class MagicPeerConnectionWrapper {
magicDataChannel = peerConnection.createDataChannel("status", init); magicDataChannel = peerConnection.createDataChannel("status", init);
magicDataChannel.registerObserver(new MagicDataChannelObserver()); magicDataChannel.registerObserver(new MagicDataChannelObserver());
if (isMCUPublisher) { if (isMCUPublisher) {
peerConnection.createOffer(magicSdpObserver, mediaConstraints); peerConnection.createOffer(magicSdpObserver, sdpConstraints);
} else if (hasMCU) { } else if (hasMCU) {
HashMap<String, String> hashMap = new HashMap<>(); HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("sessionId", sessionId); hashMap.put("sessionId", sessionId);
EventBus.getDefault().post(new WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap)); EventBus.getDefault().post(new WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap));
} else if (hasInitiated) { } else if (hasInitiated) {
peerConnection.createOffer(magicSdpObserver, mediaConstraints); peerConnection.createOffer(magicSdpObserver, sdpConstraints);
} }
} }
@ -287,6 +292,26 @@ public class MagicPeerConnectionWrapper {
} }
} }
private void restartIce() {
if (connectionAttempts <= 5) {
if (!hasMCU || isMCUPublisher) {
MediaConstraints.KeyValuePair iceRestartConstraint =
new MediaConstraints.KeyValuePair("IceRestart", "true");
if (sdpConstraints.mandatory.contains(iceRestartConstraint)) {
sdpConstraints.mandatory.add(iceRestartConstraint);
}
peerConnection.createOffer(magicSdpObserver, sdpConstraints);
} else {
// we have an MCU and this is not the publisher
// Do something if we have an MCU
}
connectionAttempts++;
}
}
private class MagicPeerConnectionObserver implements PeerConnection.Observer { private class MagicPeerConnectionObserver implements PeerConnection.Observer {
private final String TAG = "MagicPeerConnectionObserver"; private final String TAG = "MagicPeerConnectionObserver";
@ -300,6 +325,7 @@ public class MagicPeerConnectionWrapper {
"iceConnectionChangeTo: " + iceConnectionState.name() + " over " + peerConnection.hashCode() + " " + sessionId); "iceConnectionChangeTo: " + iceConnectionState.name() + " over " + peerConnection.hashCode() + " " + sessionId);
if (iceConnectionState.equals(PeerConnection.IceConnectionState.CONNECTED)) { if (iceConnectionState.equals(PeerConnection.IceConnectionState.CONNECTED)) {
connectionAttempts = 0;
/*EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType /*EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.PEER_CONNECTED, sessionId, null, null));*/ .PEER_CONNECTED, sessionId, null, null));*/
@ -314,6 +340,11 @@ public class MagicPeerConnectionWrapper {
} else if (iceConnectionState.equals(PeerConnection.IceConnectionState.CLOSED)) { } else if (iceConnectionState.equals(PeerConnection.IceConnectionState.CLOSED)) {
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.PEER_CLOSED, sessionId, null, null, videoStreamType)); .PEER_CLOSED, sessionId, null, null, videoStreamType));
connectionAttempts = 0;
} else if (iceConnectionState.equals(PeerConnection.IceConnectionState.FAILED)) {
if (MerlinTheWizard.isConnectedToInternet() && connectionAttempts < 5) {
restartIce();
}
} }
} }
@ -413,7 +444,7 @@ public class MagicPeerConnectionWrapper {
public void onSetSuccess() { public void onSetSuccess() {
if (peerConnection != null) { if (peerConnection != null) {
if (peerConnection.getLocalDescription() == null) { if (peerConnection.getLocalDescription() == null) {
peerConnection.createAnswer(magicSdpObserver, mediaConstraints); peerConnection.createAnswer(magicSdpObserver, sdpConstraints);
} }
if (peerConnection.getRemoteDescription() != null) { if (peerConnection.getRemoteDescription() != null) {

View File

@ -30,15 +30,13 @@ import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.events.NetworkEvent; import com.nextcloud.talk.events.NetworkEvent;
import com.nextcloud.talk.events.WebSocketCommunicationEvent; import com.nextcloud.talk.events.WebSocketCommunicationEvent;
import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.models.json.signaling.NCMessageWrapper; import com.nextcloud.talk.models.json.signaling.NCMessageWrapper;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage; import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import com.nextcloud.talk.models.json.websocket.*; import com.nextcloud.talk.models.json.websocket.*;
import com.nextcloud.talk.utils.LoggingUtils; import com.nextcloud.talk.utils.LoggingUtils;
import com.nextcloud.talk.utils.MagicMap; import com.nextcloud.talk.utils.MagicMap;
import com.nextcloud.talk.utils.singletons.MerlinTheWizard; import com.nextcloud.talk.utils.singletons.MerlinTheWizard;
import com.novoda.merlin.Endpoint;
import com.novoda.merlin.MerlinsBeard;
import com.novoda.merlin.ResponseCodeValidator;
import okhttp3.*; import okhttp3.*;
import okio.ByteString; import okio.ByteString;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
@ -80,10 +78,8 @@ public class MagicWebSocketInstance extends WebSocketListener {
private int restartCount = 0; private int restartCount = 0;
private boolean reconnecting = false; private boolean reconnecting = false;
private HashMap<String, String> displayNameHashMap; private HashMap<String, Participant> usersHashMap;
private HashMap<String, String> userIdSesssionHashMap;
private MerlinTheWizard merlinTheWizard;
private List<String> messagesQueue = new ArrayList<>(); private List<String> messagesQueue = new ArrayList<>();
MagicWebSocketInstance(UserEntity conversationUser, String connectionUrl, String webSocketTicket) { MagicWebSocketInstance(UserEntity conversationUser, String connectionUrl, String webSocketTicket) {
@ -93,11 +89,9 @@ public class MagicWebSocketInstance extends WebSocketListener {
this.conversationUser = conversationUser; this.conversationUser = conversationUser;
this.webSocketTicket = webSocketTicket; this.webSocketTicket = webSocketTicket;
this.webSocketConnectionHelper = new WebSocketConnectionHelper(); this.webSocketConnectionHelper = new WebSocketConnectionHelper();
this.displayNameHashMap = new HashMap<>(); this.usersHashMap = new HashMap<>();
this.userIdSesssionHashMap = new HashMap<>();
magicMap = new MagicMap(); magicMap = new MagicMap();
merlinTheWizard = new MerlinTheWizard();
connected = false; connected = false;
eventBus.register(this); eventBus.register(this);
@ -133,7 +127,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
private void restartWebSocket() { private void restartWebSocket() {
reconnecting = true; reconnecting = true;
if (merlinTheWizard.getMerlinsBeard().hasInternetAccess()) { if (MerlinTheWizard.isConnectedToInternet()) {
Request request = new Request.Builder().url(connectionUrl).build(); Request request = new Request.Builder().url(connectionUrl).build();
okHttpClient.newWebSocket(request, this); okHttpClient.newWebSocket(request, this);
restartCount++; restartCount++;
@ -155,6 +149,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
connected = true; connected = true;
reconnecting = false; reconnecting = false;
restartCount = 0; restartCount = 0;
String oldResumeId = resumeId;
HelloResponseOverallWebSocketMessage helloResponseWebSocketMessage = LoganSquare.parse(text, HelloResponseOverallWebSocketMessage.class); HelloResponseOverallWebSocketMessage helloResponseWebSocketMessage = LoganSquare.parse(text, HelloResponseOverallWebSocketMessage.class);
resumeId = helloResponseWebSocketMessage.getHelloResponseWebSocketMessage().getResumeId(); resumeId = helloResponseWebSocketMessage.getHelloResponseWebSocketMessage().getResumeId();
sessionId = helloResponseWebSocketMessage.getHelloResponseWebSocketMessage().getSessionId(); sessionId = helloResponseWebSocketMessage.getHelloResponseWebSocketMessage().getSessionId();
@ -165,7 +160,11 @@ public class MagicWebSocketInstance extends WebSocketListener {
} }
messagesQueue = new ArrayList<>(); messagesQueue = new ArrayList<>();
eventBus.post(new WebSocketCommunicationEvent("hello", null)); HashMap<String, String> helloHasHap = new HashMap<>();
if (!TextUtils.isEmpty(oldResumeId)) {
helloHasHap.put("oldResumeId", oldResumeId);
}
eventBus.post(new WebSocketCommunicationEvent("hello", helloHasHap));
break; break;
case "error": case "error":
ErrorOverallWebSocketMessage errorOverallWebSocketMessage = LoganSquare.parse(text, ErrorOverallWebSocketMessage.class); ErrorOverallWebSocketMessage errorOverallWebSocketMessage = LoganSquare.parse(text, ErrorOverallWebSocketMessage.class);
@ -187,8 +186,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
joinRoomHashMap.put("roomToken", currentRoomToken); joinRoomHashMap.put("roomToken", currentRoomToken);
eventBus.post(new WebSocketCommunicationEvent("roomJoined", joinRoomHashMap)); eventBus.post(new WebSocketCommunicationEvent("roomJoined", joinRoomHashMap));
} else { } else {
userIdSesssionHashMap = new HashMap<>(); usersHashMap = new HashMap<>();
displayNameHashMap = new HashMap<>();
} }
break; break;
case "event": case "event":
@ -216,11 +214,14 @@ public class MagicWebSocketInstance extends WebSocketListener {
} else if (eventOverallWebSocketMessage.getEventMap().get("type").equals("join")) { } else if (eventOverallWebSocketMessage.getEventMap().get("type").equals("join")) {
List<HashMap<String, Object>> joinEventMap = (List<HashMap<String, Object>>) eventOverallWebSocketMessage.getEventMap().get("join"); List<HashMap<String, Object>> joinEventMap = (List<HashMap<String, Object>>) eventOverallWebSocketMessage.getEventMap().get("join");
HashMap<String, Object> internalHashMap; HashMap<String, Object> internalHashMap;
Participant participant;
for (int i = 0; i < joinEventMap.size(); i++) { for (int i = 0; i < joinEventMap.size(); i++) {
internalHashMap = joinEventMap.get(i); internalHashMap = joinEventMap.get(i);
HashMap<String, Object> userMap = (HashMap<String, Object>) internalHashMap.get("user"); HashMap<String, Object> userMap = (HashMap<String, Object>) internalHashMap.get("user");
displayNameHashMap.put((String) internalHashMap.get("sessionid"), (String) userMap.get("displayname")); participant = new Participant();
userIdSesssionHashMap.put((String) internalHashMap.get("userid"), (String) internalHashMap.get("sessionid")); participant.setUserId((String) internalHashMap.get("userid"));
participant.setDisplayName((String) userMap.get("displayname"));
usersHashMap.put((String) internalHashMap.get("sessionid"), participant);
} }
} }
break; break;
@ -359,15 +360,29 @@ public class MagicWebSocketInstance extends WebSocketListener {
} }
public String getDisplayNameForSession(String session) { public String getDisplayNameForSession(String session) {
if (displayNameHashMap.containsKey(session)) { if (usersHashMap.containsKey(session)) {
return displayNameHashMap.get(session); return usersHashMap.get(session).getDisplayName();
} }
return NextcloudTalkApplication.getSharedApplication().getString(R.string.nc_nick_guest); return NextcloudTalkApplication.getSharedApplication().getString(R.string.nc_nick_guest);
} }
public String getSessionForUserId(String userId) { public String getSessionForUserId(String userId) {
return userIdSesssionHashMap.get(userId); for (String session : usersHashMap.keySet()) {
if (userId.equals(usersHashMap.get(session).getUserId())) {
return session;
}
}
return "";
}
public String getUserIdForSession(String session) {
if (usersHashMap.containsKey(session)) {
return usersHashMap.get(session).getUserId();
}
return "";
} }
@Subscribe(threadMode = ThreadMode.BACKGROUND) @Subscribe(threadMode = ThreadMode.BACKGROUND)
@ -376,5 +391,4 @@ public class MagicWebSocketInstance extends WebSocketListener {
restartWebSocket(); restartWebSocket();
} }
} }
} }

View File

@ -23,7 +23,7 @@
<!-- Set before a release --> <!-- Set before a release -->
<string name="nc_talk_database_encryption_key" translatable="false">HvAfHtAy/QdFYqAWFFXa1VV_Iv6ZQ1.tf5swMc^45wS_vz=Wm[oyRP5D-</string> <string name="nc_talk_database_encryption_key" translatable="false">HvAfHtAy/QdFYqAWFFXa1VV_Iv6ZQ1.tf5swMc^45wS_vz=Wm[oyRP5D-</string>
<string name="nc_talk_login_scheme" translatable="false">nc</string> <string name="nc_talk_login_scheme" translatable="false">nc</string>
<bool name="nc_is_debug">true</bool> <bool name="nc_is_debug">false</bool>
<string name="nc_app_name">Nextcloud Talk</string> <string name="nc_app_name">Nextcloud Talk</string>
<string name="nc_server_product_name">Nextcloud</string> <string name="nc_server_product_name">Nextcloud</string>