mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-19 19:49:33 +01:00
Some work
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
15586bc0cc
commit
846defc52c
@ -53,7 +53,7 @@ android {
|
|||||||
|
|
||||||
ext {
|
ext {
|
||||||
supportLibraryVersion = '26.1.0'
|
supportLibraryVersion = '26.1.0'
|
||||||
googleLibraryVersion = '11.4.2'
|
googleLibraryVersion = '11.6.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,15 +41,22 @@ import com.nextcloud.talk.api.NcApi;
|
|||||||
import com.nextcloud.talk.api.helpers.api.ApiHelper;
|
import com.nextcloud.talk.api.helpers.api.ApiHelper;
|
||||||
import com.nextcloud.talk.api.models.json.call.CallOverall;
|
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.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.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.Signaling;
|
||||||
import com.nextcloud.talk.api.models.json.signaling.SignalingOverall;
|
import com.nextcloud.talk.api.models.json.signaling.SignalingOverall;
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||||
|
import com.nextcloud.talk.events.SessionDescriptionSend;
|
||||||
import com.nextcloud.talk.persistence.entities.UserEntity;
|
import com.nextcloud.talk.persistence.entities.UserEntity;
|
||||||
import com.nextcloud.talk.webrtc.MagicPeerConnectionObserver;
|
import com.nextcloud.talk.webrtc.MagicPeerConnectionObserver;
|
||||||
import com.nextcloud.talk.webrtc.MagicSdpObserver;
|
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.AudioSource;
|
||||||
import org.webrtc.AudioTrack;
|
import org.webrtc.AudioTrack;
|
||||||
import org.webrtc.Camera1Enumerator;
|
import org.webrtc.Camera1Enumerator;
|
||||||
@ -71,6 +78,7 @@ import org.webrtc.VideoTrack;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -98,6 +106,10 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
NcApi ncApi;
|
NcApi ncApi;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EventBus eventBus;
|
||||||
|
|
||||||
PeerConnectionFactory peerConnectionFactory;
|
PeerConnectionFactory peerConnectionFactory;
|
||||||
MediaConstraints audioConstraints;
|
MediaConstraints audioConstraints;
|
||||||
MediaConstraints videoConstraints;
|
MediaConstraints videoConstraints;
|
||||||
@ -109,7 +121,7 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
VideoCapturer videoCapturer;
|
VideoCapturer videoCapturer;
|
||||||
VideoRenderer localRenderer;
|
VideoRenderer localRenderer;
|
||||||
VideoRenderer remoteRenderer;
|
VideoRenderer remoteRenderer;
|
||||||
PeerConnection localPeer, remotePeer;
|
PeerConnection localPeer;
|
||||||
boolean leavingCall = false;
|
boolean leavingCall = false;
|
||||||
BooleanSupplier booleanSupplier = () -> leavingCall;
|
BooleanSupplier booleanSupplier = () -> leavingCall;
|
||||||
Disposable signalingDisposable;
|
Disposable signalingDisposable;
|
||||||
@ -120,6 +132,7 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
private String callSession;
|
private String callSession;
|
||||||
|
|
||||||
private String credentials;
|
private String credentials;
|
||||||
|
private List<PeerConnectionWrapper> peerConnectionWrapperList = new ArrayList<>();
|
||||||
|
|
||||||
private static int getSystemUiVisibility() {
|
private static int getSystemUiVisibility() {
|
||||||
int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
|
int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
@ -130,6 +143,7 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
|
||||||
|
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
|
||||||
@ -139,7 +153,6 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
getWindow().getDecorView().setSystemUiVisibility(getSystemUiVisibility());
|
getWindow().getDecorView().setSystemUiVisibility(getSystemUiVisibility());
|
||||||
|
|
||||||
setContentView(R.layout.activity_call);
|
setContentView(R.layout.activity_call);
|
||||||
NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
|
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
roomToken = getIntent().getExtras().getString("roomToken", "");
|
roomToken = getIntent().getExtras().getString("roomToken", "");
|
||||||
@ -155,7 +168,6 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.INTERNET)
|
Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.INTERNET)
|
||||||
.onSuccess(() -> {
|
.onSuccess(() -> {
|
||||||
start();
|
start();
|
||||||
call();
|
|
||||||
})
|
})
|
||||||
.onDenied(new Runnable() {
|
.onDenied(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -237,11 +249,11 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
//Create a VideoSource instance
|
//Create a VideoSource instance
|
||||||
videoSource = peerConnectionFactory.createVideoSource(videoCapturerAndroid);
|
videoSource = peerConnectionFactory.createVideoSource(videoCapturerAndroid);
|
||||||
localVideoTrack = peerConnectionFactory.createVideoTrack("100", videoSource);
|
localVideoTrack = peerConnectionFactory.createVideoTrack("NCv0", videoSource);
|
||||||
|
|
||||||
//create an AudioSource instance
|
//create an AudioSource instance
|
||||||
audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
|
audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
|
||||||
localAudioTrack = peerConnectionFactory.createAudioTrack("101", audioSource);
|
localAudioTrack = peerConnectionFactory.createAudioTrack("NCa0", audioSource);
|
||||||
|
|
||||||
Resources r = getResources();
|
Resources r = getResources();
|
||||||
int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 120, r.getDisplayMetrics());
|
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("internalSctpDataChannels", "true"));
|
||||||
sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
|
sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
|
||||||
|
|
||||||
//creating localPeer
|
PeerConnection localPeer = alwaysGetPeerConnectionWrapperForSessionId(callSession, true).
|
||||||
localPeer = peerConnectionFactory.createPeerConnection(iceServers, sdpConstraints,
|
getPeerConnection();
|
||||||
new MagicPeerConnectionObserver() {
|
|
||||||
@Override
|
|
||||||
public void onIceCandidate(IceCandidate iceCandidate) {
|
|
||||||
super.onIceCandidate(iceCandidate);
|
|
||||||
onIceCandidateReceived(localPeer, iceCandidate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//creating local mediastream
|
//creating local mediastream
|
||||||
MediaStream stream = peerConnectionFactory.createLocalMediaStream("102");
|
MediaStream stream = peerConnectionFactory.createLocalMediaStream("NCMS");
|
||||||
stream.addTrack(localAudioTrack);
|
stream.addTrack(localAudioTrack);
|
||||||
stream.addTrack(localVideoTrack);
|
stream.addTrack(localVideoTrack);
|
||||||
localPeer.addStream(stream);
|
localPeer.addStream(stream);
|
||||||
@ -408,17 +415,29 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("usersInRoom".equals(messageType)) {
|
if ("usersInRoom".equals(messageType)) {
|
||||||
processUsersInRoom((List<Participant>) signaling.getMessageWrapper());
|
processUsersInRoom((List<HashMap<String, String>>) signaling.getMessageWrapper());
|
||||||
} else if ("message".equals(messageType)) {
|
} else if ("message".equals(messageType)) {
|
||||||
NCMessageWrapper ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(),
|
NCSignalingMessage ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(),
|
||||||
NCMessageWrapper.class);
|
NCSignalingMessage.class);
|
||||||
if (ncSignalingMessage.getSignalingMessage().getRoomType().equals("video")) {
|
if (ncSignalingMessage.getRoomType().equals("video")) {
|
||||||
switch (ncSignalingMessage.getSignalingMessage().getType()) {
|
PeerConnectionWrapper peerConnectionWrapper = alwaysGetPeerConnectionWrapperForSessionId
|
||||||
|
(ncSignalingMessage.getFrom(), ncSignalingMessage.getFrom().equals(callSession));
|
||||||
|
|
||||||
|
switch (ncSignalingMessage.getType()) {
|
||||||
case "offer":
|
case "offer":
|
||||||
break;
|
|
||||||
case "answer":
|
case "answer":
|
||||||
|
peerConnectionWrapper.setNick(ncSignalingMessage.getPayload().getNick());
|
||||||
|
peerConnectionWrapper.getPeerConnection().setLocalDescription(new MagicSdpObserver(),
|
||||||
|
new SessionDescription(SessionDescription.Type.valueOf(ncSignalingMessage.getType()
|
||||||
|
.toUpperCase()),
|
||||||
|
ncSignalingMessage
|
||||||
|
.getPayload().getSdp()));
|
||||||
break;
|
break;
|
||||||
case "candidate":
|
case "candidate":
|
||||||
|
NCIceCandidate ncIceCandidate = ncSignalingMessage.getPayload().getIceCandidate();
|
||||||
|
IceCandidate iceCandidate = new IceCandidate(ncIceCandidate.getSdpMid(),
|
||||||
|
ncIceCandidate.getSdpMLineIndex(), ncIceCandidate.getCandidate());
|
||||||
|
peerConnectionWrapper.getPeerConnection().addIceCandidate(iceCandidate);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -429,20 +448,79 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processUsersInRoom(List<Participant> users) {
|
private void processUsersInRoom(List<HashMap<String, String>> users) {
|
||||||
|
List<String> newSessions = new ArrayList<>();
|
||||||
|
List<String> oldSesssions = new ArrayList<>();
|
||||||
|
|
||||||
|
for (HashMap<String, String> 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<String> 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() {
|
|
||||||
|
|
||||||
|
private PeerConnectionWrapper alwaysGetPeerConnectionWrapperForSessionId(String sessionId, boolean isLocalPeer) {
|
||||||
//creating remotePeer
|
PeerConnectionWrapper peerConnectionWrapper;
|
||||||
remotePeer = peerConnectionFactory.createPeerConnection(iceServers, sdpConstraints,
|
if ((peerConnectionWrapper = getPeerConnectionWrapperForSessionId(sessionId)) != null) {
|
||||||
new MagicPeerConnectionObserver() {
|
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
|
@Override
|
||||||
public void onIceCandidate(IceCandidate iceCandidate) {
|
public void onIceCandidate(IceCandidate iceCandidate) {
|
||||||
super.onIceCandidate(iceCandidate);
|
super.onIceCandidate(iceCandidate);
|
||||||
onIceCandidateReceived(remotePeer, iceCandidate);
|
onIceCandidateReceived(false, iceCandidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAddStream(MediaStream mediaStream) {
|
public void onAddStream(MediaStream mediaStream) {
|
||||||
@ -455,30 +533,23 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
super.onIceGatheringChange(iceGatheringState);
|
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() {
|
private void hangup() {
|
||||||
@ -487,14 +558,10 @@ public class CallActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
dispose(null);
|
dispose(null);
|
||||||
|
|
||||||
if (localPeer != null) {
|
for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
|
||||||
localPeer.close();
|
if (peerConnectionWrapper.getPeerConnection() != null) {
|
||||||
localPeer = null;
|
peerConnectionWrapper.getPeerConnection().close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remotePeer != null) {
|
|
||||||
remotePeer.close();
|
|
||||||
remotePeer = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoCapturer != null) {
|
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.
|
//we have received ice candidate. We can set it to the other peer.
|
||||||
if (peer == localPeer) {
|
if (!isLocalPeer) {
|
||||||
remotePeer.addIceCandidate(iceCandidate);
|
for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
|
||||||
|
if (!peerConnectionWrapper.isLocal()) {
|
||||||
|
//peerConnectionWrapper.addCandidate(iceCandidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} 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<Integer>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(Disposable d) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(Integer integer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.RoomOverall;
|
||||||
import com.nextcloud.talk.api.models.json.rooms.RoomsOverall;
|
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.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.signaling.SignalingOverall;
|
||||||
import com.nextcloud.talk.api.models.json.userprofile.UserProfileOverall;
|
import com.nextcloud.talk.api.models.json.userprofile.UserProfileOverall;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
import retrofit2.http.Body;
|
||||||
import retrofit2.http.DELETE;
|
import retrofit2.http.DELETE;
|
||||||
import retrofit2.http.FieldMap;
|
import retrofit2.http.FieldMap;
|
||||||
import retrofit2.http.FormUrlEncoded;
|
import retrofit2.http.FormUrlEncoded;
|
||||||
@ -165,7 +167,8 @@ public interface NcApi {
|
|||||||
Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /signaling
|
Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /signaling
|
||||||
*/
|
*/
|
||||||
@POST
|
@POST
|
||||||
Observable<Integer> sendSignalingMessages(@Header("Authorization") String authorization, @Url String url);
|
Observable<Integer> sendSignalingMessages(@Header("Authorization") String authorization, @Url String url,
|
||||||
|
@Body NCMessageWrapper ncMessageWrapper);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /signaling
|
Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /signaling
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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.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;
|
||||||
|
}
|
@ -29,11 +29,14 @@ import lombok.Data;
|
|||||||
@JsonObject
|
@JsonObject
|
||||||
public class NCIceCandidate {
|
public class NCIceCandidate {
|
||||||
@JsonField(name = "sdpMLineIndex")
|
@JsonField(name = "sdpMLineIndex")
|
||||||
String sdpMLineIndex;
|
int sdpMLineIndex;
|
||||||
|
|
||||||
@JsonField(name = "sdpMid")
|
@JsonField(name = "sdpMid")
|
||||||
String sdpMid;
|
String sdpMid;
|
||||||
|
|
||||||
@JsonField(name = "candidate")
|
@JsonField(name = "candidate")
|
||||||
String candidate;
|
String candidate;
|
||||||
|
|
||||||
|
@JsonField(name = "type")
|
||||||
|
String type;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ import lombok.Data;
|
|||||||
public class Signaling {
|
public class Signaling {
|
||||||
@JsonField(name = "type")
|
@JsonField(name = "type")
|
||||||
String type;
|
String type;
|
||||||
//can be NCMessageWrapper or List<Participant>
|
//can be NCMessageWrapper or List<HashMap<String,String>>
|
||||||
@JsonField(name = "data")
|
@JsonField(name = "data")
|
||||||
Object messageWrapper;
|
Object messageWrapper;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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 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;
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,7 @@ import org.webrtc.RtpReceiver;
|
|||||||
public class MagicPeerConnectionObserver implements PeerConnection.Observer {
|
public class MagicPeerConnectionObserver implements PeerConnection.Observer {
|
||||||
private static final String TAG = "MagicPeerConnectionObserver";
|
private static final String TAG = "MagicPeerConnectionObserver";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
|
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
|
||||||
|
|
||||||
|
@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* 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.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<PeerConnection.IceServer> 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user