From 846defc52c7b865cd6ee8a9aef4cdaf919429ba3 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Mon, 13 Nov 2017 04:05:17 +0100 Subject: [PATCH] Some work Signed-off-by: Mario Danic --- app/build.gradle | 2 +- .../talk/activities/CallActivity.java | 263 +++++++++++++----- .../java/com/nextcloud/talk/api/NcApi.java | 5 +- .../json/signaling/DataChannelMessage.java | 36 +++ .../models/json/signaling/NCIceCandidate.java | 5 +- .../api/models/json/signaling/Signaling.java | 2 +- .../talk/events/SessionDescriptionSend.java | 45 +++ .../webrtc/MagicPeerConnectionObserver.java | 1 + .../talk/webrtc/PeerConnectionWrapper.java | 169 +++++++++++ 9 files changed, 460 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/api/models/json/signaling/DataChannelMessage.java create mode 100644 app/src/main/java/com/nextcloud/talk/events/SessionDescriptionSend.java create mode 100644 app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionWrapper.java diff --git a/app/build.gradle b/app/build.gradle index 05b7c99ac..426822a4d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -53,7 +53,7 @@ android { ext { supportLibraryVersion = '26.1.0' - googleLibraryVersion = '11.4.2' + googleLibraryVersion = '11.6.0' } 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 c43da6c61..42db6c04c 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java @@ -41,15 +41,22 @@ import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.api.helpers.api.ApiHelper; import com.nextcloud.talk.api.models.json.call.CallOverall; import com.nextcloud.talk.api.models.json.generic.GenericOverall; -import com.nextcloud.talk.api.models.json.participants.Participant; +import com.nextcloud.talk.api.models.json.signaling.NCIceCandidate; +import com.nextcloud.talk.api.models.json.signaling.NCMessagePayload; import com.nextcloud.talk.api.models.json.signaling.NCMessageWrapper; +import com.nextcloud.talk.api.models.json.signaling.NCSignalingMessage; import com.nextcloud.talk.api.models.json.signaling.Signaling; import com.nextcloud.talk.api.models.json.signaling.SignalingOverall; import com.nextcloud.talk.application.NextcloudTalkApplication; +import com.nextcloud.talk.events.SessionDescriptionSend; import com.nextcloud.talk.persistence.entities.UserEntity; import com.nextcloud.talk.webrtc.MagicPeerConnectionObserver; import com.nextcloud.talk.webrtc.MagicSdpObserver; +import com.nextcloud.talk.webrtc.PeerConnectionWrapper; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; import org.webrtc.AudioSource; import org.webrtc.AudioTrack; import org.webrtc.Camera1Enumerator; @@ -71,6 +78,7 @@ import org.webrtc.VideoTrack; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; @@ -98,6 +106,10 @@ public class CallActivity extends AppCompatActivity { @Inject NcApi ncApi; + + @Inject + EventBus eventBus; + PeerConnectionFactory peerConnectionFactory; MediaConstraints audioConstraints; MediaConstraints videoConstraints; @@ -109,7 +121,7 @@ public class CallActivity extends AppCompatActivity { VideoCapturer videoCapturer; VideoRenderer localRenderer; VideoRenderer remoteRenderer; - PeerConnection localPeer, remotePeer; + PeerConnection localPeer; boolean leavingCall = false; BooleanSupplier booleanSupplier = () -> leavingCall; Disposable signalingDisposable; @@ -120,6 +132,7 @@ public class CallActivity extends AppCompatActivity { private String callSession; private String credentials; + private List peerConnectionWrapperList = new ArrayList<>(); private static int getSystemUiVisibility() { int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -130,6 +143,7 @@ public class CallActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | @@ -139,7 +153,6 @@ public class CallActivity extends AppCompatActivity { getWindow().getDecorView().setSystemUiVisibility(getSystemUiVisibility()); setContentView(R.layout.activity_call); - NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); ButterKnife.bind(this); roomToken = getIntent().getExtras().getString("roomToken", ""); @@ -155,7 +168,6 @@ public class CallActivity extends AppCompatActivity { Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.INTERNET) .onSuccess(() -> { start(); - call(); }) .onDenied(new Runnable() { @Override @@ -237,11 +249,11 @@ public class CallActivity extends AppCompatActivity { //Create a VideoSource instance videoSource = peerConnectionFactory.createVideoSource(videoCapturerAndroid); - localVideoTrack = peerConnectionFactory.createVideoTrack("100", videoSource); + localVideoTrack = peerConnectionFactory.createVideoTrack("NCv0", videoSource); //create an AudioSource instance audioSource = peerConnectionFactory.createAudioSource(audioConstraints); - localAudioTrack = peerConnectionFactory.createAudioTrack("101", audioSource); + localAudioTrack = peerConnectionFactory.createAudioTrack("NCa0", audioSource); Resources r = getResources(); int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 120, r.getDisplayMetrics()); @@ -264,18 +276,13 @@ public class CallActivity extends AppCompatActivity { sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true")); sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true")); - //creating localPeer - localPeer = peerConnectionFactory.createPeerConnection(iceServers, sdpConstraints, - new MagicPeerConnectionObserver() { - @Override - public void onIceCandidate(IceCandidate iceCandidate) { - super.onIceCandidate(iceCandidate); - onIceCandidateReceived(localPeer, iceCandidate); - } - }); + PeerConnection localPeer = alwaysGetPeerConnectionWrapperForSessionId(callSession, true). + getPeerConnection(); + + //creating local mediastream - MediaStream stream = peerConnectionFactory.createLocalMediaStream("102"); + MediaStream stream = peerConnectionFactory.createLocalMediaStream("NCMS"); stream.addTrack(localAudioTrack); stream.addTrack(localVideoTrack); localPeer.addStream(stream); @@ -408,17 +415,29 @@ public class CallActivity extends AppCompatActivity { } if ("usersInRoom".equals(messageType)) { - processUsersInRoom((List) signaling.getMessageWrapper()); + processUsersInRoom((List>) signaling.getMessageWrapper()); } else if ("message".equals(messageType)) { - NCMessageWrapper ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(), - NCMessageWrapper.class); - if (ncSignalingMessage.getSignalingMessage().getRoomType().equals("video")) { - switch (ncSignalingMessage.getSignalingMessage().getType()) { + NCSignalingMessage ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(), + NCSignalingMessage.class); + if (ncSignalingMessage.getRoomType().equals("video")) { + PeerConnectionWrapper peerConnectionWrapper = alwaysGetPeerConnectionWrapperForSessionId + (ncSignalingMessage.getFrom(), ncSignalingMessage.getFrom().equals(callSession)); + + switch (ncSignalingMessage.getType()) { case "offer": - break; case "answer": + peerConnectionWrapper.setNick(ncSignalingMessage.getPayload().getNick()); + peerConnectionWrapper.getPeerConnection().setLocalDescription(new MagicSdpObserver(), + new SessionDescription(SessionDescription.Type.valueOf(ncSignalingMessage.getType() + .toUpperCase()), + ncSignalingMessage + .getPayload().getSdp())); break; case "candidate": + NCIceCandidate ncIceCandidate = ncSignalingMessage.getPayload().getIceCandidate(); + IceCandidate iceCandidate = new IceCandidate(ncIceCandidate.getSdpMid(), + ncIceCandidate.getSdpMLineIndex(), ncIceCandidate.getCandidate()); + peerConnectionWrapper.getPeerConnection().addIceCandidate(iceCandidate); break; default: break; @@ -429,20 +448,79 @@ public class CallActivity extends AppCompatActivity { } } - private void processUsersInRoom(List users) { + private void processUsersInRoom(List> users) { + List newSessions = new ArrayList<>(); + List oldSesssions = new ArrayList<>(); + + for (HashMap participant : users) { + if (participant.containsKey("sessionId") && !participant.get("sessionId").equals + (callSession)) { + newSessions.add(participant.get("sessionId")); + } + } + + for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) { + if (!peerConnectionWrapper.isLocal()) { + oldSesssions.add(peerConnectionWrapper.getSessionId()); + } + } + + // Calculate sessions that left the call + List leftSessions = oldSesssions; + leftSessions.removeAll(newSessions); + + // Calculate sessions that join the call + newSessions.removeAll(oldSesssions); + + if (leavingCall) { + return; + } + + PeerConnectionWrapper peerConnectionWrapper; + + for (String sessionId : newSessions) { + if (getPeerConnectionWrapperForSessionId(sessionId) == null) { + if (sessionId.compareTo(callSession) > 0) { + PeerConnectionWrapper connectionWrapper = alwaysGetPeerConnectionWrapperForSessionId(sessionId, + false); + connectionWrapper.sendOffer(); + } else { + Log.d(TAG, "Waiting for offer"); + } + + } + } + + for (String sessionId : leftSessions) { + if ((peerConnectionWrapper = getPeerConnectionWrapperForSessionId(sessionId)) != null) { + peerConnectionWrapper.getPeerConnection().close(); + peerConnectionWrapperList.remove(peerConnectionWrapper); + } + } } - private void call() { - - //creating remotePeer - remotePeer = peerConnectionFactory.createPeerConnection(iceServers, sdpConstraints, - new MagicPeerConnectionObserver() { + private PeerConnectionWrapper alwaysGetPeerConnectionWrapperForSessionId(String sessionId, boolean isLocalPeer) { + PeerConnectionWrapper peerConnectionWrapper; + if ((peerConnectionWrapper = getPeerConnectionWrapperForSessionId(sessionId)) != null) { + return peerConnectionWrapper; + } else { + MagicPeerConnectionObserver magicPeerConnectionObserver; + if (isLocalPeer) { + magicPeerConnectionObserver = new MagicPeerConnectionObserver() { + @Override + public void onIceCandidate(IceCandidate iceCandidate) { + super.onIceCandidate(iceCandidate); + onIceCandidateReceived(true, iceCandidate); + } + }; + } else { + magicPeerConnectionObserver = new MagicPeerConnectionObserver() { @Override public void onIceCandidate(IceCandidate iceCandidate) { super.onIceCandidate(iceCandidate); - onIceCandidateReceived(remotePeer, iceCandidate); + onIceCandidateReceived(false, iceCandidate); } public void onAddStream(MediaStream mediaStream) { @@ -455,30 +533,23 @@ public class CallActivity extends AppCompatActivity { super.onIceGatheringChange(iceGatheringState); } - }); - - - //creating Offer - localPeer.createOffer(new MagicSdpObserver() { - @Override - public void onCreateSuccess(SessionDescription sessionDescription) { - //we have localOffer. Set it as local desc for localpeer and remote desc for remote peer. - //try to create answer from the remote peer. - super.onCreateSuccess(sessionDescription); - localPeer.setLocalDescription(new MagicSdpObserver(), sessionDescription); - remotePeer.setRemoteDescription(new MagicSdpObserver(), sessionDescription); - remotePeer.createAnswer(new MagicSdpObserver() { - @Override - public void onCreateSuccess(SessionDescription sessionDescription) { - //remote answer generated. Now set it as local desc for remote peer and remote desc for local peer. - super.onCreateSuccess(sessionDescription); - remotePeer.setLocalDescription(new MagicSdpObserver(), sessionDescription); - localPeer.setRemoteDescription(new MagicSdpObserver(), sessionDescription); - - } - }, sdpConstraints); + }; } - }, sdpConstraints); + + peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory, + iceServers, sdpConstraints, magicPeerConnectionObserver, sessionId, isLocalPeer); + peerConnectionWrapperList.add(peerConnectionWrapper); + return peerConnectionWrapper; + } + } + + private PeerConnectionWrapper getPeerConnectionWrapperForSessionId(String sessionId) { + for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) { + if (peerConnectionWrapper.getSessionId().equals(sessionId)) { + return peerConnectionWrapper; + } + } + return null; } private void hangup() { @@ -487,14 +558,10 @@ public class CallActivity extends AppCompatActivity { dispose(null); - if (localPeer != null) { - localPeer.close(); - localPeer = null; - } - - if (remotePeer != null) { - remotePeer.close(); - remotePeer = null; + for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) { + if (peerConnectionWrapper.getPeerConnection() != null) { + peerConnectionWrapper.getPeerConnection().close(); + } } if (videoCapturer != null) { @@ -549,12 +616,17 @@ public class CallActivity extends AppCompatActivity { } - public void onIceCandidateReceived(PeerConnection peer, IceCandidate iceCandidate) { + public void onIceCandidateReceived(boolean isLocalPeer, IceCandidate iceCandidate) { //we have received ice candidate. We can set it to the other peer. - if (peer == localPeer) { - remotePeer.addIceCandidate(iceCandidate); + if (!isLocalPeer) { + for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) { + if (!peerConnectionWrapper.isLocal()) { + //peerConnectionWrapper.addCandidate(iceCandidate); + } + } } else { - localPeer.addIceCandidate(iceCandidate); + alwaysGetPeerConnectionWrapperForSessionId(callSession, true).getPeerConnection().addIceCandidate + (iceCandidate); } } @@ -581,4 +653,67 @@ public class CallActivity extends AppCompatActivity { } } + @Override + public void onStart() { + super.onStart(); + eventBus.register(this); + } + + @Override + public void onStop() { + super.onStop(); + eventBus.unregister(this); + } + + @Subscribe(threadMode = ThreadMode.BACKGROUND) + public void onMessageEvent(SessionDescriptionSend sessionDescriptionSend) { + String credentials = ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken()); + NCMessageWrapper ncMessageWrapper = new NCMessageWrapper(); + ncMessageWrapper.setEv("message"); + ncMessageWrapper.setSessionId(callSession); + // Create signaling message and payload + NCSignalingMessage ncSignalingMessage = new NCSignalingMessage(); + //ncSignalingMessage.setFrom(callSession); + ncSignalingMessage.setTo(sessionDescriptionSend.getPeerId()); + ncSignalingMessage.setRoomType("video"); + NCMessagePayload ncMessagePayload = new NCMessagePayload(); + ncMessagePayload.setType(sessionDescriptionSend.getType()); + if (!"candidate".equals(sessionDescriptionSend.getType())) { + ncMessagePayload.setSdp(sessionDescriptionSend.getSessionDescription().description); + ncMessagePayload.setNick(userEntity.getDisplayName()); + } else { + ncMessagePayload.setIceCandidate(sessionDescriptionSend.getNcIceCandidate()); + } + + // Set all we need + ncSignalingMessage.setPayload(ncMessagePayload); + ncMessageWrapper.setSignalingMessage(ncSignalingMessage); + + ncApi.sendSignalingMessages(credentials, ApiHelper.getUrlForSignaling(userEntity.getBaseUrl()), + ncMessageWrapper) + .subscribeOn(Schedulers.newThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onNext(Integer integer) { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + + } + }); + } + + } diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index 736d3d4fa..46079dc67 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -29,12 +29,14 @@ import com.nextcloud.talk.api.models.json.push.PushRegistrationOverall; import com.nextcloud.talk.api.models.json.rooms.RoomOverall; import com.nextcloud.talk.api.models.json.rooms.RoomsOverall; import com.nextcloud.talk.api.models.json.sharees.ShareesOverall; +import com.nextcloud.talk.api.models.json.signaling.NCMessageWrapper; import com.nextcloud.talk.api.models.json.signaling.SignalingOverall; import com.nextcloud.talk.api.models.json.userprofile.UserProfileOverall; import java.util.Map; import io.reactivex.Observable; +import retrofit2.http.Body; import retrofit2.http.DELETE; import retrofit2.http.FieldMap; import retrofit2.http.FormUrlEncoded; @@ -165,7 +167,8 @@ public interface NcApi { Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /signaling */ @POST - Observable sendSignalingMessages(@Header("Authorization") String authorization, @Url String url); + Observable sendSignalingMessages(@Header("Authorization") String authorization, @Url String url, + @Body NCMessageWrapper ncMessageWrapper); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /signaling diff --git a/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/DataChannelMessage.java b/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/DataChannelMessage.java new file mode 100644 index 000000000..1ce1bab7d --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/DataChannelMessage.java @@ -0,0 +1,36 @@ +/* + * 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.api.models.json.signaling; + +import com.bluelinelabs.logansquare.annotation.JsonField; +import com.bluelinelabs.logansquare.annotation.JsonObject; + +import lombok.Data; + +@Data +@JsonObject +public class DataChannelMessage { + @JsonField(name = "type") + String type; + + @JsonField(name = "payload") + String payload; +} diff --git a/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/NCIceCandidate.java b/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/NCIceCandidate.java index a0c4c146f..1db7dfc37 100644 --- a/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/NCIceCandidate.java +++ b/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/NCIceCandidate.java @@ -29,11 +29,14 @@ import lombok.Data; @JsonObject public class NCIceCandidate { @JsonField(name = "sdpMLineIndex") - String sdpMLineIndex; + int sdpMLineIndex; @JsonField(name = "sdpMid") String sdpMid; @JsonField(name = "candidate") String candidate; + + @JsonField(name = "type") + String type; } diff --git a/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/Signaling.java b/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/Signaling.java index b3a37c385..5dfbadc3b 100644 --- a/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/Signaling.java +++ b/app/src/main/java/com/nextcloud/talk/api/models/json/signaling/Signaling.java @@ -34,7 +34,7 @@ import lombok.Data; public class Signaling { @JsonField(name = "type") String type; - //can be NCMessageWrapper or List + //can be NCMessageWrapper or List> @JsonField(name = "data") Object messageWrapper; } diff --git a/app/src/main/java/com/nextcloud/talk/events/SessionDescriptionSend.java b/app/src/main/java/com/nextcloud/talk/events/SessionDescriptionSend.java new file mode 100644 index 000000000..c532e2824 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/events/SessionDescriptionSend.java @@ -0,0 +1,45 @@ +/* + * 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 android.support.annotation.Nullable; + +import com.nextcloud.talk.api.models.json.signaling.NCIceCandidate; + +import org.webrtc.SessionDescription; + +import lombok.Data; + +@Data +public class SessionDescriptionSend { + @Nullable private final SessionDescription sessionDescription; + private final String peerId; + private final String type; + @Nullable private final NCIceCandidate ncIceCandidate; + + public SessionDescriptionSend(@Nullable SessionDescription sessionDescription, String peerId, String type, + @Nullable NCIceCandidate ncIceCandidate) { + this.sessionDescription = sessionDescription; + this.peerId = peerId; + this.type = type; + this.ncIceCandidate = ncIceCandidate; + } +} diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionObserver.java b/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionObserver.java index 911be4dae..4090bce9c 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionObserver.java +++ b/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionObserver.java @@ -30,6 +30,7 @@ import org.webrtc.RtpReceiver; public class MagicPeerConnectionObserver implements PeerConnection.Observer { private static final String TAG = "MagicPeerConnectionObserver"; + @Override public void onSignalingChange(PeerConnection.SignalingState signalingState) { diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionWrapper.java b/app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionWrapper.java new file mode 100644 index 000000000..fbf6c4aea --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionWrapper.java @@ -0,0 +1,169 @@ +/* + * 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.webrtc; + +import com.nextcloud.talk.api.models.json.signaling.DataChannelMessage; +import com.nextcloud.talk.api.models.json.signaling.NCIceCandidate; +import com.nextcloud.talk.events.SessionDescriptionSend; + +import org.greenrobot.eventbus.EventBus; +import org.webrtc.DataChannel; +import org.webrtc.IceCandidate; +import org.webrtc.MediaConstraints; +import org.webrtc.PeerConnection; +import org.webrtc.PeerConnectionFactory; +import org.webrtc.SessionDescription; + +import java.nio.ByteBuffer; +import java.util.List; + +public class PeerConnectionWrapper { + private static PeerConnection peerConnection; + private String sessionId; + private String nick; + private boolean local; + private MediaConstraints mediaConstraints; + private DataChannel dataChannel; + + public PeerConnectionWrapper(PeerConnectionFactory peerConnectionFactory, + List iceServerList, + MediaConstraints mediaConstraints, + MagicPeerConnectionObserver magicPeerConnectionObserver, + String sessionId, boolean isLocalPeer) { + peerConnection = peerConnectionFactory.createPeerConnection(iceServerList, mediaConstraints, + magicPeerConnectionObserver); + this.sessionId = sessionId; + this.local = isLocalPeer; + this.mediaConstraints = mediaConstraints; + } + + public void addCandidate(IceCandidate iceCandidate) { + if (peerConnection.getRemoteDescription() != null) { + // queue + } else { + peerConnection.addIceCandidate(iceCandidate); + } + + peerConnection.addIceCandidate(iceCandidate); + NCIceCandidate ncIceCandidate = new NCIceCandidate(); + ncIceCandidate.setType("candidate"); + ncIceCandidate.setSdpMid(iceCandidate.sdpMid); + ncIceCandidate.setSdpMLineIndex(iceCandidate.sdpMLineIndex); + ncIceCandidate.setCandidate(iceCandidate.sdp); + EventBus.getDefault().post(new SessionDescriptionSend(null, sessionId, "candidate", + ncIceCandidate)); + } + + public void sendAnswer() { + + MagicSdpObserver magicSdpObserver = new MagicSdpObserver() { + public void onCreateSuccess(SessionDescription sessionDescription) { + super.onCreateSuccess(sessionDescription); + peerConnection.setLocalDescription(new MagicSdpObserver(), sessionDescription); + EventBus.getDefault().post(new SessionDescriptionSend(sessionDescription, sessionId, "answer", + null)); + } + }; + + peerConnection.createAnswer(magicSdpObserver, mediaConstraints); + } + + private void sendChannelData(DataChannelMessage dataChannelMessage) { + + ByteBuffer buffer = ByteBuffer.wrap(dataChannelMessage.toString().getBytes()); + dataChannel.send(new DataChannel.Buffer(buffer, false)); + } + + + public void sendOffer() { + DataChannel.Init dcInit = new DataChannel.Init(); + dcInit.negotiated = false; + dataChannel = peerConnection.createDataChannel("status", dcInit); + dataChannel.registerObserver(new DataChannel.Observer() { + @Override + public void onBufferedAmountChange(long l) { + + } + + @Override + public void onStateChange() { + if (dataChannel.state() == DataChannel.State.OPEN && dataChannel.label().equals("status")) { + DataChannelMessage dataChannelMessage = new DataChannelMessage(); + dataChannelMessage.setType("videoOn"); + sendChannelData(dataChannelMessage); + dataChannelMessage.setType("audioOn"); + sendChannelData(dataChannelMessage); + } + + } + + @Override + public void onMessage(DataChannel.Buffer buffer) { + ByteBuffer data = buffer.data; + byte[] bytes = new byte[data.remaining()]; + data.get(bytes); + final String command = new String(bytes); + } + }); + + MagicSdpObserver magicSdpObserver = new MagicSdpObserver() { + public void onCreateSuccess(SessionDescription sessionDescription) { + super.onCreateSuccess(sessionDescription); + peerConnection.setLocalDescription(new MagicSdpObserver(), sessionDescription); + EventBus.getDefault().post(new SessionDescriptionSend(sessionDescription, sessionId, "offer", null)); + } + }; + + peerConnection.createOffer(magicSdpObserver, mediaConstraints); + } + + public boolean isLocal() { + return local; + } + + public void setLocal(boolean local) { + this.local = local; + } + + public PeerConnection getPeerConnection() { + return peerConnection; + } + + public static void setPeerConnection(PeerConnection peerConnection) { + PeerConnectionWrapper.peerConnection = peerConnection; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public String getNick() { + return nick; + } + + public void setNick(String nick) { + this.nick = nick; + } +}