delay discovered ice candidates until JingleRtpConnection is ready to receive
otherwise setLocalDescription and the arrival of first candidates might race the rtpContentDescription being set
This commit is contained in:
parent
b6dee6da6a
commit
717c83753f
|
@ -34,6 +34,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -567,6 +568,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
|
||||
final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription);
|
||||
this.responderRtpContentMap = respondingRtpContentMap;
|
||||
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
||||
final ListenableFuture<RtpContentMap> outgoingContentMapFuture = prepareOutgoingContentMap(respondingRtpContentMap);
|
||||
Futures.addCallback(outgoingContentMapFuture,
|
||||
new FutureCallback<RtpContentMap>() {
|
||||
|
@ -872,6 +874,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
|
||||
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
|
||||
this.initiatorRtpContentMap = rtpContentMap;
|
||||
this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
||||
final ListenableFuture<RtpContentMap> outgoingContentMapFuture = encryptSessionInitiate(rtpContentMap);
|
||||
Futures.addCallback(outgoingContentMapFuture, new FutureCallback<RtpContentMap>() {
|
||||
@Override
|
||||
|
@ -1348,7 +1351,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
}
|
||||
|
||||
final boolean neverConnected = !this.stateHistory.contains(PeerConnection.PeerConnectionState.CONNECTED);
|
||||
final boolean failedOrDisconnected = Arrays.asList(PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.DISCONNECTED).contains(newState);
|
||||
final boolean failedOrDisconnected = Arrays.asList(
|
||||
PeerConnection.PeerConnectionState.FAILED,
|
||||
PeerConnection.PeerConnectionState.DISCONNECTED
|
||||
).contains(newState);
|
||||
|
||||
|
||||
if (neverConnected && failedOrDisconnected) {
|
||||
|
@ -1356,7 +1362,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state);
|
||||
return;
|
||||
}
|
||||
new Thread(this::closeWebRTCSessionAfterFailedConnection).start();
|
||||
webRTCWrapper.execute(this::closeWebRTCSessionAfterFailedConnection);
|
||||
} else if (newState == PeerConnection.PeerConnectionState.FAILED) {
|
||||
Log.d(Config.LOGTAG, "attempting to restart ICE");
|
||||
webRTCWrapper.restartIce();
|
||||
|
@ -1367,6 +1373,33 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
@Override
|
||||
public void onRenegotiationNeeded() {
|
||||
Log.d(Config.LOGTAG, "onRenegotiationNeeded()");
|
||||
this.webRTCWrapper.execute(this::renegotiate);
|
||||
}
|
||||
|
||||
private void renegotiate() {
|
||||
this.webRTCWrapper.setIsReadyToReceiveIceCandidates(false);
|
||||
try {
|
||||
final SessionDescription sessionDescription = setLocalSessionDescription();
|
||||
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
|
||||
setRenegotiatedContentMap(rtpContentMap);
|
||||
this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
||||
} catch (final Exception e) {
|
||||
Log.d(Config.LOGTAG, "failed to renegotiate", e);
|
||||
//TODO send some sort of failure (comparable to when initiating)
|
||||
}
|
||||
}
|
||||
|
||||
private void setRenegotiatedContentMap(final RtpContentMap rtpContentMap) {
|
||||
if (isInitiator()) {
|
||||
this.initiatorRtpContentMap = rtpContentMap;
|
||||
} else {
|
||||
this.responderRtpContentMap = rtpContentMap;
|
||||
}
|
||||
}
|
||||
|
||||
private SessionDescription setLocalSessionDescription() throws ExecutionException, InterruptedException {
|
||||
final org.webrtc.SessionDescription sessionDescription = this.webRTCWrapper.setLocalDescription().get();
|
||||
return SessionDescription.parse(sessionDescription.description);
|
||||
}
|
||||
|
||||
private void closeWebRTCSessionAfterFailedConnection() {
|
||||
|
|
|
@ -45,8 +45,13 @@ import org.webrtc.voiceengine.WebRtcAudioEffects;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -59,6 +64,8 @@ public class WebRTCWrapper {
|
|||
|
||||
private static final String EXTENDED_LOGGING_TAG = WebRTCWrapper.class.getSimpleName();
|
||||
|
||||
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
//we should probably keep this in sync with: https://github.com/signalapp/Signal-Android/blob/master/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java#L296
|
||||
private static final Set<String> HARDWARE_AEC_BLACKLIST = new ImmutableSet.Builder<String>()
|
||||
.add("Pixel")
|
||||
|
@ -79,6 +86,8 @@ public class WebRTCWrapper {
|
|||
private static final int CAPTURING_MAX_FRAME_RATE = 30;
|
||||
|
||||
private final EventCallback eventCallback;
|
||||
private final AtomicBoolean readyToReceivedIceCandidates = new AtomicBoolean(false);
|
||||
private final Queue<IceCandidate> iceCandidates = new LinkedList<>();
|
||||
private final AppRTCAudioManager.AudioManagerEvents audioManagerEvents = new AppRTCAudioManager.AudioManagerEvents() {
|
||||
@Override
|
||||
public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
|
||||
|
@ -125,7 +134,11 @@ public class WebRTCWrapper {
|
|||
|
||||
@Override
|
||||
public void onIceCandidate(IceCandidate iceCandidate) {
|
||||
if (readyToReceivedIceCandidates.get()) {
|
||||
eventCallback.onIceCandidate(iceCandidate);
|
||||
} else {
|
||||
iceCandidates.add(iceCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -294,7 +307,14 @@ public class WebRTCWrapper {
|
|||
}
|
||||
|
||||
void restartIce() {
|
||||
requirePeerConnection().restartIce();
|
||||
executorService.execute(()-> requirePeerConnection().restartIce());
|
||||
}
|
||||
|
||||
public void setIsReadyToReceiveIceCandidates(final boolean ready) {
|
||||
readyToReceivedIceCandidates.set(ready);
|
||||
while(ready && iceCandidates.peek() != null) {
|
||||
eventCallback.onIceCandidate(iceCandidates.poll());
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void close() {
|
||||
|
@ -528,6 +548,10 @@ public class WebRTCWrapper {
|
|||
return appRTCAudioManager;
|
||||
}
|
||||
|
||||
void execute(final Runnable command) {
|
||||
executorService.execute(command);
|
||||
}
|
||||
|
||||
public interface EventCallback {
|
||||
void onIceCandidate(IceCandidate iceCandidate);
|
||||
|
||||
|
|
Loading…
Reference in a new issue