Merge pull request #1774 from nextcloud/feature/1773/update-webrtc-library

Android update WebRTC library
This commit is contained in:
Tim Krueger 2022-02-23 13:20:09 +01:00 committed by GitHub
commit 89f64b392a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 196 additions and 196 deletions

View File

@ -4,9 +4,11 @@
* @author Mario Danic * @author Mario Danic
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Marcel Hibbe * @author Marcel Hibbe
* @author Tim Krüger
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -34,6 +36,20 @@ configurations {
ktlint ktlint
} }
def urlFile = { url, fileName ->
File file = new File("$buildDir/download/${fileName}")
file.parentFile.mkdirs()
if (!file.exists()) {
new URL(url).withInputStream { downloadStream ->
file.withOutputStream { fileOut ->
fileOut << downloadStream
}
}
}
files(file.absolutePath)
}
android { android {
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion '30.0.3' buildToolsVersion '30.0.3'
@ -134,6 +150,7 @@ android {
} }
check.dependsOn 'spotbugsGplayDebugReport', 'lint', 'ktlint', 'detekt' check.dependsOn 'spotbugsGplayDebugReport', 'lint', 'ktlint', 'detekt'
lint.dependsOn 'preBuild'
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -240,7 +257,8 @@ dependencies {
kapt "com.jakewharton:butterknife-compiler:${butterknifeVersion}" kapt "com.jakewharton:butterknife-compiler:${butterknifeVersion}"
implementation 'eu.davidea:flexible-adapter:5.1.0' implementation 'eu.davidea:flexible-adapter:5.1.0'
implementation 'eu.davidea:flexible-adapter-ui:1.0.0' implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
implementation 'org.webrtc:google-webrtc:1.0.32006' implementation urlFile('https://github.com/nextcloud-releases/talk-clients-webrtc/releases/download/96.4664.0-RC1/libwebrtc-96.4664.0.aar',
'libwebrtc-96.4664.0.aar')
implementation 'com.yarolegovich:lovely-dialog:1.1.1' implementation 'com.yarolegovich:lovely-dialog:1.1.1'
implementation 'com.yarolegovich:mp:1.1.6' implementation 'com.yarolegovich:mp:1.1.6'
implementation 'me.zhanghai.android.effortlesspermissions:library:1.1.0' implementation 'me.zhanghai.android.effortlesspermissions:library:1.1.0'

View File

@ -93,7 +93,7 @@ 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.webrtc.MagicAudioManager; import com.nextcloud.talk.webrtc.MagicAudioManager;
import com.nextcloud.talk.webrtc.MagicPeerConnectionWrapper; import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
import com.nextcloud.talk.webrtc.MagicWebRTCUtils; import com.nextcloud.talk.webrtc.MagicWebRTCUtils;
import com.nextcloud.talk.webrtc.MagicWebSocketInstance; import com.nextcloud.talk.webrtc.MagicWebSocketInstance;
import com.nextcloud.talk.webrtc.WebSocketConnectionHelper; import com.nextcloud.talk.webrtc.WebSocketConnectionHelper;
@ -213,9 +213,9 @@ public class CallActivity extends CallBaseActivity {
private UserEntity conversationUser; private UserEntity conversationUser;
private String conversationName; private String conversationName;
private String callSession; private String callSession;
private MediaStream localMediaStream; private MediaStream localStream;
private String credentials; private String credentials;
private List<MagicPeerConnectionWrapper> magicPeerConnectionWrapperList = new ArrayList<>(); private List<PeerConnectionWrapper> peerConnectionWrapperList = new ArrayList<>();
private Map<String, Participant> participantMap = new HashMap<>(); private Map<String, Participant> participantMap = new HashMap<>();
private boolean videoOn = false; private boolean videoOn = false;
@ -408,7 +408,7 @@ public class CallActivity extends CallBaseActivity {
audioConstraints = new MediaConstraints(); audioConstraints = new MediaConstraints();
videoConstraints = new MediaConstraints(); videoConstraints = new MediaConstraints();
localMediaStream = peerConnectionFactory.createLocalMediaStream("NCMS"); localStream = peerConnectionFactory.createLocalMediaStream("NCMS");
// Create and audio manager that will take care of audio routing, // Create and audio manager that will take care of audio routing,
// audio modes, audio device enumeration etc. // audio modes, audio device enumeration etc.
@ -781,7 +781,7 @@ public class CallActivity extends CallBaseActivity {
videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver()); videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
} }
localVideoTrack = peerConnectionFactory.createVideoTrack("NCv0", videoSource); localVideoTrack = peerConnectionFactory.createVideoTrack("NCv0", videoSource);
localMediaStream.addTrack(localVideoTrack); localStream.addTrack(localVideoTrack);
localVideoTrack.setEnabled(false); localVideoTrack.setEnabled(false);
localVideoTrack.addSink(binding.selfVideoRenderer); localVideoTrack.addSink(binding.selfVideoRenderer);
} }
@ -791,7 +791,7 @@ public class CallActivity extends CallBaseActivity {
audioSource = peerConnectionFactory.createAudioSource(audioConstraints); audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
localAudioTrack = peerConnectionFactory.createAudioTrack("NCa0", audioSource); localAudioTrack = peerConnectionFactory.createAudioTrack("NCa0", audioSource);
localAudioTrack.setEnabled(false); localAudioTrack.setEnabled(false);
localMediaStream.addTrack(localAudioTrack); localStream.addTrack(localAudioTrack);
} }
private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) { private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
@ -963,8 +963,8 @@ public class CallActivity extends CallBaseActivity {
} }
} }
if (localMediaStream != null && localMediaStream.videoTracks.size() > 0) { if (localStream != null && localStream.videoTracks.size() > 0) {
localMediaStream.videoTracks.get(0).setEnabled(enable); localStream.videoTracks.get(0).setEnabled(enable);
} }
if (enable) { if (enable) {
binding.selfVideoRenderer.setVisibility(View.VISIBLE); binding.selfVideoRenderer.setVisibility(View.VISIBLE);
@ -980,20 +980,20 @@ public class CallActivity extends CallBaseActivity {
binding.microphoneButton.setAlpha(0.7f); binding.microphoneButton.setAlpha(0.7f);
} }
if (localMediaStream != null && localMediaStream.audioTracks.size() > 0) { if (localStream != null && localStream.audioTracks.size() > 0) {
localMediaStream.audioTracks.get(0).setEnabled(enable); localStream.audioTracks.get(0).setEnabled(enable);
} }
} }
if (isConnectionEstablished() && magicPeerConnectionWrapperList != null) { if (isConnectionEstablished() && peerConnectionWrapperList != null) {
if (!hasMCU) { if (!hasMCU) {
for (MagicPeerConnectionWrapper magicPeerConnectionWrapper : magicPeerConnectionWrapperList) { for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
magicPeerConnectionWrapper.sendChannelData(new DataChannelMessage(message)); peerConnectionWrapper.sendChannelData(new DataChannelMessage(message));
} }
} else { } else {
for (MagicPeerConnectionWrapper magicPeerConnectionWrapper : magicPeerConnectionWrapperList) { for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
if (magicPeerConnectionWrapper.getSessionId().equals(webSocketClient.getSessionId())) { if (peerConnectionWrapper.getSessionId().equals(webSocketClient.getSessionId())) {
magicPeerConnectionWrapper.sendChannelData(new DataChannelMessage(message)); peerConnectionWrapper.sendChannelData(new DataChannelMessage(message));
break; break;
} }
} }
@ -1517,7 +1517,7 @@ public class CallActivity extends CallBaseActivity {
private void processMessage(NCSignalingMessage ncSignalingMessage) { private void processMessage(NCSignalingMessage ncSignalingMessage) {
if (ncSignalingMessage.getRoomType().equals("video") || ncSignalingMessage.getRoomType().equals("screen")) { if (ncSignalingMessage.getRoomType().equals("video") || ncSignalingMessage.getRoomType().equals("screen")) {
MagicPeerConnectionWrapper magicPeerConnectionWrapper = PeerConnectionWrapper peerConnectionWrapper =
getPeerConnectionWrapperForSessionIdAndType(ncSignalingMessage.getFrom(), getPeerConnectionWrapperForSessionIdAndType(ncSignalingMessage.getFrom(),
ncSignalingMessage.getRoomType(), false); ncSignalingMessage.getRoomType(), false);
@ -1535,7 +1535,7 @@ public class CallActivity extends CallBaseActivity {
break; break;
case "offer": case "offer":
case "answer": case "answer":
magicPeerConnectionWrapper.setNick(ncSignalingMessage.getPayload().getNick()); peerConnectionWrapper.setNick(ncSignalingMessage.getPayload().getNick());
SessionDescription sessionDescriptionWithPreferredCodec; SessionDescription sessionDescriptionWithPreferredCodec;
String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec
@ -1546,8 +1546,8 @@ public class CallActivity extends CallBaseActivity {
SessionDescription.Type.fromCanonicalForm(type), SessionDescription.Type.fromCanonicalForm(type),
sessionDescriptionStringWithPreferredCodec); sessionDescriptionStringWithPreferredCodec);
if (magicPeerConnectionWrapper.getPeerConnection() != null) { if (peerConnectionWrapper.getPeerConnection() != null) {
magicPeerConnectionWrapper.getPeerConnection().setRemoteDescription(magicPeerConnectionWrapper peerConnectionWrapper.getPeerConnection().setRemoteDescription(peerConnectionWrapper
.getMagicSdpObserver(), sessionDescriptionWithPreferredCodec); .getMagicSdpObserver(), sessionDescriptionWithPreferredCodec);
} }
break; break;
@ -1555,10 +1555,10 @@ public class CallActivity extends CallBaseActivity {
NCIceCandidate ncIceCandidate = ncSignalingMessage.getPayload().getIceCandidate(); NCIceCandidate ncIceCandidate = ncSignalingMessage.getPayload().getIceCandidate();
IceCandidate iceCandidate = new IceCandidate(ncIceCandidate.getSdpMid(), IceCandidate iceCandidate = new IceCandidate(ncIceCandidate.getSdpMid(),
ncIceCandidate.getSdpMLineIndex(), ncIceCandidate.getCandidate()); ncIceCandidate.getSdpMLineIndex(), ncIceCandidate.getCandidate());
magicPeerConnectionWrapper.addCandidate(iceCandidate); peerConnectionWrapper.addCandidate(iceCandidate);
break; break;
case "endOfCandidates": case "endOfCandidates":
magicPeerConnectionWrapper.drainIceCandidates(); peerConnectionWrapper.drainIceCandidates();
break; break;
default: default:
break; break;
@ -1608,7 +1608,7 @@ public class CallActivity extends CallBaseActivity {
peerConnectionFactory = null; peerConnectionFactory = null;
} }
localMediaStream = null;
localAudioTrack = null; localAudioTrack = null;
localVideoTrack = null; localVideoTrack = null;
@ -1618,8 +1618,16 @@ public class CallActivity extends CallBaseActivity {
} }
} }
for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) { for (int i = 0; i < peerConnectionWrapperList.size(); i++) {
endPeerConnection(magicPeerConnectionWrapperList.get(i).getSessionId(), false); endPeerConnection(peerConnectionWrapperList.get(i).getSessionId(), false);
}
if(localStream != null) {
localStream.dispose();
localStream = null;
Log.d(TAG, "Disposed localStream");
} else {
Log.d(TAG, "localStream is null");
} }
hangupNetworkCalls(shutDownView); hangupNetworkCalls(shutDownView);
@ -1703,9 +1711,9 @@ public class CallActivity extends CallBaseActivity {
} }
} }
for (MagicPeerConnectionWrapper magicPeerConnectionWrapper : magicPeerConnectionWrapperList) { for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
if (!magicPeerConnectionWrapper.isMCUPublisher()) { if (!peerConnectionWrapper.isMCUPublisher()) {
oldSessions.add(magicPeerConnectionWrapper.getSessionId()); oldSessions.add(peerConnectionWrapper.getSessionId());
} }
} }
@ -1775,39 +1783,39 @@ public class CallActivity extends CallBaseActivity {
}); });
} }
private void deleteMagicPeerConnection(MagicPeerConnectionWrapper magicPeerConnectionWrapper) { private void deletePeerConnection(PeerConnectionWrapper peerConnectionWrapper) {
magicPeerConnectionWrapper.removePeerConnection(); peerConnectionWrapper.removePeerConnection();
magicPeerConnectionWrapperList.remove(magicPeerConnectionWrapper); peerConnectionWrapperList.remove(peerConnectionWrapper);
} }
private MagicPeerConnectionWrapper getPeerConnectionWrapperForSessionId(String sessionId, String type) { private PeerConnectionWrapper getPeerConnectionWrapperForSessionId(String sessionId, String type) {
for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) { for (int i = 0; i < peerConnectionWrapperList.size(); i++) {
if (magicPeerConnectionWrapperList.get(i).getSessionId().equals(sessionId) && magicPeerConnectionWrapperList.get(i).getVideoStreamType().equals(type)) { if (peerConnectionWrapperList.get(i).getSessionId().equals(sessionId) && peerConnectionWrapperList.get(i).getVideoStreamType().equals(type)) {
return magicPeerConnectionWrapperList.get(i); return peerConnectionWrapperList.get(i);
} }
} }
return null; return null;
} }
private MagicPeerConnectionWrapper getPeerConnectionWrapperForSessionIdAndType(String sessionId, String type, boolean publisher) { private PeerConnectionWrapper getPeerConnectionWrapperForSessionIdAndType(String sessionId, String type, boolean publisher) {
MagicPeerConnectionWrapper magicPeerConnectionWrapper; PeerConnectionWrapper peerConnectionWrapper;
if ((magicPeerConnectionWrapper = getPeerConnectionWrapperForSessionId(sessionId, type)) != null) { if ((peerConnectionWrapper = getPeerConnectionWrapperForSessionId(sessionId, type)) != null) {
return magicPeerConnectionWrapper; return peerConnectionWrapper;
} else { } else {
if (hasMCU && publisher) { if (hasMCU && publisher) {
magicPeerConnectionWrapper = new MagicPeerConnectionWrapper(peerConnectionFactory, peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
iceServers, iceServers,
sdpConstraintsForMCU, sdpConstraintsForMCU,
sessionId, sessionId,
callSession, callSession,
localMediaStream, localStream,
true, true,
true, true,
type); type);
} else if (hasMCU) { } else if (hasMCU) {
magicPeerConnectionWrapper = new MagicPeerConnectionWrapper(peerConnectionFactory, peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
iceServers, iceServers,
sdpConstraints, sdpConstraints,
sessionId, sessionId,
@ -1818,17 +1826,17 @@ public class CallActivity extends CallBaseActivity {
type); type);
} else { } else {
if (!"screen".equals(type)) { if (!"screen".equals(type)) {
magicPeerConnectionWrapper = new MagicPeerConnectionWrapper(peerConnectionFactory, peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
iceServers, iceServers,
sdpConstraints, sdpConstraints,
sessionId, sessionId,
callSession, callSession,
localMediaStream, localStream,
false, false,
false, false,
type); type);
} else { } else {
magicPeerConnectionWrapper = new MagicPeerConnectionWrapper(peerConnectionFactory, peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
iceServers, iceServers,
sdpConstraints, sdpConstraints,
sessionId, sessionId,
@ -1840,21 +1848,21 @@ public class CallActivity extends CallBaseActivity {
} }
} }
magicPeerConnectionWrapperList.add(magicPeerConnectionWrapper); peerConnectionWrapperList.add(peerConnectionWrapper);
if (publisher) { if (publisher) {
startSendingNick(); startSendingNick();
} }
return magicPeerConnectionWrapper; return peerConnectionWrapper;
} }
} }
private List<MagicPeerConnectionWrapper> getPeerConnectionWrapperListForSessionId(String sessionId) { private List<PeerConnectionWrapper> getPeerConnectionWrapperListForSessionId(String sessionId) {
List<MagicPeerConnectionWrapper> internalList = new ArrayList<>(); List<PeerConnectionWrapper> internalList = new ArrayList<>();
for (MagicPeerConnectionWrapper magicPeerConnectionWrapper : magicPeerConnectionWrapperList) { for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
if (magicPeerConnectionWrapper.getSessionId().equals(sessionId)) { if (peerConnectionWrapper.getSessionId().equals(sessionId)) {
internalList.add(magicPeerConnectionWrapper); internalList.add(peerConnectionWrapper);
} }
} }
@ -1862,15 +1870,15 @@ public class CallActivity extends CallBaseActivity {
} }
private void endPeerConnection(String sessionId, boolean justScreen) { private void endPeerConnection(String sessionId, boolean justScreen) {
List<MagicPeerConnectionWrapper> magicPeerConnectionWrappers; List<PeerConnectionWrapper> peerConnectionWrappers;
MagicPeerConnectionWrapper magicPeerConnectionWrapper; PeerConnectionWrapper peerConnectionWrapper;
if (!(magicPeerConnectionWrappers = getPeerConnectionWrapperListForSessionId(sessionId)).isEmpty()) { if (!(peerConnectionWrappers = getPeerConnectionWrapperListForSessionId(sessionId)).isEmpty()) {
for (int i = 0; i < magicPeerConnectionWrappers.size(); i++) { for (int i = 0; i < peerConnectionWrappers.size(); i++) {
magicPeerConnectionWrapper = magicPeerConnectionWrappers.get(i); peerConnectionWrapper = peerConnectionWrappers.get(i);
if (magicPeerConnectionWrapper.getSessionId().equals(sessionId)) { if (peerConnectionWrapper.getSessionId().equals(sessionId)) {
if (magicPeerConnectionWrapper.getVideoStreamType().equals("screen") || !justScreen) { if (peerConnectionWrapper.getVideoStreamType().equals("screen") || !justScreen) {
runOnUiThread(() -> removeMediaStream(sessionId)); runOnUiThread(() -> removeMediaStream(sessionId));
deleteMagicPeerConnection(magicPeerConnectionWrapper); deletePeerConnection(peerConnectionWrapper);
} }
} }
} }
@ -1982,10 +1990,10 @@ public class CallActivity extends CallBaseActivity {
nickChangedPayload.put("userid", conversationUser.getUserId()); nickChangedPayload.put("userid", conversationUser.getUserId());
nickChangedPayload.put("name", conversationUser.getDisplayName()); nickChangedPayload.put("name", conversationUser.getDisplayName());
dataChannelMessage.setPayload(nickChangedPayload); dataChannelMessage.setPayload(nickChangedPayload);
final MagicPeerConnectionWrapper magicPeerConnectionWrapper; final PeerConnectionWrapper peerConnectionWrapper;
for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) { for (int i = 0; i < peerConnectionWrapperList.size(); i++) {
if (magicPeerConnectionWrapperList.get(i).isMCUPublisher()) { if (peerConnectionWrapperList.get(i).isMCUPublisher()) {
magicPeerConnectionWrapper = magicPeerConnectionWrapperList.get(i); peerConnectionWrapper = peerConnectionWrapperList.get(i);
Observable Observable
.interval(1, TimeUnit.SECONDS) .interval(1, TimeUnit.SECONDS)
.repeatUntil(() -> (!isConnectionEstablished() || isDestroyed())) .repeatUntil(() -> (!isConnectionEstablished() || isDestroyed()))
@ -1998,7 +2006,7 @@ public class CallActivity extends CallBaseActivity {
@Override @Override
public void onNext(@io.reactivex.annotations.NonNull Long aLong) { public void onNext(@io.reactivex.annotations.NonNull Long aLong) {
magicPeerConnectionWrapper.sendNickChannelData(dataChannelMessage); peerConnectionWrapper.sendNickChannelData(dataChannelMessage);
} }
@Override @Override

View File

@ -2,6 +2,8 @@
* Nextcloud Talk application * Nextcloud Talk application
* *
* @author Mario Danic * @author Mario Danic
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -37,6 +39,7 @@ import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
import com.nextcloud.talk.models.json.signaling.NCIceCandidate; import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.webrtc.AudioTrack;
import org.webrtc.DataChannel; import org.webrtc.DataChannel;
import org.webrtc.IceCandidate; import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints; import org.webrtc.MediaConstraints;
@ -46,81 +49,87 @@ import org.webrtc.PeerConnectionFactory;
import org.webrtc.RtpReceiver; import org.webrtc.RtpReceiver;
import org.webrtc.SdpObserver; import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription; import org.webrtc.SessionDescription;
import org.webrtc.VideoTrack;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Objects;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import autodagger.AutoInjector; import autodagger.AutoInjector;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
@AutoInjector(NextcloudTalkApplication.class) @AutoInjector(NextcloudTalkApplication.class)
public class MagicPeerConnectionWrapper { public class PeerConnectionWrapper {
private static final String TAG = "MagicPeerConWrapper";
private static final String TAG = PeerConnectionWrapper.class.getCanonicalName();
private List<IceCandidate> iceCandidates = new ArrayList<>(); private List<IceCandidate> iceCandidates = new ArrayList<>();
private PeerConnection peerConnection; private PeerConnection peerConnection;
private String sessionId; private String sessionId;
private String nick; private String nick;
private MediaConstraints sdpConstraints; private final MediaConstraints mediaConstraints;
private DataChannel magicDataChannel; private DataChannel dataChannel;
private MagicSdpObserver magicSdpObserver; private final MagicSdpObserver magicSdpObserver;
private MediaStream remoteMediaStream; private MediaStream remoteStream;
private boolean remoteVideoOn; private final boolean hasInitiated;
private boolean remoteAudioOn;
private boolean hasInitiated; private final MediaStream localStream;
private final boolean isMCUPublisher;
private MediaStream localMediaStream; private final String videoStreamType;
private boolean isMCUPublisher;
private boolean hasMCU;
private String videoStreamType;
private int connectionAttempts = 0;
private PeerConnection.IceConnectionState peerIceConnectionState;
@Inject @Inject
Context context; Context context;
public MagicPeerConnectionWrapper(PeerConnectionFactory peerConnectionFactory, public PeerConnectionWrapper(PeerConnectionFactory peerConnectionFactory,
List<PeerConnection.IceServer> iceServerList, List<PeerConnection.IceServer> iceServerList,
MediaConstraints sdpConstraints, MediaConstraints mediaConstraints,
String sessionId, String localSession, @Nullable MediaStream mediaStream, String sessionId, String localSession, @Nullable MediaStream localStream,
boolean isMCUPublisher, boolean hasMCU, String videoStreamType) { boolean isMCUPublisher, boolean hasMCU, String videoStreamType) {
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); Objects.requireNonNull(NextcloudTalkApplication.Companion.getSharedApplication()).getComponentApplication().inject(this);
this.localMediaStream = mediaStream; this.localStream = localStream;
this.videoStreamType = videoStreamType; this.videoStreamType = videoStreamType;
this.hasMCU = hasMCU;
this.sessionId = sessionId; this.sessionId = sessionId;
this.sdpConstraints = sdpConstraints; this.mediaConstraints = mediaConstraints;
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, sdpConstraints, PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServerList);
new MagicPeerConnectionObserver()); configuration.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
peerConnection = peerConnectionFactory.createPeerConnection(configuration, new MagicPeerConnectionObserver());
if (peerConnection != null) { if (peerConnection != null) {
if (localMediaStream != null) { if (this.localStream != null) {
peerConnection.addStream(localMediaStream); List<String> localStreamIds = Collections.singletonList(this.localStream.getId());
for(AudioTrack track : this.localStream.audioTracks) {
peerConnection.addTrack(track, localStreamIds);
}
for(VideoTrack track : this.localStream.videoTracks) {
peerConnection.addTrack(track, localStreamIds);
}
} }
if (hasMCU || hasInitiated) { if (hasMCU || hasInitiated) {
DataChannel.Init init = new DataChannel.Init(); DataChannel.Init init = new DataChannel.Init();
init.negotiated = false; init.negotiated = false;
magicDataChannel = peerConnection.createDataChannel("status", init); dataChannel = peerConnection.createDataChannel("status", init);
magicDataChannel.registerObserver(new MagicDataChannelObserver()); dataChannel.registerObserver(new MagicDataChannelObserver());
if (isMCUPublisher) { if (isMCUPublisher) {
peerConnection.createOffer(magicSdpObserver, sdpConstraints); peerConnection.createOffer(magicSdpObserver, mediaConstraints);
} else if (hasMCU && this.videoStreamType.equals("video")) { } else if (hasMCU && this.videoStreamType.equals("video")) {
// If the connection type is "screen" the client sharing the screen will send an // If the connection type is "screen" the client sharing the screen will send an
// offer; offers should be requested only for videos. // offer; offers should be requested only for videos.
@ -128,7 +137,7 @@ public class MagicPeerConnectionWrapper {
hashMap.put("sessionId", sessionId); hashMap.put("sessionId", sessionId);
EventBus.getDefault().post(new WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap)); EventBus.getDefault().post(new WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap));
} else if (!hasMCU && hasInitiated) { } else if (!hasMCU && hasInitiated) {
peerConnection.createOffer(magicSdpObserver, sdpConstraints); peerConnection.createOffer(magicSdpObserver, mediaConstraints);
} }
} }
} }
@ -139,18 +148,20 @@ public class MagicPeerConnectionWrapper {
} }
public void removePeerConnection() { public void removePeerConnection() {
if (magicDataChannel != null) { if (dataChannel != null) {
magicDataChannel.dispose(); dataChannel.dispose();
magicDataChannel = null; dataChannel = null;
Log.d(TAG, "Disposed DataChannel");
} else {
Log.d(TAG, "DataChannel is null.");
} }
if (peerConnection != null) { if (peerConnection != null) {
if (localMediaStream != null) {
peerConnection.removeStream(localMediaStream);
}
peerConnection.close(); peerConnection.close();
peerConnection = null; peerConnection = null;
Log.d(TAG, "Disposed PeerConnection");
} else {
Log.d(TAG, "PeerConnection is null.");
} }
} }
@ -179,10 +190,10 @@ public class MagicPeerConnectionWrapper {
public void sendNickChannelData(DataChannelMessageNick dataChannelMessage) { public void sendNickChannelData(DataChannelMessageNick dataChannelMessage) {
ByteBuffer buffer; ByteBuffer buffer;
if (magicDataChannel != null) { if (dataChannel != null) {
try { try {
buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes()); buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes());
magicDataChannel.send(new DataChannel.Buffer(buffer, false)); dataChannel.send(new DataChannel.Buffer(buffer, false));
} catch (IOException e) { } catch (IOException e) {
Log.d(TAG, "Failed to send channel data, attempting regular " + dataChannelMessage); Log.d(TAG, "Failed to send channel data, attempting regular " + dataChannelMessage);
} }
@ -191,10 +202,10 @@ public class MagicPeerConnectionWrapper {
public void sendChannelData(DataChannelMessage dataChannelMessage) { public void sendChannelData(DataChannelMessage dataChannelMessage) {
ByteBuffer buffer; ByteBuffer buffer;
if (magicDataChannel != null) { if (dataChannel != null) {
try { try {
buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes()); buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes());
magicDataChannel.send(new DataChannel.Buffer(buffer, false)); dataChannel.send(new DataChannel.Buffer(buffer, false));
} catch (IOException e) { } catch (IOException e) {
Log.d(TAG, "Failed to send channel data, attempting regular " + dataChannelMessage); Log.d(TAG, "Failed to send channel data, attempting regular " + dataChannelMessage);
} }
@ -217,7 +228,7 @@ public class MagicPeerConnectionWrapper {
if (!TextUtils.isEmpty(nick)) { if (!TextUtils.isEmpty(nick)) {
return nick; return nick;
} else { } else {
return NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_nick_guest); return Objects.requireNonNull(NextcloudTalkApplication.Companion.getSharedApplication()).getString(R.string.nc_nick_guest);
} }
} }
@ -226,14 +237,14 @@ public class MagicPeerConnectionWrapper {
} }
private void sendInitialMediaStatus() { private void sendInitialMediaStatus() {
if (localMediaStream != null) { if (localStream != null) {
if (localMediaStream.videoTracks.size() == 1 && localMediaStream.videoTracks.get(0).enabled()) { if (localStream.videoTracks.size() == 1 && localStream.videoTracks.get(0).enabled()) {
sendChannelData(new DataChannelMessage("videoOn")); sendChannelData(new DataChannelMessage("videoOn"));
} else { } else {
sendChannelData(new DataChannelMessage("videoOff")); sendChannelData(new DataChannelMessage("videoOff"));
} }
if (localMediaStream.audioTracks.size() == 1 && localMediaStream.audioTracks.get(0).enabled()) { if (localStream.audioTracks.size() == 1 && localStream.audioTracks.get(0).enabled()) {
sendChannelData(new DataChannelMessage("audioOn")); sendChannelData(new DataChannelMessage("audioOn"));
} else { } else {
sendChannelData(new DataChannelMessage("audioOff")); sendChannelData(new DataChannelMessage("audioOff"));
@ -254,8 +265,8 @@ public class MagicPeerConnectionWrapper {
@Override @Override
public void onStateChange() { public void onStateChange() {
if (magicDataChannel != null && magicDataChannel.state().equals(DataChannel.State.OPEN) && if (dataChannel != null && dataChannel.state().equals(DataChannel.State.OPEN) &&
magicDataChannel.label().equals("status")) { dataChannel.label().equals("status")) {
sendInitialMediaStatus(); sendInitialMediaStatus();
} }
} }
@ -292,23 +303,18 @@ public class MagicPeerConnectionWrapper {
.NICK_CHANGE, sessionId, payloadHashMap.get("name"), null, videoStreamType)); .NICK_CHANGE, sessionId, payloadHashMap.get("name"), null, videoStreamType));
} }
} }
} else if ("audioOn".equals(dataChannelMessage.getType())) { } else if ("audioOn".equals(dataChannelMessage.getType())) {
remoteAudioOn = true;
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.AUDIO_CHANGE, sessionId, null, remoteAudioOn, videoStreamType)); .AUDIO_CHANGE, sessionId, null, TRUE, videoStreamType));
} else if ("audioOff".equals(dataChannelMessage.getType())) { } else if ("audioOff".equals(dataChannelMessage.getType())) {
remoteAudioOn = false;
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.AUDIO_CHANGE, sessionId, null, remoteAudioOn, videoStreamType)); .AUDIO_CHANGE, sessionId, null, FALSE, videoStreamType));
} else if ("videoOn".equals(dataChannelMessage.getType())) { } else if ("videoOn".equals(dataChannelMessage.getType())) {
remoteVideoOn = true;
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.VIDEO_CHANGE, sessionId, null, remoteVideoOn, videoStreamType)); .VIDEO_CHANGE, sessionId, null, TRUE, videoStreamType));
} else if ("videoOff".equals(dataChannelMessage.getType())) { } else if ("videoOff".equals(dataChannelMessage.getType())) {
remoteVideoOn = false;
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.VIDEO_CHANGE, sessionId, null, remoteVideoOn, videoStreamType)); .VIDEO_CHANGE, sessionId, null, FALSE, videoStreamType));
} }
} catch (IOException e) { } catch (IOException e) {
Log.d(TAG, "Failed to parse data channel message"); Log.d(TAG, "Failed to parse data channel message");
@ -316,26 +322,6 @@ 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 {
@Override @Override
@ -344,16 +330,12 @@ public class MagicPeerConnectionWrapper {
@Override @Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) { public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
peerIceConnectionState = iceConnectionState;
Log.d("iceConnectionChangeTo: ", iceConnectionState.name() + " over " + peerConnection.hashCode() + " " + sessionId); Log.d("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
.PEER_CONNECTED, sessionId, null, null));*/
if (!isMCUPublisher) { if (!isMCUPublisher) {
EventBus.getDefault().post(new MediaStreamEvent(remoteMediaStream, sessionId, videoStreamType)); EventBus.getDefault().post(new MediaStreamEvent(remoteStream, sessionId, videoStreamType));
} }
if (hasInitiated) { if (hasInitiated) {
@ -363,11 +345,7 @@ 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)) { } else if (iceConnectionState.equals(PeerConnection.IceConnectionState.FAILED)) {
/*if (MerlinTheWizard.isConnectedToInternet() && connectionAttempts < 5) {
restartIce();
}*/
if (isMCUPublisher) { if (isMCUPublisher) {
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED, sessionId, null, null, null)); EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED, sessionId, null, null, null));
} }
@ -401,7 +379,7 @@ public class MagicPeerConnectionWrapper {
@Override @Override
public void onAddStream(MediaStream mediaStream) { public void onAddStream(MediaStream mediaStream) {
remoteMediaStream = mediaStream; remoteStream = mediaStream;
} }
@Override @Override
@ -414,8 +392,8 @@ public class MagicPeerConnectionWrapper {
@Override @Override
public void onDataChannel(DataChannel dataChannel) { public void onDataChannel(DataChannel dataChannel) {
if (dataChannel.label().equals("status") || dataChannel.label().equals("JanusDataChannel")) { if (dataChannel.label().equals("status") || dataChannel.label().equals("JanusDataChannel")) {
magicDataChannel = dataChannel; PeerConnectionWrapper.this.dataChannel = dataChannel;
magicDataChannel.registerObserver(new MagicDataChannelObserver()); PeerConnectionWrapper.this.dataChannel.registerObserver(new MagicDataChannelObserver());
} }
} }
@ -466,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, sdpConstraints); peerConnection.createAnswer(magicSdpObserver, mediaConstraints);
} }
if (peerConnection.getRemoteDescription() != null) { if (peerConnection.getRemoteDescription() != null) {
@ -475,8 +453,4 @@ public class MagicPeerConnectionWrapper {
} }
} }
} }
public PeerConnection.IceConnectionState getPeerIceConnectionState() {
return peerIceConnectionState;
}
} }

View File

@ -56,12 +56,12 @@ end
# run Lint # run Lint
puts "running Lint..." puts "running Lint..."
system './gradlew --console=plain clean lintGplayDebug' system './gradlew --console=plain lintGplayDebug'
# confirm that Lint ran w/out error # confirm that Lint ran w/out error
result = $?.to_i result = $?.to_i
if result != 0 if result != 0
puts "FAIL: failed to run ./gradlew --console=plain clean lintGplayDebug" puts "FAIL: failed to run ./gradlew --console=plain lintGplayDebug"
exit 1 exit 1
end end